WPF自定义Window路由事件绑定ViewModel ICommand报错问题咨询
问题原因
- WPF中事件不能像依赖属性一样直接绑定
ICommand,你直接在Window标签写Timer="{Binding TimerCommand}"属于语法错误,XAML会把Timer识别为依赖属性而非事件,自然报不存在的错误。 - 额外注意:你用的
System.Timers.Timer的Elapsed事件是在线程池线程触发的,直接在回调里调用RaiseEvent会触发跨线程访问UI的异常,需要先调度到UI线程执行。
解决方案
方案1:后台代码转发事件(无额外依赖,符合MVVM规范)
这种方式只是在后台做一层事件到命令的转发,没有业务逻辑,不会破坏MVVM的解耦原则:
首先修改XAML里的Window标签,将错误的绑定替换为事件处理方法关联:
<Window x:Class="CustomWindowEvent.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CustomWindowEvent" Title="MainWindow" Height="250" Width="400" Timer="OnTimerRaised">
然后修改MainWindow.xaml.cs代码,补事件处理方法同时修复跨线程问题:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var timer = new System.Timers.Timer(); timer.Elapsed += OnTimedEvent; timer.Interval = 5000; timer.Enabled = true; } private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e) { // 调度到UI线程触发路由事件,避免跨线程异常 Dispatcher.Invoke(RaiseTimerEvent); } // Create a custom routed event public static readonly RoutedEvent TimerEvent = EventManager.RegisterRoutedEvent( "Timer", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MainWindow)); // Provide CLR accessors for the event public event RoutedEventHandler Timer { add => AddHandler(TimerEvent, value); remove => RemoveHandler(TimerEvent, value); } void RaiseTimerEvent() { var newEventArgs = new RoutedEventArgs(MainWindow.TimerEvent); RaiseEvent(newEventArgs); } // 新增事件处理方法,转发到ViewModel的Command private void OnTimerRaised(object sender, RoutedEventArgs e) { if (DataContext is MainWindowViewModel vm && vm.TimerCommand.CanExecute(null)) { vm.TimerCommand.Execute(null); // 需要传参数的话把null替换成你要传的参数即可 } } }
方案2:使用XAML行为实现事件转命令(无后台代码)
如果完全不想写后台代码,可以用微软官方的XAML行为包实现:
- 首先安装NuGet包:
Microsoft.Xaml.Behaviors.Wpf - 修改XAML代码如下:
<Window x:Class="CustomWindowEvent.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CustomWindowEvent" xmlns:i="http://schemas.microsoft.com/xaml/behaviors" Title="MainWindow" Height="250" Width="400"> <i:Interaction.Triggers> <i:EventTrigger EventName="Timer"> <i:InvokeCommandAction Command="{Binding TimerCommand}" CommandParameter="{Binding}"/> <!-- 需要传参数可以自定义CommandParameter的值 --> </i:EventTrigger> </i:Interaction.Triggers> <Window.DataContext> <local:MainWindowViewModel/> </Window.DataContext> <Grid> <StackPanel> <TextBlock Text="{Binding Title}"/> <Button Width="75" Height="24" Content="Run" Command="{Binding RunCommand}"/> </StackPanel> </Grid> </Window>
同样不要忘记修复前面提到的跨线程调度问题,修改OnTimedEvent方法调度到UI线程触发路由事件。
内容的提问来源于stack exchange,提问作者Aliaksei Luferau
相关产品推荐
相关产品推荐

