WPF InvokeCommandAction with ContentControl Changed - c#

I have a content control:
<ContentControl Content="{Binding PageViewModel}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContentChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentControl>
I need to invoke MyCommand every time PageViewModel changes.
However the command is not getting triggered.
Is this possible to do?

You should handle this in the setter of the PageViewModel property:
private object pageViewModel;
public object PageViewModel
{
get { return pageViewModel; }
set
{
pageViewModel = value;
OnPropertyChanged();
MyCommand.Execute(null);
}
}
A ContentControl has no ContentChanged event. It has a protected OnContentChanged method that you could override if you create a custom ControlControl class though:
WPF: How to created a routed event for content changed?
You can create a custom event that you raise in this method.
But the MVVM way of doing this would be to invoke the command in the view model when the PageViewModel property is set.

Related

WPF Executing an ICommand on MainWindow from a UserControl inside a Page

I'm showing a list of Orders, (each Order is a UserControl), in more than one Page. Every time the user left-click the control, main window should load & show the order in a new Page using an ICommand.
x:Name of MainWindow is: mainWindow
MainViewModel
This is de view model associated to the MainWindow.
public class MainViewModel : BaseViewModel
{
public INavigator Navigator { get; set; }
public ICommand OpenOrderCommand { get; set; }
public MainViewModel(INavigator navigator) : base()
{
Navigator = navigator;
OpenOrderCommand = new RelayCommand(OpenOrder);
}
private void OpenOrder(object obj)
{
// Loads the order from db
}
}
I've added an InputBindings tag in the UserControl to detect when the user left-click it.
UserControl
<Grid.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.OpenOrderCommand,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
CommandParameter="{Binding OrderId}"/>
</Grid.InputBindings>
But OpenOrderCommand is never fired, and I get a XAML binding failure with this text:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.OpenOrderCommand; DataItem=null; target element is 'MouseBinding' (HashCode=20815867); target property is 'Command' (type 'ICommand')
How can I execute a ICommand of MainWindow from a UserControl?
UPDATE
It works fine if I use current Page instead of MainWindow.
<Grid.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.OpenOrderCommand,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:HomePage}}}"
CommandParameter="{Binding OrderId}"/>
</Grid.InputBindings>
In the error message it says it can't find the binding source. Try to bind it like this:
<Grid.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding OpenOrderCommand}"
CommandParameter="{Binding OrderId}"/>
</Grid.InputBindings>
and to set the DataContext like this:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
In my test code the Grid only raised the event when I had explecitly set a value for the Background-Property. Transparent was also a value which worked.

Referring to interaction trigger control

I am using IController interface to bind action handlers to control properties like this:
<TextBox Name="FirstNameTextBox" Margin="5" Text="{Binding FirstName, UpdateSourceTrigger=Explicit}"
MinWidth="200">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding Path=GetBindingCommand}"
CommandParameter="{Binding ElementName=FirstNameTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
I need to pass an evoking control to handler, so I do in with
CommandParameter="{Binding ElementName=FirstNameTextBox}"
This works fine and does it's job, but I need to specify each control by name and repeat almost the same code of trigger setting for each control. If there is a way to refer to a control from trigger, I could have made a resource and share it for every control, but I didn't find one. So this is the question: can I somehow refer to a control from the trigger without specifying a name?
Update
Ok, I've found a solution for this. You have "just" define your own TriggerAction like this
class SenderAccessTrigger : TriggerAction<DependencyObject>
{
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
protected override void Invoke(object parameter)
{
if(AssociatedObject!=null && Command!=null)
{
Command.Execute(AssociatedObject);
}
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(SenderAccessTrigger), new UIPropertyMetadata());
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(SenderAccessTrigger), new UIPropertyMetadata());
}
The thing is that TriggerAction class has an AssociatedObject property, which is actually the control, evoking the event. But this property is private. So to gain access to it I've defined my own TriggerAction, which executes the Command with this AssociatedObject as a parameter.
Now in xaml instead of
<i:InvokeCommandAction Command="{Binding Path=GetBindingCommand}"
CommandParameter="{Binding ElementName=FirstNameTextBox}"/>
I can do
<local:SenderAccessTrigger Command="{Binding Path=GetBindingCommand}"/>
and get an evoking control as a parameter in a Command.
But there is another problem. Interaction.Triggers appeared to be not a dependency property, so one can not assign a resource to it. So I still have to repeat the same code for every control I want to assign this command to.
How about:
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"

