Prism for WPF - How to send CancelEventArgs to viewmodel? - wpf

How to send CancelEventArgs to the viewmodel on window close? I tried following way, but CloseWindow command method always receive CancelEventArgs argument as null. How to get CancelEventArgs to viewmodel in prism way?
<!-- View-->
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="Closing">
<interactivity:InvokeCommandAction Command="{Binding CloseWindowCommand}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
ViewModel
CloseWindowCommand = DelegateCommand<CancelEventArgs>.FromAsyncHandler(CloseWindow);
public async Task CloseWindow(CancelEventArgs args)
{
//Do Stuff
}

You should use Prism's own InvokeCommandAction class:
xmlns:prism="http://prismlibrary.com/"
...
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="Closing">
<prism:InvokeCommandAction Command="{Binding CloseWindowCommand}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
The InvokeCommandAction class that is part of the Blend SDK doesn't support passing the EventArgs as a command parameter.

Related

Disable button when move mouse out of control

Say I have a Telerik RadGridView, outside there is a button. When the mouse is clicking row in the RadGridView, the button is enabled. If the mouse moves outside the RadGridView, then the button is disabled.
My code is
rgv_LostFocus(object sender, eventArgs e)
{
// do something
MyViewModel.IsButtonEnabled = false;
}
However I don't want to use code behind. Maybe using behavior?
You could use an interaction trigger from System.Windows.Interactivity and a ChangedPropertyAction from Microsoft.Expression.Interactions.dll:
<telerik:RadGridView xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<ei:ChangePropertyAction TargetObject="{Binding}" TargetName="IsButtonEnabled" Value="false" />
</i:EventTrigger>
</i:Interaction.Triggers>
...
</telerik:RadGridView>
Please refer to this blog post for information about how to handle events in a MVVM application.
Per #mm8 hint.
<telerik:RadGridView xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding LostFocusCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</telerik:RadGridView>
Then in ViewModel,
public DelegateCommand LostFocusCommand = new DelegateCommand(RadGridViewLostFocus);
In the method 'RadGridViewLostFocus' set the bool property as false;
private void RadGridViewLostFocus()
{
IsButtonEnabled = false;
}

Inner GroupBox does not swallow the Interactivity event

I have a GroupBox within a parent GroupBox. Both of them have their own
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ...}" />
</i:EventTrigger>
</i:Interaction.Triggers>
When I press the inner GroupBox it fires its own Command and then the parent Command is also triggered.
How do I prevent that? How do I make the inner GroupBox swallow the event?
You could use another implementation of TriggerAction that supports passing the event args as a command parameter to the command, for example the EventToCommand class in the MvvmLight library:
<GroupBox Header="Outer" xmlns:mvvm="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding OuterCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock>...</TextBlock>
<GroupBox Header="Inner" Grid.Row="1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<mvvm:EventToCommand Command="{Binding InnerCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock>inner...</TextBlock>
</GroupBox>
</Grid>
</GroupBox>
public class ViewModel
{
public ViewModel()
{
OuterCommand = new RelayCommand(arg => true, (arg)=> { MessageBox.Show("outer"); });
InnerCommand = new RelayCommand(arg => true,
(arg) =>
{
MessageBox.Show("inner");
MouseButtonEventArgs mbea = arg as MouseButtonEventArgs;
if (mbea != null)
mbea.Handled = true;
});
}
public RelayCommand OuterCommand { get; }
public RelayCommand InnerCommand { get; }
}
The ugly thing with this solution is that the view model has a dependency upon the view related MouseButtonEventArgs type though. If you don't like this you can implement your own behaviour as suggested by #adabyron here:
MVVM Passing EventArgs As Command Parameter
Then you could set the Handled property of the MouseButtonEventArgs directly in the behaviour instead of passing it along to the view model.

How to create CommandBinding with a Parameter?

Is there a way to create a CommandBinding with a parameter?
You can normally add bindings to commands for controls doing something like this:
<Control.CommandBindings>
<CommandBinding Command="ApplicationCommands.XXX" CanExecute="XXX_CanExecute"
Executed="XXX_Executed">
</CommandBinding>
</Control.CommandBindings>
I was wondering if there was also a way to pass a paramater?
No, you dont have a CommandParameter on the CommandBinding but you can use the InputBindings from UIElement to add for example a MouseBinding, which has a Command and CommandParameter attribute.
<Control>
<Control.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{StaticResource MyCommand}"
CommandParameter="{x:Static system:Boolean.TrueString}"/>
</Control.InputBindings>
</Control>
You have to assign the parameter value to the ICommandSource.CommandParameter property of the element that invokes the command (for example Button.CommandParameter). This property also accepts a Binding.
You can retrieve the parameter from the ExecutedRoutedEventArgs.Parameter and CanExecuteRoutedEventArgs.Parameter property.
<Window>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Delete"
CanExecute="DeleteCommand_CanExecute"
Executed="DeleteCommand_Executed" />
</Control.CommandBindings>
</Window.CommandBindings>
<Button Content="X"
Command="{x:Static ApplicationCommands.Delete}"
CommandParameter="abc" />
</Window>
private void DeleteCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
var commandParameter = e.Parameter as string; // Returns "abc"
}
private void DeleteCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
var commandParameter = e.Parameter as string; // Returns "abc"
}
When clicking the Button or an ICommandSource in general, it sends the routed command along with the command parameter up the logical tree. The CommandBinding can catch this event and handle it using the registered handlers.
The point is that the command source must define the command parameter and not the CommandBinding. This way, the same CommandBinding can handle different command sources that send different command parameters.
Have you tried the CommandParameter property?
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="MyHeader"
Command="{Binding MyCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItem}" />
</DataGrid.ContextMenu>

