MVVM: run view-only code after calling command - wpf

What I have:
using MVVM pattern
a view written in XAML
a command MyCommand in the ViewModel which gets called from several places in the view
a method DoSthInView that operates on the view, defined in codebehind
My Goal:
Whenever the command is executed, I want to call DoSthInView, no matter which control executed the command.
Question:
Since in MVVM the ViewModel does not know the View, I cannot call DoSthInView from the ViewModel. So how do call this code?
Own thoughts:
To be less abstract, this is my use case: We have one Button and one TextBox. The command takes the text which is currently in the TextBox and writes it somewhere into the model data. When this writing is done, I want to animate a green checkmark appearing and fading out (this is DoSthInView), so that the user gets a visual confirmation that the data was updated.
There are two ways of running the command:
Click the Button
Press "Enter" while the TextBox is focused
For the Button I know a way to call DoSthInView:
<Button Content="run command" Command="{Binding MyCommand}" Click={Binding DoSthInView}" />
For the TextBox, I have a KeyBinding to take the Enter key:
<TextBox>
<TextBox.InputBindings>
<KeyBinding Command="{Binding MyCommand}" Key="Enter" />
</TextBox.InputBindings>
</TextBox>
But InputBindings seem not to support events, only commands. So here I have no idea how to call DoSthInView.
But even if I found a way to call DoSthInView from within the input binding (analog to the Button), it wouldn't feel right. I am looking for a way to say "whenever MyCommand is executed, run DoSthInView" So that not every caller of MyCommand has to care for it individually, but there is just one place to handle that. Maybe this can be done in the root FrameworkElement?

What you are asking for is possible. You need to implement RelayCommand.
You can also see my other SO answer where there is an example.
Once you have RelayCommand implemented then you can do the following:
In ViewModel:
public ICommand MyCommand { get; set; }
public MyViewModel()
{
MyCommand = new RelayCommand(MyCommand_Execute);
}
private void MyCommand_Execute(object sender)
{
var myView = sender as MyView;
myView?.DoSthInView();
}
In View:
<TextBox>
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=MyCommand}" CommandParameter="{Binding}" Key="Enter"/>
</TextBox.InputBindings>
</TextBox>
While it is not recommended to mix view and viewModel, there can be scenarios where otherwise is not possible. and sometimes it can be requirements. But again this is NOT recommended.

While I am still interested in an answer to my original question (calling codebehind-code after a command is executed), Kirenenko's advice helped me to solve my actual problem regarding the animation. This answer doesn't fit the original question any more because there is no codebehind-code (the animation is solely written in XAML, leaving no codebehind-code to execute). I still put it here because it is partially useful for me.
In the ViewModel, I have this:
...
private bool _triggerBool;
public bool TriggerBool
{
get { return _triggerBool; }
set
{
if (_triggerBool != value)
{
_triggerBool = value;
NotifyPropertyChanged(nameof(TriggerBool));
}
}
}
...
public DelegateCommand MyCommand; // consists of MyCommandExecute and MyCommandCanExecute
...
public void MyCommandExecute()
{
... // actual command code
TriggerBool = true;
TriggerBool = false;
}
...
And here is the animation written in XAML and called by the DataTrigger:
<Image Source="myGreenCheckmark.png" Opacity="0">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding TriggerBool}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="1" To="0" Duration="0.0:0:0.750"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
Looks really stupid to set TriggerBool to true and then false again, but works...

Based on Leonid Malyshev's hint here is a quite clean solution using an event ("clean" regarding seperation of View and ViewModel):
ViewModel code:
public class MyViewModel
{
...
public event Action MyCommandExecuted;
public DelegateCommand MyCommand; // consists of MyCommandExecute, MyCommandCanExecute
...
private void MyCommandExecute()
{
... // actual command code
MyCommandExecuted.Invoke();
}
...
}
View Codebehind:
public partial class MyView : Window
{
public MyView(MyViewModel vm)
{
InitializeComponent();
DataConext = vm;
vm.MyCommandExecuted += DoSthInView();
}
...
private void DoSthInView()
{
...
}
...
}

Usually you have a better more MVVM-ish way to these this thing but since I don't know your case I'll assume that there is no other way.
So to this you need a dependency property in your view. A Boolean would be great and you handle it's on changed event and run DoSthInView in it. In your view model you set the value of this property and on changed gets called.
I can give you demonstration if you need. Also keep in mind that this is event driven coding which defiles MVVM. Try to use bindings and move your DoSthInView to ViewModel if possible.

