The issue is practically the same as described here: http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/542b827a-7c8e-4984-9158-9d8479b2d5b1 but I am not satisfied with the answer accepted there and feel that there must be a better solution...
I am trying to implement a 'clasic' context menu on my list view (not via the AppBar but via PopupMenu) and that works fine, however only if I set the list view into the "None" SelectionMode.
The link above correctly explains that the ListViewItem 'swallows' the right tapped event if ListView set to other than "None" selection mode. How can I override this behavior, so that the list view items gets selected AND the list view still fires the RightTapped event which I can react on?
I believe I solved the problem by subclassing both ListView and ListViewItem classes.
public class MyListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new MyListViewItem();
}
}
public class MyListViewItem : ListViewItem
{
protected override void OnRightTapped(Windows.UI.Xaml.Input.RightTappedRoutedEventArgs e)
{
base.OnRightTapped(e);
e.Handled = false; // Stop 'swallowing' the event
}
}
<CustomControls:MyListView
x:Name="MyListView"
...
SelectionMode="Single"
RightTapped="MyListView_RightTapped">
</CustomControls:MyListView>
By this simple approach MyListView_RightTapped handler is called even if SelectionMode is set to 'Single', 'Multiple' or 'Extended'. Next question however is whether this small victory leads to a good design of the application UI. I will not try to answer that question here...
You can use PointerPressed/PointerReleased events on the GridViewItem to get the same effect you would otherwise get from RightTapped or ManipulationCompleted.
<ListView
Margin="120,80,0,0"
SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style
TargetType="ListViewItem">
<Setter
Property="Width"
Value="100" />
<Setter
Property="Height"
Value="100" />
</Style>
</ListView.ItemContainerStyle>
<ListViewItem
RightTapped="ListViewItem_RightTapped_1"
PointerPressed="ListViewItem_PointerPressed_1"
PointerReleased="ListViewItem_PointerReleased_1"
ManipulationStarted="ListViewItem_ManipulationStarted_1">
<Rectangle
Height="80"
Width="80"
Fill="Red" />
</ListViewItem>
</ListView>
.
private void ListViewItem_RightTapped_1(object sender, RightTappedRoutedEventArgs e)
{
Debug.WriteLine("Tap");
}
private void ListViewItem_PointerPressed_1(object sender, PointerEventArgs e)
{
Debug.WriteLine("Press");
}
private void ListViewItem_ManipulationStarted_1(object sender, ManipulationStartedRoutedEventArgs e)
{
Debug.WriteLine("Manipulating");
}
private void ListViewItem_PointerReleased_1(object sender, PointerEventArgs e)
{
Debug.WriteLine("Release");
}
}
Output:
Press
Release
Press
Release
*EDIT
Sorry, I must have missed it somehow that these events work differently with touch and mouse. I think the answers you got might be the best you will get. Otherwise - you would need to implement your own version of a ListView. You could try pressuring them with hope to get a better solution in one of the future releases, but I would not hope for much there. From what I remember - ContextMenus are not very metro and there are better UX solutions than right-click menus. You can display the commands in the AppBar or on a details page that opens after you select an item. That could work better than trying to hack a workaround for something that was never designed to be supported.
I took @Jan Zeman's idea ( upvoted, by the way ;) ) and improved it a bit.
A few problems with the original approach:
- The event handler will have to set
e.Handled = true
, or else the event will bubble up, until the Page
itself handles the right tap event - and consequently opens the app bar.
- If no one's listening to this event, then the event will always bubble up and reach the
Page
.
- The event handler will be fired when a right tap occurs on a list's item OR on the white space surrounding the items.
This approach solves the problems above:
public class MyListView : ListView
{
public event RightTappedEventHandler ItemRightTapped;
protected override DependencyObject GetContainerForItemOverride()
{
var item = new MyListViewItem();
item.RightTapped += OnItemRightTapped;
return item;
}
protected virtual void OnItemRightTapped(object sender, RightTappedRoutedEventArgs args)
{
if (ItemRightTapped != null)
ItemRightTapped(sender, args);
args.Handled = true;
}
}
public class MyListViewItem : ListViewItem
{
protected override void OnRightTapped(RightTappedRoutedEventArgs e)
{
base.OnRightTapped(e);
// Stop 'swallowing' the event
e.Handled = false;
}
}
Now you can subscribe to the ItemRightTapped
event instead, which will be triggered if and only if an item is right tapped.
The item will mark the event as unhandled, the list will let you handle the event, and then mark it as handled - preventing it from bubbling up and hitting the Page
.