UserControl依赖于TreeView的(WPF)SelectedItem

时间:2014-10-12 09:25:05

标签: c# wpf mvvm treeview selecteditem

我有两个用户控件,一个包含TreeView,一个包含ListView

TreeView有一个itemsource和分层数据模板,用于填充节点和leafes(node = TvShow,leaf = Season)。

ListView应显示所选TreeView项目的子项(因此,所选季节):该季节的剧集。

当我在同一个窗口中定义了TreeView和Listview时,这很好用,我可以使用这样的东西:

<ListView
    x:Name="_listViewEpisodes"
    Grid.Column="2"
    ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">

如果在单独的用户控件中定义了两个控件,我该如何实现? (因为在一个用户控件的上下文中,我想念其他用户控件的上下文)

这似乎是非常基本的东西,我感到很沮丧,我自己无法弄明白。我拒绝用代码隐藏解决这个问题,到目前为止我有一个非常干净的MVVM项目,我想保持这种方式。

希望有人能给我一些建议!

2 个答案:

答案 0 :(得分:2)

首先,您必须在ViewModel中创建SelectedValue proeprty并将TreeView.SelectedItem属性绑定到它。由于SelectedItem属性是只读的,我建议您创建一个帮助程序来创建类似OneWayToSource的绑定。代码应如下所示:

    public class BindingWrapper {
    public static object GetSource(DependencyObject obj) { return (object)obj.GetValue(SourceProperty); }
    public static void SetSource(DependencyObject obj, object value) { obj.SetValue(SourceProperty, value); }
    public static object GetTarget(DependencyObject obj) { return (object)obj.GetValue(TargetProperty); }

    public static void SetTarget(DependencyObject obj, object value) { obj.SetValue(TargetProperty, value); }
    public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null));
    public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null, OnSourceChanged));

    static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        SetTarget(d, e.NewValue);
    }
}

这个想法很简单:你有两个附加属性,Source和Target。当第一个更改时,将调用PropertyChangedCallback,您只需将NewValue设置为Target属性值。在我看来,当您需要在XAML中绑定只读属性时(特别是在控件模板中),这种情况在很多情况下都很有用。

我已经创建了一个简单的模型来演示如何使用这个帮助器:

  public class ViewModel : INotifyPropertyChanged {
    public ViewModel() {
        this.values = new ObservableCollection<string>()
        {
            "first",
            "second",
            "third"
        };
    }
    ObservableCollection<string> values;
    string selectedValue;
    public ObservableCollection<string> Values { get { return values; } }        

    public string SelectedValue {
        get { return selectedValue; }
        set {
            if (Equals(selectedValue, values))
                return;
            selectedValue = value;
            if (PropertyChanged == null)
                return;
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

所以,我们有数据源,选择的值,我们将这样绑定它:

    <StackPanel>
    <TreeView ItemsSource="{Binding Values}" 
              local:BindingWrapper.Source="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Mode=OneWay}"
              local:BindingWrapper.Target="{Binding SelectedValue, Mode=OneWayToSource}"
              >
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="Header" Value="{Binding}"/>
            </Style>
        </TreeView.ItemContainerStyle>
    </TreeView>
    <TextBlock Text="{Binding SelectedValue}"/>
</StackPanel>

在从ViewModel绑定到ItemsSource的TreeView中,我创建了两个绑定,因此它们正在更改ViewModel中的SelectedValue属性。示例末尾的TextBlock仅用于表明此方法有效。

关于非常干净的MVVM - 我认为它与&#34;没有代码隐藏&#34;不同。在我的示例中,ViewModel仍然不了解您的视图,以及您是否会使用其他控件来显示您的数据,例如ListBox你将能够使用简单的双向绑定和&#34; BindingWrapper&#34;帮助程序不会使您的代码不可读或不可移植或其他任何东西。

答案 1 :(得分:0)

在ViewModel中创建一个SelectedSeason属性,并将ListView的ItemsSource绑定到SelectedSeason.Episodes

在一个完美的世界中,您现在可以在TreeView中使用双向绑定,以便在SelectedItem更改时自动更新此属性。但是,TreeView的SelectedItem属性是只读的,不能绑定。您可以使用一点代码隐藏,并为TreeView的SelectionChanged事件创建一个事件处理程序,以更新ViewModel的SelectedSeason。恕我直言,这并没有违反MVVM原则。

如果您需要纯XAML解决方案,请查看this answer