Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UWP ListView Item context menu

I'm searching internet for how to add context menu for ListView. So far I've found one that actually displays context

<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) {

}

The problem is I'm not able to get item on which the context menu was displayed. Another issue is that the context menu is displayed also outside of list view item (e.g. on borders). 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. I cannot test it because my emulators are not currently working. Since it should be universal windows app I was expecting some really easy and efficient way of creating context menus for ListView items.

like image 793
miskohut Avatar asked Apr 20 '16 11:04

miskohut


2 Answers

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

For this problem, if you add data to the ListView like this:

<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>

You can get the item's context in the RightTapped event like this:

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

In this scenario, "a" will directly get the string format content of clicked item.

If you add your data to ListView using DataTemplate like this:

<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>

and usually when using DataTemplate, we add data by ObservableCollection like this:

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 is quite simple here for test:

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

Then also we can get the DataContext in the RightTapped event:

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

But this time, "a" is actually the 'List' object (please refer to the "List" class) inside the item, because the content of the item is now a 'List' object, not a string any more. So we can get the text property of this object like this:

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;
}

I think eventually you want to edit the content in the Button click event of the Flyout, you can do it for example like this:

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).

Can you explain this? I can't quite understand it. You mean displaying the content for example in the Flyout? If so, I think the method above can solve this problem. If not, you can leave a comment, and I will see if this problem can be resolved.

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.

I think that "long click" event here indicates the Holding event like this?

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;
}

I just test it on the Mobile Emulator, it works fine. Although I wrote a quite long answer here, but the key point is quite simple, you can just use ((FrameworkElement)e.OriginalSource).DataContext to get the Context of the item.

like image 95
Grace Feng Avatar answered Sep 17 '22 20:09

Grace Feng


Use Command instead of Click event. You can pass the clicked item in 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>

In viewModel.cs

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
 }
like image 40
Archana Avatar answered Sep 19 '22 20:09

Archana