WPF Treeview复杂层次结构

时间:2015-02-20 09:13:59

标签: c# wpf xaml mvvm treeview

我在我的应用程序中使用MVVM模式。我有这些(简化版)VM类:

public class MainModule_VM
{
    ...

    public ObservableCollection<Module2601_VM> ListModules{...}

    ...
}

public class Module2601_VM
{
    ...

    public string GatewayName {...}
    public string FirmwareVersion{...}
    public string IPAddress{...}
    public string NbIoms{...}
    public ObservableCollection<Module2610_VM> ListModules{...}
    public ObservableCollection<ComPort_VM> ListCOM{...}

    ...
}

public class Module2610_VM
{
    ...

    public string ModuleName{...}

    ...
}

public class ComPort_VM
{
    ...

    public string ComPortName{...}

    ...
}

因此,一个Module2601_VM包含几个属性,以及Module2610_VM和ComPort_VM对象的列表。

我在MainModule_VM类中有一个Module2601_VM对象列表。

我想将这个Module2601的集合和它的子项绑定在Treeview中,具有以下层次结构:

网关:

  • 网关#0
    • IP
    • 固件
    • NbIoms
    • ListModules
      • 模块#0
      • 模块#1
      • ...
    • ListCOM
      • COM#1
      • ...

我的问题是,我得到了层次结构OK,但无法选择子项。它将任何网关项目及其所有子项作为一个大项目。我知道这是因为我的itemTemplate,但我无法解决这个问题。 这是xaml(在代码隐藏中设置的DataContext):

<TreeView Name="treeView1" >
    <TreeViewItem Header="Gateways" ItemsSource="{Binding ListMM}">
        <TreeViewItem.ItemTemplate>
            <DataTemplate>
                <TreeViewItem Header="{Binding GatewayName, Mode=OneWay}" IsExpanded="True">
                    <TreeViewItem Header="{Binding Path=Gateway.IPAddress, Mode=OneWay}"/>
                    <TreeViewItem Header="{Binding Path=Gateway.FirmwareVersion, Mode=OneWay}"/>
                    <TreeViewItem Header="{Binding Path=Gateway.NumberIoms, Mode=OneWay}"/>
                    <TreeViewItem Header="MIOs" ItemsSource="{Binding ListModules, Mode=OneWay}">
                        <TreeViewItem.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding ModuleName, Mode=OneWay}" />
                            </DataTemplate>
                        </TreeViewItem.ItemTemplate>
                    </TreeViewItem>
                    <TreeViewItem Header="COM" ItemsSource="{Binding ListCOM, Mode=OneWay}">
                        <TreeViewItem.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=PortCom.ComP, Mode=OneWay}"/>
                            </DataTemplate>
                        </TreeViewItem.ItemTemplate>
                    </TreeViewItem>
                </TreeViewItem>      
            </DataTemplate>
        </TreeViewItem.ItemTemplate>
    </TreeViewItem>                   
</TreeView>

有了这个我无法选择子项,它看起来像这样: picture

现在使用以下示例,我在treeView中只有一个Module2601_VM项目,一切都很好,我可以选择单个项目:

<TreeView Name="treeView2">
    <TreeViewItem Header="Gateways">
        <TreeViewItem Header="{Binding GatewayName, Mode=OneWay}">
            <TreeViewItem Header="{Binding Path=IPAddress, Mode=OneWay}"/>
            <TreeViewItem Header="{Binding Path=FirmwareVersion, Mode=OneWay}"/>
            <TreeViewItem Header="{Binding Path=NumberIoms, Mode=OneWay}"/>
            <TreeViewItem Header="MIOs" ItemsSource="{Binding ListModules, Mode=OneWay}">
                <TreeViewItem.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding ModuleName, Mode=OneWay}" />
                    </DataTemplate>
                </TreeViewItem.ItemTemplate>
            </TreeViewItem>
            <TreeViewItem Header="COM" ItemsSource="{Binding ListCOM, Mode=OneWay}">
                <TreeViewItem.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Path=ComPortName, Mode=OneWay}"/>
                    </DataTemplate>
                </TreeViewItem.ItemTemplate>
            </TreeViewItem>
        </TreeViewItem>
    </TreeViewItem>
