Home > other >  how to use the MVVM pattern correctly
how to use the MVVM pattern correctly

Time:10-21

I do not understand how to correctly use the MVVM pattern, in half the cases the example is presented as in the first, and the rest as in the second. I believe that in the first example, the database is written to and this is not true. Or am I wrong? Can you send a link to an article where the MVVM pattern is used correctly?
1)https://www.c-sharpcorner.com/UploadFile/raj1979/simple-mvvm-pattern-in-wpf/
2)https://www.c-sharpcorner.com/UploadFile/raj1979/simple-mvvm-pattern-in-wpf/

CodePudding user response:

showcasing some features of wpf implemented with MVVM

  • shortcuts -- alt letter to jump to controls -- ctrl s to save
  • validation -- implemented as tooltip
  • implementing buttons/commands
  • propertychanged to update UI

view

<Window x:Class="WpfTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTest"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:MainViewModel, IsDesignTimeCreatable=True}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <DockPanel TextElement.FontSize="25">
        <DockPanel.InputBindings>
            <KeyBinding Modifiers="Ctrl" Key="S" Command="{Binding SaveCommand}"/>
        </DockPanel.InputBindings>
        <UniformGrid Rows="1" VerticalAlignment="Top" Margin="5" DockPanel.Dock="Top">
            <Label Content="_Email" Margin="5" Target="{x:Reference EmailTextBox}"/>
            <Label Content="_Location" Margin="5" Target="{x:Reference LocationTextBox}"/>
            <Label Content="Valid from" Margin="5"/>
            <Label Content="Valid to:" Margin="5"/>
            <Label Content="Info" Margin="5"/>
            <Grid/>
        </UniformGrid>
        <UniformGrid Rows="1" VerticalAlignment="Top" Margin="5" DockPanel.Dock="Top">
            <TextBox Margin="5" Text="{Binding NewEntry.EMailAddress, UpdateSourceTrigger=PropertyChanged}" x:Name="EmailTextBox"/>
            <TextBox Margin="5" Text="{Binding NewEntry.Location, UpdateSourceTrigger=PropertyChanged}" x:Name="LocationTextBox"/>
            <DatePicker Margin="5" SelectedDate="{Binding NewEntry.ValidFrom, UpdateSourceTrigger=PropertyChanged}"/>
            <DatePicker Margin="5" SelectedDate="{Binding NewEntry.ValidTo, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBox Margin="5"/>
            <Button x:Name="button" Content="Save [Ctrl S]" Command="{Binding SaveCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5" Padding="5"/>
        </UniformGrid>
        <DataGrid ItemsSource="{Binding Entries}" Margin="5,0,0,0" IsReadOnly="True" CanUserAddRows="False" CanUserDeleteRows="False"/>
    </DockPanel>
</Window>

and viewmodel

public class MainViewModel : ObservableObject
{
    private EntryViewModel _newEntry;

    public MainViewModel()
    {
        SaveCommand = new DelegateCommand(ExecuteSave, CanExecuteSave);
        CreateNew();
    }

    public DelegateCommand SaveCommand { get; }

    public EntryViewModel NewEntry
    {
        get { return _newEntry; }
        private set
        {
            if (_newEntry != null)
                _newEntry.PropertyChanged -= NewEntry_PropertyChanged;
            SetValue(ref _newEntry, value);
            if (_newEntry != null)
                _newEntry.PropertyChanged  = NewEntry_PropertyChanged;
        }
    }

    private void NewEntry_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(EntryViewModel.HasErrors))
            SaveCommand.RaiseCanExecuteChanged();
    }

    public ObservableCollection<EntryViewModel> Entries { get; } = new ObservableCollection<EntryViewModel>();

    private bool CanExecuteSave()
    {
        return !NewEntry.HasErrors;
    }
    private void ExecuteSave()
    {
        Entries.Add(NewEntry);
        CreateNew();
    }

    private void CreateNew()
    {
        NewEntry = new EntryViewModel
        {
            EMailAddress = "[email protected]",
            Location = "empty",
            ValidFrom = DateTime.Now,
            ValidTo = DateTime.Now,
        };
    }
}

public class EntryViewModel : NotifyDataErrorInfo
{
    private string _eMailAddress;
    private string _location;
    private DateTime? _validFrom;
    private DateTime? _validTo;

    [EmailAddress]
    public string EMailAddress
    {
        get { return _eMailAddress; }
        set { SetValue(ref _eMailAddress, value); }
    }

    [Required]
    public string Location
    {
        get { return _location; }
        set { SetValue(ref _location, value); }
    }

    [CustomValidation(typeof(EntryViewModel), nameof(ValidToAfterValidFrom))]
    public DateTime? ValidFrom
    {
        get { return _validFrom; }
        set { SetValue(ref _validFrom, value); }
    }

