Windows Store App: Forwarding Mouse Events - xaml

I have a full-screen Bing map control. Over the top, I want to overlay various other controls.
<Grid>
<maps:Map x:Name="Map" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Grid Style="{StaticResource LayoutRootStyle}" Background="Transparent"
x:Name="InnerGrid">
<Grid.RowDefinitions>
<RowDefinition Height="132" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="700" />
<ColumnDefinition Width="350" />
</Grid.ColumnDefinitions>
... Content ...
The trouble is that the InnerGrid consumes the mouse events which I need the map control to receive instead. Setting the background to transparent has done nothing useful.
So, I found the RoutedEvents stuff:
internal sealed partial class PageCodeBehind : Page
{
public PageCodeBehind()
{
InitializeComponent();
InnerGrid.AddHandler(PointerPressedEvent, new PointerEventHandler(OnPointerPressedEvent), true);
InnerGrid.AddHandler(PointerMovedEvent, new PointerEventHandler(OnPointerMovedEvent), true);
}
private void OnPointerPressedEvent(object sender, PointerRoutedEventArgs pointerRoutedEventArgs)
{
// I can hit a breakpoint here...
var peer = FrameworkElementAutomationPeer.FromElement(GigMap) as MapAutomationPeer;
peer.RaiseAutomationEvent(...);
So - I can capture the event, but I have no idea how to trigger the event on the Map control. Having looked at the automation peer stuff - it seems like they are concerned with higher-level concepts than mouse-down, such as open tool-tip etc.
Any idea how I forward all events from my InnerGrid control to the Map control?
Many thanks for any help,
Jon

Ok - I found a work-around. Setting Background="{x:Null}" on the InnerGrid makes it forward events. Beautifully undocumented as always on MSDN :-)
I'd still like to know any other methods available though if anyone knows more information.
Many thanks,
Jon

Related

Issues with child page's display when using Frames

Problem
This is the layout of my main pane:
<Page
x:Class="Communities.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Communities"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Loaded="Page_Loaded" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
...
<SplitView Grid.Row="1" Name="hamburgerMenu" OpenPaneLength="200" PaneBackground="#F02A2A2A">
<SplitView.Pane>
<ListView ItemsSource="{Binding}" IsItemClickEnabled="True" ItemClick="HamburgerItemClick">
... </ListView>
</SplitView.Pane>
<Frame Name="frame" />
</SplitView>
<Grid Grid.RowSpan="3" Name="popupArea" />
</Grid>
</Page>
the frame is where I load all my pages, so that the layout is always consistant.
Now, in most of my child pages I have defined AppBar control and attached it to the BottomAppBar property of that child page:
PostView.xaml
...
<Page.BottomAppBar>
<CommandBar>
<AppBarButton Label="Back" Icon="Back" Click="TryGoBack" />
<AppBarButton Label="Refresh" Icon="Refresh" Click="TryRefreshComments" />
<AppBarButton Label="Go to Community" Icon="Go" Click="TryOpenCommunity" />
</CommandBar>
</Page.BottomAppBar>
...
Here's where the trouble starts. It works fine on PC, as the layout is mostly static on desktop. There are no software keyboards required most of the time etc. On mobile it's more problematic: Screenshots
My thoughts
It seems like the frame that is used to display the child page is causing all sorts of problems. When the AppBar is defined in the main page it positions correctly.
I'd like to avoid the keyboard covering the textbox as well as the AppBar but I don't want to get rid of the frame control. I'd also prefer it if the page got "squished" when the keyboard shows up, instead of getting pushed upwards, but I'm not sure how to display the keyboard on the frame level, instead of the entire MainPage, default level.
What would be the best way to solve this situation?
Cheers!
As you know, if we set the Page.BottomAppBar in the root of the Page, there is no issue with Touch keyboard. It seems it is the best way to add the Page.BottomAppBar.
If you want to add the Page.BottomAppBar in the other page in the Frame, you should be able to customize your UI. The UWP provides similar behavior on the appearance of the touch keyboard by handling the Showing and Hiding events exposed by the InputPane object.
We can use the InputPaneVisibilityEventArgs.OccludedRect to get the region of the application's window that the input pane is covering.
For example:
public PostView()
{
this.InitializeComponent();
InputPane.GetForCurrentView().Showing += PostView_Showing;
InputPane.GetForCurrentView().Hiding += PostView_Hiding;
}
private void PostView_Hiding(InputPane sender, InputPaneVisibilityEventArgs args)
{
MyTextBox.Margin = new Thickness(0, args.OccludedRect.Height, 0, 0);
}
private void PostView_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
{
MyTextBox.Margin = new Thickness(0, 0, 0, args.OccludedRect.Height);
}

Binding shared flyout to 2 controls in Listview's DataTemplate in UWP Windows 10

I've got a shared Flyout defined in my <Page.Resources> as follows:
<Flyout x:Name="InfoFlyout" Opened="{Binding IsOpen,
ElementName=MyListView, Mode=TwoWay}">
<Grid>
<Button Foreground="White" Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Help"/>
</StackPanel>
</Button>
</Grid>
</Flyout>
But I get An object reference not set error when compiling, so I used the code from this article (Using Windows 8.1 Flyout control with MVVM) instead.
This seems to circumvent the problem I was having with the above code. Now my shared Flyout code looks like this:
<Flyout x:Name="InfoFlyout"
helpers:FlyoutHelpers.Parent="{Binding ElementName=MyListView}"
helpers:FlyoutHelpers.IsOpen="{Binding IsOpen, Mode=TwoWay}">
<Grid>
<Button Foreground="White" Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Help"/>
</StackPanel>
</Button>
</Grid>
</Flyout>
My ListView control (i.e. x:Name="MyListView") is binded to the page's ViewModel i.e. MainPageViewModel. The IsOpen property is defined in the MainViewModel.
Now in my ListView DataTemplate, I want my Flyout to open when I press and hold the ListViewItem or when pressing a button within the ListViewItem:
<DataTemplate>
<Grid FlyoutBase.AttachedFlyout="{StaticResource InfoFlyout}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source={Binding MyImage} />
<Grid Grid.Column="1" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Width="30" Height="30"
Flyout="{StaticResource InfoFlyout}"
content="i">
</Button>
</Grid>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<actions:OpenFlyoutAction />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
</DataTemplate>
As you can see, I've got the Flyout "attached" to the Grid via:
FlyoutBase.AttachedFlyout="{StaticResource InfoFlyout}"
and I've got the same Flyout attached to the button within the ListViewItem itself via:
Flyout="{StaticResource InfoFlyout}"
I've put breakpoints on both my setter and getter for the IsOpen property and when page gets loaded, it does go into the getter but whenever I open or close my Flyout either via Holding or by pressing the 'i' button, it doesn't trigger the method below and therefore it doesn't change the IsOpen property.
private static void OnIsOpenPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) as defined in the FlyoutHelper class.
The reason I've set my ElementName to MyListView is that I want all my ListViewItem to be binded to the one property i.e. IsOpen as I need to detect whenever a flyout menu is opened irrelevant of which ListViewItem it belongs to.
How can I achieve or resolve this?
UPDATE - 1
The problem of accessing the shared menu has been resolved by using the following:
<Flyout x:Name="InfoFlyout"
helpers:FlyoutHelpers.Parent="{Binding ElementName=MyListView}"
helpers:FlyoutHelpers.IsOpen="{Binding IsOpen, Mode=TwoWay}">
and setting the button to
<Button Width="30" Height="30"
Command="{Binding InformationCommand}"
CommandParameter="{Binding}"
Flyout="{StaticResource InfoFlyout}">
Which is fine and as #ElvisXia mentioned, you can comment out the code in the OnIsOpenPropertyChanged as the positioning is already determined by the button located inside my ListViewItem.
There is however one outstanding problem. A small one btw, but nice if it can be solved. The shared flyout which is attached to the grid itself in the DataTemplate i.e.
<DataTemplate>
<Grid FlyoutBase.AttachedFlyout="{StaticResource InfoFlyout}">
It is being positioning based on the ListViewItem which technically is correct as I'm calling a different piece of code for that one i.e.
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<actions:OpenFlyoutAction />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
And the OpenFlyoutAction is defined as follows:
public class OpenFlyoutAction : DependencyObject, IAction
{
public object Execute(object sender, object parameter)
{
FrameworkElement senderElement = sender as FrameworkElement;
FlyoutBase flyoutBase = FlyoutBase.GetAttachedFlyout(senderElement);
flyoutBase.ShowAt(senderElement);
return null;
}
}
Can I somehow stop using the OpenFlyoutAction and use the same code as provided in the article to open my Flyout wherever the user is holding his/her finger on the relevant ListViewItem rather than on top or below the actual ListViewItem?
I understand it's a little bit side track from the original issue which was to share a Flyout by to controls but may as well finish it as it is somehow relevant to the issue.
Thanks.
Change the type of Parent from Button to ListView. To open flyout in particular X,Y position is not possible in WP. You can choose PopUp control instead. Here is a link which i got open the pop up in tapped position. You can use VisualTreeHelper to get PopUp control of tapped ListViewItem
By Using Windows 8.1 Flyout control with MVVM , the author use parent to control where the flyout shows up.
So the author have codes like below(FlyoutHelpers.cs):
private static void OnIsOpenPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var flyout = d as Flyout;
var parent = (ListView)d.GetValue(ParentProperty);
if (flyout != null && parent != null)
{
var newValue = (bool)e.NewValue;
if (newValue)
flyout.ShowAt(parent);
else
flyout.Hide();
}
}
He use flyout.ShowAt(parent) to let flyout show at parent element. But in your codes you have binded the flyout to the button using:
<Button Width="30" Height="30"
Flyout="{StaticResource InfoFlyout}" content="i">
</Button>
So it is not necessary to let it show at it's parent any more. To fix the problem, you can comment out the statements like below:
private static void OnIsOpenPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
//var flyout = d as Flyout;
//var parent = (ListView)d.GetValue(ParentProperty);
//if (flyout != null && parent != null)
//{
// var newValue = (bool)e.NewValue;
// if (newValue)
// flyout.ShowAt(parent);
// else
// flyout.Hide();
//}
}
Then you will see the flyout shows at the right place.