</TreeView>

如何让我的treeview像treeView1一样,但是使用treeView2选择功能? HierarchicalDataTemplate在此处不起作用,因为子项具有不同的类型。 感谢。

1 个答案:

答案 0 :(得分:0)

为了实现您描述的TreeView代表,我认为您应该使用&#34;隐含&#34; DataTemplate mechanismHierarchicalDataTemplate

关于第一点,我会创建几个类来实现&#34;简单&#34;您的可观察集合的类型:

public class Module2610_VMCollection : ObservableCollection<Module2610_VM>
{
}

public class ComPort_VMCollection : ObservableCollection<ComPort_VM>
{
}

然后Module2601_VM类将成为:

public class Module2601_VM
{
    /* ... */

    public string GatewayName {get; set;}
    public string FirmwareVersion { get; set; }
    public string IPAddress { get; set; }
    public string NbIoms { get; set; }
    public Module2610_VMCollection ListModules { get; set; }
    public ComPort_VMCollection ListCOM { get; set; }

    /* ... */
}

实际上,您的ViewModel并不反映您想要在UI中显示的结构,因此我猜您需要一个类来描述&#34;描述&#34; Module2601_VM的孩子们。这样的事情:

public class Module2601_VMChildrenDescriptor : IEnumerator, IEnumerable
{
    private readonly Module2601_VM module2601_VM;
    private int i = -1;

    public Module2601_VMChildrenDescriptor(Module2601_VM module2601_VM)
    {
        this.module2601_VM = module2601_VM;
    }

    public object Current
    {
        get
        {
            switch (i)
            {
                case 0:
                    return module2601_VM.GatewayName;
                case 1:
                    return module2601_VM.FirmwareVersion;
                case 2:
                    return module2601_VM.IPAddress;
                case 3:
                    return module2601_VM.NbIoms;
                case 4:
                    return module2601_VM.ListModules;
                case 5:
                    return module2601_VM.ListCOM;
            }

            return null;
        }
    }

    public bool MoveNext()
    {
        return ++i < 6;
    }

    public void Reset()
    {
        i = -1;
    }

    public IEnumerator GetEnumerator()
    {
        return this;
    }
}

所以完整的视图模型将是:

public class Module2601_VM
{
    private Module2601_VMChildrenDescriptor module2601_VMChildrenDescriptor;
    public Module2601_VM()
    {
        module2601_VMChildrenDescriptor = new Module2601_VMChildrenDescriptor(this);
        ListModules = new Module2610_VMCollection();
        ListCOM = new ComPort_VMCollection();
    }

    public string GatewayName {get; set;}
    public string FirmwareVersion { get; set; }
    public string IPAddress { get; set; }
    public string NbIoms { get; set; }
    public Module2610_VMCollection ListModules { get; set; }
    public ComPort_VMCollection ListCOM { get; set; }

    public IEnumerable Children
    {
        get
        {
            return module2601_VMChildrenDescriptor;
        }
    }
}

现在您只需要将XAML中的DataTemplates声明为TreeView控件的资源:

<TreeView ItemsSource="{Binding Path=ListModules}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:Module2601_VM}" ItemsSource="{Binding Path=Children, Mode=OneWay}">
            <TextBlock Text="Module2601_VM" Margin="1" />
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type sys:String}">
            <TextBlock Text="{Binding Mode=OneWay}" Margin="1" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Module2610_VM}">
            <TextBlock Text="{Binding Path=ModuleName, Mode=OneWay}" Margin="1" />
        </DataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:Module2610_VMCollection}" ItemsSource="{Binding Mode=OneWay}">
            <TextBlock Text="ListModules" Margin="1" />
        </HierarchicalDataTemplate>

        <DataTemplate DataType="{x:Type local:ComPort_VM}">
            <TextBlock Text="{Binding Path=ComPortName, Mode=OneWay}" Margin="1" />
        </DataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:ComPort_VMCollection}" ItemsSource="{Binding Mode=OneWay}">
            <TextBlock Text="ComPort_VM" Margin="1" />
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

现在,TreeView的每个节点都是可选的。 我希望它有所帮助。