如何知道ListView中哪个元素被点击?

时间:2015-10-01 07:45:12

标签: c# xaml mvvm binding windows-phone-8.1

我使用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;            
    }

1 个答案:

答案 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”的引用。adding a reference to 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);
    }
}