我使用MVVM模式
获得了一个像这样的DataTemplate的ListView<ListView ItemsSource="{Binding Source}"
IsItemClickEnabled="True"
commands:ItemsClickCommand.Command="{Binding ItemClickedCommand}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding A}" />
<Button Content="{Binding B}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ItemsClickCommand以这种方式定义
public static class ItemsClickCommand
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(BindableCommand), typeof(ItemsClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, BindableCommand value)
{
d.SetValue(CommandProperty, value);
}
public static BindableCommand GetCommand(DependencyObject d)
{
return (BindableCommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
control.ItemClick += OnItemClick;
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.OriginalSource))
command.ExecuteWithMoreParameters(e.OriginalSource, e.ClickedItem);
}
}
我问的是如何知道用户是否点击了TextBlock或Button。 我尝试在ViewModel中以这种方式处理ItemClickCommand事件以搜索VisualTree中的控件(这是最好的解决方案吗?),但是对DependencyObject的强制转换不起作用(返回始终为null)
public void ItemClicked(object originalSource, object clickedItem)
{
var source = originalSourceas DependencyObject;
if (source == null)
return;
}
答案 0 :(得分:3)
有一些解决方案可以想到
解决方案1
<ListView
x:Name="parent"
ItemsSource="{Binding Source}"
IsItemClickEnabled="True"
Margin="20">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding A}" />
<Button
Content="{Binding B}"
Command="{Binding DataContext.BCommand, ElementName=parent}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
注意ListView如何将名称设置为“parent”,其属性为:x:Name="parent"
以及按钮命令的绑定如何使用该属性。另请注意,该命令将提供一个参数,该参数是对单击元素的数据源的引用。
此页面的视图模型如下所示:
public class MainViewModel : MvxViewModel
{
public ObservableCollection<MySource> Source { get; private set; }
public MvxCommand<MySource> BCommand { get; private set; }
public MainViewModel()
{
Source = new ObservableCollection<MySource>()
{
new MySource("e1", "b1"),
new MySource("e2", "b2"),
new MySource("e3", "b3"),
};
BCommand = new MvxCommand<MySource>(ExecuteBCommand);
}
private void ExecuteBCommand(MySource source)
{
Debug.WriteLine("ExecuteBCommand. Source: A={0}, B={1}", source.A, source.B);
}
}
'MvxCommand'只是ICommand的一个特定实现。我使用MvvMCross作为我的示例代码,但您不必这样做 - 您可以使用您需要的任何MvvM实现。
如果处理命令的责任在于包含列表的页面的视图模型,则此解决方案是合适的。
解决方案2
在视图模型中处理包含列表的页面的命令可能并不总是合适的。您可能希望在更接近被单击元素的代码中移动该逻辑。在这种情况下,在其自己的用户控件中隔离元素的数据模板,创建与该用户控件背后的逻辑对应的视图模型类,并在该视图模型中实现该命令。以下是代码的外观:
ListView的XAML:
<ListView
ItemsSource="{Binding Source}"
IsItemClickEnabled="True"
Margin="20">
<ListView.ItemTemplate>
<DataTemplate>
<uc:MyElement DataContext="{Binding Converter={StaticResource MySourceToMyElementViewModelConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
表示一个元素的用户控件的XAML:
<Grid>
<StackPanel>
<TextBlock Text="{Binding Source.A}" />
<Button Content="{Binding Source.B}" Command="{Binding BCommand}" />
</StackPanel>
</Grid>
MySourceToMyElementViewModelConverter的源代码:
public class MySourceToMyElementViewModelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return new MyElementViewModel((MySource)value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
主页的视图模型:
public class MainViewModel : MvxViewModel
{
public ObservableCollection<MySource> Source { get; private set; }
public MainViewModel()
{
Source = new ObservableCollection<MySource>()
{
new MySource("e1", "b1"),
new MySource("e2", "b2"),
new MySource("e3", "b3"),
};
}
}
表示列表中一个元素的用户控件的视图模型:
public class MyElementViewModel : MvxViewModel
{
public MySource Source { get; private set; }
public MvxCommand BCommand { get; private set; }
public MyElementViewModel(MySource source)
{
Source = source;
BCommand = new MvxCommand(ExecuteBCommand);
}
private void ExecuteBCommand()
{
Debug.WriteLine("ExecuteBCommand. Source: A={0}, B={1}", Source.A, Source.B);
}
}
解决方案3
您的示例假定主页面的视图模型公开了数据模型元素的列表。像这样:
public ObservableCollection<MySource> Source { get; private set; }
可以更改主页面的视图模型,以便它显示视图模型元素列表。像这样:
public ObservableCollection<MyElementViewModel> ElementViewModelList { get; private set; }
ElementViewModelList
中的每个元素都对应Source
中的元素。如果Source
的内容在运行时更改,则此解决方案可能会稍微复杂一些。主页面的视图模型需要观察Source
并相应地更改ElementViewModelList
。更进一步,你可能想要抽象集合映射器的概念(类似于ICollectionView)并提供一些通用代码。
对于此解决方案,XAML将如下所示:
<ListView
ItemsSource="{Binding ElementViewModelList}"
IsItemClickEnabled="True"
Margin="20">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding A}" />
<Button Content="{Binding B}" Command="{Binding BCommand}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
解决方案1,2和3的注释
我看到你的原始样本不是与元素内部的按钮相关联,而是与整个元素相关联。这提出了一个问题:你要用内部按钮做什么?您是否会遇到用户可以单击元素或内部按钮的情况?就UI / UX而言,这可能不是最好的解决方案。请注意这一点。就像练习一样,为了更接近原始样本,如果您想将命令与整个元素联系起来,可以执行以下操作。
将整个元素包裹在具有自定义样式的按钮中。该样式将修改视觉处理点击的方式。最简单的形式是让点击不会产生任何视觉效果。此更改应用于 Solution 1 (它可以轻松应用于 Solution 2 和 Solution 3 )看起来像这样:
<ListView
x:Name="parent"
ItemsSource="{Binding Source}"
IsItemClickEnabled="True"
Margin="20">
<ListView.ItemTemplate>
<DataTemplate>
<Button
Command="{Binding DataContext.BCommand, ElementName=parent}"
CommandParameter="{Binding}"
Style="{StaticResource NoVisualEffectButtonStyle}">
<StackPanel>
<TextBlock Text="{Binding A}" />
<TextBlock Text="{Binding B}" />
</StackPanel>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
在这种情况下,您必须编写NoVisualEffectButtonStyle,但这是一项简单的任务。您还需要确定要与内部按钮关联的命令类型(否则为什么会有内部按钮)。或者,您更有可能将内部按钮转换为文本框等内容。
解决方案4
使用行为。
首先,添加对“Behaviors SDK”的引用。。然后修改您的XAML代码:
...
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
...
<Grid>
<ListView ItemsSource="{Binding Source}" IsItemClickEnabled="True" Margin="20">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ItemClick">
<core:InvokeCommandAction
Command="{Binding BCommand}"
InputConverter="{StaticResource ItemClickedToMySourceConverter}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding A}" />
<TextBlock Text="{Binding B}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
ItemClickedToMySourceConverter只是一个正常值转换器:
public class ItemClickedToMySourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (MySource)(((ItemClickEventArgs)value).ClickedItem);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
视图模型如下所示:
public class Main4ViewModel : MvxViewModel
{
public ObservableCollection<MySource> Source { get; private set; }
public MvxCommand<MySource> BCommand { get; private set; }
public Main4ViewModel()
{
Source = new ObservableCollection<MySource>()
{
new MySource("e1", "b1"),
new MySource("e2", "b2"),
new MySource("e3", "b3"),
};
BCommand = new MvxCommand<MySource>(ExecuteBCommand);
}
private void ExecuteBCommand(MySource source)
{
Debug.WriteLine("ExecuteBCommand. Source: A={0}, B={1}", source.A, source.B);
}
}