UWP Page Preload ( Is it possible to preload the contents of a page )

I'm working on a Windows 10 app. and i created map in my second page.. but it is too slow.. Is it possible to preload the contents of a page before displaying it to the user so that the transition from page x to page y is smooth?
Is not possible, all pages in uwp apps will be rendered when you navigated to some page.
But you can prepare the data before to navigate to your second page maybe you can save this data in the local storage and load the data in the method OnNavigatedTo in your second page it would be better.
The easiest option is probably to not make it a page at all; instead, have the map on the page you are starting from, but have its Opacity be zero. Then when you want to show it, set the Opacity to one. This if course means you pay the cost of loading the map even if the user never wants to view it, but it makes viewing instant.
Sample XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="go to map" Click="GoToMap" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid x:Name="mapLayer" xmlns:maps="using:Windows.UI.Xaml.Controls.Maps" Opacity="0" IsHitTestVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<maps:MapControl />
<Button Content="back" Click="HideMap" HorizontalAlignment="Center" Grid.Row="1"/>
</Grid>
</Grid>
And matching code:
private void GoToMap(object sender, RoutedEventArgs e)
{
mapLayer.Opacity = 1;
mapLayer.IsHitTestVisible = true;
}
private void HideMap(object sender, RoutedEventArgs e)
{
mapLayer.Opacity = 0;
mapLayer.IsHitTestVisible = false;
}

