Tommaso Belluzzo
Tommaso Belluzzo

Reputation: 23695

TreeView Populating / Binding

I created a parser that reads files formatted in the following way:

version="v4.5.32"
name="Test File"
date="2513.04.02"
players=
{
    {
        first_name="John"
        last_name="Smith"
        country=12
        id=0
    }
    {
        first_name="Mario"
        last_name="Rossi"
        country=56
        id=1
    }
}
next_player_id=2
matches=
{
    22 47 88 1045 1048 3511
}
settings=
{
    match_prefix="game"
    match_reward_scalar=1,55
    match_sets_points=
    {
        0,5 0,75 1,0
    }
    next_event_id=56
    next_event_fired=false
    next_event_probability=0,33
}
...

Basically, those files contain a list of key/value pairs in which keys are always a string and values can be either a simple value (boolean, date, float, integer, string), a record (a sublist of key/value pairs like settings) or an array (composed by simple values like matches or records like players). In order to parse and handle those values I created 3 simple classes.

1) MyPair

public sealed class MyPair
{
    public MyKey Key { get; }
    public MyValue Value { get; }

    public MyPair(MyKey key, MyValue value) { ... }

    public override String ToString()
    {
        return String.Concat(Key, " = ", Value);
    }
}

2) MyKey

public sealed class MyKey
{
    public String Name { get; }
    ... // other properties set by checking the name in the constructor

    public Key(String name) { ... }

    public override String ToString()
    {
        return Name;
    }
}

3) MyValue

public sealed class MyValue
{
    private readonly dynamic m_UnderlyingValue;
    private readonly MyValueCategory m_Category;

    public dynamic UnderlyingValue
    {
        get { return m_UnderlyingValue; }
    }

    public Boolean Container
    {
        get { return ((m_Category == ValueCategory.Array) || (m_Category == ValueCategory.Record)); }
    }

    public MyValueCategory Category
    {
        get { return m_Category; }
    }

    public MyValue(DateTime underlyingValue)
    {
        if (underlyingValue == null)
            throw new ArgumentNullException("underlyingValue");

        m_UnderlyingValue = underlyingValue;
        m_Category = MyValueCategory.DateTime;
    }

    public MyValue(Boolean underlyingValue) { ... }

    public MyValue(Double underlyingValue) { ... }

    public MyValue(Int64 underlyingValue) { ... }

    public MyValue(MyPair[] underlyingValue) { ... }

    public MyValue(MyValue[] underlyingValue) { ... }

    public MyValue(String underlyingValue) { ... }

    public override String ToString()
    {
        switch (m_Category)
        {
            case MyValueCategory.Array:
                return String.Concat("Array[", m_UnderlyingValue.Length, "]");

            case MyValueCategory.Boolean:
                return String.Concat("Boolean[", (m_UnderlyingValue ? "true" : "false"), "]");

            case MyValueCategory.DateTime:
                return String.Concat("DateTime[", m_UnderlyingValue.ToString("yyyy.MM.dd", CultureInfo.InvariantCulture), "]");

            case MyValueCategory.Float:
                return String.Concat("Float[", m_UnderlyingValue.ToString("F3", CultureInfo.InvariantCulture), "]");

            case MyValueCategory.Integer:
                return String.Concat("Integer[", m_UnderlyingValue.ToString("F0", CultureInfo.InvariantCulture), "]");

            case MyValueCategory.Record:
                return String.Concat("Record[", m_UnderlyingValue.Length, "]");

            default:
                return String.Concat("Text[", m_UnderlyingValue, "]");
        }
    }
}

public enum MyValueCategory
{
    Array,
    Boolean,
    DateTime,
    Float,
    Integer,
    Record,
    Text
}

The parsing process works like a charm and returns me a MyValue instance that works like a container / root node for everything I parsed.

I'm not using WPF forms, just plain Winforms. I would like to populate a TreeView control hierarchically with the parsed data and then make that data responsive to the changes made to the TreeView nodes. I really can't figure out how to bind data to the control itself and allow a mirrored manipulation.

Any suggestion please?

Upvotes: 2

Views: 472

Answers (1)

Kai Thoma
Kai Thoma

Reputation: 572

You can populate your TreeView recursively with this code:

protected override void OnLoad( EventArgs e )
{
    base.OnLoad( e );

    MyValue root = new MyParser().Parse( "MyFilename.own" );
    Populate( treeView1.Nodes, root.UnderlyingValue );
}

protected void Populate( TreeNodeCollection nodes, IList list )
{
    if( list is MyPair[] )
    {
        foreach( MyPair pair in list )
        {
            TreeNode node = new TreeNode();
            node.Text = pair.ToString();
            node.Tag = pair;
            nodes.Add( node );

            if( pair.Value.Container )
                Populate( node.Nodes, (IList)pair.Value.UnderlyingValue );
        }
    }

    if( list is MyValue[] )
    {
        foreach( MyValue value in list )
        {
            TreeNode node = new TreeNode();
            node.Text = value.ToString();
            node.Tag = value;
            nodes.Add( node );

            if( value.Container )
                Populate( node.Nodes, (IList)value.UnderlyingValue );
        }
    }
}

Result looks then like that:

Populated TreeView

As @Reza Aghaei already mentioned it is not possible to do this via data-binding. You have to maintain your lists manually after adding/removing a node. Setting node.Tag to the corresponding pair or value makes it easy for you to find and modify them.

Upvotes: 3

Related Questions