【问题标题】:WPF Combobox - changed Datasource results in empty ItemsSourceWPF Combobox - 更改数据源导致空 ItemsSource
【发布时间】:2020-02-20 07:49:48
【问题描述】:

我在 Page 中做一个 Combobox 的简单绑定:

XAML:

 <ComboBox x:Name="Cmb_test"  Grid.Column="2" Grid.Row="2" HorizontalAlignment="Left"  ItemsSource="{Binding}" />

背后的代码:

private void Page_Loaded(object sender, RoutedEventArgs e)
{
     //Binding of label works fine everytime
     My_label.Content = dt.Rows[0]["Column1"];

     Cmb_test.DataContext = dt.DefaultView;
     Cmb_test.SelectedValuePath = dt.Columns[3].ToString();
     Cmb_test.DisplayMemberPath = dt.Columns[4].ToString();

     //Just a check to see whether DataTable really has changed
     Console.WriteLine(dt.Rows.Count.ToString());
 }

但每当我的 DataTable "dt" 发生变化时,我的 Combobox 就不再显示项目。我知道关于这个问题已经提出了很多问题,但我能找到的只是与运行时刷新相关的问题。在我的情况下,我关闭一个页面并在 DataTable 更改时重新打开它,但结果是空的组合框。

关闭我的页面的代码,以供补充:

代码与组合框在同一页面中:

private void BtnClose_Click(object sender, RoutedEventArgs e)
{
      Cmb_test.ItemsSource = null;
      Cmb_test.DataContext = null;

      var main_window = Application.Current.MainWindow;
      var frame = (main_window as MainWindow).My_Frame;
      frame.Content = null;

}

主窗口中的代码:

private void My_Frame_Navigated(object sender, NavigationEventArgs e)
{
     if (My_Frame.Content == null)
     {
        My_Frame.RemoveBackEntry();
     }
}

编辑 - 再次尝试:

XAML:

  <Page.Resources>
        <CollectionViewSource x:Key="My_source"/>
    </Page.Resources>

  <ComboBox x:Name="Cmb_test" ItemsSource="{Binding Source={StaticResource My_source}}" DisplayMemberPath="Column1"/>

后面的代码:

 private void Page_Loaded(object sender, RoutedEventArgs e)
        {

            var combo_datasource = new CollectionViewSource();
            combo_datasource = (CollectionViewSource)this.FindResource("seznamVrstEvidenc");
            combo_datasource.Source = Tabele.dt_Sifrant.DefaultView;

        }

这里发生了什么,如何修复组合框以每次都显示它的项目?

【问题讨论】:

  • 每当您的数据表发生更改时,您到底是什么意思?当你做什么时......组合框中没有任何项目。
  • 顺便说一句.. 除非这是一个“向导”,否则您正在构建用户想要通过和返回的步骤。我推荐使用 contentcontrol 和 usercontrol 而不是框架框架和页面,而不是使用框架。我还将数据表转换为类型化列表或 observablecollection,以便您拥有属性名称而不是列索引。而且...即使 ui 是完全动态的,当前的建议是从 xaml 将 ui 构建为字符串或模板。
  • @Andy,我的 DataTable - 可以在我的一个窗口中更改 Combobox 的来源。为此,我必须先关闭我的页面。 DataTable get 的变化,我可以在除 Combobox 之外的所有其他绑定控件(文本框、标签、数据网格)中看到这些变化。你能给我看一些简单的可观察集合示例吗?
  • 如果您有麻烦,那么最简单的步骤更改就是使用像 Dapper 这样的微型计算机。 Dapper 为您提供了一系列扩展方法,使数据访问更简单。您可以为数据行定义视图模型。然后,您可以填写这些列表。 Observablecollection 有一个构造函数接受一个列表。您可以使用各种集合视图进行过滤和排序。
  • 还有。在视图模型上实现 inotifypropertychanged。如果您绑定到一个作为集合视图的公共属性,您还需要引发一个属性更改事件,以便绑定“知道”您将该集合视图切换到一个新的。

标签: c# wpf combobox itemsource


【解决方案1】:

您没有将 ComboBox 绑定到任何东西

<ComboBox x:Name="Cmb_test" [...] ItemsSource="{Binding}" />

你应该有一些收藏

<ComboBox x:Name="Cmb_test" [...] ItemsSource="{Binding MyList}" />

查看其余代码,您似乎正在将 ComboBox “手动绑定”到 DataTable。然后,您可以创建一个绑定programmatically,将 ComboBox ItemsSource 链接到 DataTable DefaultView。

但是有一个问题,如果您对“DataTable 已更改”的意思类似于

dt = new DataTable();

dt = Db.GetTable();

您可能会再次遇到同样的问题,因为绑定是在两个实例之间完成的,因此当创建新的 dt 时,您必须将其重新绑定到 ComboBox。

解决问题的另一种方法是在每次有新 DataTable 时设置 ComboBox ItemsSource。

希望对你有所帮助。

--------- 更新 --------

根据我的评论,我将在存储 DataTable dt 的类上实现 INotifyPropertyChanged。

public class ThatParticulareClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {  
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    // [...] all the other stuff

