WPF: How to attach mouse events to a viewmodel?
Asked Answered
L

6

6

I am trying to use the MVVM pattern for the first time. So I have an ItemsControl filled with my viewmodel objects, displayed using DataTemplate's; the objects are "nodes" and "edges" represented in DataTemplate's with Thumb and Polyline objects and I want to be able to detect clicks and drags on the ItemsControl in order to move the nodes and edges.

Two questions:

  • How do I attach mouse event handlers to the Polyline's and Thumb's to be handled by the little viewmodels? (I could attach a Thumb.DragDelta handler to the ItemsControl and e.OriginalSource points to the Thumb, but how do I get the corresponding viewmodel object?)
  • How do I attach mouse event handlers to the ItemsControl to detect mouse clicks and drags on blank space? (answer is below)

Note: I know it might not be considered a proper ViewModel if it's directly handling events of the View. But the important point is, I need to handle mouse events and I am not sure how to attach them.

Lorentz answered 15/5, 2009 at 18:19 Comment(0)
L
2

I figured out the answer to the second question. I needed an ItemsControl that supported scrolling, and I needed to have the items on a Grid rather than the default StackPanel. To fulfill both requirements, I used a ControlTemplate:

<!--In the resources...-->
<ControlTemplate x:Key="GraphTemplate" TargetType="ItemsControl">
    <ScrollViewer Name="ScrollViewer"
                  Padding="{TemplateBinding Padding}"
                  HorizontalScrollBarVisibility="Auto">
        ...
            <Grid Name="Panel" IsItemsHost="True"
                  Background="{TemplateBinding ItemsControl.Background}"/>
        ...
    </ScrollViewer>
</ControlTemplate>
<!--Later...-->
<ItemsControl x:Name="_itemsControl" 
              ItemsSource="{Binding Items}"
              Template="{StaticResource GraphTemplate}"
              Background="LightYellow"/>

In order to get mouse events with meaningful mouse coordinates (i.e. coordinates in scrollable space), it was necessary to obtain a reference to the grid using a strange incantation:

Grid grid = (Grid)_itemsControl.Template.FindName("Panel", _itemsControl);

Then you attach event handlers to the grid, and inside the mouse event handlers, get the mouse coordinates w.r.t. the grid using

Point p = e.GetPosition((IInputElement)sender);

In order to get mouse events on the entire surface, the control (actually the grid) must have a background, so I set Background="LightYellow" above, which propagates to the grid via a binding in the ControlTemplate.

Lorentz answered 15/5, 2009 at 19:51 Comment(0)
L
6

I found a way to handle events raised by objects in the DataTemplate.

(1) attach event handlers to the ItemsControl

<ItemsControl x:Name="_itemsControl" 
              Thumb.DragStarted="Node_DragStarted"
              Thumb.DragDelta="Node_DragDelta"
              Thumb.DragCompleted="Node_DragCompleted"
              MouseDoubleClick="OnMouseDoubleClick"
              .../>

(2) to find out which item the event applies to, treat the OriginalSource as a FrameworkElement, and get its DataContext:

void Node_DragStarted(object sender, DragStartedEventArgs e)
{
    var os = (FrameworkElement)e.OriginalSource;
    var vm = os.DataContext as ItemViewModel;
    if (vm != null)
        // do something with the item ViewModel
}
Lorentz answered 15/5, 2009 at 21:55 Comment(0)
C
3

The ViewModel should be disconnected from the GUI, so it doesn't know anything about controls or mouseclicks.

Two options:

  • Call a command in the the ViewModel, as suggested by Thomas
  • Bind the position of the thumb to a property in the ViewModel, then when the control moves around WPF will update the position values in the ViewModel.
Chairborne answered 16/5, 2009 at 2:4 Comment(4)
I see, how would you make a thumb move by itself?Lorentz
Check out codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx for an example of how to make a movable thumb control.Chairborne
That project simply moves the thumbs in code-behind by handling the DragDelta event.Lorentz
Yes. You don't want to do this?Chairborne
L
2

I figured out the answer to the second question. I needed an ItemsControl that supported scrolling, and I needed to have the items on a Grid rather than the default StackPanel. To fulfill both requirements, I used a ControlTemplate:

<!--In the resources...-->
<ControlTemplate x:Key="GraphTemplate" TargetType="ItemsControl">
    <ScrollViewer Name="ScrollViewer"
                  Padding="{TemplateBinding Padding}"
                  HorizontalScrollBarVisibility="Auto">
        ...
            <Grid Name="Panel" IsItemsHost="True"
                  Background="{TemplateBinding ItemsControl.Background}"/>
        ...
    </ScrollViewer>
</ControlTemplate>
<!--Later...-->
<ItemsControl x:Name="_itemsControl" 
              ItemsSource="{Binding Items}"
              Template="{StaticResource GraphTemplate}"
              Background="LightYellow"/>

In order to get mouse events with meaningful mouse coordinates (i.e. coordinates in scrollable space), it was necessary to obtain a reference to the grid using a strange incantation:

Grid grid = (Grid)_itemsControl.Template.FindName("Panel", _itemsControl);

Then you attach event handlers to the grid, and inside the mouse event handlers, get the mouse coordinates w.r.t. the grid using

Point p = e.GetPosition((IInputElement)sender);

In order to get mouse events on the entire surface, the control (actually the grid) must have a background, so I set Background="LightYellow" above, which propagates to the grid via a binding in the ControlTemplate.

Lorentz answered 15/5, 2009 at 19:51 Comment(0)
O
1

There are ways to do that without code-behind...

You could use the attached behavior pattern to map events to commands, see Marlon Grech's implementation here

You could also use a markup extension that I wrote to bind InputBindings to ViewModel commands, like this :

<UserControl.InputBindings>
    <MouseBinding Gesture="LeftClick" Command="{input:CommandBinding SomeCommand}"/>
</UserControl.InputBindings>

However I'm not sure it fits your specific needs...

Ophiology answered 15/5, 2009 at 23:4 Comment(1)
I don't think so, as this is a graphical interface for creating and moving graphical objects, so I need the mouse coordinates, and I need to show feedback while dragging is in progress.Lorentz
L
1

Bea Stollnitz has a drag and drop example titled "How can I drag and drop items between data bound ItemsControls?". I'd post the link, but StackOverflow isn't letting me.

You may want to split up the UI feedback while dragging is in process and the action performed when it is finally dropped.

I would agree w/Thomas and Cameron above, however. You'll want to limit the mixing/matching of event handling and data binding. If you're going the event handling route, you may not want to avoid using the term "View Model" for your objects as it generally denotes the data binding alternative.

Lemmueu answered 20/5, 2009 at 16:43 Comment(1)
This isn't actually useful for me but thanks for the interesting article!Lorentz
M
0

I am using a much more elegant method. I use Prism 2 and datatemplates. So what I have done is this:

<ItemsControl x:Name="SearchImagesList" ItemTemplate="{StaticResource SearchResultsAlbum}"   

and in the ItemTemplate I just created a button inside!

<DataTemplate x:Key="SearchResultsAlbum">                        
    <Button CommandParameter="{Binding}"                 
            Command="{Binding Source={x:Static PhotoBookPRMainModule:ServiceProvider.DesignEditorViewManager}, Path=NavigationCommands.NavigateSearchResultAction}">
Moton answered 2/11, 2010 at 16:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.