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}}"
Related
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.
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();
}
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.
I develop application with using MVVM pattern. I using MVVMLight library to do this. So if I need to handle TextBox TextChange event I write in XAML:
<I:EventTrigger EventName="TextChanged">
<I:InvokeCommandAction Command="{Binding PropertyGridTextChange}"/>
</I:EventTrigger>
where PropertyGridTextChange is Command in ViewModel. But TextBox has no Paste event!
This solution only works if application don't use MVVM pattern, because you need to have link on TextBox.
<DataTemplate x:Key="StringTemplate">
<TextBox Text="{Binding Value, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
</DataTemplate>
Important detail - TextBox placed within DataTemplate.
I have no idea how can I handle "paste event".
I want PasteCommand to be invoked when I paste text into TextBox. And I need that TextBox.Text or TextBox itself to be passed as parameter into PasteCommandMethod.
private RelayCommand<Object> _pasteCommand;
public RelayCommand<Object> PasteCommand
{
get
{
return _pasteCommand ?? (_pasteCommand =
new RelayCommand<Object>(PasteCommandMethod));
}
}
private void PasteCommandMethod(Object obj)
{
}
I can suggest answer on my question.
Class-helper.
public class TextBoxPasteBehavior
{
public static readonly DependencyProperty PasteCommandProperty =
DependencyProperty.RegisterAttached(
"PasteCommand",
typeof(ICommand),
typeof(TextBoxPasteBehavior),
new FrameworkPropertyMetadata(PasteCommandChanged)
);
public static ICommand GetPasteCommand(DependencyObject target)
{
return (ICommand)target.GetValue(PasteCommandProperty);
}
public static void SetPasteCommand(DependencyObject target, ICommand value)
{
target.SetValue(PasteCommandProperty, value);
}
static void PasteCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textBox = (TextBox)sender;
var newValue = (ICommand)e.NewValue;
if (newValue != null)
textBox.AddHandler(CommandManager.ExecutedEvent, new RoutedEventHandler(CommandExecuted), true);
else
textBox.RemoveHandler(CommandManager.ExecutedEvent, new RoutedEventHandler(CommandExecuted));
}
static void CommandExecuted(object sender, RoutedEventArgs e)
{
if (((ExecutedRoutedEventArgs)e).Command != ApplicationCommands.Paste) return;
var textBox = (TextBox)sender;
var command = GetPasteCommand(textBox);
if (command.CanExecute(null))
command.Execute(textBox);
}
}
Using in XAML. In TextBox as attribute.
TextBoxPasteBehavior.PasteCommand="{Binding PropertyGridTextPasted}"
PropertyGridTextPasted - Command in the ViewModel.
I've been struggling with this kind of problem too in recent days. My first approach would be to have a property in the VM that is bound to the text box (which I am sure you already have). Then bind an ICommand to an event to handle the on paste event:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowEditEnding">
<i:InvokeCommandAction Command="{Binding DocRowEdit}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
you need to define the namespace in the proper part of the XAML code, and then put the interaction triggers in as part of the textbox definition. Here I am capturing the RowEditEnding event to do some stuff similar to what you are attempting.
The command binding is another piece, let me know if you need more information on how that needs to be set up.
I have listview and I want to have show new window when someone double click in any position. But I have mvvm application and I don't want to have any function in code behind of xaml file, like this: How to bind a Command to double-click on a row in DataGrid and many other samples like this. I want to have method in viewmodel file and bind it like this:
<ListView ... MouseDoubleClick="{Binding myfunction}">
Thanks
This is a working example of a method to trigger a command (In the ViewModel) based on the clicked item in a list. The command in the ViewModel will get the "clicked" item as its parameter.
I'm using the Textblock.InputBindings and that might be part of the Blend SDK linked by Blachshma, but you will not need any other DLLs for this to work.
In my example the ViewModel is bound to the DataContext of the UserControl, that is why I need to use the RelativeSource FindAncestor to find the ViewModel from my TextBlock.
Edit:
Fixed the width problem by binding the Width of the TextBlock to the ActualWidth of the ListBox.
Just one problem, the double click will only work when you click inside the text in the textblock even if the list itself is much wider.
<ListView ItemsSource="{Binding Model.TablesView}" Grid.Row="1"
SelectedItem="{Binding Model.SelectedTable, Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}"
Width="{Binding Path=ActualWidth,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" >
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.MoveItemRightCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding .}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can use Attached Properties to bind any event you want.
For MouseDoubleClick:
namespace Behavior
{
public class MouseDoubleClick
{
public static DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(MouseDoubleClick),
new UIPropertyMetadata(CommandChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(MouseDoubleClick),
new UIPropertyMetadata(null));
public static void SetCommand(DependencyObject target, ICommand value)
{
target.SetValue(CommandProperty, value);
}
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Control control = target as Control;
if (control != null)
{
if ((e.NewValue != null) && (e.OldValue == null))
{
control.MouseDoubleClick += OnMouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
control.MouseDoubleClick -= OnMouseDoubleClick;
}
}
}
private static void OnMouseDoubleClick(object sender, RoutedEventArgs e)
{
Control control = sender as Control;
ICommand command = (ICommand)control.GetValue(CommandProperty);
object commandParameter = control.GetValue(CommandParameterProperty);
command.Execute(commandParameter);
}
}
}
And in Xaml:
<ListBox Behavior:MouseDoubleClick.Command="{Binding ....}"
Behavior:MouseDoubleClick.CommandParameter="{Binding ....}"/>
The easiest way to do this is to use System.Windows.Interactivity and Microsoft.Expression.Interactions (both freely available through the Blend SDK)
So start by adding the following namespaces to your view
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Next, catch the DoubleClick event and pass it to the command:
<ListView ..... >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<local:EventToCommand Command="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.myfunction}" />
</i:EventTrigger
</i:Interaction.Triggers>
</ListView>
Note: The EventToCommand used is the one from the MVVM Light Toolkit and can be downloaded here.
What it does is execute the command (myFunction) as soon as the event is triggered.
This is based on the assumption that the myFunction command is in the DataContext which the ListView users. Otherwise, modify the binding of the EventToCommand to wherever the command is.