    [CustomValidation(typeof(EntryViewModel), nameof(ValidToAfterValidFrom))]
    public DateTime? ValidTo
    {
        get { return _validTo; }
        set { SetValue(ref _validTo, value); }
    }

    public static ValidationResult ValidToAfterValidFrom(object _, ValidationContext validationContext)
    {
        var vm = (EntryViewModel)validationContext.ObjectInstance;
        vm.ClearErrors(nameof(ValidFrom));
        vm.ClearErrors(nameof(ValidTo));
        return vm.ValidFrom < vm.ValidTo ? ValidationResult.Success : new ValidationResult("valid from must be before valid to", new[] { nameof(ValidFrom), nameof(ValidTo) });
    }
}

and the used base and utility classes

public abstract class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    protected virtual void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        field = value;
        OnPropertyChanged(propertyName);
    }
}

public class NotifyDataErrorInfo : ObservableObject, INotifyDataErrorInfo
{
    private readonly IDictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

    public bool HasErrors => _errors.Values.Any(l => l.Count > 0);

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    public IEnumerable GetErrors(string propertyName = null)
    {
        return string.IsNullOrEmpty(propertyName)
            ? _errors.Values.SelectMany(l => l)
            : _errors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty<string>();
    }

    public string GetAllErrors(string seperator)
    {
        return string.Join(seperator, _errors.Values.SelectMany(l => l));
    }

    protected override void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (!Equals(field, value))
        {
            base.SetValue(ref field, value, propertyName);
            Validate(value, propertyName);
        }
    }

    protected void ValidateAndOnPropertyChanged<T>(T value, [CallerMemberName] string propertyName = null)
    {
        Validate(value, propertyName);
        OnPropertyChanged(propertyName);
    }

    protected virtual void Validate<T>(T value, [CallerMemberName] string propertyName = null)
    {
        var validationresults = new List<ValidationResult>(1);
        if (!Validator.TryValidateProperty(value, new ValidationContext(this) { MemberName = propertyName }, validationresults))
        {
            var dict = new Dictionary<string, List<ValidationResult>>();
            foreach (var result in validationresults)
            {
                foreach (var name in result.MemberNames)
                {
                    if (!dict.TryGetValue(name, out List<ValidationResult> errorsForProperty))
                        dict.Add(name, errorsForProperty = new List<ValidationResult>());
                    errorsForProperty.Add(result);
                }
            }
            foreach (var pair in dict)
            {
                if (!_errors.TryGetValue(pair.Key, out List<string> propertyErrors))
                    _errors.Add(pair.Key, propertyErrors = new List<string>());
                propertyErrors.Clear();
                propertyErrors.AddRange(validationresults.Select(vr => vr.ErrorMessage));
            }
        }
        else
        {
            if (_errors.TryGetValue(propertyName, out List<string> propertyErrors))
                propertyErrors.Clear();
        }
        OnPropertyChanged(nameof(HasErrors));
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

    protected void ValidateAll()
    {
        foreach (var errors in _errors.Values)
        {
            errors.Clear();
        }

        var validationresults = new List<ValidationResult>(1);
        if (!Validator.TryValidateObject(this, new ValidationContext(this, null, null), validationresults, true))
        {
            foreach (var result in validationresults)
            {
                foreach (var propertyName in result.MemberNames)
                {
                    if (!_errors.TryGetValue(propertyName, out List<string> propertyErrors))
                        _errors.Add(propertyName, propertyErrors = new List<string>());
                    propertyErrors.Add(result.ErrorMessage);
                }
            }
        }
        OnPropertyChanged(nameof(HasErrors));
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(string.Empty));
    }

    protected void ClearErrors(string propertyName)
    {
        if (_errors.TryGetValue(propertyName, out List<string> propertyErrors))
            propertyErrors.Clear();
    }
}

public class DelegateCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

    public DelegateCommand(Action execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute()
    {
        return _canExecute?.Invoke() ?? true;
    }
    public void Execute()
    {
        _execute.Invoke();
    }
    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
    bool ICommand.CanExecute(object parameter)
    {
        return CanExecute();
    }

    void ICommand.Execute(object parameter)
    {
        Execute();
    }
}
public class DelegateCommand<T> : ObservableObject, ICommand
{
    private readonly Action<T> _execute;
    private readonly Func<T, bool> _canExecute;

    public DelegateCommand(Action<T> execute, Func<T, bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(T parameter)
    {
        return _canExecute?.Invoke(parameter) ?? true;
    }
    public void Execute(T parameter)
    {
        _execute.Invoke(parameter);
    }
    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
    bool ICommand.CanExecute(object parameter)
    {
        return CanExecute((T)parameter);
    }

    void ICommand.Execute(object parameter)
    {
        Execute((T)parameter);
    }
}

CodePudding user response:

changed the link to the second example https://habr.com/ru/post/338518/

  • Related