【问题标题】:What is the proper way to designate the data context and bind hierarchical data in the treeview?在树视图中指定数据上下文和绑定分层数据的正确方法是什么?
【发布时间】:2014-11-25 16:45:54
【问题描述】:

我有一个相当简单的数据模型:

public class ServiceModel
{
    private static readonly IQmiServiceManager ServiceManager = Bootstrap.Instance.DomainManager.QmiServiceManager;

    private string _serviceName;
    private ObservableCollection<string> _serviceMessages;


    public string Name
    {
        get { return _serviceName; }
        private set { _serviceName = value.ToUpper(); }
    }

    public ObservableCollection<string> Messages
    {
        get { return _serviceMessages; }
        private set { _serviceMessages = value; }
    }


    public ServiceModel(string ServiceName, IList<string> ServiceMessages)
    {
        Name = ServiceName;
        Messages = new ObservableCollection<string>(ServiceMessages);
    }

}

...封装在这个视图模型中:

public class ServiceCollectionViewModel
{
    private readonly ObservableCollection<ServiceModel> _serviceModels = new ObservableCollection<ServiceModel>();

    public ObservableCollection<ServiceModel> ServiceModels
    {
        get { return _serviceModels; }
    }

} 

我有以下树视图 xaml 定义:

<TreeView Name="ServiceTree" Grid.Row="1" ItemsSource="{Binding Services}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            <Setter Property="FontWeight" Value="Normal" />
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="FontWeight" Value="Bold" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding}">
            <TextBlock Text="{Binding Name}"/>
            <HierarchicalDataTemplate.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Messages}"/>
                </DataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

树中没有输出任何内容。我已经尝试了几个关于分层数据绑定的教程,但我只是很难理解适合我具体情况的技术。

另外,应该如何设置数据上下文?我在视图的代码隐藏中设置上下文如下:

public partial class ServiceView : UserControl
{

    private readonly ServiceCollectionViewModel _serviceCollection = new ServiceCollectionViewModel();

    public ObservableCollection<ServiceModel> Services
    {
        get { return _serviceCollection.ServiceModels; }
    }

    public ServiceView()
    {
        InitializeComponent();

        DataContext = _serviceCollection;
        _serviceCollection.LoadServices();

    }
} 