Handle MainWindow events in ViewModel - WPF

I want to handle Windows event like Closing, SourceInitialized in my viewModel. I don't want to handle them in my code behind. How can I do that?
Thanks in advance.
Simply use EventToCommand.
ViewModel:
public ICommand WindowClosing
{
get
{
return new RelayCommand<CancelEventArgs>(
(args) =>{
});
}
}
and in XAML:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding WindowClosing}" />
</i:EventTrigger>
</i:Interaction.Triggers>

MVVM Light is too fast :)

I have a simple WM7 Page with a TextBox. Futher, I assigned EventToCommand (a RelayCommand<string>) to this TextBox, reacting to the TextChanged event. For testing pourposes I made additional method TextBox_TextChanged in the page's code behind. Both the command and TextBox_TextChanged print a message box with the textbox content.
Initial value of the TextBox is "ABC". Then I press D and:
TextBox_TextChanged prints ABCD.
The command prints ABC. D is missing.
Why is the command so fast?
Command declaration:
public RelayCommand<string> TextChanged {get; private set;}
Command initialization:
TextChanged = new RelayCommand<string>((s) => MessageBox.Show(s));
Command binding:
<TextBox x:Name="SearchTextBox" Margin="10,0" TextWrapping="Wrap" Text="{Binding SearchString, Mode=TwoWay}" FontStyle="Italic" TextChanged="SearchTextBox_TextChanged" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding TextChanged, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
I can't reproduce this behaviour. I have tried using EventToCommand and a Behaviour(which simply listens to TextChanged event).
Without seeing the code I suspect this might be to do with how you get the text of the search box or a logic error elsewhere.
This is a snippet of how I use EventToCommand:
<TextBox Name="SearchTextBox">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding TestTextChangedCommand,Mode=OneWay}" CommandParameter="{Binding Path=Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
<i:Interaction.Triggers>
</TextBox>
In the viewmodel
m_TestTextChangedCommand = new RelayCommand<string>(val => System.Diagnostics.Debug.WriteLine(val));
As you can see I used a commandparameter to pass the value of the textbox to the viewmodel. This way the viewmodel doesn't have to know about the textbox to get the text value.
An alternative to this approach would be to use behaviours and TwoWay binding to update a property:
<TextBox Name="SearchTextBox" Text="{Binding TextInViewModel, Mode=TwoWay}" >
<i:Interaction.Behaviors>
<sc:UpdateOnTextChangedBehavior/>
</i:Interaction.Behaviors>
</TextBox>
UpdateOnTextChangedBehavior class:
public class UpdateOnTextChangedBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged +=
new TextChangedEventHandler(AssociatedObject_TextChanged);
}
void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(((TextBox)sender).Text);
BindingExpression binding =
this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.TextChanged -=
new TextChangedEventHandler(AssociatedObject_TextChanged);
}
}
What the above does is mimick the behaviour of desktop WPF Binding with UpdateSourceTrigger=PropertyChanged, which is missing in Silverlight. So what will happen, whenever you type into the text box TextInViewModel property will get updated. This property doesn't haven to be a DependencyProperty, it could just be a normal CLR property.
This works with TextBox via parameter for RelayCommand. IOW - RelayCommand<TextBox>
<TextBox Height="72" HorizontalAlignment="Left" Margin="8,136,0,0" Name="txtFilter" Text="" VerticalAlignment="Top" Width="460" >
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding SearchedTextChanged}" CommandParameter="{Binding ElementName=txtFilter}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</TextBox>
public RelayCommand<TextBox> SearchedTextChanged { get; set; }
SearchedTextChanged = new RelayCommand<TextBox>(OnSearchedTextChanged);
private void OnSearchedTextChanged(TextBox val)
{
if (val != null)
{
System.Diagnostics.Debug.WriteLine(val.Text);
}
}
I had a similar issue and found that the databinding operation does not always fire until the TextBox loses focus. However, the Command will fire immediately.
If you want to guarantee that the databinding has occurred before you use the value, you can call the BindingExpression.UpdateSource() method on your control. Try something like this:
var bindTarget = SearchTextBox.GetBindingExpression(TextBox.TextProperty);
bindTarget.UpdateSource();
To avoid referring to your TextBox directly in your ViewModel (as you should with MVVM), you can use FocusManager.GetFocusedElement(). This is particularly useful when dealing with ApplicationBar buttons as they don't seem to receive focus when used.
Some code I sued (similar to yours Command example):
Command declaration:
public RelayCommand<string> TextChanged {get; private set;}
Command initialization:
TextChanged = new RelayCommand<string>((s) => MessageBox.Show(s));
Command binding:
<TextBox x:Name="SearchTextBox" Margin="10,0" TextWrapping="Wrap" Text="{Binding SearchString, Mode=TwoWay}" FontStyle="Italic" TextChanged="SearchTextBox_TextChanged" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding TextChanged, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
For some reasons messagebox shows a string with one character delay.

Resources