【问题标题】:WPF ComboBox setting to null after ItemsSource updated更新 ItemsSource 后 WPF ComboBox 设置为 null
【发布时间】:2016-08-04 22:25:36
【问题描述】:

我在 WPF 应用程序中遇到了 ComboBox 问题。它与其他一些问题类似,但该问题的经典解决方案似乎不起作用。

本质上和这个问题是一样的:

WPF ComboBox SelectedItem Set to Null on TabControl Switch

但是,我的 ItemsSource 已经在 SelectedItem 之后的 XAML 中,这通常会解决这个问题。

正在发生的事情是,我有一个带有组合框的视图,其中已经加载了数据,然后触发了一个事件来更新馈送到 ComboBox 的数据。 ViewModel 使用事件(由获取数据的 BackgroundWorker 触发)并使用新数据更新其作为 ItemsSource 的 ObservableCollection。像这样:

int id = (int)Invoice.Customer.DatabaseID;
Customers = new ObservableCollection<Customer>(customers);
Invoice.Customer = Customers.FirstOrDefault(x => x.DatabaseID == id);

如您所见,它试图将发票上的客户设置回原来的状态。确实会发生这种情况,通过断点观察到,但是,一旦完成,客户就会从不明来源设置回 null(我的代码没有出现在调用堆栈中,这都是框架的东西)。

组合框的 XAML 是这样的:

<ComboBox DisplayMemberPath="AccountCode"
    SelectedItem="{Binding Invoice.Customer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
    ItemsSource="{Binding Customers}"/>

总而言之,我的 ComboBox SelectedItem 在 ItemsSource 更新并确保 ItemsSource 在 SelectedItem 不执行任何操作之后被设置为 null。我真的不知道为什么它被设置为空,我不知道在哪里看。为了找到解决方案,我可以查看的任何指针或内容将不胜感激。

编辑:好的,我一直在玩它,我怀疑它与来自 BackgroundWorker 的更新有关。我在我的数据服务中使用 Timer 和 BackgroundWorker 来定期更新数据库中的客户列表,以确保数据是相对最新的。 BackgroundWorker 完成后会触发一个事件,以通知感兴趣的对象列表已更新。这似乎意味着当事件被消耗时,它们位于不同的线程中。当它以这种方式更新时,在我将 SelectedItem 设置为正确的项目后将其设置为 null,因此将 Invoice.Customer 设置为 null。我很快在我的视图中添加了一个按钮来更新客户而不使用 BackgroundWorker,这似乎每次都有效。我想定期更新数据,但我需要先弄清楚这一点。

【问题讨论】:

  • 我更新了我的 ItemsSource 并且我的 SelectedItem 保持在它应该在的位置。无法重现您的问题。显示 Invoice.Customer 属性的定义。

标签: c# wpf xaml mvvm combobox


【解决方案1】:

好的,正如我在编辑中怀疑的那样,这与某种方式的线程有关。在定时器启动更新后更新 ComboBox ItemsSource 会导致它在更新为正确的客户后设置为空。我在一个新的应用程序中确认了这种行为,该应用程序中没有所有其他位,因此我可能将它设置为 null 某处我不应该这样做(即使调用堆栈似乎严重暗示它不是我正在做)。当触发事件以更新结果时,与真实应用相比,新应用中的调用堆栈看起来完全相同。

在玩了一些不同的东西之后,我遇到的方法(在我的新应用程序中 - 没有将它部署到真正的应用程序中,但手指交叉它也可以在那里工作!)是让事件由竞争者触发更新通过 TaskFactory 运行(想法来自这里 Is it wrong to use the Dispatcher within my ViewModel?)。

在 ViewModel 中声明一个 TaskFactory:

TaskFactory uiFactory;

在你的构造函数中这样设置:

uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());

如果在数据更新后运行的事件,请执行以下操作:

private void AsyncMethods_TaskCompleted(object sender, EventArgs e)
{
    uiFactory.StartNew( () => UpdateResults());
}

在此上下文中的 UpdateResults 与更新发票上的客户相同。它获取旧 id,将 ItemsSource 设置为新集合,然后将绑定到 SelectedItem 的属性设置为新集合中的等效项。这似乎有效,并没有给我以前的奇怪行为。我将把它部署到实际的应用程序中,并希望它也能在那里工作。如果是,我会回来接受这个答案。

【讨论】:

    【解决方案2】:

    有时,当您创建对象的“新”实例时,它可能会破坏您的绑定。您可以在不调用 'new' 的情况下更新现有 Collection,或者您可以将 ObservableCollection 设为依赖属性。

    【讨论】:

      【解决方案3】:

      问题是由这两行引起的。

      Customers = new ObservableCollection<Customer>(customers);
      Invoice.Customer = Customers.FirstOrDefault(x => x.DatabaseID == id);
      

      您的组合框源是Customers,您正在再次对其进行初始化。然后,您尝试从新初始化的成员中获取数据。新初始化的成员中将没有数据。

      Customers 中没有数据。因此Invoice.Customer 可能为空。

      我不明白您为什么要初始化它并只是尝试从中获取数据。您是否跳过填充源?

      如果您错过了填充源,请先用数据填充源。然后你可以运行这段代码而不用再次初始化它,这样Invoice.Customer 就不会为空。

      Invoice.Customer = Customers.FirstOrDefault(x => x.DatabaseID == id);
      

      【讨论】:

      • 源已满。我可能解释得不够好,但我在 Invoice.Customer 集上设置了一个断点。它按预期设置客户(来自 Invoice.Customer = ...),但随后它被其他东西设置为 null,我认为它是框架中的东西,因为堆栈跟踪中只有外部代码。
      • 框架怎么能这样设置呢?不可能。一个属性永远不能像这样为空。也许您在代码本身的某处设置为 null。
      • 如果您将 ItemsSource 放在 XAML 中的 SelectedItem 之前,那么它可以按照我链接的问题自行将内容设置为 null。我在这里没有这样做,但我仍然将我的 SelectedItem 设置为 null 并且它不是来自我可以找到的代码中的任何地方。
      • 然后按照 AnjumSKhan 的建议分享您的整个代码和代码。
      猜你喜欢
      • 2023-04-01
      • 2011-09-01
      • 1970-01-01
      • 2020-06-29
      • 2019-09-11
      • 2014-01-26
      • 2014-03-08
      • 1970-01-01
      • 2013-06-30
      相关资源
      最近更新 更多