【问题标题】:First steps in WPF: how to fill in a cell in a grid?WPF 的第一步:如何填充网格中的单元格?
【发布时间】:2021-08-12 11:42:14
【问题描述】:

我来自 Delphi、Java 等,现在我需要制作我的第一个 WPF 程序。

我正在编写一个程序,用于在现有表中读取和写入信息。

我的想法很简单:

  • 创建一个网格并将其放在表单上
  • 读取表格:
    • 首先读取列名。将它们添加到网格的第一个单元格 (var_Grid.Cells[0,i] = "Column_Name";)
    • 第二次读取所有数据,当前在表中,并填入网格中(var_grid.Cells[i,j] = "data";
  • 做进一步处理

因为我想先对我的网格进行一些处理,所以我想使用一个网格组件,而不是链接到数据库(所以没有TDBGrid,对于那些了解 Delphi 的人)。

我很快意识到 WPF Grid 用于将组件添加到表单中(例如 JPanelGridLayout,适用于 Java 程序员),所以我想选择 DataGrid 组件。

但是,当我查看how to select a cell in a DataGrid 时:我看到DataGridCellInfoItemContainerGeneratorContainerFromIndex,...,所有这些只是为了获得一个单元格? (甚至没有在那个单元格中输入数据的代码)

我认为DataGrid 太复杂了,肯定有一个WPF 可视化组件,比那个更容易填充,但是在工具箱中搜索Grid 没有答案。

有人知道使用 WPF DataGrid 的更简单方法吗?或者有人知道更简单的组件可以满足我的需求吗?

在第一次 cmets 后编辑

我从ListViewGridView 开始。我很快就让GridViewColumn 工作了,但后来我遇到了一个问题,我不知道ItemsSource 的列数量(我尝试使用List<List<string>>,但那似乎太天真了:-)

第二次修改
显然之间存在不匹配:

  • 我想做什么,并且:
  • 我为什么要这样做。

我想做的是创建一个网格,在运行时,我可以定义行数和列数并填充单元格。

然而,cmets 中的讨论充斥着我为什么要这样做。

所以我想重新表述我的问题:是否有一个 WPF 可视网格组件,我可以在运行时设置和更改行号和列号并逐个单元格地填写?

如果那个网格组件有可能有一个标题行,那就太好了。

有人知道这样一个可视化的 WPF 组件吗?

提前致谢

【问题讨论】:

  • 可能是a ListView with a GridView? (顺便说一句,这个教程站点非常适合解释所有 WPF 工具。AFAIK 数据网格是如此复杂,因为它可以虚拟化和许多其他东西......
  • 处理数据,从中构建一个类型化的可观察视图模型集合。绑定到数据网格的 itemssource。不要将 ui 用作数据存储。
  • 可以将 ListView 与 DataTemplate 一起使用,如果您告诉我您打算做什么,我可以尝试为您编写一个示例。
  • 正如您所提到的,我已经开始使用 ListView 和 GridView。填列标题没问题,但表格本身就没那么简单了。
  • @DarkTemplar:我不认为可以使用 DataTemplate:我不知道我的表会是什么样子。

标签: c# wpf datagrid


【解决方案1】:

您从根本上走错了实施方式。 您想直接操作 WPF UI 元素的内容。 但是 WPF 是建立在一个非常不同的概念之上的。 在 WPF 中获取数据的主要方法是使用数据上下文。 UI元素本身通过Bindings为其属性获取值。 默认情况下,绑定使用数据上下文作为源。

因此,为了正确实现,您必须首先创建一个实体,该实体将为其属性中的一个 DataGrid 行提供数据。 然后用这些实体创建一个可观察的集合。 并将这个集合绑定到 DataGrid.ItemsSource。

DataGrid 会自动为实体的每个属性创建一个列,并在其中显示属性值。 如果您愿意,您可以自己自定义所需的 DataGrid 视图及其单元格。

因此,您的任务将简化为处理集合及其元素,而不是在 DataGrid 及其可视化树中进行相当复杂的搜索。

DataGrid 的通用源之一可以是 DataTable。 当您无法为一行实体预定义所有必需的属性时,应使用此类型。

【讨论】:

    【解决方案2】:

    正如其他人所指出的,有很多方法可以通过 DataGrid、ListView 等在表单中实现数据内容显示。通过采用 MVVM 模式,您的模型显然是数据的来源,并存储到,例如某种类型的 SQL 数据库、通过 OleDbConnection 连接的 Excel 等。

    ViewModel 是实现所有您如何获取数据和如何保存数据的核心,还公开按钮单击操作、用户输入验证等。您的 ViewModel 成为绑定的“DataContext”您的视图的源基线。您可以通过 getter/setter 公开属性来公开实际数据,例如

    public DataTable MyListOfCustomers {get; set;}
    

    然后,在您的视图模型中的某处,您运行查询除了将记录加载到此数据表对象之外什么都不做。

    视图显然是您向最终用户显示的内容。这是您帖子中简短且模棱两可的解释所不清楚的。在几乎任何应用程序中,您可能有许多数据网格代表许多不同事物的数据,但这些数据网格通常总是代表相同的内容。例如,客户屏幕将始终显示客户列表并允许搜索、排序、特定列等。库存屏幕将始终显示可用项目等。

    因此,在开发应用程序时,与其依赖于如何使其始终动态可用,不如自行定义每个可视化数据网格组件,因此它始终相同并基于已知来源。然后,只需将该用户控件添加到您需要的视图中。如果需要在该数据网格上更改任何内容,只需更改一次,使用它的任何其他视图都会得到相同的影响。

    说了这么多,绑定本身也很简单,您可能想编辑您的帖子,而不是通过受限的 cmets。发布您正在尝试做的更明确的上下文。呈现的实际数据类型。这不像你正在做一些你不能透露的全新和绝密的事情。但也许这个系统将拥有的工作类型的整体背景。它会是基于会计的数字(甚至基于库存销售)、库存系统等吗?这个数据网格会改变其自身显示的内容,还是更像我描述的每个辅助视图甚至子区域单个视图显示不同的数据网格内容。

    提供模糊的信息,即使是来自另一个开发领域,在寻求帮助时也不会为您提供帮助。

    【讨论】:

      【解决方案3】:

      如果您打算动态修改DataGrid 的列,则应选择DataTable 作为网格的数据模型。

      请注意,添加/删除列需要更新每一行以便为新列提供值,这可能会对性能产生显着影响,具体取决于表模型的大小(尤其是行数)。 在这种情况下,您应该实施数据虚拟化。您将跟踪视口,即视图中的当前项目,并根据需要更新待处理的行数据更改。

      示例

      这是一个非常基本的示例,展示了如何在运行时借助 DataTable classDataGrid 的列进行修改。

      该示例使用了数据绑定(极大地简化了逻辑)和命令:

      此外,该示例依赖于 DataGrid 的列自动生成(此功能默认启用) - 否则解决方案的复杂性当然会增加。如果没有自动列生成,您将不得不向视图添加(简单)逻辑。

      MainViewModel.cs
      此类使用RelayCommand。您可以在 Microsoft Docs: Relaying Command Logic 找到示例实现。

      public class MainViewModel : INotifyPropertyChanged
      {
        public MainViewModel()
        {
          this.TableData = new DataTable();
          AddColumn<int>("ID");
          AddColumn<string>("Username");
          AddColumn<string>("Mail");
      
          AddRow(1, "Me", "me@mail.com");
        }
      
        private void AddColumn<TColumnData>(string columnName, int columnIndex = -1)
        {
          var newColumn = new DataColumn(columnName, typeof(TColumnData));    
          this.TableData.Columns.Add(newColumn);
          if (columnIndex > -1)
          {
            newColumn.SetOrdinal(columnIndex);
          }
      
          int newColumnIndex = this.TableData.Columns.IndexOf(newColumn);
      
          // Initialize existing rows with a default value for the new column.
          // In this example the default value of the column's data type is used.
          foreach (DataRow row in this.TableData.Rows)
          {
            row[newColumnIndex] = default(TColumnData);
          }
        }
      
        // Ensure that column values are ordered by their column's index
        private void AddRow(params object[] columnValues)
        {
          DataRow rowModelWithCurrentColumns = this.TableData.NewRow();
          this.TableData.Rows.Add(rowModelWithCurrentColumns);
      
          for (int columnIndex = 0; columnIndex < this.TableData.Columns.Count; columnIndex++)
          {
            rowModelWithCurrentColumns[columnIndex] = columnValues[columnIndex];
          }
        }
      
        // Force the binding target (DataGrid) to create new column templates 
        // by changing the binding source instance
        private void OnDataTableColumnsChanged() => this.TableData = this.TableData.DefaultView.ToTable();
      
        // Add columns dynamically via an ICommand
        public ICommand AddColumnCommand => new RelayCommand(commandParameter =>
        {
          AddColumn<DateTime>($"Timestamp {this.TableData.Columns.Count}");
          OnDataTableColumnsChanged();
        });
      
        public ICommand AddRowCommand => new RelayCommand(commandParameter => AddRow(2, "You", "you@mail.com", DateTime.Now));
      
        private DataTable tableData;
        public DataTable TableData
        {
          get => this.tableData;
          set
          {
            this.tableData = value;
            OnPropertyChanged();
          }
        }
      
        /*** Implementation of INotifyPropertyChanged ***/
      
        public event PropertyChangedEventHandler PropertyChanged;
      
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") 
          => this.PropertyChanged?.Invoke(new PropertyChangedEventArgs(propertyName));
      }
      

      备注

      或者,成员AddColumnAddRow 可以实现为扩展方法。

      您可以从任何来电者那里致电AddColumnAddRow - 不仅来自ICommand。只需确保在添加/删除所有列后调用 OnDataTableColumnsChanged 将列更改提升到视图即可。
      在视图中,行不需要创建任何视图模板,因此这些行数据更改立即可见。行的单元格如何呈现在列的模板中定义。
      另一方面,视图列需要一个视图模板来告诉DataGrid 如何呈现列及其单元格。调用 OnDataTableColumnsChanged 会强制绑定目标(DataGrid)为新列生成列模板。
      如果DataTable 的数据源实际上是一个数据库,则每次查询后都会创建一个DataTable 的新实例,这会使对OnDataTableColumnsChanged 的调用变得多余。

      MainWindow.xaml

      <Window>
        <Window.DataContext>
      
          <!-- Don't forget to qualify the type with the defined XAML namespace (xmlns) -->
          <MainViewModel />
        </Window.DataContext>
        
        <StackPanel>
          <Button Content="Add Column" Command="{Binding AddColumnCommand}" />
          <Button Content="Add Row" Command="{Binding AddRowCommand}" />
          <DataGrid ItemsSource="{Binding TableData}" />
        </StackPanel>
      </Window>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-10
        • 2010-11-12
        • 2017-10-01
        • 1970-01-01
        • 2021-08-24
        • 1970-01-01
        • 2017-12-14
        相关资源
        最近更新 更多