我正忙着在WPF中创建我的第一个MVVM应用程序。
基本上我遇到的问题是我有一个TreeView(System.Windows.Controls.TreeView),它放在我的WPF窗口上,我已经决定将绑定到CommandViewModel项的ReadOnlyCollection,以及这些项目由DisplayString,Tag和RelayCommand组成。
现在在XAML中,我有了TreeView,并且我已成功将ReadOnlyCollection绑定到此。我可以查看这个,在UI中看起来一切都很好。
现在的问题是我需要将RelayCommand绑定到TreeViewItem的Command,但是从我看到的TreeViewItem没有Command。这是否迫使我在IsSelected属性中甚至在TreeView_SelectedItemChanged方法后面的代码中执行此操作,还是有办法在WPF中神奇地执行此操作?
这是我的代码:
<TreeView BorderBrush="{x:Null}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TreeView.Items>
<TreeViewItem
Header="New Commands"
ItemsSource="{Binding Commands}"
DisplayMemberPath="DisplayName"
IsExpanded="True">
</TreeViewItem>
</TreeView.Items>
理想情况下,我很想去:
<TreeView BorderBrush="{x:Null}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TreeView.Items>
<TreeViewItem
Header="New Trade"
ItemsSource="{Binding Commands}"
DisplayMemberPath="DisplayName"
IsExpanded="True"
Command="{Binding Path=Command}">
</TreeViewItem>
</TreeView.Items>
是否有人拥有允许我使用我所拥有的RelayCommand基础设施的解决方案。
谢谢你们,非常感谢!
理查德
答案 0 :(得分:25)
我知道这是前一段时间的“回答”,但由于答案并不理想,我想我会投入两分钱。我使用一种方法,允许我不必诉诸任何“样式按钮技巧”,甚至使用代码隐藏,而是保持我在MVVM中的所有分离。在TreeView中添加以下xaml:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding TreeviewSelectedItemChanged}" CommandParameter="{Binding ElementName=treeView, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
在你的xaml标题中添加:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
然后你必须在你的项目中添加对上面程序集的引用。
之后,所有内容的行为与任何其他命令相同,例如按钮或其他内容。
答案 1 :(得分:2)
感谢您对该问题的投入,是的,我确实说过我不想要一个代码背后的解决方案,但当时我仍然非常认为我只是遗漏了一些东西...所以我最终使用TreeView_SelectedItemChanged事件。
尽管Will的方法似乎是一个很好的解决方案,但就个人情况而言,我决定使用背后的代码。这样做的原因是,如果TreeViewItem具有可以绑定我的Command的“Command”属性,则View和XAML将保持不变。现在我不必更改模板或视图,我所要做的就是为TreeView_SelectedItemChanged添加代码和事件。
我的解决方案:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (sender != null)
{
var treeView = sender as TreeView;
if (treeView != null)
{
var commandViewModel = treeView.SelectedItem as CommandViewModel;
if (commandViewModel != null)
{
var mi = commandViewModel.Command.GetType().GetMethod("Execute");
mi.Invoke(commandViewModel.Command, new Object[] {null});
}
}
}
}
由于我已经将RelayCommand附加到TreeViewItem,我现在所做的只是在该特定的RelayCommand上手动调用“Execute”方法。
如果这是完全错误的做法,请告诉我......
谢谢!
答案 2 :(得分:1)
我要做的是将TreeViewItem的the Header设置为按钮,然后为按钮设置外观,使其看起来不像一个按钮,然后对按钮执行命令绑定。
您可能需要通过DataTemplate执行此操作,或者您可能需要更改TreeViewItem本身的模板。从来没有做过,但这就是我做类似的事情(比如标签页标题)。
这是我正在谈论的一个例子(你可以把它放在Kaxaml中并玩弄它):
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="ClearButan" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="border"
Padding="4"
Background="transparent">
<Grid >
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center">
</ContentPresenter>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<TreeView>
<TreeViewItem>
<Button Style="{StaticResource ClearButan}">
easy peasy
</Button>
</TreeViewItem>
</TreeView>
</Grid>
</Page>
我为按钮创建了一个新的清晰样式。然后我在TVI中放下一个按钮并设置其样式。当然,你可以使用数据模板做同样的事情。
答案 3 :(得分:0)
这是一个很好的例子,说明MVVM在WPF中是如何经过深思熟虑的。你希望有某些gui项目的Command支持,但是没有,所以你被迫通过一个精心设计的过程(如Will的例子所示)只是为了得到一个附加到某个东西的命令。
让我们希望他们在WPF 2.0中解决这个问题: - )
答案 4 :(得分:0)
我通过通用的Tag属性从Richard改进了好的解决方案:
MyView.xaml:
<TreeView SelectedItemChanged="TreeView_SelectedItemChanged" Tag="{Binding SelectTreeViewCommand}" >
<TreeViewItem Header="Item1" IsExpanded="True" Tag="Item1" />
<TreeViewItem Header="Item2" IsExpanded="True">
<TreeViewItem Header="Item21" Tag="Item21"/>
</TreeViewItem>
</TreeView>
MyView.xaml.cs
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var treeView = (TreeView)sender;
var command = (ICommand)treeView.Tag;
TreeViewItem selectedItem = (TreeViewItem)treeView.SelectedItem;
if (selectedItem.Tag != null)
{
command.Execute(selectedItem.Tag);
}
}
MyViewModel.cs
public RelayCommand selectTreeViewCommand;
[Bindable(true)]
public RelayCommand SelectTreeViewCommand => selectTreeViewCommand ?? (selectTreeViewCommand = new RelayCommand(CanSelectTreeViewCommand, ExecuteSelectTreeViewCommand));
private void ExecuteSelectTreeViewCommand(object obj)
{
Console.WriteLine(obj);
}
private bool CanSelectTreeViewCommand(object obj)
{
return true;
}
答案 5 :(得分:0)
Shaggy13spe提供的答案非常好。但是,我还是花了一些时间来理解它,所以我将扩展答案。
整个TreeView xaml看起来像这样:
<TreeView x:Name="treeView" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Tree}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding FilterMeetingsCommand}" CommandParameter="{Binding ElementName=treeView, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Id}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
在我的视图中,我有一个树集合
public ObservableCollection<TreeNode> Tree { get; set; }
TreeNode 被定义为一个简单的类:
public class TreeNode
{
public int Id { get; set; }
public string Name { get; set; }
public List<TreeNode> Nodes { get; set; }
public TreeNode(string name)
{
this.Name = name;
this.Nodes = new List<TreeNode>();
}
}
第一个要点: CommandParameter 未绑定到ViewModel的属性,但已传递给该方法。因此该方法应如下所示:
private async void FilterMeeting(object parameter){}
第二个要点:如果您传递选定的项目(在我的情况下,对象将为 TreeNode 类型),并且您将具有层次结构,则将遇到事件冒泡。因此,选择一个项目将为该特定项目和所有父项触发事件。要解决此问题,您需要了解,您只能将一个对象传递给 ViewModel 中的方法(在标准事件处理程序中不能传递两个对象),并且该对象必须是一个事件。
在这种情况下,将XAML更改为以下内容( PassEventArgsToCommand =“ True” 在这里很重要)
<TreeView x:Name="treeView" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Tree}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding FilterMeetingsCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Id}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
然后,在您的处理方法中,您将不会收到模型对象,但会收到事件args,其中包含模型对象。