How to rebind SourceItems based on a static property that becomes dynamic? - c#

In my XAML code I've got a combo box that is bound to a static property as shown below.
<ComboBox x:Name="DifferentKinds"
ItemsSource="{x:Static local:MainWindow.DifferentKinds}"/>
And the code for the property and its source.
public static Kind[] DifferentKinds
=> (Kind[])Enum.GetValues(typeof(Kind));
public enum Kind { WeeHee, BuuHuu }
I just learned that there'll be more kinds in the future. They won't be created particularly often but it's uncertain how many they might become with time. So, instead of adding new elements to the enum, I'll read in these from the DB.
For the simplicity of the example, let's say we read in those values every time the property is accessed. The solution becomes a private fields that is read in from the DB before the execution of InitializeComponent() starts. Then, I serve those values as a static property still, like so.
public MainWindow()
{
PopulateDifferentKinds();
InitializeComponent();
}
private static IEnumerable<Kind> _allDifferentKinds;
public static IEnumerable<Kind> AllDifferentKinds
=> _allDifferentKinds.Where(element => element.Active);
public class Kind
{
public String Name { get; set; }
public bool Active { get; set; }
public override string ToString() { return Name; }
}
Is this approach creating a huge problem that I miss to see?
Is there a better way to bind the items in the bombo box to the values from DB?

