选择TreeView项目时显示不同的UI元素



我是WPF的新手-我想为我的服务器创建一个测试仪我希望在应用程序的右侧有一个TreeView,每当用户选择一个节点时,右侧就会显示相应的项目。例如,我有一个Connection节点,它下面有许多Sessions节点,Connection和Session有不同的参数。我已经使用mvvm构建了树视图,一切都很好,但我如何才能实现第二个目标?

xaml

<Window x:Class="Tree1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:self ="clr-namespace:Tree1"
    xmlns:models ="clr-namespace:Tree1.Models"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type self:TestPlanViewModel}" ItemsSource="{Binding Connections}">
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type self:ConnectionViewModel}" ItemsSource="{Binding Sessions}">
        <StackPanel>
            <TextBlock Text="Connection" Margin="10, 0, 0,0"></TextBlock>
        </StackPanel>
    </HierarchicalDataTemplate>
    <DataTemplate DataType="{x:Type self:SessionViewModel}">
        <StackPanel Orientation="Horizontal">
            <TextBlock  Margin="10,0,0,0" Text="Session"></TextBlock>
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<Grid Margin="10">
    <Button Height="23" VerticalAlignment="Top" Margin="277,10,144,0" Name="addSessionBtn" Width="76"></Button>
    <TreeView Name="testPlanTview" Margin="0,10,283,0" SelectedItemChanged="testPlanTview_SelectedItemChanged">
        <TreeViewItem ItemsSource="{Binding Connections}" Header="Test Plan">
        </TreeViewItem>
    </TreeView>
</Grid>

您可以使用DataTemplates。你的问题有两种可能的解决办法。

首先,让我们创建一个基类:

public abstract class SelectableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private bool isSelected;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public bool IsSelected
    {
        get
        {
            return isSelected;
        }
        set
        {
            if (isSelected != value)
            {
                isSelected = value;
                OnPropertyChanged("IsSelected");
            }
        }
    }
}

对于我们的例子,我们有两个类:

  • 汽车,其属性为"Hp"(int)和"入学"(DateTime)
  • 人员,其属性为"姓名"(字符串)和"姓氏"(字符串

二者都扩展了CCD_ 1。现在ViewModel(当然它只是一个示例):

public class ViewModel : SelectableObject
{
    private ArrayList tree = new ArrayList();
    private ObjectWrapper current;
    private Car car = new Car();
    private Person person = new Person();
    public ViewModel()
    {
        car.Hp = 120;
        car.Matriculation = DateTime.Today;
        car.PropertyChanged += new PropertyChangedEventHandler(OnItemPropertyChanged);
        person.Name = "John";
        person.Surname = "Doe";
        person.PropertyChanged += new PropertyChangedEventHandler(OnItemPropertyChanged);
        tree.Add(car);
        tree.Add(person);
    }
    private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        SelectableObject impl = (SelectableObject)sender;
        if (e.PropertyName == "IsSelected" && impl.IsSelected)
        {
            Current = new ObjectWrapper(impl);
        }
    }
    public ObjectWrapper Current
    {
        get
        {
            return current;
        }
        private set
        {
            current = value;
            OnPropertyChanged("Current");
        }
    }
    public IEnumerable Tree
    {
        get
        {
            return tree;
        }
    }
}

ViewModel使用ObjectWrapper类:

public class ObjectWrapper
{
    private readonly object wrappedInstance;
    private readonly ReadOnlyCollection<PropertyWrapper> propertyWrappers;
    public ObjectWrapper(object instance)
    {
        Collection<PropertyWrapper> collection = new Collection<PropertyWrapper>();
        Type instanceType;
        wrappedInstance = instance;
        instanceType = instance.GetType();
        foreach (PropertyInfo propertyInfo in instanceType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance))
        {
            collection.Add(new PropertyWrapper(instance, propertyInfo));
        }
        propertyWrappers = new ReadOnlyCollection<PropertyWrapper>(collection);
    }
    public ReadOnlyCollection<PropertyWrapper> PropertyWrappers
    {
        get
        {
            return propertyWrappers;
        }
    }
    public object Instance { get { return wrappedInstance; } }
}
public class PropertyWrapper
{
    private readonly object instance;
    private readonly PropertyInfo propertyInfo;
    public PropertyWrapper(object instance, PropertyInfo propertyInfo)
    {
        this.instance = instance;
        this.propertyInfo = propertyInfo;
    }
    public string Label
    {
        get
        {
            return propertyInfo.Name;
        }
    }
    public Type PropertyType
    {
        get
        {
            return propertyInfo.PropertyType;
        }
    }
    public object Value
    {
        get
        {
            return propertyInfo.GetValue(instance, null);
        }
        set
        {
            propertyInfo.SetValue(instance, value, null);
        }
    }
}

