0
votes

I have a WinForms TreeView to represent my calculator data. When I read my data in, I create my TreeView and tag each TreeView node with the corresponding object. Below is a sample TreeView.

SUM
    SUM
        TIMES
            UnitsSold
            UnitPrice
        IncomeFromContracts
    NEGATIVE SUM
        Rent
        Wages
        TIMES
            UnitsSold
            UnitMaterialCost

I create the TreeView from a data structure such as:

class Operand {
    bool IsNegated {get; set;}
}

class Operator : Operand {
    enum OperatorEnum { SUM, DIFFERENCE, TIMES }
    OperatorEnum MyEnum {get; set;}
    IList<Operand> Children {get; set;}
}

class VariableName : Operand {
    string Name {get; set;}
}

Each TreeNode is tagged with the corresponding Operand. When an item in the TreeView is selected, the tagged item is exposed in a panel so that you can change its properties. E.g. when I select an item, you get a checkbox where you can set the IsNegated value. I have successfully bound that control to the Operand object using:

negateCheckBox.DataBindings.Add("Checked", requirementTree.SelectedNode.Tag, "IsNegated");

Clicking the checkbox appropriately updates the underlying data, but it doesn't cause the TreeView to refresh its data, so even though the return value of the item's ToString() method has changed (it now has "NEGATIVE" prefixed), because the TreeNode was already created with that string, it doesn't update.

I also have some "Add" and "Remove" buttons which make additional nodes in the TreeView and remove them, but these all function manually -- update the underlying data structures, then rebuild the entire TreeView (this causes the view to refresh, so I can verify my other bindings are working).

What steps do I need to take so that the TreeView isn't populated manually, but is fully tied to my data structure, so that changes like this are immediately reflected? It seems like there are numerous ways to bind data in WinForms and I am unclear as to which apply in which situations.

Thanks so much for the help!

1
TreeView nodes don't have any support for data-binding, so you need to handle the case manually. You can find the node with tag and reflect changes.Reza Aghaei
Also instead of relying on UI events, lick click of button, rely on events of your data structure.Reza Aghaei

1 Answers

1
votes

Based on Reza's comment that data binding is not supported for TreeView, I came up with a reasonable workaround.

Each "model" class implements INotifyPropertyChanged and raises the event when any of its properties change.

class Operand : INotifyPropertyChanged
{
    public bool IsNegated
    {
        get { return m_isNegated; }
        set { m_isNegated = value; RaisePropertyChanged(); }
    }
    private bool m_isNegated = false;

    private void RaisePropertyChanged([CallerMemberName]string prop = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
    }
}

I then added a dummy controller which links the model to the TreeView node:

public class OperandTreeNodeController

{
    private Requirement m_model;
    private TreeNode m_view;

    public OperandTreeNodeController(Requirement model, TreeNode view)
    {
        m_model = model;
        m_view = view;

        m_model.PropertyChanged += Model_PropertyChanged;
    }

    private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        m_view.Text = m_model.ToString();
    }
}

Now, whenever I create a TreeNode, I also create a controller. There's a bit of a memory "leak" here because these can keep being created and appended to the model's PropertyChanged event and the garbage collector may never clean them up, but I've handled that manually in my code by adding a Destroy() function and tracking/destroying them manually.

This seems to accomplish most of my goals, so I'm going to stick with it unless someone has another idea.