WPF Button Command not firing ICommand in ViewModel

I have a View with a button as follows:
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
Command="{Binding DataContext.CmdTestButtonClicked}"
CommandParameter="{Binding}" />
In the view's code-behind, I set the DataContext to the ViewModel:
public GlobalSettings()
{
InitializeComponent();
...
DataContext = Helpers.IoCHelper.GlobalSettingsVM;
...
}
My ViewModel derives from a base class which exposes the ICommand:
public class GlobalSettingsVM : CollectionViewModel<GlobalSettings> { ... }
public abstract class CollectionViewModel<TModel> : IInstallModuleViewModel, INotifyPropertyChanged,
INotifyDataErrorInfo where TModel : Model, new()
{
...
public ICommand CmdTestButtonClicked
{
get
{
return _testButtonClicked ??
(_testButtonClicked = new RelayCommand(TestButtonClicked));
}
}
protected virtual void TestButtonClicked(object o)
{
// I never get here
}
}
I don't have any other issues using this pattern throughout my application, however all my other implementations have the Button within a ListView, so there I have to use RelativeSource={RelativeSource AncestorType={x:Type ListView}}.
Why would this command never fire? Do I need to set a RelativeSource here as well?
This
Command="{Binding DataContext.CmdTestButtonClicked}"
Implies that the Command will look for a property called DataContext in the object to which the button is bound.
If the DataContext of the button is a GlobalSettingsVM this should work:
Command="{Binding CmdTestButtonClicked}"
You could also use the MVVM Light toolkit wich is very convenient and helping on these situations.
You would get Something like this :
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
<i:Interaction.Triggers>
<i:EventTrigger EventName="OnClick" >
<Command:EventToCommand Command="{Binding DataContext.CmdTestButtonClicked}" CommandParameter="{Binding}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In my case, I was listening to PreviewMouseLeftButtonDown under constructor of xaml.cs class which was stopping command event callback to the view model.
this.PreviewMouseLeftButtonDown += (s, e) => DragMove();
Instead of above line, in xaml file for window added MouseLeftButtonDown="Window_MouseLeftButtonDown" click handler and handled window drag within it, as below
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}

How to pass the updated text by CommandParameter in TextChagned event?

This is an interesting case. I use MVVMLight to catch the event TextChagned of a textbox and pass it to a command in ViewModel and somehow the text value passed by CommandParameter is still the old text before the update.
Anyone knows how to get the new text?
<Grid>
<TextBox x:Name="myTextBox" HorizontalAlignment="Left"
Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="Hello"
VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding Mode=OneWay,Path=TextChangedCommand}"
PassEventArgsToCommand="True"
CommandParameter="{Binding Path=Text,ElementName=myTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="Button" HorizontalAlignment="Left" Margin="352,214,0,0"
VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real"
////}
}
private RelayCommand<string> _textChangedCommand;
public RelayCommand<string> TextChangedCommand
{
get
{
if (this._textChangedCommand == null)
{
this._textChangedCommand = new RelayCommand<string>(this.TextChanged);
}
return this._textChangedCommand;
}
}
private void TextChanged(string input)
{
MessageBox.Show(input);
}
}
I was digging into the code of MVVM Light and in EventToCommand.cs's Invoke method it seems that the parameter still has its old value. I didn't look further, but maybe this is a bug in MVVM.
There is a workaround you can do though.
Create a new class implementing IEventArgsConverter, something similar to this:
public class TextChangedEventArgsConverter : IEventArgsConverter
{
public object Convert(object value, object parameter)
{
var textChangedEventArgs = (TextChangedEventArgs) value;
return ((TextBox) textChangedEventArgs.Source).Text;
}
}
Add it to the Resources collection of your Window or ResourceDictionary where you have the EventToCommand that's not working.
<Window.Resources>
<textChanged:TextChangedEventArgsConverter x:Key="TextChangedEventArgsConverter" />
</Window.Resources>
And change the EventToCommand to the following:
<cmd:EventToCommand Command="{Binding Mode=OneWay,Path=TextChangedCommand}"
PassEventArgsToCommand="True"
EventArgsConverter="{StaticResource TextChangedEventArgsConverter}" />
So the specified EventArgsConverter will receive the actual TextChangedEventArgs and will extract the Text itself, which will be the correct value.
With these changes, I was able to achieve what you are looking for.
By the way: don't use CommandParameter and PassEventArgsToCommand="True" at the same time. As the documentation says, if you are setting PassEventArgsToCommand to true, the type parameter of your RelayCommand should be the event argument type of the event which is TextChangedEventArgs in this case.

