UWP ListView 项目上下文菜单

UWP ListView Item context menu

我正在互联网上搜索如何为 ListView 添加上下文菜单。到目前为止,我已经找到了一个实际显示上下文的

<ListView>
    ...
    RightTapped="ContactsListView_RightTapped" >
    ...
    <ListView.Resources>
        <MenuFlyout x:Name="allContactsMenuFlyout">
            <MenuFlyout.Items>
                <MenuFlyoutItem x:Name="Edit"  Text="Edit"/>
                <MenuFlyoutItem x:Name="Remove" Text="Remove"    Click="Remove_Click"/>
            </MenuFlyout.Items>
        </MenuFlyout>
    </ListView.Resources>
    ...
</ListView>

private void ContactsListView_RightTapped(object sender, RightTappedRoutedEventArgs e) {
    ListView listView = (ListView)sender;
    allContactsMenuFlyout.ShowAt(listView, e.GetPosition(listView));
}

private void Remove_Click(object sender, RoutedEventArgs e) {

}

问题是我无法获取显示上下文菜单的项目。另一个问题是上下文菜单也显示在列表视图项目之外(例如在边框上)。由于触发的事件是 RightTapped,我不确定在移动设备上长按时是否会显示上下文菜单。我无法对其进行测试,因为我的模拟器目前无法正常工作。因为它应该是通用的 windows 应用程序,所以我期待有一些非常简单有效的方法来为 ListView 项目创建上下文菜单。

使用 Command 而不是 Click 事件。您可以在 CommandParameter

中传递点击的项目
<MenuFlyout x:Name="allContactsMenuFlyout">
                <MenuFlyout.Items>
                    <MenuFlyoutItem x:Name="Edit"  Text="Edit"/>
                    <MenuFlyoutItem x:Name="Remove" Text="Remove" Command="{Binding Path=DeleteItemTappedCommand}" CommandParameter="{Binding ElementName=ArchivedMessages_ListView, Path=SelectedItem}"/>
                </MenuFlyout.Items>
   </MenuFlyout>

在您的 ViewModel 中

public DelegateCommand<object> DeleteItemTappedCommand { get; set; }

 public YourViewModel()
 {
     DeleteItemTappedCommand = new DelegateCommand<object>(DeleteItemClicked);
 }

 private void DeleteItemClicked(object obj)
 {
     // adjust object type to your templated source type
 }

CommunityToolkit.MVVM 用户:

[ICommand]
private void DeleteItemClicked(object obj)
{
    // adjust object type to your templated source type
}

The problem is I'm not able to get item on which the context menu was displayed.

对于这个问题,如果你像这样向ListView添加数据:

<ListView RightTapped="ListView_RightTapped">
    <x:String>First Item</x:String>
    <x:String>Second Item</x:String>
    <x:String>Third Item</x:String>
    <x:String>Fourth Item</x:String>

    <ListView.Resources>
        <MenuFlyout x:Name="allContactsMenuFlyout">
            <MenuFlyout.Items>
                <MenuFlyoutItem x:Name="Edit"  Text="Edit" />
                <MenuFlyoutItem x:Name="Remove" Text="Remove"    Click="Remove_Click" />
            </MenuFlyout.Items>
        </MenuFlyout>
    </ListView.Resources>
</ListView>

您可以像这样在 RightTapped 事件中获取项目的上下文:

private void ListView_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    ListView listView = (ListView)sender;
    allContactsMenuFlyout.ShowAt(listView, e.GetPosition(listView));
    var a = ((FrameworkElement)e.OriginalSource).DataContext;
}

这种场景下,"a"会直接获取点击item的字符串格式内容

如果您像这样使用 DataTemplate 将数据添加到 ListView

<ListView RightTapped="ListView_RightTapped" ItemsSource="{x:Bind list}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding text}" />
        </DataTemplate>
    </ListView.ItemTemplate>
    <ListView.Resources>
        <MenuFlyout x:Name="allContactsMenuFlyout">
            <MenuFlyout.Items>
                <MenuFlyoutItem x:Name="Edit"  Text="Edit" />
                <MenuFlyoutItem x:Name="Remove" Text="Remove"    Click="Remove_Click" />
            </MenuFlyout.Items>
        </MenuFlyout>
    </ListView.Resources>