第一个解决方案(最简单的解决方案)您可以使用隐式数据模板:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="600">
    <DockPanel>
        <TreeView ItemsSource="{Binding Tree}" DockPanel.Dock="Left" Margin="5">
            <TreeView.ItemContainerStyle>
                <Style BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                </Style>
            </TreeView.ItemContainerStyle>
        </TreeView>

        <ContentControl Content="{Binding Path=Current.Instance, Mode=OneWay}" Margin="5">
            <ContentControl.Resources>
                <DataTemplate DataType="{x:Type local:Car}">
                    ... define your car template here ...
                </DataTemplate>
                <DataTemplate DataType="{x:Type local:Person}">
                    ... define your person template here ...
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </DockPanel>
</Window>

第二个解决方案(imho是最好的解决方案)您可以通过使用ItemsControl(这里我使用了DateTime和Int控件的Extended WPF Toolkit)来利用ObjectWrapper对象:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="600">
    <Window.Resources>
        <local:ItemTemplateSelector x:Key="ItemTemplateSelector" />
        <DataTemplate x:Key="{x:Type sys:String}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
                <TextBox Text="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="{x:Type sys:DateTime}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
                <toolkit:DateTimePicker Value="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="{x:Type sys:Int32}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=Label, Mode=OneWay}" VerticalAlignment="Center" />
                <toolkit:IntegerUpDown Value="{Binding Path=Value, Mode=TwoWay}" Margin="5" />
            </StackPanel>
        </DataTemplate>

    </Window.Resources>
    <DockPanel>
        <TreeView ItemsSource="{Binding Tree}" DockPanel.Dock="Left" Margin="5">
            <TreeView.ItemContainerStyle>
                <Style BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                </Style>
            </TreeView.ItemContainerStyle>
        </TreeView>
        <ItemsControl ItemsSource="{Binding Path=Current.PropertyWrappers, Mode=OneWay}"
                      Margin="5" ItemTemplateSelector="{StaticResource ItemTemplateSelector}" />
    </DockPanel>
</Window>

ItemTemplateSelector的实现并不困难:

public class ItemTemplateSelector : DataTemplateSelector
{
    public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
    {
        PropertyWrapper propertyWrapper = (PropertyWrapper)item;
        FrameworkElement frameworkElement = (FrameworkElement)container;
        DataTemplate dataTemplate = (DataTemplate)frameworkElement.TryFindResource(propertyWrapper.PropertyType);
        return dataTemplate;
    }
}

我的答案很长,但我想向你展示你可以使用的两条路。当然,您可以通过使用属性来改进PropertyWrapper

如果您已经在使用MVVM,我建议使用System.Windows.Interactivity和Prism区域来实现您需要的功能。
例如:Xaml:

 <TreeView Name="testPlanTview" Margin="0,10,283,0">
    <TreeViewItem ItemsSource="{Binding Connections}" Header="Test Plan">
   <i:Interaction.Triggers>
         <i:EventTrigger EventName="SelectedItemChanged">
             <i:InvokeCommandAction Command="{Binding OpenNewViewCommand}" 
              CommandParameter="{Binding SelectedItem,ElementName=testPlanTview}"/>
         </i:EventTrigger>
   </i:Interaction.Triggers>
</TreeViewItem>
</TreeView>
  <ContentControl prism:RegionManager.RegionName="MyRegion"/>

视图模型:

 public ICommand OpenNewViewCommand
        {
            get { return this.selectedCommand; }
        }

在视图模型构造函数中添加:

  this.selectedCommand = new DelegateCommand<YourModel>(this.SelectedExecute);

命令:

  private void SelectedExecute(YourModel parameter)
{
 this.regionManager.RequestNavigate(RegionNames.MyRegion, new Uri("ViewToNavigate", UriKind.Relative), parameters);
}


请注意,这只是关于如何使用棱镜实现导航的一个示例
有关我建议的更多信息,您可以在此处查看msdn链接

最新更新