7

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.

Kurtevich
  • 345
  • 8
  • 18
  • Google for interactivity triggers. – Rohit Vats Jan 22 '14 at 14:28
  • or just cast the DataContext of the view as ViewModel and do what ever you want to do.ViewModel vm = this.DataContext as ViewModel; then you can do vm.SomeAction – Krishna Jan 22 '14 at 14:33
  • @RohitVats Thanks, I saw using of them in some examples, but it didn't look like what I need. Now I see that I was wrong. – Kurtevich Jan 22 '14 at 14:47

3 Answers3

9

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}" />
Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • This looks interesting... I need to check how it works. – Kurtevich Jan 22 '14 at 14:45
  • This works, and I also like how clean and simple and simple this solution is. I've been also advised about interactivity triggers, but my opinion is that this is better. Thanks! – Kurtevich Jan 22 '14 at 15:19
  • Can you show the Attached Event way? I could not find out how to register event handler at VM using attached event because all routed events do not support binding or aren’t they? – Yarl Jun 19 '17 at 17:26
  • @Uzivatel828, there is a link to a page that explains Attached Events in my answer. If that doesn't help, I suggest that you ask a new question, specific to your actual problem. – Sheridan Jun 28 '17 at 14:25
  • @Sheridan: I know how to implement attached events. I could not find out how to use attached events to handle non-attached routed events. Maybe I misunderstood your answer. – Yarl Jun 29 '17 at 12:17
2

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.

Mashton
  • 6,037
  • 2
  • 25
  • 35
  • Yes, the data is coming from ViewModel, but the event is raised by a 3dparty WinForms control when filtering is done inside of it. – Kurtevich Jan 22 '14 at 16:03
  • 1
    Oh ok. Then I'd use the Interactivity Namespace, as it takes a lot less code than writing new attached properties: ` ` – Mashton Jan 23 '14 at 09:49
0

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

BRAHIM Kamel
  • 13,492
  • 1
  • 36
  • 47