How can I invoke a command on the ViewModel on SelectionChanged of a ListView?

In a MVVM/WPF environment, I want to invoke a command (ComputeCommand) on the ViewModel when the SelectionChanged event of a ListView is raised. How can this be done, either in XAML or in C#?
Here is my command class. I have tried MainViewModel.Instance.MyCommand.Execute(); in the codebehind, but it's doesn't accept that.
public class ComputeCommand : ICommand
{
public ComputeCommand(Action updateReport)
{
_executeMethod = updateReport;
}
Action _executeMethod;
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeMethod.Invoke();
}
}
I really recommend the use of a Mvvm framework like MVVM Light, so you can do something like this:
XAML:
xmlns:MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
xmlns:Custom="clr-namespace:System.Windows.Interactivity; assembly=System.Windows.Interactivity"
<ListBox>
...
<Custom:Interaction.Triggers>
<Custom:EventTrigger EventName="SelectionChanged ">
<MvvmLight_Command:EventToCommand PassEventArgsToCommand="False" Command="{Binding Path=ComputeCommand}"/>
</Custom:EventTrigger>
</Custom:Interaction.Triggers>
</Listbox>
ViewModel:
public RelayCommand ComputeCommand{ get; private set; }
This is IMO an elegant way to keep your events wiring clean and tidy.
To answwer your question - you are missing a parameter. THis call should work:
MainViewModel.Instance.MyCommand.Execute(null);
However, you dont need an ICommand for that, this interface serves different purpose.
What you need is to either handle SelectionChanged on view side
var vm = DataContext as YourViewModelType;
if (vm != null)
{
vm.Compute(); //some public method, declared in your viewmodel
}
or to handle it on viewmodel side by binding to IsSelected property of item container
In general: To invoke a command when an event of a control is raised, you can use EventTriggers.
<ListView>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" >
<i:InvokeCommandAction Command="{Binding CommandToBindTo}" CommandParameter="{Binding CommandParameterToBindTo}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
To do this, you need to reference System.Windows.Interactivity.dll in your XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
That being said, you should use a MVMM framework, for example MVVM to simplify the implementation of commands in general. It is not maintainable in the long run to have a single class for every command you need. A framework like MVVMLight or PRISM provides DelegateCommands which allow you to create new commands from delegates (methods on your ViewModel) directly.
First you have to define a binding for Command, so the function that have to be
executed on the invokation of the command.
This can be done in XAML, like:
<CommandBinding Command="name_of_the_namespace:ComputeCommand" Executed="ComputeCommandHandler" />
After, you can, for example in some class initialize a command, like:
public class AppCommands {
public static readonly ICommand ComputeCommand =
new RoutedCommand("ComputeCommand", typeof(AppCommands));
}
and after can use it like:
AppCommands.ComputeCommand.Execute(sender);
When you're dealing with WPF, so MVVM pattern, you need to write the much more code the usual, but benefit from flexibility of it.

Categories

Resources