如何避免多个UserControl重复定义相同EventTrigger,寻求更优实现方案
WPF 统一事件绑定的两种优化实现方案
你当前的ContentControl方案可以正常运行,但存在多余视觉树开销、代码冗余的问题,下面提供你需要的自定义EventsUserControl实现,以及更优的解耦方案:
方案1:自定义EventsUserControl基类(满足DI关联需求)
第一步:定义ViewModel约束接口
首先定义接口让所有需要绑定这两个事件的ViewModel实现,方便DI容器扫描匹配:
public interface IWindowEventAware { ICommand WindowLoadedCommand { get; } ICommand WindowClosedCommand { get; } }
第二步:实现EventsUserControl基类
直接继承UserControl,内置事件绑定逻辑,不需要依赖Interaction触发器:
public class EventsUserControl : UserControl { public EventsUserControl() { Loaded += OnLoaded; // 绑定宿主窗口的Closed事件 Loaded += (s, e) => { if (Window.GetWindow(this) is Window window) { window.Closed += (o, args) => ExecuteCommand(WindowClosedCommand); } }; } private void OnLoaded(object sender, RoutedEventArgs e) { ExecuteCommand(WindowLoadedCommand); } private void ExecuteCommand(ICommand command) { if (command?.CanExecute(null) == true) { command.Execute(null); } } public ICommand WindowLoadedCommand { get => (ICommand)GetValue(WindowLoadedCommandProperty); set => SetValue(WindowLoadedCommandProperty, value); } public static readonly DependencyProperty WindowLoadedCommandProperty = DependencyProperty.Register(nameof(WindowLoadedCommand), typeof(ICommand), typeof(EventsUserControl), new PropertyMetadata(null)); public ICommand WindowClosedCommand { get => (ICommand)GetValue(WindowClosedCommandProperty); set => SetValue(WindowClosedCommandProperty, value); } public static readonly DependencyProperty WindowClosedCommandProperty = DependencyProperty.Register(nameof(WindowClosedCommand), typeof(ICommand), typeof(EventsUserControl), new PropertyMetadata(null)); // 自动绑定ViewModel实现的接口命令,不需要手动在XAML写绑定 protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); if (DataContext is IWindowEventAware vm) { WindowLoadedCommand = vm.WindowLoadedCommand; WindowClosedCommand = vm.WindowClosedCommand; } } }
第三步:使用方式
业务UserControl直接继承该基类即可,不需要套任何容器:
<local:EventsUserControl x:Class="MyNamespace.MyUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:YourBaseClassNamespace"> <Grid> 页面内容... </Grid> </local:EventsUserControl>
DI容器可以直接扫描所有继承自EventsUserControl的控件,要求其绑定的ViewModel实现IWindowEventAware接口即可。
方案2:附加行为(更优的无侵入实现)
如果不想强制要求UserControl继承基类,可以用附加行为实现,完全解耦,适合所有FrameworkElement使用:
public static class WindowEventsBehavior { public static readonly DependencyProperty EnableWindowEventsProperty = DependencyProperty.RegisterAttached( "EnableWindowEvents", typeof(bool), typeof(WindowEventsBehavior), new PropertyMetadata(false, OnEnableWindowEventsChanged)); private static void OnEnableWindowEventsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not FrameworkElement element) return; if ((bool)e.NewValue) { element.Loaded += ElementOnLoaded; } else { element.Loaded -= ElementOnLoaded; } } private static void ElementOnLoaded(object sender, RoutedEventArgs e) { if (sender is FrameworkElement { DataContext: IWindowEventAware vm } element) { if (vm.WindowLoadedCommand?.CanExecute(null) == true) { vm.WindowLoadedCommand.Execute(null); } // 绑定宿主窗口Closed事件 if (Window.GetWindow(element) is Window window) { window.Closed += (o, args) => { if (vm.WindowClosedCommand?.CanExecute(null) == true) { vm.WindowClosedCommand.Execute(null); } }; } } } public static void SetEnableWindowEvents(DependencyObject element, bool value) { element.SetValue(EnableWindowEventsProperty, value); } public static bool GetEnableWindowEvents(DependencyObject element) { return (bool)element.GetValue(EnableWindowEventsProperty); } }
使用时只需要在任意UserControl的根节点加一个附加属性即可:
<UserControl x:Class="MyNamespace.MyUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:behaviors="clr-namespace:YourBehaviorNamespace" behaviors:WindowEventsBehavior.EnableWindowEvents="True"> <Grid> 页面内容... </Grid> </UserControl>
方案对比
- 自定义基类方案:适合需要强约束、DI统一扫描注册的场景,没有多余视觉树开销,代码维护简单
- 附加行为方案:适合存量项目改造、不想修改继承结构的场景,灵活性最高
- 原有ContentControl方案:多了一层视觉树开销,代码冗余度更高,不如前两个方案
内容的提问来源于stack exchange,提问作者Michael280
相关产品推荐
相关产品推荐