The main problem I see here is that calling the PopulateDifferentKinds method in the view's constructor will create a performance problem. While this method is running and the database is being queried, your UI is being blocked.
This could be improved using a class that loads your data on a background thread and uses a PropertyChanged event to signal that the data has been loaded:
public class Kind
{
public string Name { get; set; }
public bool Active { get; set; }
public int Value { get; set; }
}
public class AppEnumValues : INotifyPropertyChanged
{
private static readonly Lazy<AppEnumValues> current
= new Lazy<AppEnumValues>(() => new AppEnumValues(), LazyThreadSafetyMode.ExecutionAndPublication);
public static AppEnumValues Current
{
get { return current.Value; }
}
public Kind[] AllDifferentKinds { get; private set; }
public bool IsLoaded { get; private set; }
private AppEnumValues()
{
Task.Run(() => this.LoadEnumValuesFromDb())
.ContinueWith(t => this.OnAllPropertiesChanged());
}
protected virtual void OnAllPropertiesChanged()
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(string.Empty));
}
}
private void LoadEnumValuesFromDb()
{
// This simulates some latency
Thread.Sleep(2000);
// Call your data service here and load the values
var kinds = new[]
{
new Kind {Active = true, Name = "WeeHee", Value = 1},
new Kind {Active = true, Name = "BuuHuu", Value = 2}
};
this.AllDifferentKinds = kinds;
this.IsLoaded = true;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
You could extend this with properties for each extensible "enum" you need in your application. Implementing the Singleton pattern, this would load its data in background the first time it is used. You could bind your ComboBoxes like this:
<ComboBox ItemsSource="{Binding Source={x:Static wpfApplication2:AppEnumValues.Current},Path=AllDifferentKinds}"
IsEnabled="{Binding Source={x:Static wpfApplication2:AppEnumValues.Current},Path=IsLoaded}"
DisplayMemberPath="Name" />
While the data is being loaded, the ComboBox would be disabled.
I would recommend looking into MVVM and Dependency Injection. This will enhance your WPF application architecture and make things like that easy: You wouldn't provide a static property or singleton, which has bad testability and extensibility, but you could use constructor injection to give the AppEnumValues provider into your View Model and then bind your view to it.

Related

C# WPF MVVM undo system with INotifyPropertyChanged

I am attempting to make an undo system, where when a property on an object that implements INotifyPropertyChanged is changed, the property name and its old value is pushed onto a stack via a KeyValuePair. When the user clicks "Undo" it then pops from the stack and uses reflection to set the property's value to its old value.
The problem with this is that it calls OnPropertyChanged again, so the property and its restored value is added to the undo stack a second time. On the other hand, I still want it to call OnPropertyChanged since I want the view to update its bindings.
There's obviously something wrong with how I'm designing it, but I can't seem to figure out another way of going about it.
Here's my model
internal class MyModel : INotifyPropertyChangedExtended
{
private string testProperty1 = "";
public string TestProperty1
{
get { return testProperty1; }
set {
var oldValue = testProperty1;
testProperty1 = value;
OnPropertyChanged(nameof(TestProperty1), oldValue);
}
}
private string testProperty2 = "";
public string TestProperty2
{
get { return testProperty2; }
set {
var oldValue = testProperty2;
testProperty2 = value;
OnPropertyChanged(nameof(TestProperty2), oldValue);
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName, object oldValue)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgsExtended(propertyName, oldValue));
}
}
}
Here's my INotifyPropertyChangedExtended interface
public class PropertyChangedEventArgsExtended : PropertyChangedEventArgs
{
public virtual object OldValue { get; private set; }
public PropertyChangedEventArgsExtended(string propertyName, object oldValue)
: base(propertyName)
{
OldValue = oldValue;
}
}
public class INotifyPropertyChangedExtended : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName, object oldValue)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgsExtended(propertyName, oldValue));
}
}
And here's my view model
internal class MyViewModel
{
public MyModel MyModel { get; set; } = new();
public Stack<KeyValuePair<string, object>> PropertyStateStack = new();
public RelayCommand Undo { get; set; }
public MyViewModel()
{
SetupCommands();
MyModel.PropertyChanged += MyModel_PropertyChanged;
}
private void MyModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var args = e as PropertyChangedEventArgsExtended;
if (args.OldValue != null)
{
PropertyStateStack.Push(new KeyValuePair<string, object>(args.PropertyName, args.OldValue));
}
}
private void SetupCommands()
{
Undo = new RelayCommand(o =>
{
KeyValuePair<string, object> propertyState = PropertyStateStack.Pop();
PropertyInfo? property = MyModel.GetType().GetProperty(propertyState.Key);
if (property != null)
{
property.SetValue(MyModel, Convert.ChangeType(propertyState.Value, property.PropertyType), null);
}
});
}
}
EDIT: I did research the "memento pattern" but I couldn't get it to work with INotifyPropertyChanged, since as soon as I set MyModel to a backup of it the bindings to the view stopped working.
Implementing Memento or a variant is the right way. Opposed to storing the particular modifying undo action e.g., Action<T> (another good solution), Memento has a higher memory footprint (as it stores the complete object state), but allows random access to the stored states.
The key point is that when implementing Memento properly, you don't have to rely on reflection, which will only make your code slow and heavy.
The following example uses the IEditableObject interface to implement the Memento pattern (variant). The implementation supports undo and redo. The TextBox class is implementing undo/redo in a similar way using the same interface. The advantage is that you have full control over when to record the object's state. You can even cancel the ongoing modification.
This example clones the complete object to backup the state. Because objects can be quite expensive, for example when they allocate resources, it could make sense to introduce an immutable data model that actually stores the values of the public editable properties. Now, instead of cloning the complete object you would only clone the immutable data model. This can improve the performance in critical scenarios.
See the example provided by the IEditableObject link above to learn how to introduce an immutable data model that holds the object's data.
The actual undo/redo logic is encapsulated in the example's abstract StateTracker<TStateObject> class. StateTracker<TStateObject> implements the aforementioned IEditableObject and the ICloneable interface. To add convenience, StateTracker<TStateObject> also implements a custom IUndoable interface (to enable anonymous usage of the public undo/redo API).
Every class that needs to support state tracking (undo/redo) must extend the abstract StateTracker<TStateObject> to provide a ICloneable.Clone and a StateTracker.UpdateState implementation.
The following example is very basic. It allows undo and redo, but does not support random access to undo/redo states. You would have to use an index based backing store like List<T> to implement such a feature.
IUndoable.cs
Enable anonymous access to the undo/redo API.
public interface IUndoable
{
bool TryUndo();
bool TryRedo();
}
StateTracker.cs
Encapsulates the actual undo/redo logic to avoid duplicate implementations
for each type that is supposed to support undo/redo.
You can consider to add a public UndoCommand and RedoCommand to this class and let the commands invoke TryUndo and TryRedo respectively.
public abstract class StateTracker<TStateObject> : IEditableObject, IUndoable, ICloneable
{
public bool IsInEditMode { get; private set; }
private Stack<TStateObject> UndoMemory { get; }
private Stack<TStateObject> RedoMemory { get; }
private TStateObject StateBeforeEdit { get; set; }
private bool IsUpdatingState { get; set; }
protected StateTracker()
{
this.UndoMemory = new Stack<TStateObject>();
this.RedoMemory = new Stack<TStateObject>();
}
public abstract TStateObject Clone();
protected abstract void UpdateState(TStateObject state);
object ICloneable.Clone() => Clone();
public bool TryUndo()
{
if (!this.UndoMemory.TryPop(out TStateObject previousState))
{
return false;
}
this.IsUpdatingState = true;
this.StateBeforeEdit = Clone();
this.RedoMemory.Push(this.StateBeforeEdit);
UpdateState(previousState);
this.IsUpdatingState = false;
return true;
}
public bool TryRedo()
{
if (!this.RedoMemory.TryPop(out TStateObject nextState))
{
return false;
}
this.IsUpdatingState = true;
this.StateBeforeEdit = Clone();
this.UndoMemory.Push(this.StateBeforeEdit);
UpdateState(nextState);
this.IsUpdatingState = false;
return true;
}
// Start recording the changes
public void BeginEdit()
{
if (this.IsInEditMode || this.IsUpdatingState)
{
return;
}
this.IsInEditMode = true;
// Create the snapshot before the instance is changed
this.StateBeforeEdit = Clone();
}
// Abort recording the changes
public void CancelEdit()
{
if (!this.IsInEditMode)
{
return;
}
// Restore the original state
UpdateState(this.StateBeforeEdit);
this.IsInEditMode = false;
}
// Commit recorded changes
public void EndEdit()
{
if (!this.IsInEditMode || this.IsUpdatingState)
{
return;
}
// Commit the snapshot of the original state after the instance was changed without cancellation
this.UndoMemory.Push(this.StateBeforeEdit);
this.IsInEditMode = false;
}
}
MyModel.cs
public class MyModel : StateTracker<MyModel>, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MyModel()
{
}
// Copy constructor
private MyModel(MyModel originalInstance)
{
// Don't raise PropertyChanged to avoid the loop of death
this.testProperty1 = originalInstance.TestProperty1;
this.testProperty2 = originalInstance.TestProperty2;
}
// Create a deep copy using the copy constructor
public override MyModel Clone()
{
var copyOfInstance = new MyModel(this);
return copyOfInstance;
}
protected override void UpdateState(MyModel state)
{
// UpdateState() is called by the StateTracker
// which internally guards against the infinite loop
this.TestProperty1 = state.TestProperty1;
this.TestProperty2 = state.TestProperty2;
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private string testProperty1;
public string TestProperty1
{
get => this.testProperty1;
set
{
this.testProperty1 = value;
OnPropertyChanged();
}
}
private string testProperty2;
public string TestProperty2
{
get => this.testProperty2;
set
{
this.testProperty2 = value;
OnPropertyChanged();
}
}
}
Example
The following example stores the state of a TextBox, that binds to a MyModel instance. When the TextBox receives focus, the MyModel.BeginEdit method is called to start recording the input. When the TextBox loses focus the recorded state is pushed onto the undo stack by calling the MyModel.EndEdit method.
MainWindow.xaml
<Window>
<Window.DataContext>
<local:MyModel />
</Window.DataContext>
<StackPanel>
<Button Content="Undo"
Click="OnUndoButtonClick" />
<Button Content="Redo"
Click="OnRedoButtonClick" />
<TextBox Text="{Binding TestProperty1, UpdateSourceTrigger=PropertyChanged}"
GotFocus="OnTextBoxGotFocus"
LostFocus="OnTextBoxLostFocus" />
</StackPanel>
</Window>
MainWindow.xaml.cs
Because of the defined interfaces we can handle undo/redo without knowing the actual data type.
private void OnTextBoxGotFocus(object sender, RoutedEventArgs e)
=> ((sender as FrameworkElement).DataContext as IEditableObject).BeginEdit();
private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
=> ((sender as FrameworkElement).DataContext as IEditableObject).EndEdit();
private void OnUndoButtonClick(object sender, RoutedEventArgs e)
=> _ = ((sender as FrameworkElement).DataContext as IUndoable).TryUndo();
private void OnRedoButtonClick(object sender, RoutedEventArgs e)
=> _ = ((sender as FrameworkElement).DataContext as IUndoable).TryRedo();
An alternative flow could be that the MyModel class internally calls BeginEdit and EndEdit inside the relevant property setters (before accepting the new value and after accepting the new value). In case of the TextBox, the advantage of this solution is that it allows to record every single input.
In this scenario, the GotFocus and LostFocus event handlers previously defined on the TextBox (example above) are not needed and related code must be removed:
MyModel.cs
public class MyModel : StateTracker<MyModel>, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MyModel()
{
}
// Copy constructor
private MyModel(MyModel originalInstance)
{
// Don't raise PropertyChanged to avoid the loop of death
this.testProperty1 = originalInstance.TestProperty1;
this.testProperty2 = originalInstance.TestProperty2;
}
// Create a deep copy using the copy constructor
public override MyModel Clone()
{
var copyOfInstance = new MyModel(this);
return copyOfInstance;
}
protected override void UpdateState(MyModel state)
{
// UpdateState() is called by the StateTracker
// which internally guards against the infinite loop
this.TestProperty1 = state.TestProperty1;
this.TestProperty2 = state.TestProperty2;
}
private void RecordPropertyChange<TValue>(ref TValue backingField, TValue newValue)
{
BeginEdit();
backingField = newValue;
EndEdit();
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private string testProperty1;
public string TestProperty1
{
get => this.testProperty1;
set
{
RecordPropertyChange(ref this.testProperty1, value);
OnPropertyChanged();
}
}
private string testProperty2;
public string TestProperty2
{
get => this.testProperty2;
set
{
RecordPropertyChange(ref this.testProperty2, value);
OnPropertyChanged();
}
}
}
Remarks
If extending StateTracker is not an option (e.g., because it would introduce a multi-inheritance issue), you can always make use of composition (for example add a private property of type StateTracker to your undoable model to replace inheritance).
Just create a new class that extends StateTracker to implement the abstract members. Then define a private property of this new type in your undoable model. Now, let the model reference this private property to access the undo/redo API.
While composition is to be favored, this example chooses inheritance as this concept feels more natural to most. It may helps to understand the basic idea.

