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

时间:2015-03-20 13:26:15

标签: c# wpf mvvm treeview

我是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>

2 个答案:

答案 0 :(得分:1)

您可以使用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");
            }
        }
    }
}

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

  • Car,其属性为“Hp”(int)和“Matriculation”(DateTime)
  • 人物,其属性为“姓名”(字符串)和“姓氏”(字符串)

它们都延伸SelectableObject。 现在是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);
        }
    }
}

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

<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 the best one) 你可以利用ObjectWrapper对象,使用ItemsControl(这里我使用Extended WPF Toolkit作为DateTime和Int控件):

<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

答案 1 :(得分:0)

如果你已经在使用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链接here