How to detect a tap on header of currently selected PivotItem - c#

I have a Pivot control. I want a special behavior -- everytime the user taps the header of currently chosen PivotItem, I want to react to that. However, this reaction can not happen when the tapped header does not belong to the currently selected pivot item.
My plan was as follows:
For each PivotItem create a custom header and associate its tap event with a handler:
<phone:PivotItem DataContext="{Binding}" ContentTemplate="{StaticResource MyTemplate}" Content="{Binding}" x:Name="itemA">
<phone:PivotItem.Header>
<TextBlock x:Name="headerA" Text="A" Tap = "HeaderA_Tapped"/>
</phone:PivotItem.Header>
</phone:PivotItem>
And in the handler, test whether the tapped item is currently selected, if yes, react:
protected void HeaderA_Tapped(object sender, GestureEventArgs e)
{
if (mainPivot.SelectedItem.Equals(itemA))
{
//selected item is the same pivotItem that reported tapping event
react();
}
}
It seemed pretty straightforward, but after giving it a try I found out that the tap event was reported only AFTER the selection changed event. In cases, where the user taps currently not selected pivotItem header, the pivot will change the selection accordingly (default behavior that I want to keep), and only then it reports the tap event. However, that is too late for my code, because in that moment the tapped header and currently selected item are already the same.
Is there any way how I can detect whether the tap initiated a selection change? Or is there a way to revert the order of events? I guess currently the WP event model sinks the event from the root of Visual Tree down to leafs -> therefore the Pivot gets to handle it sooner, and only then it gets to header TextBlock.

I think you should track Pivot SelectedIndex and update SelectedIndex in Selection_Changed event by using a Timer.
Some think like this:
int selectedIndex;
func Selection_ChangedEvent()
{
//use a timer to update selectedIndex
//something like: Timer.do(selectedIndex = Pivot.SelectedIndex, 100)
//fire after a specify time. adjust this for the best result.
}
And in your tapped event
HeaderA_Tapped()
{
if(selectedIndex == "index of item A in Pivot")
{
react();
}
}

Create Pivot selection changed event
<phone:Pivot Title="MY APPLICATION" x:Name="MainPivot" SelectionChanged="Pivot_SelectionChanged">
Add the event handlers to cs file
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
switch (MainPivot.SelectedIndex)
{
case 0:
// do something
break;
case 1:
//do something
break;
}
}
For more details see this search
https://www.google.co.in/?gfe_rd=cr&ei=Y1XdVZruLqfG8AfUuquIAQ&gws_rd=ssl#q=wp8+c%23+pivot+selectionchanged
https://social.msdn.microsoft.com/forums/windowsapps/en-US/1baf74fa-0ddd-4226-a02d-a7fc9f80374d/pivot-static-header-like-twitter-app

