使用BeginInvoke设置时不会触发RelayCommand

时间:2015-11-02 14:19:03

标签: c# wpf mvvm mvvm-light dispatcher

我有一个简单的应用程序,其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>

1 个答案:

答案 0 :(得分:0)

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

那就是说,你在你的代码中有一个严重的问题:你使用捕获的循环变量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)) }))); } } 之前将ID转换为字符串。这具有为循环的每次迭代创建要捕获的单独变量的效果,确保每个委托调用获得其自己的变量的私有副本(以及仅需要评估BeginInvoke()的快乐副作用每循环迭代:))。

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

如果以上内容无法帮助您解决问题,请编辑您的问题,以便它包含可靠地重现问题的良好代码示例。


顺便说一句:在您的i.ToString()方法中,您的陈述看起来完全是多余的:ChangeName()。我不知道你的意思是什么,但它实际上只是返回一个Items.Select(x=>x.ID== Item.ID);对象,当枚举时,它将返回一个元素为IEnumerable<bool>的序列,其中{{1}传入的true对象的值与在相应的原始ID元素中找到的值相同。代码不会将返回的Item对象用于任何事情,更不用说实际枚举它,因此简单地丢弃该对象,并且该语句在程序中没有实际效果。