I am developing something using wpf mvvm.
Currently, my implementation style is a bit unusual.
This is to prevent the sauce from gathering in one place.
Async void should be avoided, but I don't know how to avoid it.
Any solution?
Below is the source I used. (RelayCommand is generic and I need to create a new async command.)
<View.xaml>, <View.xaml.cs>
<BusyIndicator IsBusy="{IsBusy, mode=TwoWay}">
<Listbox ItemsSource="{Binding Models}"/>
<Button Command="{Binding OnClickButtonCommand}"
CommandParameter="whether the value(string) exists or not"/>
</BusyIndicator>
this.DataContext = new ViewModel();
<ViewModel.cs>
public ViewModel() { }
private isBusy;
public isBusy
{
get => isBusy;
set => SetField(ref isBusy, value);
}
public ObservableCollection<Model> Models { get; set; }
public ICommand OnClickButtonCommand { get => new ButtonCommand(this); }
<ButtonCommand.cs>
public ButtonCommand : RelayCommand
{
private ViewModel viewModel;
public ViewModel(ViewModel viewModel)
{
this.viewModel = viewModel
}
public override async void Execute(object obj)
{
viewModel.IsBusy = true;
await Task.Delay(1500);
viewModel.Models = new ObservableCollection<Model>(await Data());
viewModel.IsBusy = false;
}
private async Task<List<Model>> Data()
{
await Task.Delay(100);
var data = new List<Model>();
...
return data;
}
}
*Inserting await delay is to prevent debugging warning.
<CommandBase.cs>
public interface IRelayCommand : ICommand
{
new void Execute(object obj);
}
public abstract class RelayCommand : IRelayCommand
{
public RelayCommand() { }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
bool ICommand.CanExecute(object obj)
{
if (obj?.ToString().Length == 0)
{
return false;
}
else
{
return true;
}
//return canExecute == null || this.canExecute.Invoke(parameter);
}
public abstract void Execute(object obj);
}
And if my code is weird or there is better code, please let me know!
Thanks. :)
https://johnthiriet.com/mvvm-going-async-with-async-command/
I've seen a lot of this site and other samples, but it's an example using a typical fuction,
so I can't apply it...
I'm using the AsyncCommand implementation from James Montemagno's MVVM-Helpers library (currently also implemented in the Xamarin Community Toolkit), you can take a look at the implementation here: AsyncCommand implementation
You can use it like this:
Install the Refactored.MvvmHelpers nuget package.
MyViewModel.cs
public class MyViewModel {
//other code goes here (Models collection, IsBusy, etc)
public ICommand DoWorkCommand => new AsyncCommand(DoWorkAsync);
public ICommand DoWorkWithParameterCommand => new AsyncCommand<bool>(DoWorkWithParameterAsync);
private async Task DoWorkAsync(){
//simulate work
await Task.Delay(1000);
}
private async Task DoWorkWithParameterAsync(bool itemExists){
//simulate work
await Task.Delay(1000);
}
}
MyView.cs
<BusyIndicator IsBusy="{IsBusy, mode=TwoWay}">
<Listbox ItemsSource="{Binding Models}"/>
<Button Command="{Binding DoWorkCommand}" />
<Button Command="{Binding DoWorkWithParameterCommand}" CommandParameter="False"/>
</BusyIndicator>
An example of my implementation of base classes:
BaseInpc,
RelayCommand, RelayCommandAsync,
RelayCommand<T>, RelayCommandAsync<T>.
There are a lot of verbose documentation tags in the code.
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Simplified
{
/// <summary>Base class with implementation of the <see cref="INotifyPropertyChanged"/> interface.</summary>
public abstract class BaseInpc : INotifyPropertyChanged
{
/// <inheritdoc cref="INotifyPropertyChanged"/>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>The protected method for raising the event <see cref = "PropertyChanged"/>.</summary>
/// <param name="propertyName">The name of the changed property.
/// If the value is not specified, the name of the method in which the call was made is used.</param>
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary> Protected method for assigning a value to a field and raising
/// an event <see cref = "PropertyChanged" />. </summary>
/// <typeparam name = "T"> The type of the field and assigned value. </typeparam>
/// <param name = "propertyFiled"> Field reference. </param>
/// <param name = "newValue"> The value to assign. </param>
/// <param name = "propertyName"> The name of the changed property.
/// If no value is specified, then the name of the method
/// in which the call was made is used. </param>
/// <remarks> The method is intended for use in the property setter. <br/>
/// To check for changes,
/// used the <see cref = "object.Equals (object, object)" /> method.
/// If the assigned value is not equivalent to the field value,
/// then it is assigned to the field. <br/>
/// After the assignment, an event is created <see cref = "PropertyChanged" />
/// by calling the method <see cref = "RaisePropertyChanged (string)" />
/// passing the parameter <paramref name = "propertyName" />. <br/>
/// After the event is created,
/// the <see cref = "OnPropertyChanged (string, object, object)" />
/// method is called. </remarks>
protected void Set<T>(ref T propertyFiled, in T newValue, [CallerMemberName] in string propertyName = null)
{
if (!Equals(propertyFiled, newValue))
{
T oldValue = propertyFiled;
propertyFiled = newValue;
RaisePropertyChanged(propertyName);
OnPropertyChanged(propertyName, oldValue, newValue);
}
}
/// <summary> The protected virtual method is called after the property has been assigned a value and after the event is raised <see cref = "PropertyChanged" />. </summary>
/// <param name = "propertyName"> The name of the changed property. </param>
/// <param name = "oldValue"> The old value of the property. </param>
/// <param name = "newValue"> The new value of the property. </param>
/// <remarks> Can be overridden in derived classes to respond to property value changes. <br/>
/// It is recommended to call the base method as the first operator in the overridden method. <br/>
/// If the overridden method does not call the base class, then an unwanted change in the base class logic is possible. </remarks>
protected virtual void OnPropertyChanged(in string propertyName, in object oldValue, in object newValue) { }
}
}
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
namespace Simplified
{
/// <summary> A class that implements <see cref = "ICommand" />. <br/>
/// Implementation taken from <see href="https://www.cyberforum.ru/wpf-silverlight/thread2390714-page4.html#post13535649"/>
/// and added a constructor for methods without a parameter.</summary>
public class RelayCommand : ICommand
{
protected readonly CanExecuteHandler<object> canExecute;
protected readonly ExecuteHandler<object> execute;
private readonly EventHandler requerySuggested;
/// <inheritdoc cref="ICommand.CanExecuteChanged"/>
public event EventHandler CanExecuteChanged;
/// <summary> Command constructor. </summary>
/// <param name = "execute"> Command method to execute. </param>
/// <param name = "canExecute"> Method that returns the state of the command. </param>
public RelayCommand(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute = null)
: this()
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
this.canExecute = canExecute;
}
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
: this
(
p => execute(),
p => canExecute?.Invoke() ?? true
)
{ }
private readonly Dispatcher dispatcher = Application.Current.Dispatcher;
/// <summary> The method that raises the event <see cref="CanExecuteChanged"/>.</summary>
public void RaiseCanExecuteChanged()
{
if (dispatcher.CheckAccess())
{
invalidate();
}
else
{
_ = dispatcher.BeginInvoke(invalidate);
}
}
private readonly Action invalidate;
private RelayCommand()
{
invalidate = () => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
requerySuggested = (o, e) => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
CommandManager.RequerySuggested += requerySuggested;
}
/// <inheritdoc cref="ICommand.CanExecute(object)"/>
public bool CanExecute(object parameter)
{
return canExecute?.Invoke(parameter) ?? true;
}
/// <inheritdoc cref="ICommand.Execute(object)"/>
public void Execute(object parameter)
{
execute?.Invoke(parameter);
}
}
}
using System;
using System.Collections;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Simplified
{
/// <summary>A class that implements asynchronous commands with the execution
/// of the <see cref="RelayCommand.Execute(object)"/> method in Task. </summary>
/// <remarks>Only one call to the <see cref="RelayCommand.Execute(object)"/>
/// method is allowed at a time.
/// During the execution of the method, the <see cref="IsBusy"/> flag is set and
/// the <see cref="RelayCommand.CanExecute(object)"/> method of the command will return false.
/// The <see cref="INotifyDataErrorInfo"/> interface is implemented to notify about
/// an erroneous call to the <see cref="RelayCommand.Execute(object)"/> method before
/// the previous execution completes and about exceptions during the
/// execution of <see cref="RelayCommand.Execute(object)"/>.
/// To notify about changes in property values, the <see cref="INotifyPropertyChanged"/>
/// interface is implemented.</remarks>
public class RelayCommandAsync : RelayCommand, ICommand, INotifyPropertyChanged, INotifyDataErrorInfo
{
/// <inheritdoc cref="INotifyPropertyChanged.PropertyChanged"/>
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc cref="INotifyDataErrorInfo.ErrorsChanged"/>
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
/// <summary>The command is in the execution state of the <see cref="RelayCommand.Execute(object)"/> method.</summary>
public bool IsBusy { get; private set; }
/// <inheritdoc cref="INotifyDataErrorInfo.HasErrors"/>
public bool HasErrors { get; private set; }
/// <summary>Exception from the last execution of the <see cref="RelayCommand.Execute(object)"/> method.</summary>
public Exception ExecuteException { get; private set; }
// A flag indicating a "call to execute busy a command" error.
private bool isBusyExecuteError;
/// <summary>Sets a value to the <see cref="IsBisy"/> property and notifies of its change.</summary>
/// <param name="isBusy">The value for the property.</param>
protected void SetIsBusy(bool isBusy)
{
if (IsBusy != isBusy)
{
IsBusy = isBusy;
PropertyChanged?.Invoke(this, Args.IsBusyPropertyEventArgs);
RaiseCanExecuteChanged();
}
}
/// <summary>Sets the HasErrors property and reports an entity-level error.</summary>
/// <param name="hasErrors">The value for the property.</param>
protected void SetEntityHasErrors(bool hasErrors)
=> SetHasErrors(hasErrors, Args.EntityLevelErrorsEventArgs);
/// <summary>Sets the HasErrors property and reports an entity or property level error.</summary>
/// <param name="hasErrors">The value for the property.</param>
/// <param name="args">Argument with data about the error level.</param>
protected void SetHasErrors(bool hasErrors, DataErrorsChangedEventArgs args)
{
if (HasErrors != hasErrors)
{
HasErrors = hasErrors;
PropertyChanged?.Invoke(this, Args.HasErrorsPropertyEventArgs);
}
ErrorsChanged?.Invoke(this, args);
}
/// <summary>Sets a value to the <see cref="ExecuteException"/> property and notifies of its change.</summary>
/// <param name="exception">The value for the property.</param>
protected void SetExecuteException(Exception exception)
{
if (ExecuteException != exception)
{
ExecuteException = exception;
PropertyChanged?.Invoke(this, Args.ExecuteExceptionPropertyEventArgs);
}
}
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
public RelayCommandAsync(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute = null)
: this(new AsyncData(execute, canExecute))
{ }
/// <inheritdoc cref="RelayCommand(ExecuteHandler, CanExecuteHandler)"/>
public RelayCommandAsync(ExecuteHandler execute, CanExecuteHandler canExecute = null)
: this(new AsyncData(execute, canExecute))
{ }
// The field for storing additional, auxiliary data generated
// during the generation of the asynchronous method, wrapping
// the one obtained in the constructor.
private readonly AsyncData data;
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
protected RelayCommandAsync(AsyncData data)
: base(data.ExecuteAsync, data.CanExecuteAsync)
{
this.data = data;
this.data.commandAsync = this;
}
/// <inheritdoc cref="INotifyDataErrorInfo.GetErrors(string)"/>
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
if (isBusyExecuteError)
{
yield return Args.BusyExecuteErrorMessage;
}
if (ExecuteException != null)
{
yield return ExecuteException;
}
}
IEnumerable errors = GetErrorsOverride(propertyName);
if (errors != null)
{
foreach (var error in errors)
{
yield return error;
}
}
}
/// <summary>Method overridden in derived classes to add error information</summary>
/// <param name="propertyName">The name of the property to retrieve validation
/// errors for; or null or Empty, to retrieve entity-level errors.</param>
/// <returns>The validation errors for the property or entity.</returns>
protected virtual IEnumerable GetErrorsOverride(string propertyName)
=> null;
/// <summary>A class with persistent elements to avoid re-creating them frequently.</summary>
public static class Args
{
public const string BusyExecuteErrorMessage = "Called the execution of a command when it is busy.";
public static readonly PropertyChangedEventArgs IsBusyPropertyEventArgs = new PropertyChangedEventArgs(nameof(IsBusy));
public static readonly PropertyChangedEventArgs HasErrorsPropertyEventArgs = new PropertyChangedEventArgs(nameof(HasErrors));
public static readonly PropertyChangedEventArgs ExecuteExceptionPropertyEventArgs = new PropertyChangedEventArgs(nameof(ExecuteException));
public static readonly DataErrorsChangedEventArgs EntityLevelErrorsEventArgs = new DataErrorsChangedEventArgs(string.Empty);
}
/// <summary>A class for storing additional, auxiliary data and methods that are generated
/// when generating asynchronous methods that wrap the synchronous methods received
/// in the constructor.</summary>
protected class AsyncData
{
public RelayCommandAsync commandAsync;
public async void ExecuteAsync(object parameter)
{
if (commandAsync.IsBusy)
{
commandAsync.isBusyExecuteError = true;
commandAsync.SetEntityHasErrors(true);
}
else
{
commandAsync.SetIsBusy(true);
try
{
await Task.Run(() => execute(parameter));
commandAsync.isBusyExecuteError = false;
commandAsync.SetExecuteException(null);
commandAsync.SetEntityHasErrors(false);
}
catch (Exception ex)
{
commandAsync.SetExecuteException(ex);
commandAsync.SetEntityHasErrors(true);
}
finally
{
commandAsync.SetIsBusy(false);
}
}
}
public CanExecuteHandler<object> CanExecuteAsync { get; }
private bool canExecuteNullAsync(object parameter) => !commandAsync.IsBusy;
private bool canExecuteAsync(object parameter) => !commandAsync.IsBusy && canExecute(parameter);
private readonly ExecuteHandler<object> execute;
private readonly CanExecuteHandler<object> canExecute;
/// <inheritdoc cref="AsyncData(ExecuteHandler, CanExecuteHandler)"/>
public AsyncData(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute)
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
if (canExecute == null)
{
CanExecuteAsync = canExecuteNullAsync;
}
else
{
this.canExecute = canExecute;
CanExecuteAsync = canExecuteAsync;
}
}
/// <summary>Creates an instance.</summary>
/// <param name="execute">Synchronous Execute method.</param>
/// <param name="canExecute">Synchronous CanExecute method.</param>
public AsyncData(ExecuteHandler execute, CanExecuteHandler canExecute)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
this.execute = p => execute();
if (canExecute == null)
{
CanExecuteAsync = canExecuteNullAsync;
}
else
{
this.canExecute = p => canExecute();
CanExecuteAsync = canExecuteAsync;
}
}
}
}
}
Some of the classes did not fit into the answer.
Take them from here.
An example of using RelayCommand for your code.
public class ViewModel : BaseInpc
{
private bool _isBusy;
public bool IsBusy
{
get => _isBusy;
set => Set(ref _isBusy, value);
}
public ObservableCollection<Model> Models { get; set; }
// Under no circumstances should you get an instance of a command in this way.
// As such, each time the property is accessed, a NEW instance of the command is created.
// As a result, in each element of the Window, in the ViewMode, etc. there are different instances everywhere.
// This is a very common cause of various bugs.
//
//public ICommand OnClickButtonCommand { get => new ButtonCommand(this); }
// One of the options for the correct team creation.
private RelayCommand _onClickButtonCommand;
public RelayCommand OnClickButtonCommand => _onClickButtonCommand
?? (_onClickButtonCommand = new RelayCommand(ExecuteOnClick));
// Executing command method
private async void ExecuteOnClick(object parameter)
{
IsBusy = true;
await Task.Delay(1500);
Models = new ObservableCollection<Model>(await Data());
IsBusy = false;
}
private async Task<List<Model>> Data()
{
await Task.Delay(100);
var data = new List<Model>();
// Some code
return data;
}
}
In such an implementation, you can create a method for each command, its own IsBusy property and other members of which it needs.
But to a large extent, all this (except for the synchronous executing method) can be encapsulated in the command itself.
Encapsulating the execution method does not make sense, since it is because of it that the command instances differ from each other.
My example using asynchronous commands
In the example, a blinking background, in order to understand at what moment the GUI lags occur - blinking during lags stops.
There are also several buttons to demonstrate the difference between synchronous and asynchronous method execution, and how errors and exceptions in them affect applications.
Click them in various combinations.
When errors and exceptions appear, they are displayed under the buttons at the bottom of the window.
If you have any questions during testing - ask.
using Simplified;
using System;
using System.Threading;
namespace RelayCommandAsyncTest
{
public class TestViewModel : BaseInpc
{
private int _count;
// Command execution counter (common for all commands)
public int Count { get => _count; set => Set(ref _count, value); }
// Аsynchronous command
public RelayCommandAsync TestCommandAsync { get; }
// Synchronous command
public RelayCommand TestCommand { get; }
// Synchronous command with Lags
public RelayCommand TestLagsCommand { get; }
// Synchronous command executing asynchronous command
// without checking it IsBusy.
public RelayCommand TestExecuteCommandAsync { get; }
// Long running execution method for validating
// an asynchronous command
private void ExecuteForAsync(object obj)
{
Thread.Sleep(2000);
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
}
// Long running execution method for creating
// lags in a synchronous command
private void ExecuteLags(object obj)
{
isExecuteLagsCommand = true;
TestLagsCommand.RaiseCanExecuteChanged();
Thread.Sleep(2000);
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
isExecuteLagsCommand = false;
TestLagsCommand.RaiseCanExecuteChanged();
}
bool isExecuteLagsCommand;
// Fast executing method for checking lags with
// a synchronous command
private void Execute(object obj)
{
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
}
// A command that executes an asynchronous command without checking if it is busy.
// If the asynchronous command is busy, it will generate a validation error.
private void ExecuteCommandAsync(object obj)
{
Count++;
TestCommandAsync.Execute(obj);
}
public TestViewModel()
{
TestCommandAsync = new RelayCommandAsync(ExecuteForAsync);
TestCommand = new RelayCommand(Execute);
TestLagsCommand = new RelayCommand(ExecuteLags, p => !isExecuteLagsCommand);
TestExecuteCommandAsync = new RelayCommand(ExecuteCommandAsync);
timer = new Timer
(
_ => TestCommandAsync.RaiseCanExecuteChanged(),
null,
0,
5000
);
}
// Timer to check for raising CanExecuteChanged from any thread.
private readonly Timer timer;
}
}
<Window x:Class="RelayCommandAsyncTest.CommandTestWindow"
x:Name="main"
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:RelayCommandAsyncTest" xmlns:common="clr-namespace:Common;assembly=Common"
mc:Ignorable="d"
Title="CommandTestWindow" Height="1000" Width="800"
FontSize="30">
<FrameworkElement.Triggers>
<EventTrigger RoutedEvent="Loaded" SourceName="main">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="FlashingBrush"
Storyboard.TargetProperty="Color"
Duration="0:0:0.5"
To="LightYellow"
AutoReverse="True"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</FrameworkElement.Triggers>
<FrameworkElement.DataContext>
<local:TestViewModel/>
</FrameworkElement.DataContext>
<FrameworkElement.Resources>
<ControlTemplate x:Key="validationTemplate">
<Border BorderThickness="5"
BorderBrush="Red">
<Grid IsHitTestVisible="False">
<Viewbox>
<TextBlock Foreground="Red" FontSize="80"
Text="! ! !"/>
</Viewbox>
<AdornedElementPlaceholder/>
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="Button">
<Setter Property="Validation.ErrorTemplate" Value="{DynamicResource validationTemplate}"/>
</Style>
</FrameworkElement.Resources>
<Grid>
<Grid.Background>
<SolidColorBrush x:Name="FlashingBrush" Color="LightGreen"/>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto" />
<RowDefinition/>
</Grid.RowDefinitions>
<UniformGrid Columns="2">
<Button x:Name="button" Content="Async throw Exception"
Command="{Binding TestCommandAsync}"
CommandParameter="qwerty" Margin="15"/>
<Button Content="Async Normal"
Command="{Binding TestCommandAsync}"
CommandParameter="2000" Margin="15"/>
<Button Content="Sync Lags"
Command="{Binding TestLagsCommand}"
CommandParameter="2000" Margin="15"/>
<Button Content="IsBusy is not checked"
Command="{Binding TestExecuteCommandAsync}" Margin="15"
CommandParameter="0">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding TestCommandAsync.HasErrors}"
Value="True">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontWeight" Value="ExtraBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Content="Sync throw Exception"
Command="{Binding TestCommand}"
CommandParameter="qwerty" Margin="15"/>
<Button Content="Sync Normal"
Command="{Binding TestCommand}" Margin="15"
CommandParameter="2000"/>
</UniformGrid>
<TextBlock Grid.Row="1" Margin="5">
<Run Text="IsBusy:"/>
<Run Text="{Binding TestCommandAsync.IsBusy, Mode=OneWay}"/>
<Run Text=" "/>
<Run Text="HasErrors:"/>
<Run Text="{Binding TestCommandAsync.HasErrors, Mode=OneWay}"/>
<Run Text=" "/>
<Run Text="Count:"/>
<Run Text="{Binding Count}"/>
</TextBlock>
<ScrollViewer Grid.Row="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding ElementName=button, Path=(Validation.Errors)}"
DisplayMemberPath="ErrorContent"
FontSize="15"/>
</ScrollViewer>
</Grid>
</Window>
I have a XAML and CS file for a UserControl. I have my data stored in a Singleton class which implements INotifyPropertyChanged, and it binds to a ListBox in the UserControl.
This is the XAML databinding:
<ListBox Name="ModsListBox"
ItemsSource="{Binding ModControls}"
Visibility="Visible"
Width="350"
Height="Auto">
</ListBox>
The datacontext is being set in the CS file as followings:
DataContext = ModDirector.Instance;
InitializeComponent();
In the code there is a method for adding elements which adds the datastructure which is being bound and then calls OnPropertyChanged(), however the UI never updates.
/// <summary>
/// Adds a mod and sends event to update UI elements bound to ModContols
/// </summary>
/// <param name="modUserControl"></param>
/// <param name="index"></param>
public void AddMod(ModUserControl modUserControl, int? index = null)
{
if (index != null)
{
_modControls.Insert(index.Value, modUserControl);
}
else
{
_modControls.Add(modUserControl);
}
OnPropertyChanged("ModControls");
}
Just for completion here is the property it is being bound to:
/* Properties */
public List<ModUserControl> ModControls
{
get { return _modControls; }
set
{
_modControls = value;
OnPropertyChanged();
}
}
/* End Properties */
And the code for OnPropertyChanged
/// <summary>
/// Fires PropertyChanged event notifying the UI elements bound
/// </summary>
/// <param name="propertyName"></param>
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Is there some reason the event would not be propagated?
Change your List<ModUserControl> to ObservableCollection<ModUserControl>.
Your public List<ModUserControl> ModControls should probably be an ObservableCollection<> instead, so you can remove your manual calls to OnPropertyChanged("ModControls");. Your ModControls did not actually change. It's still the very same instance.
In my WPF MVVM project application, click event command (AddUserCmd) is not get firing. Can you please find out the issue from my code part?
User.xaml
<Window x:Class="JP.User"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window" Title="User" WindowStartupLocation="CenterScreen"
Width="795" Height="500">
...
...
<TextBox Canvas.Left="114" TextWrapping="Wrap" Canvas.Top="98" RenderTransformOrigin="-0.093,-0.274" Width="157" Height="20" Background="{x:Null}" Foreground="#FF6B6C6C" Text="{Binding Path=Name}">
<TextBox Canvas.Left="114" TextWrapping="Wrap" Canvas.Top="129" RenderTransformOrigin="-0.093,-0.274" Width="157" Height="21" Background="{x:Null}" Foreground="#FF6B6C6C" Text="{Binding Path=Pwd}">
...
...
<Button Content="Save" Height="23" Name="button2" Width="75" Margin="605,0,0,0" Command="{Binding AddUserCmd}"/>
...
...
</Window>
UserViewModel.cs
public class UserViewModel : INotifyPropertyChanged
{
#region Private Variables
//The Variables are meant to be readonly as we mustnot change the address of any of them by creating new instances.
//Problem with new istances is that since address changes the binding becomes invalid.
//Instantiate all the variables in the constructor.
private readonly JanathaPOS.Model.User _user;
private readonly ObservableCollection<JanathaPOS.Model.User> _users;
private readonly UserManager _userManager;
private readonly ICommand _addUserCmd;
//private readonly ICommand _deleteUserCmd;
#endregion
#region Constructors
/// <summary>
/// Instatiates all the readonly variables
/// </summary>
public UserViewModel()
{
_user = new JanathaPOS.Model.User();
_userManager = new UserManager();
_users = new ObservableCollection<Model.User>();
_addUserCmd = new RelayCommand(Add, CanAdd);
}
#endregion
#region Public Properties
/// <summary>
/// Gets or Sets User Name. Ready to be binded to UI.
/// Impelments INotifyPropertyChanged which enables the binded element to refresh itself whenever the value changes.
/// </summary>
public string Name
{
get
{
return _user.Name;
}
set
{
_user.Name = value;
OnPropertyChanged("Name");
}
}
/// <summary>
/// Gets or Sets User Password. Ready to be binded to UI.
/// Impelments INotifyPropertyChanged which enables the binded element to refresh itself whenever the value changes.
/// </summary>
public string Pwd
{
get
{
return _user.Pwd;
}
set
{
_user.Pwd = value;
OnPropertyChanged("Pwd");
}
}
/// <summary>
/// Gets the Users. Used to maintain the User List.
/// Since this observable collection it makes sure all changes will automatically reflect in UI
/// as it implements both CollectionChanged, PropertyChanged;
/// </summary>
public ObservableCollection<JanathaPOS.Model.User> Users
{
get
{
return _users;
}
}
#endregion
#region Command Properties
/// <summary>
/// Gets the AddPatientCommand. Used for Add patient Button Operations
/// </summary>
public ICommand AddUserCmd
{
get
{
return _addUserCmd;
}
}
#endregion
#region Commands
#region AddCommand
/// <summary>
/// Add operation of the AddUserCmd.
/// Operation that will be performormed on the control click.
/// </summary>
/// <param name="obj"></param>
public void Add(object obj)
{
//Always create a new instance of patient before adding.
//Otherwise we will endup sending the same instance that is binded, to the BL which will cause complications
var user = new JanathaPOS.Model.User { Id = "123", Name = Name, Pwd = Pwd };
//Add patient will be successfull only if the patient with same ID does not exist.
if (_userManager.Add(user))
{
Users.Add(user);
ResetUser();
MessageBox.Show("User add Successful!");
}
else
{
MessageBox.Show("User with this ID already exists!");
}
}
/// <summary>
/// CanAdd operation of the AddUserCmd.
/// Tells us if the control is to be enabled or disabled.
/// This method will be fired repeatedly as long as the view is open.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool CanAdd(object obj)
{
//Enable the Button only if the mandatory fields are filled
if (Name != string.Empty && Pwd != string.Empty)
return true;
return false;
}
#endregion
#endregion
#region Private Methods
/// <summary>
/// Resets the Patient properties which will in turn auto reset the UI aswell
/// </summary>
private void ResetUser()
{
//Id = string.Empty;
Name = string.Empty;
Pwd = string.Empty;
}
#endregion
#region INotifyPropertyChanged Members
/// <summary>
/// Event to which the view's controls will subscribe.
/// This will enable them to refresh themselves when the binded property changes provided you fire this event.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// When property is changed call this method to fire the PropertyChanged Event
/// </summary>
/// <param name="propertyName"></param>
public void OnPropertyChanged(string propertyName)
{
//Fire the PropertyChanged event in case somebody subscribed to it
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
RelayCommand.cs
public class RelayCommand: ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
I've searched and searched but can't obtain a proper, helpful answer.
I have a MainWindow wpf window. Its DataContext is set to its ViewModel.
I have a ListView which is binded to an ObservableCollection in the ViewModel:
<ListView Grid.Row="1" Grid.Column="0" Margin="2" Name="sources_ListView" Grid.RowSpan="1" ItemsSource="{Binding Path=Sources}">
<ListView.View>
<GridView>
<GridViewColumn Width="290" Header="Name"
DisplayMemberBinding="{Binding Path=OriginalPath}"/>
<GridViewColumn Width="80" Header="Type"
DisplayMemberBinding="{Binding Path=Type}"/>
</GridView>
</ListView.View>
</ListView>
RelayCommand:
public ICommand BrowseFileFolderCommand
{
get
{
if (_browseFileFolderCommand == null)
{
_browseFileFolderCommand = new RelayCommand(o =>
{
_sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
}, null);
}
return _browseFileFolderCommand;
}
}
Now obviously what the Lambda function does, wouldn't work in the real world as I have taken it out of context, but accept the fact that it does add SourceItem to the ObservableCollection _sources and that there is a Public Sources which gets the _sources. I have also made the type that ObservableCollection takes use INotifyChangedProperty.
When I use that RelayCommand which is inside a button which adds a source to the ObservableCollection, the ListView doesn't update?
Thanks for any help
EDIT SourceItem:
public class SourceItem : ISourceItem, INotifyPropertyChanged
{
DirectoryInfo _sourceFolder;
public DirectoryInfo SourceFolder { get { return _sourceFolder; } private set { _sourceFolder = value; } }
FileInfo _sourceFile;
public FileInfo SourceFiles { get { return _sourceFile; } private set { _sourceFile = value; } }
string _originalPath;
public string OriginalPath { get { return _originalPath; } private set { _originalPath = value; OnPropertyChanged("OriginalPath"); } }
bool _isFolder;
public bool IsFolder { get { return _isFolder; } }
// display friendly property of IsFolder
public string Type { get { return _isFolder == true ? "Folder" : "File"; } }
public SourceItem(string originalPath, DirectoryInfo sourceFolder)
{
_originalPath = originalPath;
_sourceFolder = sourceFolder;
_sourceFile = null;
_isFolder = true;
}
public SourceItem(string originalPath, FileInfo sourceFile)
{
_originalPath = originalPath;
_sourceFile = sourceFile;
_sourceFolder = null;
_isFolder = false;
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real.
// public, instance property on this object
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = String.Format("Invalid property name: {0}", propertyName);
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion
}
Use the public version of the property to add the new item
Sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
You are currently adding the item to the private version of your property (_sources), while your UI is bound to the public version of the property (Sources), so your UI does not get the CollectionChanged notification the private version of the property raises, so does not know it needs to update.
The alternative is to simply raise the PropertyChanged event for your class manually to tell the UI to update. This is usually what I do when I want to add a lot of items to my collection at the same time, but only have the UI update once.
_sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
RaisePropertyChanged("Sources");
The mainpage:
MainPage.xaml
<Canvas x:Name="LayoutRoot" Background="White">
</Canvas>
MainPage.xaml.cs
List<Usol> list = new List<Usol>();
for (int i = 0; i < 10; i++)
{
var element = new Usol();
list.Add(element);
Canvas.SetTop(element, i * 25);
LayoutRoot.Children.Add(list[i]);
}
foreach (var item in list)
{
item.context.name = "Varken";
}
A usercontrol
Usol.xaml
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Name}" />
</Grid>
Usol.xaml.cs
public Context context;
public Usol()
{
InitializeComponent();
context = new Context();
this.DataContext = context;
}
A class
Context.cs
public class Context : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region Fields
/// <summary>
/// Field Declaration for the <see cref="Name"/>
/// </summary>
private string name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the Name
/// </summary>
public string Name
{
get { return name; }
set
{
if (this.name != value)
{
this.name = value;
OnPropertyChanged("Name");
}
}
}
#endregion
}
Situation
I have created this small test application to copy a problem I have in a bigger application. It works about the same way (not exactly, but close enough).
It adds several custom made usercontrols and each get a own instance of a datacontext class.
However, none of the properties are willing to update themselfs due to a empty PropertyChangedEventHandler.
Question
Why is public event PropertyChangedEventHandler PropertyChanged; always null?
Context.cs needs to implement INotifyPropertyChanged interface. Are you doing that?
Edit: Post your update.
I have generally seen this kind of problem when programmers create "two" instances of Model/ViewModel. While you attach one instance with View, it's always the other one that gets update (which ofcourse will have a null PropertyChanged subscribers). Thus, you must make sure that your view is using the same instance as being updated at other parts.
Hope my point is clear.
Your code is wrong,
OnPropertyChanged("Name"); <-- should update "name" not "Name"
You are firing event saying that "Name" is changed, but name of property is "name", C# and binding are case sensitive.
Change it to,
#region Fields
/// <summary>
/// Field Declaration for the <see cref="name"/>
/// </summary>
private string _Name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the name
/// </summary>
public string Name
{
get { return _Name; }
set
{
if (this._Name != value)
{
this._Name = value;
OnPropertyChanged("Name");
}
}
}
#endregion
From C# 6 on wards, please use nameof() keyword...
#region Fields
/// <summary>
/// Field Declaration for the <see cref="name"/>
/// </summary>
private string _Name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the name
/// </summary>
public string Name
{
get { return _Name; }
set
{
if (this._Name != value)
{
this._Name = value;
OnPropertyChanged(nameof(Name));
}
}
}
#endregion