OK, so after some read of my Windows Phone 8 Development Internals book by Andrew Whitechapel and Sean McKenna (do not ask why I did not do it sooner) I got a working solution.
I am not going into full fledged detailed discussion of events, just to point out conclusions relevant to my issue. It seems that in the WP8 event model there are at least two types of events, lower level routed events and logical touch gesture events. The routed events are routed through visual tree from the most concrete element (in my case the header TextBlock control) to the root (therefore Pivot should have get it later). Logical events are not routed, they appear only on a single element (e.g., my TextBlock control), and therefore their handlers have to be registered on the particular element. I assume that the logical events are raised by the element based on the lower level routed event. Applied to my case, it seems that first the lower-level MouseLeftButtonDown (or button up) event was raised, routed to Pivot that used it to change selection, and only then my TextBlock control created the logical Tap event. This would result in behavior that I observed on my emulator. I am not sure about the way how the routed events are really routed and how the logical events are created, so if I made a mistake in my conclusions, please correct me. However, I was able to solve my problem.
Using aforementioned assumptions I decided to listen to lower-level routed events instead of logical tap gesture. Using this MouseLeftButtonUp event I was notified before the new PivotItem was selected, thus giving me chance to check whether the raised MouseLeftButtonUp was originated by currently selected PivotItem.
And so finally the solution:
My XAML Pivot definition (notice that now I am handling MouseLeftButtonUp events on the TextBlock controls, instead of logical Tap events):
<phone:Pivot Title="Pivot" Name="mainPivot">
<phone:PivotItem DataContext="{Binding A}" ContentTemplate="{StaticResource MyTemplate}" Content="{Binding A}">
<phone:PivotItem.Header>
<TextBlock Text="A" MouseLeftButtonUp="HeaderHandlerA"/>
</phone:PivotItem.Header>
</phone:PivotItem>
<phone:PivotItem DataContext="{Binding B}" ContentTemplate="{StaticResource MyTemplate}" Content="{Binding B}">
<phone:PivotItem.Header>
<TextBlock Text="B" MouseLeftButtonUp="HeaderHandlerB"/>
</phone:PivotItem.Header>
</phone:PivotItem>
</phone:Pivot>
Since I have a different handler for each PivotItem header, I can simply test selected index with a constant. The corresponding handlers in the code-behind:
private void HeaderHandlerA(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// HeaderHandlerA is allways called within the first PivotItem,
// therefore I can just test whether selected index is 0 (index of the first PivotItem)
if (mainPivot.SelectedIndex == 0)
{
OnSelectedItemTapped();
}
// else the "tapped" header was not the selected one and I do nothing
}
private void HeaderHandlerB(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (mainPivot.SelectedIndex == 1)
{
OnSelectedItemTapped();
}
}
Maybe not the most elegant solution, but it works...

Related

WPF ComboBox PreviewMouseDown