Xaml bindings don't update (and everything seems like it should work) [duplicate]

This question already has an answer here:
Notify binding for static properties in static classes
(1 answer)
Closed 7 months ago.
I've been working on a unique kind of project for a while now and recently I've written a custom "binding system"(for external code) which works great, however today I needed to get some MVVM style bindings to work(for internal UI). After an entire day of googling and trying different things, I still don't have an answer or working code. I'm almost at a point where I'll just add "normal" bindings to my existing binding system and call it a day.
anyway... my question...
I'm trying to make a one-way binding from a ViewModel class to a UI element. There is are some "rules" I have to conform to though (like all (public) properties MUST be in a static class). At design-time everything works and VS can resolve the bindings (if the datacontext is set in xaml, NOT in cs). The bindings even update once to their default value at startup, but NOT when the property source changed.
TLDR;
read the bold text :)
Code:
[Public static class]
here the property is set by external code at runtime
public static class StaticClass
{
public static string ExampleProperty
{
get
{
return ViewModel.Instance.ExampleProperty;
}
set
{
if (ViewModel.Instance.ExampleProperty != value) ViewModel.Instance.ExampleProperty = value;
}
}
}
[ViewModel]
a singleton class that holds the non-static backing field for the static properties in the class above and implements INotifyPropertyChanged
internal class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static ViewModel _instance = null;
internal static ViewModel Instance
{
get
{
if (_instance is null) _instance = new();
return _instance;
}
}
private static string _exampleProperty { get; set; } = "Pls work";
public string ExampleProperty
{
get
{
return _exampleProperty;
}
set
{
_exampleProperty = value;
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (propertyName is not null) PropertyChanged?.Invoke(null, new(propertyName));
}
}
[Xaml example binding]
<Button Content="{Binding ExampleProperty, UpdateSourceTrigger=PropertyChanged}" Click="Button_Click"/>
[MainWindow.cs]
obviously a test project so this just changes the ExampleProperty to a random number on the Button.Click event
public partial class MainWindow : Window
{
Random random = new();
public MainWindow()
{
InitializeComponent();
this.DataContext = ViewModel.Instance;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
StaticClass.ExampleProperty = random.Next(0, 69).ToString();
}
}
so what am I doing wrong? Any help is greatly appreciated.
The expression
DataContext = new ViewModel();
assigns a different instance of the ViewModel class to the DataContext than the one returned from ViewModel.Instance, so that
StaticClass.ExampleProperty = random.Next(0, 69).ToString();
does not affect the DataContext. You should have assigned
DataContext = ViewModel.Instance;
Your ViewModel class does however not implement the singleton pattern correctly - it would have to avoid the creation of more than one instance, e.g. by declaring a private constructor.
The view does not need to use the singleton aspect of the view model at all. In order to access the current view model instance, cast the DataContext:
private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
ViewModel vm = (ViewModel)btn.DataContext;
vm.ExampleProperty = random.Next(0, 69).ToString();
}
Thanks to a comment on the question:
Also be aware that it is possible to bind directly to static properties of a static class, even with change notification, thus eliminating the need for a singleton. See e.g. here: stackoverflow.com/a/41823852/1136211
(and answer) I've had success with both a static and a non-static binding (FINALLY...).
For binding UI to a static class
The static class:
public static class StaticClass
{
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
#region Properties
public static string _exampleProperty = "Default value";
public static string ExampleProperty
{
get
{
return _exampleProperty;
}
set
{
if (_exampleProperty != value)
{
_exampleProperty = value;
OnStaticPropertyChanged();
}
}
}
#endregion
private static void OnStaticPropertyChanged([CallerMemberName]string propertyName = null)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
}
How to bind to UI:
<TextBlock Text="{Binding Path=(local:StaticClass.ExampleProperty)}"/>
How to set the property:
StaticClass.ExampleProperty = "New value that automatically updates the UI :)";
For binding UI to a non-static class
Use the code from the other answer.

