WPF Treeview 复杂层次结构
WPF Treeview complex hierarchy
我在我的应用程序中使用 MVVM 模式。我有这些(简化版)VM classes :
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 class 中有一个 Module2601_VM 对象列表。
我想将 Module2601 的这个集合及其子项绑定到树视图中,具有以下层次结构:
网关:
- 0 号网关
- IP
- 固件
- 铌离子
- 列表模块
- 模块#0
- 模块#1
- ...
- ListCOM
- COM #1
- ...
我的问题是,我的层次结构没问题,但不能 select 子项。它将任何网关项目及其所有子项目作为一个大项目。我知道这是因为我的 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>
用这个我不能select子项,它看起来像这样:
picture
现在使用以下示例,我的 treeView 中只有一个 Module2601_VM 项,一切都很好,我可以 select 个单独的项:
<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 selection 功能?
HierarchicalDataTemplate 在这里不起作用,因为子项的类型不同。
谢谢
为了实现您描述的 TreeView 表示,我认为您应该使用 "implicit" DataTemplate mechanism 和 HierarchicalDataTemplate
.
关于第一点,我会创建几个 classes 来为您的可观察集合创建一个 "simple" 类型:
public class Module2610_VMCollection : ObservableCollection<Module2610_VM>
{
}
public class ComPort_VMCollection : ObservableCollection<ComPort_VM>
{
}
那么Module2601_VM class会变成:
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 中显示的结构,所以我猜您需要一个 class 到 "describe" 的 children 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 中的数据模板声明为 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 的每个节点都是可选的。
希望对你有帮助。
我在我的应用程序中使用 MVVM 模式。我有这些(简化版)VM classes :
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 class 中有一个 Module2601_VM 对象列表。
我想将 Module2601 的这个集合及其子项绑定到树视图中,具有以下层次结构:
网关:
- 0 号网关
- IP
- 固件
- 铌离子
- 列表模块
- 模块#0
- 模块#1
- ...
- ListCOM
- COM #1
- ...
我的问题是,我的层次结构没问题,但不能 select 子项。它将任何网关项目及其所有子项目作为一个大项目。我知道这是因为我的 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>
用这个我不能select子项,它看起来像这样: picture
现在使用以下示例,我的 treeView 中只有一个 Module2601_VM 项,一切都很好,我可以 select 个单独的项:
<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 selection 功能? HierarchicalDataTemplate 在这里不起作用,因为子项的类型不同。 谢谢
为了实现您描述的 TreeView 表示,我认为您应该使用 "implicit" DataTemplate mechanism 和 HierarchicalDataTemplate
.
关于第一点,我会创建几个 classes 来为您的可观察集合创建一个 "simple" 类型:
public class Module2610_VMCollection : ObservableCollection<Module2610_VM>
{
}
public class ComPort_VMCollection : ObservableCollection<ComPort_VM>
{
}
那么Module2601_VM class会变成:
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 中显示的结构,所以我猜您需要一个 class 到 "describe" 的 children 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 中的数据模板声明为 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 的每个节点都是可选的。 希望对你有帮助。