WPF中Button绑定的Command首次点击无法触发如何解决?
WPF 列表按钮首次点击Command不触发问题排查与解决
问题场景
我创建了一个为Button和ComboBox做了Command绑定的简单UI,希望在ComboBox的LostFocus事件触发时,根据下拉框选中值填充Button列表。
相关代码
MainWindow.xaml.cs
public partial class MainWindow : Window { public MainWindow() { ViewModel ViewModel = new ViewModel(); this.DataContext = ViewModel; //Test Data Feeded ViewModel.SelectedServer = ViewModel.ServerList.First(); ViewModel.MyList = new ObservableCollection<Model>(); for (int i = 0; i < Convert.ToInt16(ViewModel.SelectedServer); i++) { ViewModel.MyList.Add(new Model() { Name = (i + 1).ToString() }); } InitializeComponent(); txtServer.Focus(); } }
ViewModel.cs
public class ViewModel: BaseDataMdel { ObservableCollection<Model> _MyList; List<string> serverList; string selectedServer; void Validated() { //Bsaed upon some validation we repopulate the collection if (MyList == null) MyList = new ObservableCollection<Model>(); else MyList.Clear(); for (int i = 0; i < Convert.ToInt16(SelectedServer); i++) { MyList.Add(new Model() { Name = (i + 1).ToString() }); } } private void ActionRequestedEvent(object param) { switch(param.ToString()) { case "DataSetServerLostFocus": { Validated(); } break; default: MessageBox.Show(param.ToString()); break; } } private ICommand _ActionCommand1; public ICommand ActionCommand1 { get { if (_ActionCommand1 == null) this._ActionCommand1 = new RelayCommand(param => { ActionRequestedEvent(param); }); return _ActionCommand1; } set { _ActionCommand = value; } } private ICommand _ActionCommand; public ICommand ActionCommand { get { if (_ActionCommand == null) this._ActionCommand = new RelayCommand(param => { ActionRequestedEvent(param); }); return _ActionCommand; } set { _ActionCommand = value; } } public ObservableCollection<Model> MyList { get { return _MyList; } set { _MyList = value; RaisePropertyChanged(nameof(MyList)); } } public List<string> ServerList { get { return serverList; } set { serverList = value; RaisePropertyChanged(nameof(ServerList)); } } public string SelectedServer { get { return selectedServer; } set { selectedServer = value; RaisePropertyChanged(nameof(SelectedServer)); } } public ViewModel() { ServerList = new List<string>() { "1", "2", "3", "4", "5" }; } }
XAML代码
<Window x:Class="WpfApplication26.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:WpfApplication26" mc:Ignorable="d" Name="Group" Title="MainWindow" Height="350" Width="525"> <StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center"> <ComboBox Width="150" Name="txtServer" ItemsSource="{Binding ServerList}" SelectedItem="{Binding SelectedServer}" Height="25"> <i:Interaction.Triggers> <i:EventTrigger EventName="LostFocus"> <i:InvokeCommandAction Command="{Binding ActionCommand}" CommandParameter="DataSetServerLostFocus"/> </i:EventTrigger> </i:Interaction.Triggers> </ComboBox> <GroupBox Header="Test Case" Width="175" Height="200"> <ItemsControl ItemsSource="{Binding MyList}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="2"> <TextBox Width="120" Text="{Binding Name}"/> <Button Content=".." Height="25" Width="25" Margin="3 0 0 0" Command="{Binding DataContext.ActionCommand,ElementName=Group}" CommandParameter="{Binding Name}"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </GroupBox> </StackPanel> </Window>
复现步骤
- 运行代码
- 点击列表内的Button
- 首次点击时Command不会被触发
已知原因
该问题是由集合重新遍历渲染导致:点击按钮时会先触发ComboBox的LostFocus事件,此时集合被清空重建,原有按钮元素被销毁,对应的点击命令无法完成触发流程。
已尝试的解决方法
- 在集合重新遍历前将点击的Button存入临时变量,待集合更新完成后编程触发按钮点击事件,该方法仅能部分解决问题,因为
Window.GetWindow(button)对备份的按钮会返回null。
可行解决方案
方案1:低优先级调度集合刷新(最简便)
点击按钮的输入事件优先级高于界面渲染调度,可将集合刷新逻辑用Dispatcher低优先级延迟执行,确保按钮点击命令先处理完成:
修改ViewModel中ActionRequestedEvent的DataSetServerLostFocus分支:
case "DataSetServerLostFocus": { // 使用Input优先级,保证输入类事件先执行完成 Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() => { Validated(); })); } break;
方案2:修改触发事件为SelectionChanged
如果业务逻辑允许,将ComboBox的触发事件从LostFocus改为SelectionChanged,仅在选中值真实变化时才刷新集合,避免无意义的集合重建,也能规避该问题。
修改XAML中ComboBox的触发器配置:
<i:EventTrigger EventName="SelectionChanged"> <i:InvokeCommandAction Command="{Binding ActionCommand}" CommandParameter="DataSetServerLostFocus"/> </i:EventTrigger>
方案3:优化集合更新逻辑
不要每次刷新都全量清空重建集合,改为对比现有集合和目标集合的差异,仅做元素的新增、删除操作,保留已有列表元素,原有按钮不会被销毁,点击事件自然可以正常触发。
内容的提问来源于stack exchange,提问作者Bala
相关产品推荐
相关产品推荐

