< Summary

Information
Class: MyNet.UI.ViewModels.Crud.CollectionEditionViewModel<T>
Assembly: MyNet.UI
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.UI/ViewModels/Crud/CollectionEditionViewModel.cs
Tag: 323_28699572109
Line coverage
73%
Covered lines: 47
Uncovered lines: 17
Coverable lines: 64
Total lines: 208
Line coverage: 73.4%
Branch coverage
75%
Covered branches: 18
Total branches: 24
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
set_IsDirty(...)100%11100%
LoadItems(...)100%44100%
CanAdd(...)100%210%
CanRemove(...)100%210%
EnsureEditableRow()100%210%
OnItemsStateChanged()100%210%
AddItem(...)0%620%
RemoveItem(...)50%6683.33%
Add(...)50%2275%
Remove(...)100%11100%
HandleCollectionChanged(...)100%44100%
HandleItemPropertyChanged(...)100%210%
MarkDirtyIfNeeded()100%22100%
SubscribeToItems(...)100%22100%
UnsubscribeFromItems(...)100%22100%
DisposeManagedResources()100%210%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.UI/ViewModels/Crud/CollectionEditionViewModel.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="CollectionEditionViewModel.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System.Collections.Generic;
 8using System.Collections.ObjectModel;
 9using System.Collections.Specialized;
 10using System.ComponentModel;
 11using System.Linq;
 12using System.Windows.Input;
 13using MyNet.UI.Commands;
 14
 15namespace MyNet.UI.ViewModels.Crud;
 16
 17/// <summary>
 18/// Provides a reusable base implementation for editable collections.
 19/// </summary>
 20/// <typeparam name="TRow">The edited row view model type.</typeparam>
 21public abstract class CollectionEditionViewModel<TRow> : ViewModelBase, IEditionStateViewModel
 22    where TRow : class, INotifyPropertyChanged
 23{
 24    private bool _isLoading;
 25
 26    /// <summary>
 27    /// Initializes a new instance of the <see cref="CollectionEditionViewModel{TRow}"/> class.
 28    /// </summary>
 29    /// <param name="commandFactory">Optional command factory used to create commands.</param>
 1230    protected CollectionEditionViewModel(ICommandFactory? commandFactory = null)
 31    {
 1232        var commands = commandFactory.GetOrDefault();
 33
 1234        AddCommand = commands.Create<TRow>(Add, CanAdd);
 1235        RemoveCommand = commands.Create<TRow>(Remove, CanRemove);
 36
 1237        Items.CollectionChanged += HandleCollectionChanged;
 1238    }
 39
 40    /// <summary>
 41    /// Gets the command that adds a row.
 42    /// </summary>
 43    public ICommand AddCommand { get; }
 44
 45    /// <summary>
 46    /// Gets the command that removes a row.
 47    /// </summary>
 48    public ICommand RemoveCommand { get; }
 49
 50    /// <summary>
 51    /// Gets the edited rows.
 52    /// </summary>
 1253    public ObservableCollection<TRow> Items { get; } = [];
 54
 55    /// <inheritdoc />
 1856    public bool IsDirty { get; private set => SetProperty(ref field, value); }
 57
 58    /// <summary>
 59    /// Replaces current rows with <paramref name="items"/> and marks the editor as clean.
 60    /// </summary>
 61    /// <param name="items">The rows to load.</param>
 62    protected void LoadItems(IEnumerable<TRow>? items)
 63    {
 1264        _isLoading = true;
 65
 66        try
 67        {
 1268            UnsubscribeFromItems(Items);
 1269            Items.Clear();
 70
 1271            if (items is not null)
 72            {
 6673                foreach (var item in items)
 2174                    Items.Add(item);
 75            }
 76
 1277            EnsureEditableRow();
 1278            SubscribeToItems(Items);
 1279            IsDirty = false;
 1280            OnItemsStateChanged();
 1281        }
 82        finally
 83        {
 1284            _isLoading = false;
 1285        }
 1286    }
 87
 88    /// <summary>
 89    /// Creates a new row to add to <see cref="Items"/>.
 90    /// </summary>
 91    /// <returns>The new row instance.</returns>
 92    protected abstract TRow CreateNewItem();
 93
 94    /// <summary>
 95    /// Determines whether a row can trigger add.
 96    /// </summary>
 97    /// <param name="item">The source row.</param>
 98    /// <returns><see langword="true"/> when add is allowed.</returns>
 099    protected virtual bool CanAdd(TRow? item) => item is not null;
 100
 101    /// <summary>
 102    /// Determines whether a row can be removed.
 103    /// </summary>
 104    /// <param name="item">The row to remove.</param>
 105    /// <returns><see langword="true"/> when remove is allowed.</returns>
 0106    protected virtual bool CanRemove(TRow? item) => item is not null;
 107
 108    /// <summary>
 109    /// Ensures editor constraints after a load/remove operation.
 110    /// </summary>
 111    protected virtual void EnsureEditableRow()
 112    {
 0113    }
 114
 115    /// <summary>
 116    /// Called after row collection/state changed.
 117    /// </summary>
 118    protected virtual void OnItemsStateChanged()
 119    {
 0120    }
 121
 122    /// <summary>
 123    /// Adds the specified item to <see cref="Items"/>.
 124    /// </summary>
 125    /// <param name="item">The item to add.</param>
 126    /// <returns><see langword="true"/> when the item was added; otherwise <see langword="false"/>.</returns>
 127    protected bool AddItem(TRow? item)
 128    {
 0129        if (item is null)
 0130            return false;
 131
 0132        Items.Add(item);
 0133        return true;
 134    }
 135
 136    /// <summary>
 137    /// Removes the specified item from <see cref="Items"/> when allowed.
 138    /// </summary>
 139    /// <param name="item">The item to remove.</param>
 140    /// <returns><see langword="true"/> when the item was removed; otherwise <see langword="false"/>.</returns>
 141    protected bool RemoveItem(TRow? item)
 142    {
 3143        if (!CanRemove(item) || item is null)
 0144            return false;
 145
 3146        var removed = Items.Remove(item);
 147
 3148        if (removed)
 3149            EnsureEditableRow();
 150
 3151        return removed;
 152    }
 153
 154    private void Add(TRow? item)
 155    {
 3156        if (!CanAdd(item))
 0157            return;
 158
 3159        Items.Add(CreateNewItem());
 3160    }
 161
 3162    private void Remove(TRow? item) => _ = RemoveItem(item);
 163
 164    private void HandleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
 165    {
 39166        if (e.OldItems is not null)
 3167            UnsubscribeFromItems(e.OldItems.Cast<TRow>());
 168
 39169        if (e.NewItems is not null)
 24170            SubscribeToItems(e.NewItems.Cast<TRow>());
 171
 39172        MarkDirtyIfNeeded();
 39173        OnItemsStateChanged();
 39174    }
 175
 176    private void HandleItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
 177    {
 0178        MarkDirtyIfNeeded();
 0179        OnItemsStateChanged();
 0180    }
 181
 182    private void MarkDirtyIfNeeded()
 183    {
 39184        if (!_isLoading)
 6185            IsDirty = true;
 39186    }
 187
 188    private void SubscribeToItems(IEnumerable<TRow> items)
 189    {
 162190        foreach (var item in items)
 45191            item.PropertyChanged += HandleItemPropertyChanged;
 36192    }
 193
 194    private void UnsubscribeFromItems(IEnumerable<TRow> items)
 195    {
 36196        foreach (var item in items)
 3197            item.PropertyChanged -= HandleItemPropertyChanged;
 15198    }
 199
 200    /// <inheritdoc />
 201    protected override void DisposeManagedResources()
 202    {
 0203        Items.CollectionChanged -= HandleCollectionChanged;
 0204        UnsubscribeFromItems(Items);
 0205        base.DisposeManagedResources();
 0206    }
 207}
 208