【问题标题】:RelayCommand not fired when set with BeginInvoke使用 BeginInvoke 设置时未触发 RelayCommand
【发布时间】:2016-02-02 11:04:57
【问题描述】:

我有一个简单的应用程序,它有一个ObservableCollection<Item> 集合绑定到UniformGrid。如果我使用:

Items.Add(new Item 
          { 
              ID = i.ToString(), 
              Name = i.ToString(), 
              TestCommand = new RelayCommand<Item>((Item) => ChangeName(Item)) 
          });

然后绑定到 UI 的 RelayCommand 按预期触发,但如果我将上一行更改为:

Application.Current.Dispatcher.BeginInvoke(
    new Action(()=>
    { 
        Items.Add(new Item 
                  {
                      ID=i.ToString(),
                      Name=i.ToString(),
                      TestCommand=new RelayCommand<Item>((Item)=>ChangeName(Item)) 
                  });
     }));

UI 不会调用RelayCommand。你能解释一下为什么吗?

代码:

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new Data();
        }
    }

    public class Item:INotifyPropertyChanged
    {
        private string id;

        public string ID
        {
            get
            {
                return this.id;
            }

            set
            {
                this.id = value;
                RaisePropertyChanged("ID");
            }
        }        

        private string name;

        public string Name
        {
            get
            {
                return this.name;
            }

            set
            {
                this.name = value;
                this.RaisePropertyChanged("Name");
            }
        }

        public RelayCommand<Item> TestCommand { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string info)
        {
            if (PropertyChanged!=null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }        
        }
    }

    public class Data
    {
        public ObservableCollection<Item> Items { get; set; }

        public Data()
        {
            Items = new ObservableCollection<Item>();
            CreateBoxes();
        }

        public void ChangeName(Item Item)
        {
            Items.Select(x=>x.ID== Item.ID);
            Item.Name = "Changed";
        }

        public void CreateBoxes()
        {
            for (int i=0;i<4;i++)
            {
               //Application.Current.Dispatcher.BeginInvoke( new Action(()=>{ Items.Add(new Item {ID=i.ToString(),Name=i.ToString(),TestCommand=new RelayCommand<Item>((Item)=>ChangeName(Item)) });}));
                Items.Add(new Item { ID = i.ToString(), Name = i.ToString(), TestCommand = new RelayCommand<Item>((Item) => ChangeName(Item)) });
            }
        }
    }
}

Xaml 代码:

 <Grid>
    <ListView ItemsSource="{Binding Items}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Border BorderThickness="1" BorderBrush="LightBlue">
                    <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <Label Grid.Column="0" Grid.Row="0" Content="ID:"/>
                        <Label Grid.Column="1" Grid.Row="0" Content="Name:"/>
                        <Label Grid.Column="0" Grid.Row="1" Content="{Binding ID}"/>
                        <Label Grid.Column="1" Grid.Row="1" Content="{Binding Name}"/>
                        <Button Grid.Row="2" Grid.ColumnSpan="2" Content="Test" Command="{Binding TestCommand}" CommandParameter="{Binding .}"/>
                    </Grid>
                </Border>
            </DataTemplate>
        </ListView.ItemTemplate>
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Rows="2" Columns="2">
                </UniformGrid>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
    </ListView>
</Grid>

【问题讨论】:

    标签: c# wpf mvvm mvvm-light dispatcher


    【解决方案1】:

    我无法重现您描述的问题。当我使用您共享的代码时(添加一个简单的RelayCommand&lt;T&gt; 实现,因为您省略了它),单击按钮时会正确调用该命令,无论是我直接调用Items.Add() 还是使用BeginInvoke() 稍后调用它.

    也就是说,您确实在您的代码中存在严重问题:您正在使用捕获的循环变量i 来调用BeginInvoke(),而不是作用域的本地新变量for 循环的块。这引入了这样一种可能性,即在执行任何调用的委托时,循环变量将增加到循环值的末尾,即4

    在您的示例中,您应该更改 CreateBoxes() 方法,使其看起来更像这样:

    public void CreateBoxes()
    {
        for (int i = 0; i < 4; i++)
        {
            string itemText = i.ToString();
    
            Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                Items.Add(new Item
                {
                    ID = itemText,
                    Name = itemText,
                    TestCommand = new RelayCommand<Item>(item => ChangeName(item))
                })));
        }
    }
    

    即在调用 BeginInvoke() 之前将 ID 转换为字符串。这具有为循环的每次迭代创建要捕获的单独变量的效果,确保每个委托调用都获得其自己的变量私有副本(以及每个循环只需评估一次 i.ToString() 的快乐副作用迭代:) )。

    如果没有 a good, minimal, complete code example 实际重现您描述的问题(即按钮未调用命令),我不知道上述是否与该问题有关。根据您的实际代码的样子,错误处理捕获的变量可能会导致调用命令出现问题,或者它可能与此特定错误完全无关。

    如果以上内容不能帮助您解决问题,请编辑您的问题,使其包含一个能够可靠地重现问题的良好代码示例。


    顺便说一句:在您的 ChangeName() 方法中,您有一个看起来完全多余的语句:Items.Select(x=&gt;x.ID== Item.ID);。我不知道你的意思是什么,但它实际上所做的只是返回一个 IEnumerable&lt;bool&gt; 对象,枚举时将返回一个序列,其中元素为 true 其中 ID 传递的值-in Item 对象与在相应的原始 Items 元素中找到的对象相同。代码没有将返回的IEnumerable&lt;bool&gt;对象用于任何事情,更不用说实际枚举它了,所以该对象被简单地丢弃,语句在程序中没有实际作用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-27
      相关资源
      最近更新 更多