I have a combobox that is editable and a textbox.
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="86,149,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" Margin="282,150,0,0" IsEditable="True" PreviewMouseDown="ComboBox_PreviewMouseDown"/>
I don't understand why ComboBox_PreviewMouseDown does not trigger, when the focus is on the textbox and I click on the combobox. It just highlights the text in the combobox and sets the focus. Clicking in the combobox when it already has the focus, PreviewMouseDown fires.
Is that what's happening here? Why is a PreviewMouseDown in an unfocused combobox not working?
When ComboBox.IsEditable is set to True, the ComboBox internally sets the focus (and keyboard focus) to the edit TextBox to make it instantly available for text input. This makes total sense as the intention when clicking the edit TextBox is always to enter or edit some text. Otherwise, the user would have to click the TextBox twice to make it receive focus for text input (keyboard focus).
So, to prevent focus stealing, the author marked the MouseDown event as handled i.e. RoutedEventArgs.Handled is set to true. (This is the reason why most non-preview events are marked handled by most controls).
Also, the author wanted to prevent the moving of the caret when clicked into the edit TextBox for the first time (to give it focus): the PreviewMouseDown event's RoutedEventArgs.Handled will only be set to true, if the edit TextBox has no keyboard focus and the drop-down panel is closed. (That's why the second click into the TextBox will pass through to be handled by an added event handler).
To achieve the behavior you expect, you have to handle the UIElement.PreviewGotKeyboardFocus event or the attached Keyboard.PreviewGotKeyboardFocusevent on the ComboBox.
Alternatively register the event handler using the UIElement.AddHandler method and set the handledEventsToo parameter to true:
this.MyComboBox.AddHandler(
UIElement.PreviewMouseDownEvent,
new RoutedEventHandler(MyComboBox_PreviewMouseDown),
true);
I ran into this same issue myself. A simple and effective workaround is to wrap your ComboBox in a lightweight ContentPresenter, then attach your PreviewMouseDown handler to that, like so:
<ContentPresenter x:Name="MyComboBoxWrapper"
PreviewMouseDown="MyComboBoxWrapper_PreviewMouseDown">
<ContentPresenter.Content>
<ComboBox x:Name="MyComboBox" />
</ContentPresenter.Content>
</ContentPresenter>
Additionally, since this control gets the PreviewMouseDown event before the ComboBox does, you not only can use it to pre-process events before the ComboBox even sees them, but you can cut off the ComboBox entirely by setting the event arg's handled property to 'true.'
Works like a charm! No subclassing or other tricks needed and it only requires a lightweight control in the tree!
Notes
As some may have considered, technically you could attach the PreviewMouseDown event to any ancestor of your ComboBox, but you then may have to include logic in that handler to determine if you're actually clicking on the ComboBox vs something else.
By using an explicit ContentPresenter (an incredibly lightweight element that itself doesn't have any rendering logic. It simply hosts other elements), you now have a dedicated PreviewMouseDown handler just for this control. Plus, it makes it more portable should you need to move it around since the two items can travel together.

UWP - WinRT xaml toolkit TreeView in Flyout, changes selection after flyout closes

I am currently trying to use WinRT xaml ToolKit TreeView component since it is not supported in UWP natively.
TreeView is inside a button Flyout. When I press the button I want Flyout to appear so I can select an item from the tree. And I bind a command from ViewModel to SelectedItemChanged event:
<Button
x:Name="btnFilter"
HorizontalAlignment="Right"
Command="{Binding OpenFiltersCommand}"
Style="{StaticResource SecondaryMenuButtonStyle}">
<StackPanel Orientation="Horizontal">
<Image
Width="28"
Margin="0,0,4,0"
Source="{StaticResource FilterIcon}" />
<TextBlock x:Uid="Filter" Style="{StaticResource GrayTextBlockStyle}" />
</StackPanel>
<FlyoutBase.AttachedFlyout>
<controls1:CustomFlyout IsOpen="{Binding IsFiltersOpen, Mode=TwoWay}" Parent="{Binding ElementName=btnFilter}">
<controls2:TreeView
ItemContainerStyle="{StaticResource DefaultTreeViewItem}"
ItemTemplate="{StaticResource TreeViewItemTemplate}"
ItemsSource="{Binding BuildingTree}"
Style="{StaticResource DefaultTreeViewStyle}">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectedItemChanged">
<core:InvokeCommandAction Command="{Binding ChangeRoomCommand}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</controls2:TreeView>
</controls1:CustomFlyout>
</FlyoutBase.AttachedFlyout>
</Button>
After I select an item from TreeView, SelectedItemChanged event fires as it should, but afterwards I close the Flyout and the event fires again. The second time it usually fires saying new selected element is next after current's parent. So for example if I have this structure:
1
--1.0
--1.1
--1.2
2
--2.0
--2.1
So if I select --1.1 first SelectedItemChanged will fire with --1.1 element, and afterwards it will fire with 2 as new selected item.
Note: I am using CustomFlyout component which I can close from ViewModel, but I also tested this with regular Flyout and same thing happens after I close Flyout by clicking outside of it.
UPDATE: I've downloaded WinRT code and started debugging the TreeView component locally. In TreeViewItem.cs I've found the source of the problem in this function:
protected override void OnGotFocus(RoutedEventArgs e)
{
// Since the GotFocus event will bubble up to the parent
// TreeViewItem (which will make it think it's also selected), it
// needs to ignore that event when it's first been handled by one of
// its nested children. We use the IgnoreNextGotFocus flag to
// notify our parent that GotFocus has already been handled.
TreeViewItem parent = ParentTreeViewItem;
if (parent != null)
{
parent.CancelGotFocusBubble = true;
}
try
{
if (Interaction.AllowGotFocus(e) && !CancelGotFocusBubble)
{
// Select the item when it's focused
Select(true);
// ActivateAsync the selection
IsSelectionActive = true;
UpdateVisualState(true);
Interaction.OnGotFocusBase();
base.OnGotFocus(e);
}
}
finally
{
CancelGotFocusBubble = false;
}
}
From what I can tell when element gets focus sometimes it fails to find it's parent and prevent propagating the event upwards in the tree.
I've also opened an issue on their github repository
As I said previously it seems this is a bug in WinRT TreeView component however I managed to prevent (hard fix) this behavior by binding "IsEnabled" property to IsFiltersOpen property from my ViewModel.
This way when I select my element I close the Flyout and disable TreeView component, which prevents it from updating.

GridView in UWP

I are facing issue with GridView Control. We had a working Windows Store App on 8.1 where GridView left and right mouse clicks had different functionality. In the case of left mouse click, we used to use “ItemClick” event which performs navigation to another XAML page. On right click of GridItem, it gets selected and shows the appbar, we have used “SelectionChanged” event for this.
We are now migrating our existing windows store app to UWP Application, we have used same gridView Code, we find significant difference in functionality and look & feel, we don’t see GridView Item Selected like above picture. We see “ItemClick” and “SelectionChanged” are working together. The flow is something like that on left click on the item, the control goes to SelectionChanged event and then ItemClick. We were not able to differentiate actions like Left Mouse Click and Right Mouse click, since both events are getting fired up upon clicking on left click/tapping. We have different functionality on left and right clicks of mouse.
Need help on how to mimic windows 8.1 functionality in UWP.
My requirement was the I wanted to use Right Click/Long Tapped to select an item and take an action accordingly from App Bar Buttons and on Left Click/Tap should redirect me to the next XAML Page. The problem I was facing was the on Right Click, I wasnt able to detect that which items of GridView has been clicked and how can I add that into SelectedItem.
What I did was, I introduced extra Grid in DataTemplate of GridView. Within this Grid, I added RightTapped event.
The sample code snippet is
<GridView x:Name="ItemGridView"
ItemsSource="{Binding Source={StaticResource ItemsViewSource}}"
IsItemClickEnabled="True"
SelectionMode="Single" ItemClick="ItemGridView_ItemClick"
SelectionChanged="ItemGridView_SelectionChanged">
<GridView.ItemTemplate>
<DataTemplate>
<Grid RightTapped="Grid_RightTapped">
<Border Background="White" BorderThickness="0" Width="210" Height="85">
<TextBlock Text="{Binding FileName}" />
</Border>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The event name is Grid_RightTapped. This helped me detect that from which GridViewItem, I got the long tap/right click.
The code-behind for this is:
private void Grid_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
Song selectedItem = (sender as Grid).DataContext as Song;
//the above line will get the exact GridViewItem where the User Clicked
this.ItemGridView.SelectedItem = selectedItem;
//the above line will add the item into SelectedItem and hence, I can take any action after this which I require
}
}
The reason we are doing this way is, because now we can add clicked item into the GridView SelectedItem using Right Click. Now in UWP, clicked items are added into SelectedItem using left click only. And with left click, I can navigate to another page using ItemClick event.
You are correct, there has been a change in the interaction model behavior. According to MSDN article How to change the interaction mode (XAML)
For selection, set IsItemClickEnabled to false and SelectionMode to
any value except ListViewSelectionMode.None and handle the
SelectionChanged event (ItemClick is not raised in this case).
For invoke, set IsItemClickEnabled to true and SelectionMode to
ListViewSelectionMode.None and handle the ItemClick event
(SelectionChanged is not raised in this case).
Another combination is to set IsItemClickEnabled to false and
SelectionMode to ListViewSelectionMode.None. This is the read-only
configuration.
A final configuration, which is used least often, is to set
IsItemClickEnabled to true and SelectionMode to any value except
ListViewSelectionMode.None. In this configuration first ItemClick is
raised and then SelectionChanged is raised.
You seem to be using the last option - IsItemClickEnabled is set to true and SelectionMode is set to something that's not None. According the Microsoft, this is used least often so maybe it would be a good idea to rethink this design?
Since you haven't shared any code that you already tried, I will just throw in one idea: maybe playing around with Tappedand RightTapped event handlers could help you differentiate between the two more easily?
To identify left and right click, for right click you can use RightTapped event
<GridView x:Name="categoryItemsGV"
Margin="5,5,0,0"
IsItemClickEnabled="True"
ItemClick="categoryItemsGV_ItemClick"
IsRightTapEnabled="True"
RightTapped="categoryItemsGV_RightTapped"
SelectionMode="Single"
SizeChanged="categoryItemsGV_SizeChanged"
ItemsSource="{Binding}">
and .cs code is below:
private void categoryItemsGV_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
var tablemod = (sender as GridView).SelectedItem;
}
From RightTapped the item over which the mouse was right clicked can be obtained from e.OriginalSource
<GridView x:Name="myGridView" VerticalAlignment="Center">
<GridView.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Reset"/>
<MenuFlyoutSeparator/>
<MenuFlyoutItem Text="Repeat"/>
<MenuFlyoutItem Text="Shuffle"/>
</MenuFlyout>
</GridView.ContextFlyout>
</GridView>
Private Sub myGridView_RightTapped(sender As Object, e As RightTappedRoutedEventArgs) Handles myGridView.RightTapped
myGridView.SelectedItem = e.OriginalSource
End Sub
Now that RightClick has selected the desired item, further action like delete, copy can be executed on it.