WPF ObservableCollection binding to DataGrid not refreshing UI

I currently facing the issue that my DataGrid binding is not refreshing the UI.
My ViewModel and Object inherit from INotifyPropertyChanged.
Here is my code:
XAML:
<DataGrid Grid.Row="2" DataContext="{StaticResource MainViewModel}" ItemsSource="{Binding TestCollection, Mode=OneWay}" AutoGenerateColumns="True"/>
ViewModel:
public class MainViewModel: ViewModelBase
{
private ObservableCollection<ProductDisplayItem> _testCollection;
public ObservableCollection<ProductDisplayItem> TestCollection
{
get => _testCollection;
set => SetProperty(ref _testCollection, value);
}
private async void SendSearch()
{
//MyCode
.....
IEnumerable<ProductDisplayItem> displayItems = DisplayItemHelper.ConvertToDisplayItems(products);
TestCollection = new ObservableCollection<ProductDisplayItem>(displayItems);
}
}
My Object:
public class ProductDisplayItem: ViewModelBase
{
private string _mfrPartNumber;
private double _unitPrice;
private int _stock;
public string MfrPartNumber
{
get => _mfrPartNumber;
set => SetProperty(ref _mfrPartNumber, value);
}
public double UnitPrice
{
get => _unitPrice;
set => SetProperty(ref _unitPrice, value);
}
public int Stock
{
get => _stock;
set => SetProperty(ref _stock , value);
}
public ProductDisplayItem()
{
}
public ProductDisplayItem(string mfrp, double unitPrice, int stock)
{
MfrPartNumber = mfrp;
UnitPrice = unitPrice;
Stock = stock;
}
}
And my ViewModelBase:
public abstract class ViewModelBase: IDisposable, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public void Dispose()
{
}
}
I also tried to add the items to the ObservableCollection instead of creating a new one, but with the same result.
I hope anyone can help me with that.
Thanks in advance
The most common cause of such errors is confusion about ViewModel instances: UI elements are bound to one instance, and you are modifying a collection in another instance.
Since WPF MVVM usually provides for using the main ViewModel in only one instance, try using Singleton.
Fresh topic with a similar question: Is it a correct approach to create static viewModel in MVVM?
First implementation option from there:
1) If:
in general, in principle, under no circumstances is it assumed that a ViewModel can have several instances at the assembly level in which it is created;
if this does not create any security problems, since the static instance can be accessed by everyone;
if static values are sufficient to create a single instance. In most cases, this means that the ViewModel has only one non-parameterized constructor.
Then in this case it is worth using Singleton.
Example:
public class MainWindowViewModel : ViewModelBase
{
// The only instance available outside of this class.
public static MainWindowViewModel Instanse { get; }
= new MainWindowViewModel();
// All constructors must be MANDATORY HIDDEN.
private MainWindowViewModel()
{
// Some code
}
// Some code
}
To get this instance in XAML, x: Static is used.
You can get the entire instance, or create a binding to a separate property.
<SomeElement
DataContext="{x:Static vm:MainWindowViewModel.Instance}"/>
<SomeElement
Command="{Binding ButtonCommandEvent,
Source={x:Static vm:MainWindowViewModel.Instance}}"/>
Ok I figured it out. It's the DataContext...
Works fine after removing it from xaml.