Related

System.Windows.Input.KeyBinding doesn't check CanExecute

So I have a simple KeyBinding on an input element that executes a command to kick off some analysis.
<dxe:TextEdit.InputBindings>
<KeyBinding Key="Enter" Command="{Binding StartAnalysisCommand}" />
</dxe:TextEdit.InputBindings>
There are a few other simple input elements that, when changed, call RaiseCanExecuteChanged on the command. This propagates to the UI button, disabling it and preventing it from executing. But this CanExecute state seems to be entirely ignored by the KeyBinding event, before and after the RaiseCanExecuteChanged is called.
Tested using a normal WPF TextBox, and it calls CanExecute once you press Enter. Must indeed be an issue in the 3rd party control.
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<TextBox>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="ApplicationCommands.New"/>
</TextBox.InputBindings>
</TextBox>
Edit: Example with a RelayCommand:
public class ViewModel
{
private RelayCommand _cmd;
public RelayCommand Cmd {
get { return _cmd ?? (_cmd = new RelayCommand(Executed, CanExecute)); }
}
public void Executed() { throw new NotImplementedException(); }
public bool CanExecute()
{
return true;
}
}
And the binding with the ViewModel as the context.
<KeyBinding Key="Enter" Command="{Binding Cmd}"/>
Okay, I figured out what the problem was. Thanks everyone for helping out--your answers caused me to realize the problem. Turns out it wasn't a matter of the CanExecute being called, but rather the timing of when the binding was updated. CanExecute was being called, but with the previous value of the binding.
I used the solution found on this SO answer to accept the value on Enter and the program now works as I had originally expected.

Bind to Datacontext property in button trigger?

I understand my problem however I'm looking for advice on a solution:
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyle}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="MouseOverControl" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
I'm trying to bind to a property inside my datacontext, basically I want to tell the DC when a control in my UI has the mouse over it. I think I'll only need this for two buttons and it doesn't matter which one it's over, therefore I don't need to bother with a complicated solution (I hope).
Problem is at the moment it's looking for Button.MouseOverControl which obviously doesn't exist, I'd like to understand how you might go about accessing the DC instead.
Thanks!
EDIT: So I've attempted to go down the attached property/behavior route, here is what I have so far:
public static class MouseBehaviour
{
public static readonly DependencyProperty MouseOverProperty
= DependencyProperty.RegisterAttached(
"MouseOver",
typeof(bool),
typeof(MouseBehaviour),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
MouseOverBindingPropertyChanged));
public static bool GetMouseOver(DependencyObject obj)
{
return (bool)obj.GetValue(MouseOverProperty);
}
public static void SetMouseOver(DependencyObject obj, bool value)
{
obj.SetValue(MouseOverProperty, value);
}
private static void MouseOverBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
if (element != null)
{
// Unsure about this method..
}
}
}
Also I've added this to my button to try and link them, it appears to work:
ex:MouseBehaviour.MouseOver="{Binding MouseOverControl}"
However nothing happens, this is because I think it's working the wrong way around at the moment, so it is expecting my DC property to change but I want it so the MouseOverControl in my DC reflects the value of the IsMouseOver property of my button. Would it be as simple as:
SetMouseOver(element, element.IsMouseOver);
Or something similar?
First thing come to mind is binding IsMouseOver property to MouseOverControl property in viewmodel directly without trigger. Unfortunately that scenario is not supported.
One possible workaround to address that limitation is using event that is raised whenever IsMouseOver property changed to trigger method/command in viewmodel. We can do that using interaction triggers. Since IsMouseOverChanged event doesn't exist, we can use 2 events (MouseEnter and MouseLeave) as alternative.
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ei:CallMethodAction MethodName="MouseEnter" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeave">
<ei:CallMethodAction MethodName="MouseLeave" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Then have corresponding methods in viewmodel :
public void MouseEnter()
{
MouseOverControl = true;
}
public void MouseLeave()
{
MouseOverControl = false;
}
Another possible way is by creating attached behavior for MouseOver so that you can bind it to viewmodel's property as demonstrated in this blog post.
So I ended up solving this by adding my own action to update a property because CallMethodAction is only available in Blend 4 which at the time I'm unable to use.
This question helped me considerably: Setting a property with an EventTrigger
In particular I'd like to direct you to user Neutrino's answer on that page (Here), the only part I needed to change was the XAML implementation:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ex:SetPropertyAction PropertyName="MouseOverControl" TargetObject="{Binding}"
PropertyValue="true" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<ex:SetPropertyAction PropertyName="MouseOverControl" TargetObject="{Binding}"
PropertyValue="false"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Quick explanation is whenever the mouse enters a button I've added these triggers to, it sets a property in my viewmodel/datacontext to mirror this, perfect! Credit to har07 for providing several alternate solutions which also would have worked in different situations (and if I could figure out attached behaviors!!)