Global event handler for TextBox getting focus by mouse click

I have a situation when I want to detect when a TextBox, anywhere in the application, has been brought into focus by the user clicking on it with the mouse, or touch. I have "solved" this by adding a global event handler like this:
Application.Current.MainWindow.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(txt_MouseLeftButtonUp), true);
...
void txt_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is TextBox)
{
// Do somthing
}
}
However, if the user clicks the edges of a textbox instead of in the middle, quite often an hosting control (Grid, Border etc.) receives the mouse event and somehow passes this on to the contained TextBox, that will handle it and receive focus. This makes the approach above fruitless as the TextBox will not be the e.OriginalSource in this case and I have found no way of identifying that a TextBox was brought into focus by click.
Deriving TextBox and overriding OnMouseDown for instance, will catch this event and I guess this path could be explored to find a solution to the problem but that would require me to use a custom TextBox everywhere.
Anyone out there with a nice solution for this?
This is an example that will trigger the problem. By clicking the edges of the TextBoxes, the grid will handle the mouse event and focus will be passed on to the TextBox.
<Grid>
<Grid HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Red">
<TextBox>2323</TextBox>
</Grid>
<Grid Margin="200,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Red" Focusable="False">
<TextBox>2323</TextBox>
</Grid>
</Grid>
The GotMouseCapture event seems to work:
AddHandler(UIElement.GotMouseCaptureEvent,
new MouseEventHandler(OnGotMouseCapture), true);
...
void OnGotMouseCapture(object sender, MouseEventArgs e)
{
if (e.OriginalSource is TextBox)
{
// ...
}
}
When I click on TextBox elements with the mouse this event handler is fired, however focus changes made via keyboard do not fire the event.
Simply handle the GotFocus event for each TextBox.

listbox focusing works totally wrong with shortcuts

When I tab into a ListBox control, the first item gets focused. When I have a label and set the target property to the ListBox (as shown in the code below) and then use the dedicated Alt shortcut then it will focus not the first item but the listbox itself (listbox border becomes dotted). What is the best way to avoid this unwanted behavior? Is there a way to disable focusing on the listbox itself and only allow focusing on the items?
Example code:
<Label Content="_Label" Margin="0,10,0,88" Name="MyLabel" Target="{Binding ElementName=MyListBox}" Height="Auto" />
<ListBox Width="100" Name="MyListBox" Margin="46,0,639,0" />
Behavior:
By setting Target you explicitly asked focus to move to listBox. In case you want to put it on first listBox item, you have to do it manually.
One way would be to hook GotFocus event and set focus to next available item using TravelRequest object which wil put it on first listBox item.
XAML:
<ListBox Width="100" Name="MyListBox" Margin="46,0,639,0"
GotFocus="MyListBox_GotFocus"/>
Code behind:
private void MyListBox_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource == sender)
{
TraversalRequest request = new TraversalRequest(FocusNavigationDirection.Next);
MyListBox.MoveFocus(request);
}
}

Categories