Where to set a Command?

I have seen two different ways to define the Commands
public DelegateCommand OpenCommand {get;set;}
public ViewModelConstructor()
{
OpenCommand = new DelegateCommand(OpenMethod);
}
Wheras other people decide to set it directly, as it is uncommon for a Command to change.
public DelegateCommand => new DelegateCommand(OpenMethod);
this seems a like a cleaner way to do it, but maybe it is expected to define them in the constructor so it is clear for other people.
What are the conventions on where to define objects such as Commands that won't change during Runtime?
As #vasily.sib mentions, the second method will create a new instance on each call.
I find the second method actually causes problems with the way RaiseCanExecuteChanged affects Views because of this. Consider the following ViewModel:
public class ViewModel
{
private bool CanEx { get; set; }
public DelegateCommand XCommand => new DelegateCommand(X, Can);
public DelegateCommand YCommand { get; set; }
public DelegateCommand SwitchCommand { get; set; }
public ViewModel()
{
CanEx = true;
YCommand = new DelegateCommand(Y, Can);
SwitchCommand = new DelegateCommand(Switch);
}
private void X(object obj) => System.Diagnostics.Debug.WriteLine("X");
private void Y(object obj) => System.Diagnostics.Debug.WriteLine("Y");
private bool Can(object obj) => CanEx;
private void Switch(object obj)
{
CanEx = !CanEx;
XCommand.RaiseCanExecuteChanged();
YCommand.RaiseCanExecuteChanged();
}
}
When I create a View from this ViewModel, with Buttons for each command, the Button for XCommand will not show as disabled when I fire SwitchCommand.
This seems to have something to do with the fact that at the time when RaiseCanExecuteChanged() is called, it is null.

