【问题标题】:Responding to object property changes within a class object hierarchy响应类对象层次结构中的对象属性更改
【发布时间】:2020-04-23 00:08:24
【问题描述】:

我正在开发一个应用程序,它使用分层对象结构并在 DataGridView 内的主 GUI 上显示这些对象的一些关键对象属性。当基础数据更改时,这些值必须更新。我考虑了几个选项:

  1. 将各个 DataGridView 单元格绑定到相关的对象属性。我知道这是不可能的,DGV 绑定就是全部或全部。
  2. 在网格单元格上动态定位文本框并绑定它们,但这看起来很混乱。
  3. 创建一个仅引用相关对象属性的中间列表/数组/集合,然后将该列表用作 DataGridView 的数据源。
  4. 响应 PropertyChanged 事件。复杂的是我有多个课程。顶级对象存在于 UI 范围内,并且有一个子对象,该子对象又可能有多个自己的子对象,依此类推。 UI 可以访问所有对象的属性,但不能访问它们的事件。

我一直在考虑将 PropertyChanged 事件从它发生在链上的任何级别传递,以便它可以在 UI 中进行处理。因此,在特定类中,我想在该类和任何子类中响应 OnPropertyChanged,并使引发的任何事件对父类可用。因此事件会沿着树向上流动到顶部。

我认为,我了解如何分别执行这两个步骤,参考以下内容:

Handling OnPropertyChanged

Pass click event of child control to the parent control

但是,虽然我认为这两者可以结合起来,但我不太确定如何做到这一点。在 UI 中我得到了这个:

project.PropertyChanged += new PropertyChangedEventHandler(ProjectPropertyChanged);

private void ProjectPropertyChanged(object sender, PropertyChangedEventArgs e) {
    MessageBox.Show("In main UI. Project property changed!");
}

然后再往下一层,我得到了这样的东西:

public class Project : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public Project() {
        childObject.PropertyChanged += new PropertyChangedEventHandler(ProjectPropertyChanged);
    }
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        ProjectPropertyChanged(sender, e); // this doesn't work due to different parameters
    }
    private void PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // Event available to parent class
    }
}

这个想法是每个类将其自己的 OnPropertyChanged() 事件传递给其 PropertyChanged() 方法,并响应其子级的 OnPropertyChanged() 事件,并将所有事件公开给父类。

如果这样做,理想情况下,我希望了解哪些属性发生了变化,以便做出相应的响应。

最直接的问题是由于参数不同,ProjectPropertyChanged 和 OnPropertyChanged 之间缺乏兼容性。不过,更根本的是,我不确定这种方法是否可行或最优。

如何最好地做到这一点?

【问题讨论】:

  • child 对象是否修改了主对象的属性? -- DataGridView 不能呈现嵌套对象值,所有对象都需要位于相同的层次结构级别。将您的对象绑定到 BindingSource(此类处理属性更改通知)并将 DGV 绑定到 BindingSource。或者使用 DataTable 来呈现数据:此类会自行引发通知更改(您可以将 DataTable 绑定到 BindingSource 以获得更多导航选项)。
  • 正如我所说,我不认为数据绑定到 DGV 是显而易见的解决方案,因为其中的属性属于多个对象。您是否建议我使用包含来自不同对象的属性的中间列表并将其用作数据源?
  • DGV 中的数据只能以列和行的形式呈现,因此没有嵌套对象,您必须扁平化层次结构。你打算如何做以及你想在 DGV 中展示什么,你没有说。无论如何都需要一个表示层。如果您的表示层引发通知更改,则满足 UI 要求,BindingSource 可以处理您的类对象和 UI(在本例中为 DGV,但任何其他处理 DataBindings 的控件)之间的事务。
  • 如我所说,DGV 显示了相关对象的主要属性(即用户最感兴趣的那些)。我意识到直接的 DGV 绑定不支持嵌套对象,因此不建议这样做。但是,我已经考虑并正在考虑使用仅包含要显示的那些属性的附加列表。据推测,只要它包含引用而不是值,它就会起作用。 UI 范围内的类级 List 可能会这样做...
  • 大量 cmets 没有错澄清您的问题。我可以建议您在问题中添加数据结构的详细信息/示例,并更详细地描述该结构如何在数据网格中显示。您想在网格中显示什么以及它在模型中的结构尚不清楚。

标签: c# oop events


【解决方案1】:

回答我自己的问题:

我尝试使用 DataTable(根据 this question.)使用中间绑定源执行此操作,但未成功。问题是我无法创建对数据对象的引用。 DataTable 似乎包含值。

所以我最终使用了一种我更确定但不太优雅的方法,这是我上面问题中的选项 2。我将标签放置在需要绑定数据值的位置,并绑定到这些标签。这行得通。

通过一些简化,并假设我们的对象是动物,我的解决方案是:

Label[,] dashboardLabels = new Label[3,14];

private void Form1_Shown(object sender, EventArgs e)
{
  CreateLabels(); // create and position labels (no binding yet)
}

public void CreateLabels(int cols = 3, int rows = 14)
{

    for (int col = 0; col < cols; col++)
    {
        for (int row = 0; row < rows; row++)
        {
            // Create label...
            Label l = new Label();
            l.Text = "N/A";
            l.ForeColor = Color.Red
            dashboardLabels[col, row] = l;
            this.Controls.Add(l);

            // Position label over DGV cell...
            Point dgvCell = dataGridView1.GetCellDisplayRectangle(col + 2, rowNumbersLabel[row], false).Location;
            Point dgvGrid = dataGridView1.Location;
            l.Left = dgvGrid.X + dgvCell.X;
            l.Top = dgvGrid.Y + dgvCell.Y;

        }
    }
}
private void UpdateLabels(List<Dog> dogs)
{
    for (int i = 0; i < dogs; i++)
    {
        if (!dashboardLabels[i, 0].Visible) dashboardLabels[i, 0].Visible = true;
        if (dogs[i].IsSetUp) BindLabel(dashboardLabels[i, 0], dogs[i],"Name");
    }
}
private void BindLabel(Label l, Dog dog, string observation)
    {
        Binding b = new Binding("Text", dog, observation);
        l.DataBindings.Add(b);
        l.ForeColor = Color.Green;
    }
}

然后在创建对象时,我调用UpdateLabels()。如果未初始化,此时标签将显示红色“N/A”。如果已初始化,标签将变为绿色并绑定到对象的名称,以便从该点开始自动更新。

我做了很多搜索,发现的信息表明 DataGridView 不支持复杂的数据绑定,即它几乎是一个 DGV 的一个类,或者根本不支持。我也找不到替代的类似网格的控件。

【讨论】:

    猜你喜欢
    • 2022-06-28
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多