After studying several Q&A on stackoverflow, some tutorials and of course the official documentation, I trying to use ApplicationCommands in my WPF Prism MVVM application.
My current approach
After trying different solutions I found out, I ended up with following constellation:
I am using the AttachCommandBindingsBehavior class mentioned in this answer, which will be used like this in the view:
<UserControl>
<i:Interaction.Behaviors>
<localBehaviors:AttachCommandBindingsBehavior CommandBindings="{Binding CommandBindings}"/>
</i:Interaction.Behaviors>
</UserControl>
MyViewModel contains a CommandBindingCollection property, which will be populated in the constructor:
public CommandBindingCollection CommandBindings { get; } = new CommandBindingCollection();
public MyViewModel()
{
this.CommandBindings.AddRange(new[]
{
new CommandBinding(ApplicationCommands.Save, this.Save, this.CanSave),
new CommandBinding(ApplicationCommands.Open, this.Open)
});
}
The UserControl MyView contains two buttons:
<Button Command="ApplicationCommands.Open" Content="Open" />
<Button Command="ApplicationCommands.Save" Content="Save" />
My first question at this point is: Are the Executed() and CanExecute() methods already bound to the Command-DependencyProperty of the Button? Since it does not work, what did I forgot or made wrong?
My second question is: How can I trigger the CanExecute of the Command the Button is bound to? The actual use-case: MyViewModel.CanSave() returns true, when the user successfully executed the MyViewModel.Open() method. Usually, I would call an DelegateCommand.RaiseCanExecuteChanged(), but calling ApplicationCommands.Save.RaiseCanExecuteChanged() does not execute MyViewModel.CanSave().
Feel free to ask for more information. I will really appreciate your answers. Thank you!
Since it does not work, what did I forgot or made wrong?
The CommandBindings property on your behavior is an ObservableCollection<CommandBinding>, but you're binding it to a CommandBindingCollection in your view model. Change your view model's property to a ObservableCollection<CommandBinding>.
There are also some problems with the AttachCommandBindingsBehavior you linked to. I'm not sure why the answer was accepted, because it's actually quite broken. The tweaked version below should work, though.
public class AttachCommandBindingsBehavior : Behavior<FrameworkElement>
{
public ObservableCollection<CommandBinding> CommandBindings
{
get => (ObservableCollection<CommandBinding>)GetValue(CommandBindingsProperty);
set => SetValue(CommandBindingsProperty, value);
}
public static readonly DependencyProperty CommandBindingsProperty =
DependencyProperty.Register(
"CommandBindings",
typeof(ObservableCollection<CommandBinding>),
typeof(AttachCommandBindingsBehavior),
new PropertyMetadata(null, OnCommandBindingsChanged));
private static void OnCommandBindingsChanged(
DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var b = sender as AttachCommandBindingsBehavior;
if (b == null)
return;
var oldBindings = e.OldValue as ObservableCollection<CommandBinding>;
if (oldBindings != null)
oldBindings.CollectionChanged -= b.OnCommandBindingsCollectionChanged;
var newBindings = e.NewValue as ObservableCollection<CommandBinding>;
if (newBindings != null)
newBindings.CollectionChanged += b.OnCommandBindingsCollectionChanged;
b.UpdateCommandBindings();
}
protected override void OnAttached()
{
base.OnAttached();
UpdateCommandBindings();
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.CommandBindings.Clear();
}
private void UpdateCommandBindings()
{
if (AssociatedObject == null)
return;
AssociatedObject.CommandBindings.Clear();
if (CommandBindings != null)
AssociatedObject.CommandBindings.AddRange(CommandBindings);
CommandManager.InvalidateRequerySuggested();
}
private void OnCommandBindingsCollectionChanged(
object sender,
NotifyCollectionChangedEventArgs e)
{
UpdateCommandBindings();
}
}
How can I trigger the CanExecute of the Command the Button is bound to?
You can suggest that WPF's routed command system reevaluate all commands by calling CommandManager.InvalidateRequerySuggested(). The actual reevaluation will occur asynchronously at the Background dispatcher priority. This is the explicit way to do it, but you should know that this already happens implicitly upon certain actions, like focus changes and mouse/keyboard button-up events. The WPF developers tried to make automatic command requerying as seamless as possible, so it "just works" most of the time.
The actual use-case: MyViewModel.CanSave() returns true [...]
Just to be clear, as a CanExecuteRoutedEventHandler, your CanSave method should return void and set CanExecute to true on the event argument.
Related
In my View I'm using a component (custom control), which provides some functions. I want to invoke one of them when my ViewModel receives an event it is subscribed to.
I want to do this as cleanly as possible, since there might be more functions I would be using this way.
I know I can create a variable like "InvokeFunctionA", bind to this variable and create OnChange method in my View which will invoke the corresponding function. But it's quite a lot of code required just to invoke a single function. And an extra variable, which seems quite unnesessary, too.
Is there a better way to do this? Like, maybe a View can pass some kind of a handler function to ViewModel which will do the work? I've made quite a lot of research but haven't yet found anything that suits my problem. Or maybe I'm missing something obvious?
[ edit ]
Haukinger solution works for now (done this way: https://blog.machinezoo.com/expose-wpf-control-to-view-model-iii ), but I don't think it's the cleanest solution (Instead of providing access to a few functions, I'm exposing whole control to the ViewModel).
In a perfect MVVM-world (as you are asking for a clean solution), the ViewModel does not call anything that is located in the view (neither directly nor indirectly). I'd approach the problem like this:
If 'component' is NOT a usercontrol, try moving it to the ViewModel and use bindings or commands in the view to operate your 'component'.
If 'component' is a usercontrol, give 'component' a dependency property and fill it via a binding with your property of the ViewModel. Inside of 'compontent' you can register value change callback of your dependency property to start your work. <local:UserControlComponent MyDependencyProperty="{Binding PropertyInViewModel}" />
As a last resort:
You could add a C# event to the viewmodel and handle it in your code-behind inside the view.
Instead of an event, you could alternatively use IObservable pattern (https://learn.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=netframework-4.8, https://github.com/dotnet/reactive)
For completeness sake a no-go option: Prism has an EventAggregator that can be used for loose communication. I've had to remove the usage of EventAggregator from a rather big App, because it was not maintainable any more.
Expose a dependency property in your view whose type is the provided interface, bind it to a property on your view model, then call the method on the interface on the view model property from the view model.
To clarify, I don't mean to expose the component itself, rather an interface that contains exactly one method. The view has to have a private class that implements the interface and routes to the actual component, as well as converting arguments and results so that types belonging to the components need not be present in the interface.
But I'm with sa.he in that this whole situation should be avoided in the first place. It may not be possible, depending on the third party components used, though.
Yes, invoking view's methods from VM is very much against pure MVVM and there's not going to be a 'clean' solution.
But it can be done at least half decently. You would need to create a special attached property (or behavior, but property seems to be a better choice in this scenario) and an ICommand property in VM, then bind the AP to the property with OneWayToSource binding and use command invocation in VM. It would still be a lot of code, but once it's done, you would only need to create new properties in the VM.
Below is some code that I wrote, consider it as a starting point, you can add support for command parameters and converters.
public class MethodDelegation : DependencyObject
{
public static readonly DependencyProperty CommandDelegatesProperty =
DependencyProperty.RegisterAttached("CommandDelegatesInternal", typeof(CommandDelegatesCollection), typeof(MethodDelegation), new PropertyMetadata(null));
private MethodDelegation() { }
public static CommandDelegatesCollection GetCommandDelegates(DependencyObject obj)
{
if (obj.GetValue(CommandDelegatesProperty) is null)
{
SetCommandDelegates(obj, new CommandDelegatesCollection(obj));
}
return (CommandDelegatesCollection)obj.GetValue(CommandDelegatesProperty);
}
public static void SetCommandDelegates(DependencyObject obj, CommandDelegatesCollection value)
{
obj.SetValue(CommandDelegatesProperty, value);
}
}
public class CommandDelegatesCollection : FreezableCollection<CommandDelegate>
{
public CommandDelegatesCollection()
{
}
public CommandDelegatesCollection(DependencyObject targetObject)
{
TargetObject = targetObject;
((INotifyCollectionChanged)this).CollectionChanged += UpdateDelegatesTargetObjects;
}
public DependencyObject TargetObject { get; }
protected override Freezable CreateInstanceCore()
{
return new CommandDelegatesCollection();
}
private void UpdateDelegatesTargetObjects(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (CommandDelegate commandDelegate in e?.NewItems ?? Array.Empty<CommandDelegate>())
{
commandDelegate.TargetObject = TargetObject;
}
}
}
public class CommandDelegate : Freezable
{
public static readonly DependencyProperty MethodNameProperty =
DependencyProperty.Register("MethodName", typeof(string), typeof(CommandDelegate), new PropertyMetadata(string.Empty, MethodName_Changed));
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandDelegate), new PropertyMetadata(null));
public static readonly DependencyProperty TargetObjectProperty =
DependencyProperty.Register("TargetObject", typeof(DependencyObject), typeof(CommandDelegate), new PropertyMetadata(null, TargetObject_Changed));
private MethodInfo _method;
public string MethodName
{
get { return (string)GetValue(MethodNameProperty); }
set { SetValue(MethodNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public DependencyObject TargetObject
{
get { return (DependencyObject)GetValue(TargetObjectProperty); }
set { SetValue(TargetObjectProperty, value); }
}
protected override Freezable CreateInstanceCore()
{
return new CommandDelegate();
}
private static void MethodName_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var del = (CommandDelegate)d;
del.UpdateMethod();
del.UpdateCommand();
}
private static void TargetObject_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var del = (CommandDelegate)d;
del.UpdateMethod();
del.UpdateCommand();
}
private void UpdateMethod()
{
_method = TargetObject?.GetType()?.GetMethod(MethodName);
}
private void UpdateCommand()
{
Command = new RelayCommand(() => _method.Invoke(TargetObject, Array.Empty<object>()));
}
}
The XAML usage is as follows:
<TextBox>
<l:MethodDelegation.CommandDelegates>
<l:CommandDelegate MethodName="Focus"
Command="{Binding TestCommand, Mode=OneWayToSource}" />
</l:MethodDelegation.CommandDelegates>
</TextBox>
Bubble your event upwards. Have your VM publish some event of its own. Your V can subscribe to it (if it wishes).
The downside is that you'll need codebehind, where ideally a V should be XAML-only as far as possible. The upside is that your VM remains quite aloof (i.e. it's not dependent on any specific controls used by the V). It says "something has happened worthy of note", but it doesn't assume either that (a) anyone is particularly listening, or (b) it leaves it to the listener (in your case, the V) to decide exactly what to action to take (i.e. how to change the UI).
It's a perennial problem - how does a VM cause a V to update somehow, and as far as I can tell it is still something to be debated.
The mechanism above, I've got a vague recollection that Prism itself might include something similar. I'm fairly sure it uses something akin to INotifyPropertyChanged (i.e. some interface or other) rather than an "event" as we might understand it just from a working knowledge of .net. You might even be able to use this mechanism to dispense with codebehind altogether. The downside of using Prism in the first place is its bulk, but if you're already using it anyway...
It's for you to decide how clean this is. I decided that a bit of codebehind was preferable to the VM meddling directly with the UI.
I'm using MVVM to bind a ComboBox to a ViewModel, and I have few question about heavy actions and selection change.
I want to trigger some actions when the selected item is changed, my initial approach was to put the logic in the setter of the field to which the selected item is binded.
So my first question is, is this good practice or there is a better approach?
Those actions may be very expensive in time and resources (need to retrieve data through a web service) and I don't want the UI to freeze, so lately I've started to send a message from the set which is received in the view's code-behind and that call a ViewModel command asynchronously.
Am I just wasting time or does this make any sense?
The problem is that when I'm debugging the UI sometimes freeze anyway (it doesn't happened on release). Reading here and there I've come to know that it may be debugger related, can anyone confirm this behavior on VS 2015?
Additional information
As requested I provide some examples. This is my first approach:
(XAML)
<ComboBox SelectedItem="{Binding SelectedField}"/>
(ViewModel)
public class ViewModel
{
private MyObject _selectedField = null;
public MyObject SelectedField
{
get
{
return _selectedField;
}
set
{
if(_selectedField != value)
{
// Expensive action
_selectedField = value;
RaisePropertyChanged(() => SelectedField);
}
}
}
}
The expensive action make some web service calls and may take long, is this design good or is there a better way to achieve this?
My second approach is through messages, as shown in this example:
(ViewModel)
public class ViewModel
{
private MyObject _selectedField = null;
public MyObject SelectedField
{
get
{
return _selectedField;
}
set
{
if(_selectedField != value)
{
Messenger.Default.Send(new DoStuffMessage());
_selectedField = value;
RaisePropertyChanged(() => SelectedField);
}
}
}
private RelayCommand _doStuffCommand = null;
public ICommand DoStuffCommand
{
get
{
if (_doStuffCommand == null)
_doStuffCommand = new RelayCommand(async () => await DoStuff());
return _doStuffCommand;
}
}
private async Task DoStuff()
{
// Expensive action
}
}
(Code-behind)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<DoStuffMessage>(this, DoStuffMessage_Handler);
}
private void DoStuffMessage_Handler(DoStuffMessage msg)
{
(DataContext as ViewModel).DoStuffCommand.Execute(null);
}
}
Is this approach better or is just bad and useless?
For MVVM, I prefer to use RelayCommands to bind an EventTrigger in XAML to an ICommand in the viewmodel. I feel this creates the best separation of code and is clearer than adding a lot of logic to my setters, where it might be overlooked during troubleshooting. Here is an overview of the process: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx
This is to wire up a button and pass in a parameter, so obviously you would need to modify it for your use case, but it will show the basic technique. In XAML:
<Button Content="Click Me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Custom:EventToCommand Command="{Binding MyCommand}" CommandParameter="foo"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
In your VM:
public static ICommand MyCommand { get; set; } // declare an ICommand - bind to this!
public MainViewModel(IDataService dataService)
{
// associate your ICommand with a method. If you don't use a parameter, you don't need the lambda expression here.
MyCommand = new RelayCommand<string>((paramater) => MyCommandMethod(parameter));
}
public void MyCommandMethod(string parameter)
{
Debug.WriteLine("This is the code I want to run in my VM. The parameter is " + parameter);
}
I use the [free] MVVMLight toolkit for my applications, which was written by the guy who wrote the article that I linked to, but a lot of this is baked into .Net also. Using Expression Blend can make it easier to wire this stuff up when you are designing.
You can do whatever you like in setter as long as it is async.
private string _test;
public string Test
{
get { return _test; }
set
{
Task.Run(() =>
{
//do stuff
});
_test = value;
}
}
If you don't want to place logic in setter, because for example the Single Responsibility principle is violated, you should use interactions to catch the SelectionChange event and call a command in VM which should call an async method.
Here you have a sample that uses interactions : cute link
That's it!
I have a class Device that uses the INotifyPropertyChanged, it is tested and it works.
Now I have a deviceMonitor that is the UI representation of this device. In the code I have a reference to Device and I want to link changes in the device to changes in the UI (two way is not needed, but clicking the deviceMonitor should call a certain function of the device)
I'm using expression Blend with VS2015 so guidance based on where to click to get it to work would be extremely welcome.
this is a mockup of the device
public class Device : INotifyPropertyChanged
{
public string Name { ... } //uses NotifyPropertyChanged in the set
// other properties and their relative private vars.
}
Then the xaml.cs for the GUI, here I have a reference to the dll containing the Device:
public partial class DeviceControl : UserControl
{
public Device myDevice = new Device();
public DeviceControl()
{
InitializeComponent();
// here I tried setting the datacontest to the myDevice
// also tried to set the dataContext in Blend and here grab a
// reference to it and store it in myDevice. But nothing workerd
}
public void ChangeDevName()
{
this.myDevice.DeviceName = "Test";
//UI Representation of deviceName never changed
}
}
Then the XAML
<UserControl>
<UserControl.DataContext>
<recoveriX:RecoverixDevice DeviceName="thisIsAName"/>
</UserControl.DataContext>
<Grid>
<TextBlock x:Name="title" Text="{Binding DeviceName}"/>
</Grid>
</UserControl>
This might work:
In your DeviceControl UserControl, wire up events for OnLoaded and OnUnloaded of the control.
In the code-behind for the event handlers, subscribe/unsubscribe to the PropertyChanged event of UserControl's DataContext (this.DataContext) ; like so:
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.DataContext is INotifyPropertyChanged)
{
((INotifyPropertyChanged)this.DataContext).PropertyChanged += OnDataContextPropertyChanged;
}
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (this.DataContext is INotifyPropertyChanged)
{
((INotifyPropertyChanged)this.DataContext).PropertyChanged -= OnDataContextPropertyChanged;
}
}
private void OnDataContextPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// You could also just update every time something is changed.
// As an example you could check for the "Name" property being changed.
if (e.PropertyName == nameof(Device.Name))
{
title.Text = this.DataContext.Name;
}
}
An important note to make is the if (this.myDevice is INotifyPropertyChanged) check.
It ensures the Device class inherits from INotifyPropertyChanged.
Providing it does, it casts the Device being your DataContext (this.DataContext) to (INotifyPropertyChanged) so you can subscribe to the PropertyChanged event from the INotifyPropertyChanged interface.
Then, when a property on the DataContext is changed, your handler will be fired. Obviously you can put what you want to do in the code of OnMyDevicePropertyChanged, I've just used "Name" as an example.
Hope this helps!
EDIT
Furthermore; you could also store a private field of type Device in the UserControl's code-behind. A bit like so:
private Device _viewModel; // You could also use the interface (like 'IDevice'), too.
Then in your `OnLoaded' event, store it in the field:
if (this.DataContext is INotifyPropertyChanged)
{
this.viewModel = this.DataContext;
// Wire up your PropertyChanged handler as before.
}
And on your OnUnloaded event, just unsubscribe from the viewModel if it is not null:
if (this.viewModel != null)
{
this.viewModel.PropertyChanged -= OnDataContextPropertyChanged;
}
This also gives you a bit more flexibility when you've got the DataContext stored as a field, as you can use it within other methods (if you use any more in your code behind - you shouldn't...; but it saves CPU time casting it to INotifyPropertyChanged all the time.
For future reference I would look at Implementing MVVM Practices into your projects.
Good luck!
Problem was overwriting the private device, setting the datacontext fixed the thing.
This is the final class:
public partial class DeviceControl : UserControl
private Device _device = new Device();
public DeviceControl()
{
InitializeComponnents();
this.DataContext = _device;
}
public void SetDevice(Device d)
{
//This fails:
//_device = d;
//This works
this.DataContext = d;
}
I'm have implemented a custom TextBox:
public class MyTextBox : TextBox
{
// ...
}
that I'm using from XAML:
<MyTextBox Text="{Binding MyProperty}" />
and it's bound to a property in my ViewModel.
public class MyDataContext : INotifyPropertyChanged
{
public string MyProperty
{
get { return _myPropertyBackingField; }
set
{
_myPropertyBackingField = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyProperty"));
}
}
// ...
}
Question: How can I, in MyTextBox, detect that MyProperty is changed?
MyProperty = "NewValue";
Preferably, I would like to distinguish a programmatical change from when the change was triggered by the user editing the value. That is, I don't think overriding OnPropertyChanged works for me.
You can register to the PropertyChanged event of the TextBox's DataContext.
var dataContext = DataContext as MyDataContext;
dataContext.PropertyChanged += dataContext_PropertyChanged;
// check for the propertyname and react
void dataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "MyProperty")
{
// Do things
}
}
So if your viewmodel raises PropertyChanged you textbox also gets notified. But I think that's bad practice. What do you want to achieve?
OP here.
I realised after a while that it's the binding that keeps track of updating the TextBox from the source (DataContext). So a possible path to take would be to call GetBindingExpression(TextProperty) and work something out from that.
However, I solved it by overriding TextBoxBase.OnTextChanged:
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (!IsFocused)
{
// Do stuff here
}
}
Since the control is not focused, the change must have been done programatically. This is not perfect since a programatical change might come when the TextBox has focus, but it is good enough for me.
I obviously don't get this somewhere.
I have created a UserControl, the bare bones of which is:
private readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register("SaveCommand", typeof(ICommand),
typeof(ctlToolbarEdit));
private readonly DependencyProperty IsSaveEnabledProperty =
DependencyProperty.Register("IsSaveEnabled", typeof(bool),
typeof(ctlToolbarEdit), new PropertyMetadata(
new PropertyChangedCallback(OnIsSaveEnabledChanged)));
public ctlToolbarEdit()
{
InitializeComponent();
}
public bool IsSaveEnabled
{
get { return (bool)GetValue(IsSaveEnabledProperty); }
set { SetValue(IsSaveEnabledProperty, value); }
}
public static void OnIsSaveEnabledChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((ctlToolbarEdit)d).cmdSave.IsEnabled = (bool)e.NewValue;
}
#region Command Handlers
public ICommand SaveCommand
{
get { return (ICommand)GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
private void cmdSave_Click(object sender, RoutedEventArgs e)
{
if (SaveCommand != null)
SaveCommand.Execute(null);
}
#endregion
Excellent. You can see what I am doing ... handling the click event of the button, and basically firing up the command.
The form (lets call that Form1 for the time being ... but note that this is actually a UserControl: common practice, I believe, in MVVM) that is hosting the control has the following line:
<ctl:ctlToolbarEdit HorizontalAlignment="Right" Grid.Row="1"
SaveCommand="{Binding Save}" IsSaveEnabled="{Binding IsValid}" />
This works great. I have an ICommand in my ViewModel called 'Save' and the ViewModel is correctly presenting the IsValid property.
So far so very good.
Now I want to have my new usercontrol also on Form2 (which is also a usercontrol - common practice, I believe, on MVVM). As it happens, Form1 and Form2 are on the screen at the same time.
It compiles, but I get a runtime exception:
'SaveCommand' property was already registered by 'ctlToolbarEdit'."
... leading me to believe that I don't get 'commands' at all.
Why can I not use my usercontrol in more than one place?
If I cannot, what would you suggest is another way to do this?
Very frustrating!
Thanks for any help.
Try making your dependency properties static. Otherwise it is getting re-registered every time you instantiate a new control. Your usage of the MVVM commands looks good otherwise and sounds like you have a good grasp on it.