WPF: dynamic menu with buttons

I have a simple wpf-mvvm application where you can create and edit records. Something like this:
If you create a new record there are a "create" and "cancel" button.
If you edit an existing record there is a "edit", "delete" and "cancel" button.
I don't want to use two different form. I would like to use one, and create a dynamic menu, where I can choose which buttons are visible.
The xaml now is something like this:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button MinWidth="93" Command="{Binding CreateCommand}>
Create
</Button>
<Button MinWidth="93" Command="{Binding EditCommand}>
Edit
</Button>
<Button MinWidth="93" Command="{Binding DeleteCommand}>
Delete
</Button>
<Button MinWidth="93" Command="{Binding CancelCommand}>
Cancel
</Button>
</StackPanel>
What is the best way to do this?
I've had a similar situation. There are two options (at least, as always):
Use the CanExecute method of the commands and let them return true or false, depending on the type of record you want to edit. The CanExecute value toggles the IsEnabled property of the control it is bound to. This means, if you want to hide the control, you need to 'push' the IsEnabled value to the Visibility value, for example by using a style trigger.
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</Trigger>
</Style.Triggers>
That would be the standard approach, I guess, and probably makes sense for you.
I had more dynamic circumstances and wanted to create the buttons dynamically. This can easily be done, when you define a Collection of CommandViewModels in your ViewModel. The CommandViewModel can have a name property which you display in a button and the command you want to execute. Then you can use this collection to populate an ItemsControl with buttons. Probably a bit of a overkill for your situation, but it refers to the title of your question and maybe you find it interesting and can use it at some point.
In short, the ViewModels:
public class CommandViewModel : ViewModelBase
{
public ICommand Command { get { return ... } }
public string Name { get; set; }
}
public class MainViewModel : ViewModelBase
{
...
ObservableCollection<CommandViewModel> Commands { get; private set; }
public MainViewModel()
{
Commands = new ObservableCollection<CommandViewModel>();
// Creates the ViewModels for the commands you want to offer
PopulateCommands();
}
}
And in the XAML looks something like:
<ItemsControl ItemsSource="{Binding Commands}"}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Command}" Content="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
That makes a dynamic menu...
Have fun.
If you are using MVVM, then you have a ViewModel which is your DataContext which contains the Create, edit, delete, and cancel commands.
Have your ViewModel have an instance of a record. If you edit it, pass in the instance to be edited. Else for creating a record set that to null.
Create your commands and have the CanExecute functionality check if the record that was passed in was null or not. (null represents Creating a new record, else editing). If you set the CanExecute of your commands to false, the buttons bound to it will automatically be disabled.

Enable button based on TextBox value (WPF)

