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/