-1

I am developing an application which uses a hierarchical object structure and displays a few key object properties from those objects on the main GUI within a DataGridView. Those values must update when the underlying data changes. I have considered a few options:

  1. Bind the individual DataGridView cells to the relevant object properties. I understand that this is not possible, and DGV binding is all or nothing.
  2. Dynamically position Textboxes over the grid cells and bind those, but this seems messy.
  3. Create an intermediate list/array/collection which references only the relevant object properties, and then use that list as a data source for the DataGridView.
  4. Respond to the PropertyChanged events. The complication is that I have got multiple classes. The top-level object exists within the UI scope, and has a child object which in turn may have multiple child objects of its own, and so on. The UI can access properties of all objects, but not their events.

I have been looking at passing the PropertyChanged event from whichever level it occurs up the chain so that it can be handled within the UI. So within a particular class I want to respond to OnPropertyChanged within that class, and within any children, and make any events raised available to the parent class. Thus events would flow up the tree to the top.

I understand how to do the two steps individually, I think, with reference to the following:

Handling OnPropertyChanged

Pass click event of child control to the parent control

However, although I presume the two can be combined, I am not quite sure how to do this. In the UI I have got this:

project.PropertyChanged += new PropertyChangedEventHandler(ProjectPropertyChanged);

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

And then one level down I have got something like this:

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
    }
}

The idea being that each class would pass its own OnPropertyChanged() events to its PropertyChanged() method, and respond to its children's OnPropertyChanged() events, and expose all to the parent class.

If doing this, ideally I would like to retain knowledge of which property changed in order to respond accordingly.

The most immediate issue is lack of compatibility between ProjectPropertyChanged and OnPropertyChanged due to different parameters. More fundamentally, though, I am not sure whether this method is workable or optimal.

How best to do this?

wotnot
  • 261
  • 1
  • 12
  • Do the *child* objects modify properties of main object? -- A DataGridView cannot present nested objects values, all object need to be on the same *hierarchy level*. Bind your object to a BindingSource (this class handles the property change notification) and bind the DGV to the BindingSource. Or use a DataTable to present the data: this class raises notification changes on its own (you can bind a DataTable to a BindingSource for more *navigation* options). – Jimi Jan 04 '20 at 23:48
  • As I said, I don't see data binding to the DGV as the obvious solution since the properties in it belong to multiple objects. Are you proposing that I use an intermediate list containing properties from the different objects and use that as the data source? – wotnot Jan 04 '20 at 23:53
  • Data in a DGV can only be presented in Columns and Rows, so no nested objects, you have to *flatten the hierarchy*. How you plan to do it and what you want to present in the DGV, you didn't say. A presentation layer is needed anyway. If your presentation layer raises notification changes, the UI requirements are met, a BindingSource can handle the transaction between you class object(s) and the UI (a DGV, in this case, but any other control that handles DataBindings). – Jimi Jan 04 '20 at 23:58
  • The DGV displays, as I said, the main properties of the objects in question (i.e. those of most interest to the user). I realise that straightforward DGV binding does not support nested objects, hence not proposing to do it. However I have considered, and am considering, use of an additional list containing only those properties that are to be displayed. Presumably that would work as long as it contained references rather than values. A class-level List in the UI scope might do it... – wotnot Jan 05 '20 at 00:09
  • You're mixing parts that don't belong. What objects are subject to changes? Where do these changes take place? Do changes in objects presented at UI level propagate to other objects? Not a UI concern, since it's not part of the presentation, this is handled internally (your classes do). Do *child* objects changes propagate to the UI layer/bound objects? The main object/layer raises notification changes, the UI receives the notifications and updates itself. Do you want to handle the presentation layer object in a `List`?. Simplified but possible; you need a BindingsSource anyway. – Jimi Jan 05 '20 at 00:17
  • All objects are subject to changes, which could result from internal calculations or by the user via a PropertyGrid. Changes to one object probably never propagate to other objects, but changes at all levels must be reflected by the UI. The child objects are the issue since the UI does not have a reference to them and is not notified of changes to their properties. If I am going to use a binding source (my preferred method, in theory) then as far as I can see it would have to be a separate subset data source built specifically for that purpose. – wotnot Jan 05 '20 at 00:37
  • *The child objects are the issue since the UI does not have a reference to them and is not notified of changes to their properties*. Since the UI is not *connected* to these objects, it doesn't care if these objects are changed. Maybe the objects up in the food chain are. The child changes are then handled by the Parent objects. Unless you have a flaw in the class model design. – Jimi Jan 05 '20 at 00:52
  • Stack Overflow is telling me to avoid extended discussions in the comments so I intend to leave this here, but yes having the parent object handle the child's events (and specifically doing so in a chain) was the subject of my question. – wotnot Jan 05 '20 at 01:06
  • 1
    Nothing wrong with lots of comments *to clarify your question*. Can I suggest you add details/examples of your data structure to the question and describe in more detail how that structure is to be shown in the data grid. There is lack of clarity about what you want to show in the grid and how it is structured in your model. – andrew Jan 05 '20 at 01:26
  • ... and that's what I wrote about from the beginning: you need a **presentation layer**. A BindingSource (an ObservableCollection etc.) handle the `UI<->model` transactions through notifications. – Jimi Jan 05 '20 at 01:27
  • Andrew, Thanks for your reply. I have a hierarchical object structure, from which I am displaying a few key properties in the UI. To use a typical animal example, say one person with two dogs, each dog has some fleas, each flea has six legs, a length and a mass. User interface displays in a DGV the number of fleas on each dog, and mass of each flea, and this must reflect changes as they occur. UI has a reference to person but not dogs or fleas. That's it. – wotnot Jan 05 '20 at 01:54
  • Jimi, I am not averse to using a BindingSource if that is the best approach. It would have to be, presumably, a subset created for that purpose. – wotnot Jan 05 '20 at 01:58

1 Answers1

0

To answer my own question:

I tried unsuccessfully to do this with an intermediate binding source, using a DataTable (as per this question.). The problem was that I couldn't create references to the data objects. The DataTable seemed to contain values.

So I ended up using a method I was more sure about, but is less elegant, which is option 2 in my question above. I position Labels where I need bound data values, and bind to those labels. This works.

With some simplification, and pretending that our objects are animals, my solution was this:

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;
    }
}

Then when the objects are created, I call UpdateLabels(). If not initialised, the label will show 'N/A' in red at this point. If initialised, the label will become green and will be bound to the object's name so it will update automatically from that point on.

I did much searching and the information I was finding suggested that a DataGridView does not support complex data binding i.e. it is pretty much one class to one DGV, or not at all. I couldn't find an alternative grid-like control which would do it either.

wotnot
  • 261
  • 1
  • 12