【问题标题】:WPF ComboBox resets selected item when item-source changesWPF ComboBox 在项目源更改时重置所选项目
【发布时间】:2011-01-14 18:22:09
【问题描述】:

考虑以下 XAML:

<ComboBox Name="CompanyComboBox" 
    HorizontalAlignment="Stretch"
    ItemsSource="{Binding Path=GlobalData.Companies}" 
    SelectedValuePath="Id"
    SelectedValue="{Binding Customer.CompanyId, ValidatesOnDataErrors=True}"
    DisplayMemberPath="Name" />

GlobalData.Companies 是公司的集合 (IEnumerable&lt;Company&gt;);这个集合可以在后台重新加载(它是从网络服务下载的)。发生这种情况时,ComboBox 通过绑定正确地重新加载项目。但是,作为副作用,它还会重置所选项目!

我使用 Reflector 来检查组合框源,显然这是预期的行为。

有什么“好”的方法可以解决这个问题吗?我想要实现的是,如果用户选择“公司 A”并随后重新加载公司列表,则“公司 A”保持选中状态(假设它在新列表中)。

【问题讨论】:

    标签: wpf combobox selecteditem


    【解决方案1】:

    请尝试使用以下代码。 为组合框启用以下属性

    IsSynchronizedWithCurrentItem="True"

    【讨论】:

      【解决方案2】:

      也许您可以使用ObservableCollection&lt;Company&gt; 代替您的IEnumerable&lt;Company&gt;?然后,在后台更改时,您只会添加/删除新列表中的新/不存在的项目,选定的项目应该保留,除非它被更改删除。

      你可以update your observable collection in a separate thread with a small hack-around

      【讨论】:

      • 这可能是解决方案,尽管它不像我想的那么好/容易:-|
      • 可能没有必要从后台线程更新集合。在后台从 Web 服务获取当前集合,然后(假设您使用的是 BackgroundWorker)在 RunWorkerCompleted 事件中更新 ObservableCollection&lt;Company&gt;
      【解决方案3】:

      hmm,我不知道这是否是一种“不错”的方式,但是如果您可以在重新加载之前访问所选项目,您可以保存它(或其密钥或其他东西),然后以编程方式再次选择它重新加载完成。

      快速样机:

      var selectedItem = myCombo.SelectedItem;
      DoReload();
      myCombo.SelectedItem = selectedItem;
      

      但我认为您的意思是本手册以外的另一种解决方法?
      希望这对您有所帮助...

      更新
      好的,我明白了,来自后台线程。
      您是否也在使用 ICollectionView 来绑定您的组合框?如果是这样,您可以使用 CurrentItem 属性来保留引用。我做了一个快速模型,这正在我的设置中工作。这假设您有对您的 UI 的引用:

      XAML

      <Grid VerticalAlignment="Top">  
          <Grid.ColumnDefinitions>
              <ColumnDefinition />
              <ColumnDefinition />
          </Grid.ColumnDefinitions>
          <ComboBox ItemsSource="{Binding Items}" IsSynchronizedWithCurrentItem="True" Grid.Column="0" Grid.Row="0" DisplayMemberPath="Name"/>
          <Button Command="{Binding UpdateCommand}" Grid.Column="1" Grid.Row="0">Update</Button>
      </Grid>
      

      视图/视图模型

      public partial class Window1 : Window {
         public Window1() {
              InitializeComponent();
              this.DataContext = new ViewModel(this);
         }
      }
      
      public class ViewModel
      {
          private readonly Window1 window;
          private ObservableCollection<Item> items;
          private ICollectionView view;
      
          public ViewModel(Window1 window) {
              this.window = window;
              items = new ObservableCollection<Item>
                  {
                      new Item("qwerty"),
                      new Item("hello"),
                      new Item("world"),
                  };
      
              view = CollectionViewSource.GetDefaultView(items);
          }
      
          public ObservableCollection<Item> Items { get { return items; } }
      
          public ICommand UpdateCommand {
              get { return new RelayCommand(DoUpdate); }
          }
      
          public Item SelectedItem { get; set; }
      
          private void DoUpdate(object obj) {
              var act = new Func<List<Item>>(DoUpdateAsync);
              act.BeginInvoke(CallBack, act);
          }
      
          private List<Item> DoUpdateAsync() {
              return new List<Item> {
                      new Item("hello"),
                      new Item("world"),
                      new Item("qwerty"),
                  };
          }
      
          private void CallBack(IAsyncResult result) {
              try {
                  var act = (Func<List<Item>>)result.AsyncState;
                  var list = act.EndInvoke(result);
      
                  window.Dispatcher.Invoke(new Action<List<Item>>(delegate(List<Item> lst) {
                                                                          var current = lst.Single(i => i.Name == ((Item)view.CurrentItem).Name);
                                                                          Items.Clear();
                                                                          lst.ForEach(Items.Add);
                                                                          view.MoveCurrentTo(current);
                                                                      }), list);
      
              } catch(Exception exc){ Debug.WriteLine(exc); }
          }
      }
      
      public class Item {
          public Item(string name) {
              Name = name; 
          }
          public string Name { get; set; }
      }
      

      如果所选项目不再在列表中,您将需要进行一些处理。
      IsSynchronizedWithCurrentItem 属性在这里很重要,否则它将不起作用!
      此外,对主窗口的引用方式应该是 DI 框架。

      【讨论】:

      • 问题是 DoReload() 是由一个不知道 GUI 的类在后台执行的。 GUI 通过 Binding 和 INotifyPropertyChanged 收到有关新值的通知。
      • 如果在刷新过程中选择值的描述可以改变,在DoReload()之后添加“my_Combo.SelectedIndex = -1”。这将导致在重置所选项目后更新所选索引时刷新显示的描述。
      【解决方案4】:

      正如 Yacoder 所指出的,这与对象相等性有关。只要您绑定 SelectedValue 而不是 SelectedItem,您就可以将 ItemsSource 定义为匿名类型集合。那么这个问题就不会发生(如果您需要从数据库中读取值,它也会更快)。

      【讨论】:

        猜你喜欢
        • 2011-09-24
        • 2011-01-09
        • 2016-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-11
        • 1970-01-01
        • 2011-04-19
        相关资源
        最近更新 更多