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.
Related
I'm relatively new to WPF and I am struggling to manage the focus of an element at runtime.
I have a simple user control with a TextBox inside
<UserControl [...]
IsVisibleChanged="UserControl_IsVisibleChanged">
[...]
<TextBox x:Name="myTextBox" [...] />
</UserControl>
That I added on my WPF window
<ctrl:MyPanel
x:Name="myPanel"
Visibility="{Binding MyBooleanProperty}"
Panel.ZIndex="999" />
MyBooleanProperty is changing at runtime under some logic and the panel is showing up accordingly.
I need to have keyboard focus on myTextBox everytime myPanel becomes visible so user can enter data without using mouse, tab key or anything else.
Here's the logic on the event handler of IsVisibleChanged
private void UserControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (this.Visibility == Visibility.Visible)
{
myTextBox.Focus();
myTextBox.SelectAll();
}
}
This works, but if I click any button on the window before myPanel becomes visible then I cannot set focus in myTextBox.
I've tried many things, for example setting
Focusable="False"
on the buttons with no luck.
Thanks in advance for your help!
After a little more searching I found a workaround based on this answer by Rachel:
Dispatcher.BeginInvoke(DispatcherPriority.Input,
new Action(delegate () {
myTextBox.Focus();
Keyboard.Focus(myTextBox);
myTextBox.SelectAll();
}));
Delegating the focus action actually works.
I have a Page, inside I generate a few copies of a UserControl. The page is supposed to do stuff
when the UserControl_MouseDown event is fired. Everything works fine until the user clickes
on a part of the UserControl where a Control is present. As you would expect, the Control
prevents the UserControl_MouseDown event to fire.
Page
//Fired when the StackPanel is clicked but not when the TextBox is.
private void UserControl_MouseDown(object sender, MouseButtonEventArgs e)
{
//Do stuff
}
UserControl
<StackPanel>
<TextBox/>
</StackPanel>
As controls tend to swallow events you could rather use the PreviewMouseDown-Event:
<UserControl PreviewMouseDown="UserControl_MouseDown">
<StackPanel>
<TextBox />
</StackPanel>
</UserControl>
Read this: https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.previewmousedown?view=netframework-4.8
I have an image which displays a delete button when tapped. What I need is for the delete button to disappear when the image has LostFocus.
Typically, for say a textbox I'd just use something like the following.
tb.LostFocus += tbOnLostFocus;
private void tbOnLostFocus(object sender, RoutedEventArgs e)
{
delBtn.Visibility = Visibility.Collapsed;
}
My issue is that the same code just isn't firing on an image. I vaguely remember reading somewhere a while ago that LostFocus events wont fire on an image as it isn't a focusable element. Not sure if my memory is correct as I can't find a reference to it now.
Has anyone found a suitable workaround or managed to achieve a similar result?
You can achieve this by using MenuFlyout. Once the image is tapped it will show delete button. if the pointer is tapped anywhere other than clicking on delete button it will be collapsed
<Image Source="ms-appx:///Assets/1.jpg" Tapped="Image_Tapped">
<Image.Resources>
<MenuFlyout x:Name="DeleteMenuFlyout">
<MenuFlyout.Items>
<MenuFlyoutItem x:Name="delete" Click="Delete_Click" Text="Delete" />
</MenuFlyout.Items>
</MenuFlyout>
</Image.Resources>
</Image>
//C#
private void Image_Tapped(object sender,TappedRoutedEventArgs e)
{
DeleteMenuFlyout.ShowAt(sender as FrameworkElement);
}
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...
Here I have sample window with a grid. I need to capture event when key is pressed. But it is not raising when I click grid area and then press key. It will work only if Textbox is focused. I know it will work if I capture it from Window. But I have other application with few usercontrols and I need to capture it from distinct ones. I tried to set Focusable.false for Window and true for Grid but it not helps.
Any solutions?
<Window x:Class="Beta.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Closed="Window_Closed_1" Focusable="False">
<Grid KeyDown="Grid_KeyDown_1" Focusable="True">
<TextBox x:Name="tbCount" HorizontalAlignment="Left" Height="35" Margin="310,49,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="83"/>
</Grid>
Right this is weird. This is clearly a focus problem, still I can not understand why the grid do not take Focus, even when we click on it.
Though there is a workaround: create an handler for the loaded event of the grid:
<Grid x:Name="theGrid" KeyDown="Grid_KeyDown_1" Focusable="True" Loaded="TheGrid_OnLoaded">
And then force focus in your code behind:
private void TheGrid_OnLoaded(object sender, RoutedEventArgs e)
{
theGrid.Focus();
}
Your keydown event will work after that.
Hope it helps.
I had the same issue with a Universal Windows Platform (UWP) app. I attached the event to a grid in XAML but it would only work when the focus was on the TextBox. I found the answer (not just a workaround) on MSDN: https://social.msdn.microsoft.com/Forums/en-US/56272bc6-6085-426a-8939-f48d71ab12ca/page-keydown-event-not-firing?forum=winappswithcsharp
In summary, according to that post, the event won't fire because when focus to the TextBox is lost, it's passed higher up so the Grid won't get it. Window.Current.CoreWindow.KeyDown should be used instead. I've added my event handlers to the page loaded event like this:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
Window.Current.CoreWindow.KeyDown += coreWindow_KeyDown;
Window.Current.CoreWindow.KeyUp += CoreWindow_KeyUp;
}
This works as expected for me.
I tried using the Focus Method too, to no avail, until I set the Focusable property to true ( It was default to False. )
I had the same problem, I've used PreviewKeyDownevent and it worked for me.