Windows Phone items scroll issue while SIP keyboard appear

I'm facing two issues with the code following while I'm making a simple chat app.
The code shows a textblock at the top of the page and two textbox stack at the bottom. Plus a listbox which will be auto height to fill the remaining gap.
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle Height="1" Fill="White" VerticalAlignment="Top"/>
<TextBlock Text="Hello World!" Grid.Row="0" FontSize="36"/>
<Listbox Grid.Row="1"/>
<TextBox Grid.Row="2"/>
<TextBox Grid.Row="3"/>
</Grid>
When I click on one of the top textboxes, the SIP keyboard becomes visible and all content in the page is pushed up. The main issue here is that the top textblock disappears and hides over the top. How can I keep it on top and not moving while the SIP keyboard is viewed?
When the most bottom textbox has focus, the SIP keyboard appears and pushes all content up. In this case, the keyboard will just fit and be sticky to that textbox. However, when the other textbox has focus, the keyboard will make a gap between them. How can I make the keyboard behave as it does when the most bottom one is focused?
When the keyboard pops and unpops, a TranslateTransform runs on the PhoneApplicationFrame, translating the whole screen up and down.
Based on this article, you should be able to get the value of the translation. As you can observe an animation moves the Y property from zero to a specific negative value (based on the control you took focus on).
Option 1: I have not been able to write a descent way of handling this value but you should be able to resize your controls to fit in what's left of the screen.
Option 2.0 (bad): You can cancel or remove this animation. The keyboard will be on top of the screen without any movement. Your turn now to move/resize your controls to fit the remaining space.
public MainPage()
{
this.InitializeComponent();
PhoneApplicationFrame frame = (App.Current as App).RootFrame;
var group = (frame.RenderTransform as TransformGroup);
group.Children.RemoveAt(0); // remove translate transform
}
Option 2.1: There's an issue with 2.0: removing the transform will prevent you from being notified about the keyboard. Setting up a reverse animation on your page's child when the Y property changes will "kind of cancel" the original translation.
<Grid x:Name="LayoutRoot" Background="Transparent" VerticalAlignment="Stretch">
<Grid.RenderTransform>
<TransformGroup>
<TranslateTransform />
</TransformGroup>
</Grid.RenderTransform>
static void OnRootFrameTransformChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
// ... edit from the blog article ...
MainPage page = source as MainPage;
page.lb.Items.Add(newvalue);
var oppositeTransform = (TranslateTransform)((TransformGroup)page.RenderTransform).Children[0];
if (newvalue < 0.0)
{
page.IsSipVisibleGuess.IsChecked = true;
oppositeTransform.Y = -newvalue;
}
else if (newvalue == 0.0)
{
page.IsSipVisibleGuess.IsChecked = false;
oppositeTransform.Y = 0;
}
I'm sorry none of these options will magically solve the problem but it may help you code what fits best for your app.
If you find a better solution out of this, please post it as an answer.

