【问题标题】:Data Grid control not showing ObservableCollection data数据网格控件不显示 ObservableCollection 数据
【发布时间】:2018-05-16 15:08:00
【问题描述】:

总的来说,我对 WPF 和 C# 比较陌生。我正在玩它,遇到了一个问题,我觉得这对专家来说是小菜一碟,但我不知道我做错了什么。 我正在尝试创建一个简单的 DataGrid 控件(在 TabControl 内)并将其绑定到 ObservableCollection 对象。 我使用微软在其数据绑定概述中提供的Data Binding Demo 作为我的代码的基础。

主窗口 XAML:

   <Window x:Class="PetProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PetProject"
        mc:Ignorable="d"
        Title="PetProject" Height="350" Width="525">
    <Window.Resources>
        <CollectionViewSource 
              Source="{Binding Source={x:Static Application.Current}, Path=Dogs}"   
              x:Key="DogsDataView" />
    </Window.Resources>



    <Grid Margin="8,8,8,8">
        <TabControl>
            <TabItem Header="Dogs">
                <DataGrid ItemsSource="{Binding Source={StaticResource DogsDataView}}">
                </DataGrid>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

代码隐藏:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;

    namespace PetProject
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        /// 


        public partial class MainWindow : Window
        {
            CollectionViewSource DogsDataView;

            public MainWindow()
            {
                InitializeComponent();
                DogsDataView = (CollectionViewSource)(this.Resources["DogsDataView"]);
            }
        }
    }

应用 XAML 是

   <Application x:Class="PetProject.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:local="clr-namespace:PetProject"
                 Startup="AppStartup">
                 <!--StartupUri="MainWindow.xaml"-->
    </Application>

代码隐藏:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace PetProject
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private ObservableCollection<Dog> dogs = new ObservableCollection<Dog>();

        void AppStartup(object sender, StartupEventArgs args)
        {
            LoadData();
            MainWindow mainWindow = new MainWindow();
            mainWindow.Show();

        }

        public ObservableCollection<Dog> Dogs
        {
            get { return this.dogs; }
            set { this.dogs = value; }
        }

        private void LoadData() {
            Dog Johnny = new Dog("Johnny",1325);
            Dog Diamond = new Dog("Diamond",1327);
            this.Dogs.Add(Johnny);
            this.Dogs.Add(Diamond);
        }


    }
}

