C# 用户控制依赖于TreeView';s(WPF)选择编辑项
我有两个用户控件,一个包含C# 用户控制依赖于TreeView';s(WPF)选择编辑项,c#,wpf,mvvm,treeview,selecteditem,C#,Wpf,Mvvm,Treeview,Selecteditem,我有两个用户控件,一个包含TreeView,一个包含ListView TreeView有一个itemsource和分层数据模板,用于填充节点和叶(node=TvShow,leaf=seasure) 列表视图应显示所选TreeView项目的子项(即所选季节):该季节的剧集 当我在同一个窗口中定义了TreeView和Listview时,这一切都很好,我可以使用如下内容: <ListView x:Name="_listViewEpisodes" Grid.Column="2"
TreeView
,一个包含ListView
TreeView
有一个itemsource和分层数据模板,用于填充节点和叶(node=TvShow,leaf=seasure)
列表视图
应显示所选TreeView项目的子项(即所选季节):该季节的剧集
当我在同一个窗口中定义了TreeView和Listview时,这一切都很好,我可以使用如下内容:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
<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>
当两个控件都在单独的用户控件中定义时,如何实现这一点?(因为在一个用户控件的上下文中,我错过了另一个用户控件的上下文)
这似乎是一件非常基本的事情,我感到沮丧,因为我自己无法解决它。我拒绝用代码隐藏来解决这个问题,到目前为止,我有一个非常干净的MVVM项目,我希望保持这种方式
希望有人能给我一些建议 在ViewModel中创建一个
SelectedSeason
属性,并将ListView的ItemsSource
绑定到SelectedSeason.Spices
在一个完美的世界中,您现在可以在树视图中使用双向绑定,在SelectedItem
更改时自动更新此属性。但是,TreeView的SelectedItem
属性是只读的,无法绑定。您只需使用少量代码,就可以为TreeView的SelectionChanged
事件创建一个事件处理程序,以更新ViewModel的SelectedSeason
。我认为这不会违反MVVM原则
如果您想要一个纯XAML解决方案,那就看一看。首先,您必须在ViewModel中创建SelectedValue项目,并将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);
}
}
想法很简单:您有两个附加属性,源和目标。当第一个更改时,将调用PropertyChangedCallback,您只需将NewValue设置为目标属性值。在我看来,当您需要在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;
}
公共类视图模型:INotifyPropertyChanged{
公共视图模型(){
this.values=新的ObservableCollection()
{
“第一”,
“第二”,
“第三”
};
}
可观察收集值;
字符串selectedValue;
公共ObservableCollection值{get{return Values;}}
公共字符串SelectedValue{
获取{返回selectedValue;}
设置{
如果(等于(选择的值、值))
返回;
selectedValue=值;
if(PropertyChanged==null)
返回;
PropertyChanged(这是新的PropertyChangedEventArgs(“SelectedValue”);
}
}
公共事件属性更改事件处理程序属性更改;
}
所以,我们有数据源,选择值,我们将像这样绑定它:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
<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——我认为它与“无代码隐藏”不同。在我的示例中,ViewModel仍然对您的视图一无所知,如果您使用另一个控件来显示您的数据,例如ListBox,您将能够使用简单的双向绑定,“BindingWrapper”助手将不会使您的代码不可读或不可移植或其他任何内容。您将其标记为MVVM,因此,将这两个控件绑定到VM上的SelectedEpisodes属性。正确的方向是将这两个控件(treeview和listview)绑定到同一个viewmodel。然而,我想知道如何将
TreeView
的SelectedItem
绑定到viewmodel。正如我看到的,TreeView的SelectedItem
是只读的。除非将viewmodel实现为一个DependencyObject
,以支持一些SelectedItem
bindable属性。但是那样的话,我们仍然需要一些代码。使用codebehind时,我们还可以处理TreeView
的SelectedItemChanged
事件,并相应地更新viewmodel的SelectedItem
。@KingKing这种情况下的最佳解决方案是创建一个包装器对象,其中两个附加属性相互绑定。因此,我们将第一个道具绑定到SelectedItem,第二个道具绑定到viewmodel@Alexis看起来你是个专家。不确定。我在上面尝试的解决方案实际上可以正常工作,尽管它需要一些代码,因为TreeView的SelectedItem
是只读的。如果可能的话,你应该发布这个问题的答案。甚至定义一个包装器也确实是在背后使用代码。事实上,我们无法避免代码落后,我们可以尽量避免使用它。@Alexis,您能不能添加一个示例,说明带有2个附加属性的包装器如何解决我的问题?也许我的“无代码落后”政策会走得更远,但每次我继续努力实现这一目标,我最终会得到真正干净的代码,所以我仍然充满希望。提前谢谢!