XAML button click or tap event not raised in usercontrol

I have a XAML user control with a collection of buttons.
My XAML is as follows:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" Width="85"
>
<Grid Background="#7F010305" HorizontalAlignment="Left">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="Zoom" Grid.Row="0" Grid.Column="0" Width="40" Height="40" Margin="15,0,15,15" Click="OnClicked" Tapped="OnClicked" />
<Button x:Name="Pan" Grid.Row="1" Grid.Column="0" Width="40" Height="40" Margin="15,0,15,15" Click="OnClicked" Tapped="OnClicked" />
<Button x:Name="Contrast" Grid.Row="2" Grid.Column="0" Width="40" Height="40" Margin="15,0,15,15" Click="OnClicked" Tapped="OnClicked"/>
<Button x:Name="Brightness" Grid.Row="3" Grid.Column="0" Width="40" Height="40" Margin="15,0,15,15" Click="OnClicked" Tapped="OnClicked"/>
</Grid>
Somehow the click or the tap event is not getting raised. I tried various options but the Click or Tapped event is not getting fired.
Any hints?
From looking at your code I notice that you have both the Click and Tapped event handlers are pointing to the same method ("OnClicked"). These events, however, have different signatures for delegates. The Click event expects a method with signature (http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.primitives.buttonbase.click.aspx):
void ClickDelegate(object sender, RoutedEventArgs e)
while the Tapped event expects a method with signature (http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.input.tappedeventhandler.aspx):
void TappedDelegate(object sender, TappedRoutedEventArgs e)
So the signatures of the two methods aren't equivalent and it seems unlikely that you would be able to use the same method for both events since they require different delegate signatures (The code that runs behind the scenes to merge the logic in your xaml to your code behind should be looking for a method void OnClick(object, RoutedEventArgs) for your Click handler and a method void OnClick(object, TappedRoutedEventArgs) for your Tapped handler). Also if the runtime doesn't find a matching handler method (at least for wirnt in 8.1) I have found through testing that the event handler hook up will silently fail rather than throwing an exception (I am pretty sure that for WPF this causes an error, which makes it slightly confusing).
Long story short without being able to see your code behind my best guess is that OnClick doesn't have a signature that matches the delegate types of either the Clicked or Tapped event. Beyond that I am also wondering why you hooked into both Tapped and Clicked. Tapped is the generalized event that basically does the same thing as Clicked and then some (see Are Click, Tapped, and PointerPressed synonymous in WinRT-XAML?). I can't say for sure whether or not hooking into both of them may cause some weird issues (such as both the Clicked and Tapped being called for a single press) but it seems like you would probably just want to be subscribing to the Tapped event.
EDIT:
Just to add another point. Sometimes I have had it happen that even if you do everything right (where right here equals you have painstakingly double checked everything and it is all correct as far as you can tell) the event handler still doesn't work. Honestly I have only ever seen this happening when there are much more intricate combinations of components than what you have (such as those involving pretty much every custom type of thing you can imagine... custom panels, datatemplateselectors, itemtemplateselectors, etc) and am not sure if I am just constantly overlooking something from time to time or if there are legitimate bugs in the Microsoft code.
Anyway, the workaround is always to avoid hooking it up the event handler in the 'normal' way (like what you are doing here). To do this give the button a name and add the handler in the code behind (either by directly using the name if the button isn't in a template or by using GetTemplateChild from OnApplyTemplate to grab it if it is in a template). Then you can just do something like this for a button named 'foo':
foo.Tapped += (sender, args) => { /*do something here */ };