Dog 只是一个实现 INotifyPropertyChanged 接口的类(它现在什么都不做):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace PetProject
{
    public class Dog : INotifyPropertyChanged
    {
        private string name;
        private string number;

        public event PropertyChangedEventHandler PropertyChanged;

        public Dog(string name, int number)
        {
            this.name = name;
            this.number = number.ToString("D4");
        }


        protected void NotifyPropertyChanged(string propName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

在理解 DataGrid 未填充的原因方面,我将不胜感激。 此外,任何关于不良编码习惯或改进代码的建议都将非常受欢迎,因为我正处于一个非常初始的经验学习阶段。 谢谢!

【问题讨论】:

  • 删除 DogsDataView = (CollectionViewSource)(this.Resources["DogsDataView"]);
  • 嗨,你能解释一下为什么这是必要的吗?似乎可以在不删除它的情况下工作(在纠正了我遇到的私人/公共问题之后)。我现在意识到它现在什么都不做 - 这可能会在未来发生变化。
  • 因为你设置 DogsDataView = DogsDataView ;一旦你在窗口中使用了命名对象,一旦你通过使用资源得到相同的对象
  • 我不明白。我将资源添加到 MainWindow xaml。现在我在代码隐藏中声明它,但是如果我希望它引用相同的数据,我不需要通过xaml资源引用吗?
  • 您的 res 有一个键/名称,因此在 MainWindow 中使用它就像这样。DogsDataView - 不需要在 MainWindow 中声明变量(可能将 x:Key 更改为 x:Name) - 顺便说一句,在看看你是否做 MVVM !!

标签: c# wpf data-binding


【解决方案1】:

您不能绑定到私有字段。您只能绑定到公共属性。就 DataGrid 而言,Dog 没有要显示的信息。

public class Dog : INotifyPropertyChanged
{
    private string _name;
    private string _number;

    public Dog(string name, int number)
    {
        Name = name;
        Number = number.ToString("D4");
    }

    public String Name
    {
        get { return _name; }
        set
        {
            if (value != _name)
            {
                _name = value;
                NotifyPropertyChanged(nameof(Name));
            }
        }
    }

    public String Number
    {
        get { return _number; }
        set
        {
            if (value != _number)
            {
                _number = value;
                NotifyPropertyChanged(nameof(Number));
            }
        }
    }

我在您的私有字段前加上下划线,因为这是标准做法。这是标准做法,因为有两个仅按大小写不同的标识符会导致混淆和错误。

【讨论】:

  • 谢谢。我只需要习惯公私分离。
【解决方案2】:

首先,我建议您阅读 MVVM 原理,然后选择一个 MVVM 框架与 WPF 一起使用。例如MVVM light toolkit 是开始和理解 MVVM 的好选择。

对于您的示例,这里只是对您的代码的一些说明:

  • 我建议您将所有“业务”数据分组到一个 viewModel 类中(请参阅网络上的 MVVM 实践)-App 类中没有任何内容...
  • 此 ViewModel 将实现“INotifyPropertyChanged”接口
  • 因此 Dogs 属性 将位于此 ViewModel 中,并将在其设置器中引发“PropertyChanged”事件(您的示例中目前不是这种情况)
  • 有几个 MVVM 框架可以自动将您的视图“绑定”到您的视图模型,但要理解,主要目标是使用适当的 ViewModel 设置您的 Window.DataContext
  • 这就是您可以在 App.xaml 中恢复的原因:StartupUri="MainWindow.xaml"
  • 然后要加载您的 ViewModel,您可以执行类似的操作来加载您的 Dogs 集合:

    public partial class MainWindow : Window
    {
        public MainWindow()
             {
                InitializeComponent();
                Loaded += MainWindow_Loaded;
             }
    
            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                // For test: LOAD & SET your DataContext here
                //
                var myDogViewmodel = new DogViewModel();
                myDogViewModel.LoadData();
                this.DataContext = myDogViewmodel;
            }
        }
    
  • 您的 ViewModel 应该如下所示:

    public class DogViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private ObservableCollection<Dog> _dogs;
        public ObservableCollection<Dog> Dogs
        {
            get { return _dogs; }
            set
            {
                _dogs = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Dogs"));
            }
        }
        public void LoadData()
        {
            // ....
        }
    }
    
  • 那么你的Dog 类也必须实现 INotifuPropertyChanged 接口:

        public class Dog : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            private string _name;
            private int _number;     
    
            public string Name
            {
                get => _name;
                set
                {
                     if (_name != value)
                     { 
                        _name = value;
                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
                     }
                }
            }
    
            public int Number
            {
                get => _number;
                set
                {
                    if (_number != value)
                    {
                        _number = value;
                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Number"));
                    }
                }
            }
        }
    
  • 最后,在您的 MainWindow.xaml 中:

>

<Window x:Class="PetProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:PetProject"
    mc:Ignorable="d"
    Title="PetProject" Height="350" Width="525">

<Grid Margin="8,8,8,8">
    <TabControl>
        <TabItem Header="Dogs">
            <DataGrid ItemsSource="{Binding Dogs}" />
        </TabItem>
    </TabControl>
</Grid>

它现在应该可以工作了;)如果清楚,请告诉我。熟悉 MVVM...

【讨论】:

  • 感谢您的 cmets。我肯定会熟悉 MVVM 设计原理。看起来对我打算做的事情很有希望。
  • 你能解释一下PropertyChanged之间的区别吗?.Invoke(this, new PropertyChangedEventArgs("Number"));和 this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
  • 这里不需要 MVVMLight。 OP 以通常的方式设置 DataContext 要好得多。
  • 如果你直接调用PropertyChanged(...),你必须确定它不是“NULL”,否则你会得到一个异常。带有 "?.Invoke()" 的语法意味着如果 PropertyChanged 为 null(没有人在监听此事件),它将不会调用(抛出/执行)该事件,因此更安全(检查此:blogs.msdn.microsoft.com/jerrynixon/2014/02/26/…
  • 如果您能告诉我您认为何时更适合使用 MVVM 以及何时不需要,我将不胜感激。这段(非常)初始代码有望成为一个中等大小的 GUI,其背后有一些并非完全无关紧要的交互逻辑。
猜你喜欢
  • 2011-01-25
  • 2013-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-06
  • 1970-01-01
  • 1970-01-01
  • 2018-07-05
相关资源
最近更新 更多