I'm implementing a control with gesture interactions for Windows Universal app. But I've found an issue, that if I define gesture setting for a container than parent TextBox control will not be clickable after that.
Here is a simplified layout code:
<Page x:Class="App.MainPage">
<Grid x:Name="RootGrid" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" />
<Button Grid.Row="1" Content="Click" />
</Grid>
</Page>
Here is a simplified code, which allows to reproduce the behavior:
public sealed partial class MainPage : Page
{
private GestureRecognizer _gr = new GestureRecognizer();
public FrameworkElement Container { get; set; }
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.Container = this.RootGrid;
this.Container.PointerCanceled += OnPointerCanceled;
this.Container.PointerPressed += OnPointerPressed;
this.Container.PointerMoved += OnPointerMoved;
this.Container.PointerReleased += OnPointerReleased;
_gr.CrossSlideHorizontally = true;
_gr.GestureSettings = GestureSettings.ManipulationTranslateRailsX;
}
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
_gr.CompleteGesture();
e.Handled = true;
}
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
_gr.ProcessDownEvent(e.GetCurrentPoint(null));
this.Container.CapturePointer(e.Pointer);
e.Handled = true;
}
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
_gr.ProcessMoveEvents(e.GetIntermediatePoints(null));
e.Handled = true;
}
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
_gr.ProcessUpEvent(e.GetCurrentPoint(null));
e.Handled = true;
}
}
Debuggig said me that the main reason of this behavior is OnPointerPressed handler. This method is called when I click on the RootGrid and TextBox, but doesn't when I click on the button. object sender is always Windows.UI.Xaml.Controls.Grid so I cannot determine is it TextBox or not.
What is the most interesting that the same code work as expected for Windows app, but doesn't work for Windows Phone 8.1 app.
Could you give me any suggestion how to implement gesture recognition without affecting on controls inside?
I haven't found a better solution than adding PointerPressed event handler for TextBox control:
private void TextBox_OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
e.Handled = true;
}
It prevents calling OnPointerPressed for this.Container and allows using TextBox in a typical way. Not the best solution but it works as well for me.
Related
I'm trying to dynamically access my CommandBar from frames to control its back button. How can I ensure the CommandBar back button is hidden on the first frame (Frame1) whilst being visible and clickable on the second frame (Frame2)?
MainPage.xaml
<Page>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CommandBar>
<CommandBar.Content>
<Button
Click="Back_Click"
x:FieldModifier="public"
Style="{StaticResource NavigationBackButtonNormalStyle}"
Name="BackButton"
VerticalAlignment="Top" />
</CommandBar.Content>
</CommandBar>
<Frame Name="MyFrame"/>
</Grid>
</Page>
MainPage.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
Current = this;
Frame_Main.Navigate(typeof(Frame1));
}
public static MainPage Current;
private void Back_Click(object sender, RoutedEventArgs e)
{
On_BackRequested();
}
private bool On_BackRequested()
{
if (this.Frame.CanGoBack)
{
this.Frame.GoBack();
return true;
}
return false;
}
private void BackInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
On_BackRequested();
args.Handled = true;
}
}
Frame1.cs
public sealed partial class Frame1: Page
{
public Frame1()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MainPage.Current.BackButton.Visibility = Visibility.Collapsed;
}
}
Frame2.cs
public sealed partial class Frame2: Page
{
public Frame2()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MainPage.Current.BackButton.Visibility = Visibility.Visible;
MainPage.Current.BackButton.IsEnabled = true;
}
}
If you want to access the Back Button from Frame1 and Frame2, you could try to set the x:FieldModifier of BackButton as public. In this case, it will be public and you can access the button by its x:name from other pages. In addition, it's better to put the click event in the MainPage instead of Frame2. For example:
MainPage.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CommandBar>
<CommandBar.Content>
<Button x:FieldModifier="public" Click="Back_Click" Name="BackButton" Style="{StaticResource NavigationBackButtonNormalStyle}" VerticalAlignment="Top"/>
</CommandBar.Content>
</CommandBar>
<Frame Name="MyFrame" Grid.Row="1"/>
</Grid>
MainPage.xaml.cs:
You need to define a public static MainPage instance to let other pages access your button through this instance.
public MainPage()
{
this.InitializeComponent();
Current = this;
MyFrame.Navigate(typeof(Frame1));
}
public static MainPage Current;
private void Back_Click(object sender, RoutedEventArgs e)
{
On_BackRequested();
}
private bool On_BackRequested()
{
if (MyFrame.CanGoBack)
{
MyFrame.GoBack();
return true;
}
return false;
}
private void BackInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
On_BackRequested();
args.Handled = true;
}
Frame1.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MainPage.Current.BackButton.Visibility = Visibility.Collapsed;
}
Frame2.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MainPage.Current.BackButton.Visibility = Visibility.Visible;
MainPage.Current.BackButton.IsEnabled = true;
}
Or you can define a property to bind with the Visibility of Button, when you navigate to next page, you can pass the property in the Navigate method(e.g. Frame_Main.Navigate(typeof(Frame1), VM);) and then in the OnNavigatedTo event to change its value.
I have a visible grid that has to collapse when I click outside of it. I solved it for half.
MainPage.xaml code:
<StackPanel Width="400" VerticalAlignment="Center">
<Button x:Name="btnOne" Content="Button One" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="30"/>
<Button x:Name="btnShowGrid" Content="Show Grid" Click="btnShowGrid_Click" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="30"/>
<Grid x:Name="ControlGrid" PointerEntered="ControlGrid_PointerEntered" PointerExited="ControlGrid_PointerExited" HorizontalAlignment="Center" Visibility="Visible" Background="LightGreen" Height="300" VerticalAlignment="Top" Width="300" Margin="30"/>
</StackPanel>
MainPage.xaml.cs code:
bool PointerInGrid = false;
public MainPage()
{
this.InitializeComponent();
}
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
base.OnPointerPressed(e);
if (!PointerInGrid)
{
ControlGrid.Visibility = Visibility.Collapsed;
}
}
private void btnShowGrid_Click(object sender, RoutedEventArgs e)
{
ControlGrid.Visibility = Visibility.Visible;
}
private void ControlGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
PointerInGrid = true;
}
private void ControlGrid_PointerExited(object sender, PointerRoutedEventArgs e)
{
PointerInGrid = false;
}
If I click into the Grid this remains visible, if I click out the grid goes to collapsed, so far everything is fine but, if I click the btnOne the grid remains visible.
So is possible detect a global click to collapse the grid?
As always thanks in advance.
Code that works in windows form:
const int WM_PARENTNOTIFY = 0x0210;
const int WM_LBUTTONDOWN = 0x0201;
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if (!DesignMode)
{
if (m.Msg == WM_PARENTNOTIFY)
{
if (m.WParam.ToInt32() == WM_LBUTTONDOWN)
{
MessageBox.Show("Clicked!");
}
}
}
base.WndProc(ref m);
}
The PointerPressed event is swallowed by the button so it cannot reach the underlying parent, where the event handler OnPointerPressed is executed.
There is a technique to pass the event down to the parent, please refer to this answer.
You can do it like this, in the page's constructor
public MainPage()
{
this.InitializeComponent();
//...other code
//then add this line
this.btnOne.AddHandler(UIElement.PointerPressedEvent,
new PointerEventHandler((s, e) => { e.Handled = false; }), true);
}
A second thought: Maybe you can just handle btnOne’s PointerPressed event, and in the handler set its event argument’s Handled property as false. Don’t need to use AddHandler, I mean. Give it a try.
As far as I known, the UWP is sandboxed, we can't use WndProc method to receive Window event Messages.
Specific Windows Runtime controls may have class-based handling for the PointerPressed input event. If so, the control probably has an override for the method OnPointerPressed. Typically the event is marked handled by the class handler, and the PointerPressed event is not raised for handling by any user code handlers on that control. For example, ButtonBase has class handling that handles PointerPressed and instead fires Click.
For more info, please refer PointerPressed.
When we click the Button, the OnPointerPressed event will not be fired.
If you want to your Button can fire OnPointerPressed event, you can create a class that inherited the Button class. In the class, you can override the OnPointerPresse event.
For example:
class MyButton: Button
{
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
}
}
By the way, if we override OnPointerPressed event in class that inherited the Button class, the Click event will not be fired.
The same questions has been asked many times on this site and I have read most of them. But I have a special problem (maybe?) that couldn't figure it out after hours of struggling and reading SO posts.
The problem is -simply explained, I have a WPF form which contains a Connect button. If this button is pressed a textblock must appear on that form, displaying the word "Connecting...". Upon pressing the button, some handshaking operations are done in the associated C# code which takes some time. If the program fails to connect, the textblock must change to "Failed!". Otherwise, it changes to "Succeed."
Now for this simple problem, I wrote in my XAML:
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button x:Name="connecting" Content="Connect" FontWeight="Bold" Click="startConnection"
Width="60" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0"/>
<TextBlock x:Name="comm_stat" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Content}"/>
</Grid>
</Window>
And the C# code (inspired by this answer):
using System;
using System.Text;
using System.Windows;
using System.ComponentModel;
namespace WpfTest
{
public class DynamicObj : INotifyPropertyChanged
{
public DynamicObj() : this(string.Empty) { }
public DynamicObj(string txt) { Content = txt; }
private string _name;
public string Content
{
get { return _name; }
set {
_name = value;
OnPropertyChanged("Content");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
comm_stat.DataContext = new DynamicObj();
}
private void startConnection(object sender, RoutedEventArgs e)
{
comm_stat.Text = "Connecting...";
bool connect2device = false;
// do the handshaking operations. the result is then assigned to connect2device
comm_stat.Text = connect2device ? "Succeed." : "Failed!";
// some other operations
}
}
}
Now the problem is, whenever I click the button, no text is appeared in the textblock. Because the program waits for the startConnection method to reach its end and then updates the bonded textblock. But I want the textblock to change right after pressing the button. How can I do this?
You can use BackgroundWorker as such:
bool connect2device = false;
private void startConnection(object sender, RoutedEventArgs e)
{
comm_stat.Text = "Connecting...";
// do the handshaking operations. the result is then assigned to connect2device
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.RunWorkerCompleted += Completed;
worker.RunWorkerAsync();
}
private void Completed(object sender, RunWorkerCompletedEventArgs e)
{
comm_stat.Text = connect2device ? "Succeed." : "Failed!";
}
private void DoWork(object sender, DoWorkEventArgs e)
{
//Change with actual work.
Thread.Sleep(1000);
connect2device = true;
}
One side note is that you actually do not use bindings to change the text. comm_stat.Text = "Connecting..."; sets the text property directly and the DynamicObj object is not used at all. It might be good for you to read a few tutorial on MVVM.
I want to enable Drag'n'Drop of a child control inside a parent that has IsManipulationEnabled = true.
When manipulation is enabled, touch events don't get promoted to mouse events. In order to enable promotion, one should handle touch events before manipulation logic steps in (see example). I've tried that and it works... until I call DoDragDrop for the first time. Then I no longer receive mouse events. Why?
Here's a minimal code to reproduce the issue. All drag and drop handling was removed for the sake of readability.
XAML:
<Window x:Class="Test.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" IsManipulationEnabled="True">
<Grid>
<Border Background="Red"
x:Name="Border"
TouchDown="Border_OnTouchDown"
MouseDown="Border_OnMouseDown"
TouchUp="Border_OnTouchUp"
Width="100" Height="50" />
</Grid>
</Window>
C#:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Border_OnTouchDown(object sender, TouchEventArgs e)
{
Debug.WriteLine("Border_OnTouchDown");
e.Handled = true;
e.TouchDevice.Capture((FrameworkElement)sender);
}
private void Border_OnMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Border_OnMouseDown!");
DragDrop.DoDragDrop((DependencyObject)sender, "", DragDropEffects.All);
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
Debug.WriteLine("OnManipulationStarted");
base.OnManipulationStarted(e);
}
private void Border_OnTouchUp(object sender, TouchEventArgs e)
{
((FrameworkElement)sender).ReleaseTouchCapture(e.TouchDevice);
e.Handled = true;
}
}
Output:
Border_OnTouchDown
Border_OnMouseDown! <- works first time
Border_OnTouchDown
Border_OnTouchDown <- no longer works, no matter how many times I tap
Border_OnTouchDown
Border_OnTouchDown
Border_OnTouchDown
...
If I don't call DoDragDrop in MouseDown - events get promoted as they should.
Looks like this is a bug in .NET. I had v4.5.2 installed. Now I've installed v4.6 and the problem is no longer reproducible.
I didn't even have to retarget the project to v4.6 or recompile it: just installing the new runtime fixed everything.
This solution works for any framework version:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Border_OnTouchDown(object sender, TouchEventArgs e)
{
Debug.WriteLine("Border_OnTouchDown");
IsManipulationEnabled = false;
e.TouchDevice.Capture(Border);
}
private void Border_OnMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Border_OnMouseDown!");
DragDrop.DoDragDrop((DependencyObject)sender, "", DragDropEffects.All);
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
Debug.WriteLine("OnManipulationStarted");
base.OnManipulationStarted(e);
}
private void Border_OnTouchUp(object sender, TouchEventArgs e)
{
Border.ReleaseTouchCapture(e.TouchDevice);
IsManipulationEnabled = true;
}
}
Here we are basically disabling manipulation if the user touched the Border. Since drag&drop operation could (and probably would) end outside of the Border, we also need to capture the touch input to be sure to receive the TouchUp event to re-enable manipulation.
I have a WebBrowser control and I want to show some url by parameter on this control. Until the webbrower loaded the page, I need to show some progressbar or animation.
Please help me, here's what I have:
public partial class brw : PhoneApplicationPage
{
public brw()
{
InitializeComponent();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
string parameterValue = NavigationContext.QueryString["parameter"];
System.Uri uri = new System.Uri(parameterValue);
webbrowser.Source = uri;
}
private void WebBrowser_Navigating_1(object sender, Microsoft.Phone.Controls.NavigatingEventArgs e)
{
progressbar.Visibility = Visibility.Visible;
}
private void WebBrowser_Navigated_1(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
progressbar.Visibility = Visibility.Collapsed;
}
private void PhoneApplicationPage_Loaded_1(object sender, System.Windows.RoutedEventArgs e)
{
}
}
Thank you
This can be achieved by using LoadCompleted property.
XAML:
<ProgressBar x:Name="progressbar" IsIndeterminate="True"/>
<phone:WebBrowser x:Name="webbrw" IsScriptEnabled="True" LoadCompleted="yesLoaded"/>
.cs:
private void yesLoaded(object sender, NavigationEventArgs e)
{
this.progressbar.Visibility = Visibility.Collapsed;
this.progressbar.IsIndeterminate = False;
}
Have a look at this sample.
Hope it helps!
try this:
webbrowser.Navigate(new Uri(parameterValue));
instead of
webbrowser.Source = uri;
set Progressbar's Property IsIndeterminate="True"
<ProgressBar Foreground="Orange" x:Name="progressbar" Visibility="Collapsed" IsIndeterminate="True" Height="4" HorizontalAlignment="Left" Margin="10,66,0,0" VerticalAlignment="Top" Width="460" />