This is MVVM application. There is a window and related view model class.
There is TextBox, Button and ListBox on form. Button is bound to DelegateCommand that has CanExecute function. Idea is that user enters some data in text box, presses button and data is appended to list box.
I would like to enable command (and button) when user enters correct data in TextBox. Things work like this now:
CanExecute() method contains code that checks if data in property bound to text box is correct.
Text box is bound to property in view model
UpdateSourceTrigger is set to PropertyChanged and property in view model is updated after each key user presses.
Problem is that CanExecute() does not fire when user enters data in text box. It doesn't fire even when text box lose focus.
How could I make this work?
Edit:
Re Yanko's comment:
Delegate command is implemented in MVVM toolkit template and when you create new MVVM project, there is Delegate command in solution. As much as I saw in Prism videos this should be the same class (or at least very similar).
Here is XAML snippet:
...
<UserControl.Resources>
<views:CommandReference x:Key="AddObjectCommandReference"
Command="{Binding AddObjectCommand}" />
</UserControl.Resources>
...
<TextBox Text="{Binding ObjectName, UpdateSourceTrigger=PropertyChanged}"> </TextBox>
<Button Command="{StaticResource AddObjectCommandReference}">Add</Button>
...
View model:
// Property bound to textbox
public string ObjectName
{
get { return objectName; }
set {
objectName = value;
OnPropertyChanged("ObjectName");
}
}
// Command bound to button
public ICommand AddObjectCommand
{
get
{
if (addObjectCommand == null)
{
addObjectCommand = new DelegateCommand(AddObject, CanAddObject);
}
return addObjectCommand;
}
}
private void AddObject()
{
if (ObjectName == null || ObjectName.Length == 0)
return;
objectNames.AddSourceFile(ObjectName);
OnPropertyChanged("ObjectNames"); // refresh listbox
}
private bool CanAddObject()
{
return ObjectName != null && ObjectName.Length > 0;
}
As I wrote in the first part of question, following things work:
property setter for ObjectName is triggered on every keypress in textbox
if I put return true; in CanAddObject(), command is active (button to)
It looks to me that binding is correct.
Thing that I don't know is how to make CanExecute() fire in setter of ObjectName property from above code.
Re Ben's and Abe's answers:
CanExecuteChanged() is event handler and compiler complains:
The event
'System.Windows.Input.ICommand.CanExecuteChanged'
can only appear on the left hand side
of += or -=
there are only two more members of ICommand: Execute() and CanExecute()
Do you have some example that shows how can I make command call CanExecute().
I found command manager helper class in DelegateCommand.cs and I'll look into it, maybe there is some mechanism that could help.
Anyway, idea that in order to activate command based on user input, one needs to "nudge" command object in property setter code looks clumsy. It will introduce dependencies and one of big points of MVVM is reducing them.
Edit 2:
I tried to activate CanExecute by calling addObjectCommand.RaiseCanExecuteChanged() to ObjectName property setter from above code. This does not help either. CanExecute() is fired few times when form is initialized, but after that it never gets executed again. This is the code:
// Property bound to textbox
public string ObjectName
{
get { return objectName; }
set {
objectName = value;
addObjectCommand.RaiseCanExecuteChanged();
OnPropertyChanged("ObjectName");
}
}
Edit 3: Solution
As Yanko Yankov and JerKimball wrote, problem is static resource. When I changed button binding like Yanko suggested:
<Button Command="{Binding AddObjectCommand}">Add</Button>
things started to work immediately. I don't even need RaiseCanExecuteChanged(). Now CanExecute fires automatically.
Why did I use static resource in first place?
Original code was from WPF MVVM toolkit manual. Example in that manual defines commands as static resource and then binds it to menu item. Difference is that instead of string property in my example, MVVM manual works with ObservableCollection.
Edit 4: Final explanation
I finally got it. All I needed to do was to read comment in CommandReference class. It says:
/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when
/// databinding from XAML.
/// </summary>
So, CommandReference is used for KeyBinding, it is not for binding in visual elements. In above code, command references defined in resources would work for KeyBinding, which I don't have on this user control.
Of course, sample code that came with WPF MVVM toolkit were correct, but I misread it and used CommandReference in visual elements binding.
This WPF MVVM really is tricky sometimes.
Things look much clearer now with the edits, thanks! This might be a stupid question (I'm somewhat tired of a long day's work), but why don't you bind to the command directly, instead of through a static resource?
<Button Command="{Binding AddObjectCommand}">Add</Button>
Since you are using the DelegateCommand, you can call it's RaiseCanExecuteChanged method when your text property changes. I'm not sure what you are trying to accomplish with your CommandReference resource, but typically you just bind the commands directly to the button element's Command property:
<TextBox Text="{Binding ObjectName, UpdateSourceTrigger=ValueChanged}" />
<Button Command="{Binding AddObjectCommand}" Content="Add" />
This would be the relevant portion of your view model:
public string ObjectName
{
get { return objectName; }
set
{
if (value == objectName) return;
value = objectName;
AddObjectCommand.RaiseCanExecuteChanged();
OnPropertyChanged("ObjectName");
}
}
Try raising CanExecuteChanged when your property changes. The command binding is really distinct from the property binding and buttons bound to commands are alerted to a change in status by the CanExecuteChanged event.
In your case, you could fire a check when you do the PropertyChanged on the bound property that would evaluate it and set the command's internal CanExecute flag and then raise CanExecuteChanged. More of a "push" into the ICommand object than a "pull".
Echoing Abe here, but the "right" path to take here is using:
public void RaiseCanExecuteChanged();
exposed on DelegateCommand. As far as dependencies go, I don't think you're really doing anything "bad" by raising this when the property that the command depends on changes within the ViewModel. In that case, the coupling is more or less contained wholly within the ViewModel.
So, taking your above example, in your setter for "ObjectName", you would call RaiseCanExecuteChanged on the command "AddObjectCommand".
I know this is an old question but I personally think it's easier to bind the textbox Length to button's IsEnabled property, e.g.:
<TextBox Name="txtbox" Width="100" Height="30"/>
<Button Content="SomeButton " Width="100" Height="30"
IsEnabled="{Binding ElementName=txtbox, Path=Text.Length, Mode=OneWay}"></Button>
If ElementName binding does not work, use:
<Entry x:Name="Number1" Text="{Binding Number1Text}" Keyboard="Numeric"></Entry>
<Entry x:Name="Number2" Text="{Binding Number2Text}" Keyboard="Numeric"></Entry>
<Button Text="Calculate" x:Name="btnCalculate" Command="{Binding CalculateCommand}" IsEnabled="{Binding Source={x:Reference Number1, Number2}, Path=Text.Length, Mode=OneWay}"></Button>
or use:
<Entry x:Name="Number1" Text="{Binding Number1Text}" Placeholder="Number 1" Keyboard="Numeric"></Entry>
<Entry x:Name="Number2" Text="{Binding Number2Text}" Placeholder="Number 2" Keyboard="Numeric"></Entry>
<Button VerticalOptions="Center" Text="Calculate" x:Name="btnCalculate" Command="{Binding CalculateCommand}">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Source={x:Reference Number1, Number2},
Path=Text.Length}"
Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Button.Triggers>

