Reputation: 345
The case is: I have a control's event that I want my ViewModel to react on. Currently I'm doing this by executing a command of invisible button like in the example below.
In View.xaml:
<Control x:Name="SearchResultGrid" ... DataRefreshed="SearchResultRefreshed" />
<Button x:Name="SearchResultRefreshedButton" Visibility="Collapsed" Command="{Binding SearchResultRefreshedCommand}" />
In View.xaml.cs:
private void SearchResultRefreshed(object sender, EventArgs e)
{
if (SearchResultRefreshedButton.Command != null)
{
SearchResultRefreshedButton.Command.Execute(SearchResultGrid.ResultRowCount);
}
}
This works good, but it looks like a hack to me. I'm wondering if there is better (standard) way of doing this? I could not find any examples and this is what I "invented" myself.
Upvotes: 7
Views: 7186
Reputation: 69959
Using MVVM, the general way to handle events is to simply wrap them in Attached Properties, or use Attached Events. Here is an example using the PreviewKeyDown
event in an Attached Property:
public static DependencyProperty PreviewKeyDownProperty = DependencyProperty.RegisterAttached("PreviewKeyDown", typeof(KeyEventHandler), typeof(TextBoxProperties), new UIPropertyMetadata(null, OnPreviewKeyDownChanged));
public static KeyEventHandler GetPreviewKeyDown(DependencyObject dependencyObject)
{
return (KeyEventHandler)dependencyObject.GetValue(PreviewKeyDownProperty);
}
public static void SetPreviewKeyDown(DependencyObject dependencyObject, KeyEventHandler value)
{
dependencyObject.SetValue(PreviewKeyDownProperty, value);
}
public static void OnPreviewKeyDownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
TextBox textBox = dependencyObject as TextBox;
if (e.OldValue == null && e.NewValue != null) textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
else if (e.OldValue != null && e.NewValue == null) textBox.PreviewKeyDown -= TextBox_PreviewKeyDown;
}
private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
KeyEventHandler eventHandler = GetPreviewKeyDown(textBox);
if (eventHandler != null) eventHandler(sender, e);
}
Note that it is just as easy (and better too) to use an ICommand
instead of the actual KeyEventArgs
object which shouldn't really be in the view model. Just create an Attached Property of type ICommand
and call that from this TextBox_PreviewKeyDown
handler instead:
private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
ICommand command = PreviewKeyDownCommand(textBox);
if (command != null && command.CanExecute(textBox)) command.Execute(textBox);
}
Either way, it would be used something like this:
<TextBox TextBoxProperties.PreviewKeyDown="SomeKeyEventHandler" />
Or if you used the preferred ICommand
method:
<TextBox TextBoxProperties.PreviewKeyDownCommand="{Binding SomeCommand}" />
Upvotes: 9
Reputation: 6415
Personally I've never had a need to use an attached property to deal with a control's event. In your example, of a control wanting to know when the 'SearchResultRefreshed' and then informing the ViewModel through the hidden control ... why doesn't the ViewModel already know that the results have been refreshed?
If the results are coming from the ViewModel in the first place, and binding is used to display them within your control, then the knowledge that the search results have been refreshed should be driven by your ViewModel - not your view.
In only a few cases have I found a need to break away from ICommands and data-binding.
Upvotes: 2
Reputation: 13755
You should add a dependency property DataRefreshed
to your control in order to bind on it
here an example how you can do it
public static readonly DependencyProperty DataRefreshedProperty = DependencyProperty.Register(
"DataRefreshed",
typeof(bool),
typeof("typeof yourcontrol here "),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnDataRefreshedChanged)
)
);
public bool DataRefreshed
{
get { return (bool)GetValue(DataRefreshedProperty); }
set { SetValue(DataRefreshedProperty, value); }
}
Then you can manipulate your property like any other WPF property for example SearchResultRefreshed
which is defined in your ViewModel
<Control x:Name="SearchResultGrid" ... DataRefreshed="{Binding SearchResultRefreshed}" />
<Button x:Name="SearchResultRefreshedButton" Visibility="Collapsed" Command="{Binding SearchResultRefreshedCommand}" />
take a look at the following tutorial to understand more dependecyproperty and attachedproperty
Upvotes: 0