</ListView>

通常在使用DataTemplate时,我们通过ObservableCollection添加数据,像这样:

private ObservableCollection<List> list = new ObservableCollection<List>();

public MainPage()
{
    this.InitializeComponent();
    list.Clear();
    list.Add(new List { text = "Item 1" });
    list.Add(new List { text = "Item 2" });
    list.Add(new List { text = "Item 3" });
    list.Add(new List { text = "Item 4" });
    list.Add(new List { text = "Item 5" });
}

"List" class这里很简单,测试一下:

public class List
{
    public string text { get; set; }
}

然后我们也可以在RightTapped事件中得到DataContext

private void ListView_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    ListView listView = (ListView)sender;
    allContactsMenuFlyout.ShowAt(listView, e.GetPosition(listView));
    var a = ((FrameworkElement)e.OriginalSource).DataContext;
}

但是这次"a"实际上是item里面的'List'对象(请参考"List"class),因为item的内容是现在是 'List' 对象,不再是字符串。所以我们可以这样得到这个对象的文本属性:

private void ListView_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    ListView listView = (ListView)sender;
    allContactsMenuFlyout.ShowAt(listView, e.GetPosition(listView));
    var a = ((FrameworkElement)e.OriginalSource).DataContext as List;
    var content = a.text;
}

我认为最终你想编辑 Flyout 的 Button click 事件中的内容,你可以这样做,例如:

private string content;

private void ListView_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    ListView listView = (ListView)sender;
    allContactsMenuFlyout.ShowAt(listView, e.GetPosition(listView));
    var a = ((FrameworkElement)e.OriginalSource).DataContext as List;
    content = a.text;
}

private void Remove_Click(object sender, RoutedEventArgs e)
{
    foreach (var item in list.ToList())
    {
        if (item.text == content)
        {
            list.Remove(item);
        }
    }
    content = "";
}

Another issue is that the context menu is displayed also outside of list view item (e.g. on borders).

你能解释一下吗?我不是很明白。您的意思是在 Flyout 中显示内容?如果是这样,我认为上面的方法可以解决这个问题。如果不行,可以留言,我看看能不能解决这个问题。

And since the event that is triggered is RightTapped, I'm not sure if the context menu would be displayed on long click on mobile devices.

我觉得这里的"long click"事件是指这样的Holding事件?

private void ListView_Holding(object sender, HoldingRoutedEventArgs e)
{
    ListView listView = (ListView)sender;
    allContactsMenuFlyout.ShowAt(listView, e.GetPosition(listView));
    var a = ((FrameworkElement)e.OriginalSource).DataContext as List;
    content = a.text;
}

我刚刚在移动模拟器上测试了它,它工作正常。虽然我在这里写了一个很长的答案,但是关键点很简单,你可以使用 ((FrameworkElement)e.OriginalSource).DataContext 来获取项目的上下文。

在数据模板中添加浮出控件。使用命令来处理事件。 请在此处查看示例代码:

<DataTemplate x:Name="ListItemTemplate" >
    <Grid  x:Name="gridItem"  RightTapped="gridItem_RightTapped">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Image Name="imgProduct" Width="50" Height="50" Grid.Column="0" Source="{Binding ProductUrl}" Margin="0,5,10,5" VerticalAlignment="Center" ></Image>
        <TextBlock Name="tbName" Text="{Binding Name}" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch"  ></TextBlock>
        <FlyoutBase.AttachedFlyout>
            <MenuFlyout>
                <MenuFlyoutItem Text="Delete" Command="{Binding DataContext.DeleteCommand, ElementName=contentGrid}" CommandParameter="{Binding}"  />
            </MenuFlyout>
        </FlyoutBase.AttachedFlyout>
    </Grid>
</DataTemplate>

后面的代码:

private void gridItem_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement);
}

您可以在这里获得完整的解决方案:https://code.msdn.microsoft.com/How-to-implement-flyout-ef52517f