What is the best way to start an animation when a bound value changes?

This is a situation that comes up often:
In the View, you have a control bound to a ViewModel property (backed by a INotifyPropertyChanged). For example:
<TextBlock Text="{Binding Path=Subtotal}"/>
When the property changes, you need to bring the user attention to the fact with some creative animation. How I can utilize the fact that the view is already wired to the notification and avoid creating much of the extra code (or at least create it once and re-use). Data triggers are probably the best choice, but I do not know how to make them fire on any value change versus on some specific value.
The following options come to mind:
raise an additional event in the ViewModel, subscribe in the View code-behind.
create a datatrigger bound to the property mentioned using a convertor that would return true if the value is changing.
create a datatrigger bound to a new boolean property on the ViewModel which is used to "signal" the change.
create a behavior attached to the control which would subscribe to the control's dependency property change and start the animation.
Which one do you like/use? Did I miss any options?
P.S. It would be nice (but not critical) if the solution would provide a possibility to start the animation first and reflect the value change when it is ended.
Ok, this is what I came to after some experimenting.
I have created an Expression Blend 3 trigger with a dependency property (I named it Subscription). I bind the Subscription to the same value that my TextBlock is bound to and this trigger is attached to a ControlStoryboardAction from Expression Blend 3.
Here's the trigger:
public class DataTriggerPlus : TriggerBase<DependencyObject>
{
public static readonly DependencyProperty SubscriptionProperty =
DependencyProperty.Register("Subscription",
typeof(string),
typeof(DataTriggerPlus),
new FrameworkPropertyMetadata("",
new PropertyChangedCallback(OnSubscriptionChanged)));
public string Subscription
{
get { return (string)GetValue(SubscriptionProperty); }
set { SetValue(SubscriptionProperty, value); }
}
private static void OnSubscriptionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((DataTriggerPlus)d).InvokeActions(null);
}
}
Here's how it is attached to the storyboard:
<TextBlock x:Name="textBlock" Text="{Binding TestProp}" Background="White">
<i:Interaction.Triggers>
<local:DataTriggerPlus Subscription="{Binding TestProp}">
<im:ControlStoryboardAction
Storyboard="{StaticResource Storyboard1}"/>
</local:DataTriggerPlus>
</i:Interaction.Triggers>
</TextBlock>
I like this approach a lot, great job Blend 3 designers!
Edit: answering Drew comment...
Yes, it ships with Blend. You can just include Microsoft.Expression.Interactions.dll and System.Windows.Interactivity into your project.
And yes, it is verbose (I have asked if somebody figured out a good way to apply behaviours via Styles in this question) - but there is also a benefit of flexibility. For example you can not only start a storyboard, but also switch a state or do some other action from the same trigger.
You can create a trigger that will start the animation.
Something like this:
<Style>
<Style.Triggers>
<Trigger
Property="ViewModelProperty"
Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="YourStoryBoard" />
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
As for the issue for the issue of setting the value once the animation has completed, this is a bit of a pain. As far as I'm aware you'd need to use the completed event on the storyboard, this requires code behind, which is something you want to avoid with MVVM.
I've tried using EventTriggers to bind to the completed events, but that also introduces some complications. See here for more details.

Resources