Wpf 向每个用户控件添加触发器

Wpf 向每个用户控件添加触发器,wpf,xaml,Wpf,Xaml,我的应用程序有大约15个不同的用户控件,它们在运行时统一加载到内容区域 我的项目100%符合MVVM,因此我将以下XAML插入到每个UserControl的XAML中: <UserControl ... xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" > <intr:Interaction.Triggers>

我的应用程序有大约15个不同的用户控件,它们在运行时统一加载到内容区域

我的项目100%符合MVVM,因此我将以下XAML插入到每个UserControl的XAML中:

<UserControl
    ...
    xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
>

    <intr:Interaction.Triggers>
        <intr:EventTrigger EventName="Loaded">
            <intr:InvokeCommandAction Command="{Binding ViewLoadedCommand}"/>
        </intr:EventTrigger>
    </intr:Interaction.Triggers>

    <!-- Rest of UserControl content here -->

</UserControl>

显然,这并不理想,也违反了DRY

将此XAML应用于这组用户控件的最佳方式是什么?(但不是每个用户控件,因此简单的
是不合适的)。

您可以使用Interaction.Triggers创建一个“基本负载”用户控件,只需在其中放置一个ContentPresenter,将真正的内容绑定到该控件即可

<UserControl x:class="OneOfMyOtherControls">
   <MyBaseUserControl>
     <!-- your UserControl Content-->
   </MyBaseUserControl>
</UserControl>

我使用作为附加属性实现的行为。与System.Windows.Interactive相比,它有两个主要优势:

  • 它可以在样式中定义
  • 视图中的xaml代码要少得多
在您的情况下,视图可能如下所示:

<UserControl ...
             my:AttachedCommands.LoadedCommand="{Binding ViewLoadedCommand}">
“我的所有视图”都使用此样式:

<Style x:Key="ViewBaseStyle">
    <Setter Property="my:ViewModelLifeCycleBehavior.ActivateOnLoad" Value="True" />
提示:
在VisualStudio中为View和ViewModel创建视图。这很简单,节省了很多时间。项目模板可以包含带有触发器/行为的xaml代码,指向您的基本样式、您的
d:DataContext
定义和您的viewmodel类。

为什么不使用键进行样式设置?@Nikita实际上,我不知道如何编写一个样式来设置复合和复杂的附件属性,如
Interaction.Triggers
。ViewLoadedCommand位于哪里?这在您的视图模型中吗?你确定要在每次加载用户控件时调用此命令吗?@MikeEason正确,这是一个
RelayCommand
,是的,我希望在每次加载时都调用它,它是
onload
的代理。似乎这是不可能的,我们需要检查是否有解决方法。
<Style x:Key="ViewBaseStyle">
    <Setter Property="my:ViewModelLifeCycleBehavior.ActivateOnLoad" Value="True" />
public static class ViewModelLifeCycleBehavior
{
    public static readonly DependencyProperty ActivateOnLoadProperty = DependencyProperty.RegisterAttached("ActivateOnLoad", typeof (bool), typeof (ViewModelLifeCycleBehavior),
        new PropertyMetadata(ActivateOnLoadPropertyChanged));

    public static void SetActivateOnLoad(FrameworkElement element, bool value)
    {
        element.SetValue(ActivateOnLoadProperty, value);
    }

    public static bool GetActivateOnLoad(FrameworkElement element)
    {
        return (bool)element.GetValue(ActivateOnLoadProperty);
    }


    private static void ActivateOnLoadPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    { 
        if (DesignerProperties.GetIsInDesignMode(obj)) return;
        var element = (FrameworkElement)obj;

        element.Loaded -= ElementLoaded;
        element.Unloaded -= ElementUnloaded;

        if ((bool) args.NewValue == true)
        {
            element.Loaded += ElementLoaded;
            element.Unloaded += ElementUnloaded;
        }
    }



    static void ElementLoaded(object sender, RoutedEventArgs e)
    {
        var element = (FrameworkElement) sender;
        var viewModel = (IViewModelLifeCycle) element.DataContext;
        if (viewModel == null)
        {
            DependencyPropertyChangedEventHandler dataContextChanged = null;
            dataContextChanged = (o, _e) =>
            {
                ElementLoaded(sender, e);
                element.DataContextChanged -= dataContextChanged;
            };
            element.DataContextChanged += dataContextChanged;
        }
        else if (element.ActualHeight > 0 && element.ActualWidth > 0) //to avoid activating twice since loaded event is called twice on TabItems' subtrees
        {
            viewModel.Activate(null);
        } 
    }

    private static void ElementUnloaded(object sender, RoutedEventArgs e)
    {
        var element = (FrameworkElement)sender;
        var viewModel = (IViewModelLifeCycle)element.DataContext;
        viewModel.Deactivate();
    }


}