【问题讨论】:

    标签: c# wpf data-binding treeview hierarchicaldatatemplate


    【解决方案1】:

    你似乎很困惑。您正在尝试将数据绑定到您的 ServiceView.Services 属性,那么 ServiceCollectionViewModel 类的用途是什么?视图模型的唯一目的是提供其视图所需的所有数据(和功能)

    现在我们可以走不同的路线......这一切都取决于你真正想要什么。如果要从UserControl 外部将数据绑定到ServiceView.Services 属性,则必须将Services 属性声明为DependencyProperty。在您的UserControl 中,您将使用RelativeSource Binding 将数据绑定到属性,如下所示:

    外面:

    <YourPrefix:ServiceView Services="{Binding YourExternalViewModelProperty}" />
    

    内部:

    <TreeView Name="ServiceTree" Grid.Row="1" ItemsSource="{Binding Services, 
        RelativeSource={RelativeSource AncestorType={x:Type YourPrefix:ServiceView}}}">
        ...
    </TreeView>
    

    使用此方法,无需将DataContext 设置为任何内容,因为我们使用RelativeSource 设置数据源。或者,您可以将DataContext 设置为内部UserControl 代码或视图模型的实例(从内部或外部),然后通常像这样进行数据绑定:

    <TreeView Name="ServiceTree" Grid.Row="1" ItemsSource="{Binding Services}">
        ...
    </TreeView>
    

    因此,回顾一下,如果您将DataContext 设置为对象的实例,那么您的Binding Paths 会查看该对象中的属性以自行解决。否则,如果您在Binding Path 中设置了RelativeSource(或ElementName),那么您将更改数据源仅限Binding,并且您的Binding Paths 应该是属性取而代之的是那些对象。


    更新>>>

    HierarchicalDataTemplate Class 基本上是对常规旧 DataTemplate 类的轻微扩展,可以认为是带有 ItemsSource 属性的 DataTemplate。因此,如果您可以定义DataTemplate 的内容,那么您可以定义HierarchicalDataTemplate 的内容...只需确保将HierarchicalDataTemplate.ItemsSource 属性设置为有效的集合属性...在您的情况下, Messages 属性:

    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type YourPrefix:ServiceModel}" 
            ItemsSource="{Binding Messages}"> <!-- Collection Property In ServiceModel -->
            <TextBlock Text="{Binding Name}" /> <!-- Property In ServiceModel -->
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
    

    如果您对DataTemplate 不太清楚,请查看MSDN 上的Data Binding Overview 页面。

    【讨论】:

    • HierarchicalDataTemplate 在 XAML 中应该是什么样子?我按照你所说的在这里选择 internal 路线。我正在获取服务名称,但没有一个 children 节点包含在 ServiceModel 中定义的消息 (ObservableCollection&lt;string&gt;)。
    【解决方案2】:

    这里有很多问题。

    1. ItemsSource 绑定应该是ServiceModels 而不是Services,因为这是ServiceCollectionViewModel 中的属性名称。所以这样你就可以获得顶级项目。

    2. 您正在尝试将项目样式内部绑定到 IsExpandedIsSelected 属性,但您的视图模型上没有此类属性。

    3. 在您的HierarchicalDataTemplate 中,您将ItemsSource 属性直接绑定到TreeViewItemDataContext,在本例中为ServiceModel。要么使 ServiceModel 实现 IEnumerable&lt;string&gt; 暴露您的消息,要么直接绑定到 Messages 属性:

    4. 第二层HierarchicalDataTemplateText属性绑定到Messages,但是这一层的DataContextString类型,没有Messages属性。所以在这里你应该绑定到数据上下文本身:

    关于数据上下文,您最好在 XAML 中使用静态资源对其进行初始化。此外,定义分层数据模板的更简洁的方法是使用基于项目类型的 implicit 模板。因为您真的不想为更多的层次结构级别(例如:4、5)内联定义层次结构模板。请参阅下面完全修复的 XAML:

    <Grid>
        <Grid.Resources>
            <!--Data context for the whole Grid-->
            <myNamespace:ServiceCollectionViewModel x:Key="MyViewModel" />
        </Grid.Resources>
        <TreeView Name="ServiceTree"
                  DataContext="{StaticResource MyViewModel}"
                  ItemsSource="{Binding ServiceModels}">
            <TreeView.Resources>
    
                <!--Implicit style for TreeViewItem, IsExpanded and IsSelected have no binding because 
                they are not used by view-models-->
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="FontWeight" Value="Normal" />
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="FontWeight" Value="Bold" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
    
                <!--Template for the first level items-->
                <HierarchicalDataTemplate DataType="{x:Type myNamespace:ServiceModel}" ItemsSource="{Binding Messages}">
                    <TextBlock Text="{Binding Name}"/>
                </HierarchicalDataTemplate>
    
                <!--Template for the second level, due to the fact that String is not hierarchical
                regular DataTemplate is enough-->
                <DataTemplate DataType="{x:Type system:String}">
                    <TextBlock Text="{Binding}"/>
                </DataTemplate>
    
            </TreeView.Resources>
        </TreeView>
    </Grid>
    

    我相信这个更易于理解,因此更易于维护。

    【讨论】:

    • 添加到 xaml 声明:xmlns:system="clr-namespace:System;assembly=mscorlib"
    • 我在这个例子中专注于正确的 TreeView 声明的本质,而不是提供一个成熟的 UserControl。如果您认为有必要,请随时编辑答案。
    • 在不得不将 ViewModels 和 Models 移动到与 Views 相同的项目后,树视图中仍然没有显示任何内容。否则,感谢您提供的信息,它仍然有助于找出其他一些技术。
    • 我完全使用了您在问题中显示的视图模型并且它有效。您如何填充 ServiceModels 集合?您是否在 XAML、调试输出中看到任何警告/错误?
    猜你喜欢
    • 1970-01-01
    • 2017-02-03
    • 2020-03-24
    • 2020-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-04
    相关资源
    最近更新 更多