WPF: How to bind MouseEnter event from an ItemsControl.ItemTemplate to elements outside this ItemsControl

3.6k Views Asked by At

I'm new to WPF but I manage to advance slowly towards writing my first serious project. I'm not sure I use the correct terms so please bear with me.

I implemented a set of ItemsControl (User Controls). The item source is a collection of items. Each item holds much data including it's own ID. I would like the User Control to change a property when the mouse hovers over another control outside this set of ItemsControl. To complicate things the other control is also an element of another set of ItemsControl.

I already managed to store the ID of the hovered-over control in a top-level property (In the top DataContext) but I can't find a way to bind to it from within the element buried inside the DataTemplate.

Here's a screenshot:

Selecting Channel 14

In this example the user hovers over channel 14 - as a result, bottom axes X and Z should highlight (I chose them arbitrarily - according to data stored in the database).

I'd be grateful for any idea. Examples will be most welcome!

2

There are 2 best solutions below

3
On

Assuming that: 1) You are using the MVVM design pattern. 2) That there is an underlying ViewModel for each of the items in your ItemsControl.

Then what you need to do is handle the MouseEnter and MouseLeave events in your view and have them trigger Commands in your view model. Then have those ViewModels update properties in your other ViewModels to have them highlight the appropriate items in the other ItemsControl.

<UserControl x:Class="ClassName"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
             xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4">
    <i:Interaction.Triggers>
        <!-- this will call the associated commands on your viewmodel when the mouse enters/leaves the corresponding view in your itemscontrol, from there you can create the viewmodel functionality you'd like-->
        <i:EventTrigger EventName="MouseEnter">
            <i:InvokeCommandAction Command="{Binding MouseEnterCommand}"/>
        </i:EventTrigger>
        <i:EventTrigger EventName="MouseLeave">
            <i:InvokeCommandAction Command="{Binding MouseLeaveCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <Grid Height="10" Width="10">
        <!--the content of your usercontrol-->  
    </Grid>

</UserControl>

Once you get the correct commands notifying your viewmodel that the mouse is hovering over it (or left it). You can manage the state of your viewmodels to create the affects you are looking for.

2
On

I really like the Blend SDK for stuff like this.

Since you mentioned you have the hovered-over ID setting in a property, you could start with a PropertyChangedTrigger at the root level. Next, you will probably want to invoke a command (rather than just a method), since your action includes a parameter (the ID). Use InvokeCommandAction for this. You can trigger a command either on the view or the view-model. If you want to trigger it on the view, then you'll probably want to use ElementName as the binding.

Here's an example.

<UserControl>
    <i:Interaction.Triggers>
        <!-- When "SelectedID" changes, invoke the "SelectedIDChangedCommand" on the 
             element "AxesAndButtons".  Pass the value of "SelectedID" as the 
             command parameter -->
        <ei:PropertyChangedTrigger Binding="{Binding SelectedID}">
            <i:InvokeCommandAction CommandParameter="{Binding SelectedID}"
                Command="{Binding ElementName=AxesAndButtons,Path=SelectedIDChangedCommand}" />
        </ei:PropertyChangedTrigger>
    <i:Interaction.Triggers>

    <my:AxesAndButtonsControl x:Name="AxesAndButtons">
    </my:AxesAndButtonsControl>
</UserControl>

I have assumed that ID property that gets changed is called "SelectedID" (and is a property of the root data context). Also, that your target user control has a defined ICommand "SelectedIDChangedCommand" dependency property that performs the update. That is, something like this:

public class AxesAndButtonsControl : UserControl
{
    public static readonly DependencyProperty SelectedIDChangedCommand = DependencyProperty.Register("SelectedIDChangedCommand", 
        typeof(ICommand), 
        typeof(AxesAndButtonsControl),
        new PropertyMetadata(null));

    public AxesAndButtonsControl()
    {
        SelectedIDChangedCommand = new DelegateCommand(id => {
            // highlight "X" and "Z" or whatever
        });
    }
}

Edit I just noticed that maybe you haven't bound the MouseOver event to update the SelectedID property yet. If that's the case, then you should be able to use an EventTrigger along with a ChangePropertyAction (in a FindAncestor or ElementName binding) to update it. Something like this:

<DataTemplate>
    <Grid>
        <i:Interaction.Triggers>
            <!-- When "MouseEnter" gets triggered, set the property "SelectedID" on the 
                 data context of the closest "UserControl" parent to the value of "ItemID" 
                 in the current data context -->
            <i:EventTrigger EventName="MouseEnter">
                <ei:ChangePropertyAction Value="{Binding ItemID}"
                    TargetObject="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext}"
                    PropertyName="SelectedID" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Grid>
</DataTemplate>