    public void MethodThatUpdateDataTable()
    {
        // Update DataTable
        NotifyPropertyChanged(nameof(dt));
    }
}

应该可以。如果您对 DataTable 对象的更改仅来自用户(来自视图),那么您应该在控件上注册,该控件公开 DataTable 和结束编辑事件(类似于 DataGrid.RowEditEnding)。此时您调用 NotifyPropertyChanged(nameof(dt)); (确保此调用来自包含 DataTable dt 的同一类)

Implementation

【讨论】:

  • 再次查看标记 Frederico。看到 ItemsSource="{Binding}" 吗?和 Cmb_test.DataContext = dt.DefaultView;
  • 是的,我正在试验。如果我是你,我会在更新时调用 DataTable 上的 NotifyPropertyChanged 来实现 INotifyPropertyChanged 的​​ link。每当我有东西要给你看时,我都会编辑我的答案
  • 是的,Wpf 很强大,但有时会让人困惑。
  • 我会尝试做一个示例repo,分享给大家,敬请期待。
  • @FedericoRossi,谢谢,现在我明白了,很好的解决方案。它也将很好地用于进一步的项目:)
【解决方案2】:

我找到了解决方案。在您熟悉 WPF 和绑定控件之前,我必须说一件非常令人沮丧的事情...

我阅读了大量文章以了解在 WPF 中您应该使用 MVVM 模式来正确绑定控件。然而,有很多不同的方法可以做到这一点,所以我最终得到了一些至少对我来说难以理解的东西:

1.) 创建模型类。在这里,您定义 DataTable 列并将它们作为属性公开。这个类需要继承自INotifyPropertyChanged

 class Test_Model : INotifyPropertyChanged
 {
        private string col1;
        private string col2;

        public string Col1
        {
            get
            {
                return col1;
            }
            set
            {
                col1 = value;
                OnPropertyChanged("Col1");
            }
        }
        public string Col2
        {
            get
            {
                return col2;
            }
            set
            {
                col2 = value;
                OnPropertyChanged("Col2");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
   }

2.) 创建 Viev_Model 类。在这里您创建一个 IList(Test_Model 类型)并用 DataTable 填充它,我为此使用了 LINQ - 这也是一个挑战。接下来,您再次公开 IList 类型的属性,以便将其用作您的数据源:

 class Test_ViewModel
 {
        private IList<Test_Model> _comboData;

        public Test_ViewModel()
        {
            _comboData = dt.AsEnumerable().
                Select(row => new Test_Model
                {
                    Col1 = row.Field<string>(0),
                    Col2 = row.Field<string>(1)
                }).ToList();
        }

        public IList<Test_Model> ComboData
        {
            get { return _comboData; }
            set { _comboData = value; }
        }
  }

3.) 在您的窗口或页面中,将 DataContext 分配给 View_Model 类。这是在构造函数中完成的,如下所示:

 public partial class My_Page: Page
 {
        public My_Page()
        {
            InitializeComponent();
            this.DataContext = new Test_ViewModel(); //assign ViewModel class
        }

        //...
  }

4.) 将组合框绑定到 View_Model 类

  <ComboBox x:Name="Cmb_Test" ItemsSource="{Binding Path= ComboData}" 
  DisplayMemberPath="Col1" SelectedValuePath="Col1" SelectedIndex="0"/>

然后它终于奏效了。虽然我对解决方案不满意,但我必须找出更简单的方法来进行正确的绑定。我通常有很多控件要绑定 & 如果我要为我需要的每个 DataTable 都这样做,我最终会在类中编写大量代码。

【讨论】:

  • 您的解决方案是正确的(尽管我仍然怀疑它是否会更新,以防您从Test_ViewModel._comboData 添加或删除Test_Model 的实例)。我们试图提出一种更快捷的操作方式。它仍然通过 INotifyPropertyChanged 但至少您只能调用一次。你让你的代码隐藏实现 INotifyPropertyChanged 等,你需要的evetytime,你可以调用 OnPropertyChanged(nameof(dt)) 来进行“绑定”,所以控件(在这种情况下是一个 BomboBox)更新。调用该事件的好时机是在编辑完成后触发的事件。
  • @FedericoRossi 感谢您的所有帮助。我也会试试你的例子,因为它比我的短,所以我猜这不是 MVVM 模式。对于更新问题 - 幸运的是我还没有处理这个问题,在这个 čase Combobox 仅用于显示项目。虽然它包含 SelecfionChanged 事件,但我从中刷新 Datagrid (使用不同的静态 DataTable )。但是 Datagrid 只需设置 dt.DefaultView 就可以正常工作,所以我仍然很困惑为什么 Combobox 的行为不同。测试完成后我会通知您并接受您的回答,如果它对我有用。
  • 不客气!不,我的示例不是 mvvm,它是一种快速的解决方法
猜你喜欢
  • 2014-01-26
  • 2011-10-03
  • 2020-01-15
  • 2019-09-11
  • 2017-08-20
  • 2013-06-30
  • 2015-04-07
  • 1970-01-01
  • 2018-04-09
相关资源
最近更新 更多