MVVM binding to CLR Events

2019-01-18 15:43发布

问题:

How do you go about binding to a CLR event using the mvvm pattern?

For routed events I am using the EventToCommandTrigger from Cinch's framework and that is working great.

I checked out the Behaviors and Effects from the Expression Blend Samples and it looks like the DataEventTrigger is what I should use, but the sample is a little confusing.

I want the IsVisibleChanged event to fire my IsVisibleChangedCommand. I am also not sure what code needs to go in the ViewModel to support this.

<i:Interaction.Triggers>

    <i:EventTrigger EventName="SelectedItemChanged">
        <framework:EventToCommandTrigger Command="{Binding SelectedMenuItemChangedCommand}"
                                        CommandParameter="{Binding SelectedValue, ElementName=lstClusters}" />
    </i:EventTrigger>

    <framework:DataEventTrigger EventName="IsVisibleChanged"
                            Source="{Binding IsVisibleChangedCommand}">
    </framework:DataEventTrigger>

</i:Interaction.Triggers>

回答1:

You can use the Expression Blend SDK for invoking commands in response to events in general, however my experience has been that not all events are supported by EventTrigger.

For example, this seems to work:

<Label Content="LabelText">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseMove">
            <i:InvokeCommandAction Command="{Binding IsVisibleChangedCommand, Mode=OneWay}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Label>

But this doesn't:

<Label Content="LabelText">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="IsVisibleChanged">
            <i:InvokeCommandAction Command="{Binding IsVisibleChangedCommand, Mode=OneWay}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Label>

I'm not sure why, but Blend SDK does not seem to like the IsVisible property. You can do something similar but using Visibility (instead of IsVisible) with a PropertyChangedTrigger like this:

<Label x:Name="label1" Content="LabelText">
    <i:Interaction.Triggers>
        <ei:PropertyChangedTrigger Binding="{Binding Visibility, ElementName=label1}">
            <i:InvokeCommandAction Command="{Binding IsVisibleChangedCommand, Mode=OneWay}"/>
        </ei:PropertyChangedTrigger>
    </i:Interaction.Triggers>
</Label>

BTW - Here are the namespaces:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

Also, here is another solution to your specific problem that does not require the Blend SDK:

Instead of directly binding an event to a command, you can bind the Visibility property of your object to a viewmodel property and execute the command from the property setter like this:

View:

<UserControl x:Class="SampleApp.Views.EventBindingDemoView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Height="200" Width="200">
    <UserControl.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </UserControl.Resources>
    <Grid>
        <Label Content="LabelText" Visibility="{Binding Path=ElementIsVisible, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>

        <!-- The rest of your view here -->

    </Grid>
</UserControl>

ViewModel (ViewModelBase should implement INotifyPropertyChanged and OnPropertyChanged(string propertyName) ):

public class EventBindingDemoViewModel : ViewModelBase
{
    private bool ElementIsVisibleField = true; // or false if it should initially be hidden
    public bool ElementIsVisible
    {
        get { return this.ElementIsVisibleField; }
        set
        {
            if (this.ElementIsVisibleField != value)
            {
                this.ElementIsVisibleField = value;
                this.OnPropertyChanged("ElementIsVisible");

                // Execute command
                this.IsVisibleChangedCommand.Execute(null);
            }
        }
    }

    public ICommand IsVisibleChangedCommand;

    // The rest of your viewmodel here
}

Either way should work.