Updating dependent properties using MVVM

Some properties on my viewmodel:
public ObservableCollection<Task> Tasks { get; set; }
public int Count
{
get { return Tasks.Count; }
}
public int Completed
{
get { return Tasks.Count(t => t.IsComplete); }
}
What's the best way to update these properties when Tasks changes?
My current method:
public TaskViewModel()
{
Tasks = new ObservableCollection<Task>(repository.LoadTasks());
Tasks.CollectionChanged += (s, e) =>
{
OnPropertyChanged("Count");
OnPropertyChanged("Completed");
};
}
Is there a more elegant way to do this?
With respect to Count, you don't have to do this at all. Simply bind to Tasks.Count and your bindings will get notified of the change by the ObservableCollection.
Completed is a different story, because this is outside of ObservableCollection. Still, from the level of the abstraction/interface, you really want Completed to be a property of that Tasks collection.
For this, I think a better approach would be to create "sub" view-model for your Tasks property:
public class TasksViewModel : ObservableCollection<Task>
{
public int Completed
{
get { return this.Count(t => t.IsComplete); }
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if(e.PropertyName == "Count") NotifyCompletedChanged();
}
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
NotifyCompletedChanged();
}
void NotifyCompletedChanged()
{
OnPropertyChanged(_completedChangedArgs);
}
readonly PropertyChangedEventArgs _completedChangedArgs = new PropertyChangedEventArgs("Completed");
}
This gives you all of the benefits of the ObservableCollection, and effectively makes the Completed property part of it. We still haven't captured only the cases where the number of completed items truly changes, but we have reduced the number of redundant notifications somewhat.
Now the viewmodel just has the property:
public TasksViewModel Tasks { get; set; }
…and you can bind to Tasks, Tasks.Count, and Tasks.Completed with ease.
As an alternative, if you would rather create these other properties on the "main" view-model, you can take this notion of a subclassed ObservableCollection<T> to create one with some method where you can pass in an Action<string> delegate, which would represent raising a property change notification on the main view-model, and some list of property names. This collection could then effectively raise the property change notifications on the view-model:
public class ObservableCollectionWithSubscribers<T> : ObservableCollection<T>
{
Action<string> _notificationAction = s => { }; // do nothing, by default
readonly IList<string> _subscribedProperties = new List<string>();
public void SubscribeToChanges(Action<string> notificationAction, params string[] properties)
{
_notificationAction = notificationAction;
foreach (var property in properties)
_subscribedProperties.Add(property);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
NotifySubscribers();
}
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
NotifySubscribers();
}
void NotifySubscribers()
{
foreach (var property in _subscribedProperties)
_notificationAction(property);
}
}
You could even leave the property type as ObservableCollection<Task>.
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
var tasks = new ObservableCollectionWithSubscribers<Task>();
tasks.SubscribeToChanges(Notify, "Completed");
Tasks = tasks;
}
public ObservableCollection<Task> Tasks { get; private set; }
public int Completed
{
get { return Tasks.Count(t => t.IsComplete); }
}
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string property)
{
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(property));
}
}
Looks rather elegant to me. I really don't know how you'd make that more succinct.
(How odd to write an answer like this. If somebody actually comes up with something more elegant, I might delete this.)
Okay, I noticed one thing, unrelated to the original question: Your Tasks property has a public setter. Make it private set;, or you'll need to implement the set with a backing field so you can remove the delegate on the previous instance, replace and wire up the new one, and do OnPropertyChanged with "Tasks", "Count", and "Completed". (And seeing how Tasks is set in the constructor, I'm guessing private set; is the better option.)
Doesn't make notifying about Count and Completed more elegant, but it fixes a bug.
And many MVVM frameworks get the property name from a lambda, so that instead of OnPropertyChanged("Count"), you can write OnPropertyChanged(() => Count) so that it will follow renames done with the help of refactoring tools. I don't think renaming happens all that often, though, but it does avoid some string literals.

Categories

Resources