Is it possible to detect Keyboard focus events globally? - wpf

The following events can be used, but, they must be attach for each element:
GotKeyboardFocus, LostKeyboardFocus
Is there a way in .NET WPF to globally detect if the focused element changed ? without having to add event listeners for all possible elements ?

You can do this in any class with this:
//In the constructor
EventManager.RegisterClassHandler(
typeof(UIElement),
Keyboard.PreviewGotKeyboardFocusEvent,
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
...
private void OnPreviewGotKeyboardFocus(object sender,
KeyboardFocusChangedEventArgs e)
{
// Your code here
}

You can hook to the tunneling preview events:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525"
PreviewGotKeyboardFocus="Window_PreviewGotKeyboardFocus"
PreviewLostKeyboardFocus="Window_PreviewLostKeyboardFocus">
....
This way, as shown above, the window would be notified before all descendants when any of the descendants gets or loses the keyboard focus.
Read this for more information.

You can add a routed event handler to your main window and specify you're interested in handled events.
mainWindow.AddHandler(
UIElement.GotKeyboardFocusEvent,
OnElementGotKeyboardFocus,
true
);

Have a look at how Microsoft trigger CommandManager.RequerySuggested event when focus changes: they subscribe to InputManager.PostProcessInput event.
ReferenceSource
Simple example:
static KeyboardControl()
{
InputManager.Current.PostProcessInput += InputManager_PostProcessInput;
}
static void InputManager_PostProcessInput(object sender, ProcessInputEventArgs e)
{
if (e.StagingItem.Input.RoutedEvent == Keyboard.GotKeyboardFocusEvent ||
e.StagingItem.Input.RoutedEvent == Keyboard.LostKeyboardFocusEvent)
{
KeyboardFocusChangedEventArgs focusArgs = (KeyboardFocusChangedEventArgs)e.StagingItem.Input;
KeyboardControl.IsOpen = focusArgs.NewFocus is TextBoxBase;
}
}
This also works in multi-window applications.

Related

Where to unsubcribe from an event for a custom control

I am wondering at what point do I unsubscribe from an event, up to this point I was usually unsubscribing on the line before subscribing (or most of the time the event was called from xaml, which then is handled by xaml and there is no need to do any extra work).
But now I'm in a situation when I want to subscribe at the constructor so, where do I unsubscribe? I tried to do it inside unloaded event,
but my control is often unloaded and then loaded again without recreating it.
Edit
To make it clear I want to unsubscribe when the object is not needed anymore, I was hoping that there is Dispose method I can override or something like this.
Any ideas?
Sample code
public class MYListBox : ListBox
{
public MYListBox()
{
SelectionChanged += MYListBox_SelectionChanged;
Unloaded += MYListBox_Unloaded;
}
private void MYListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void MYListBox_Unloaded(object sender, RoutedEventArgs e)
{
SelectionChanged -= MYListBox_SelectionChanged;
Unloaded -= MYListBox_Unloaded;
}
}
xaml
<UserControl x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1">
<wpfApplication1:MYListBox />
</UserControl >
So sometimes, I will allow the user navigate somewhere else and then allow it to comeback to the same instance. Another time when the user navigates when he comes back it will be a new instance. So at the second situation I thought I need to unsubcribe from that event.
Thank you :)
It looks like you've used ListBox as a base class, is there a reason you can't just override the SelectionChanged event and not deal with the event subscriptions?
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
// Do custom work.
// Call base class implementation.
base.OnSelectionChanged(e);
}

Control Initialization Order Fiasco

Consider the following code:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Slider ValueChanged="slider_ValueChanged/>
<TextBox x:Name="counter"/>
</StackPanel>
</Window>
and
namespace Project1
{
public partial class Window1 : Window
{
public MainWindow() { InitializeComponent(); }
void slider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
counter.Text = e.NewValue.ToString();
}
}
}
Slider will raise its ValueChanged event during initialization while counter is still null.
This is an example of a larger problem that I've been running into using WPF, that UI events can fire at any time, and that there is no single place where I can put my initialization code so that it's guaranteed to run after all the pointers owned by the WPF system have been initialized but before any UI events have fired.
What is the most elegant way to deal with this? The fact that this specific example should use data binding is beside the point.
There are many ways to deal with this, depending on your situation
First off, you could simply recognize the fact that the object might not be initialized and check for that before processing. For example,
if (counter.Text != null)
counter.Text = e.NewValue.ToString();
Second, you could attach your events in the Loaded event of the object so they don't fire until after the object has been initialized.
void Counter_Loaded(object sender, EventArgs e)
{
slider.ValueChanged += Slider_ValueChanged;
}
void Counter_Unloaded(object sender, EventArgs e)
{
slider.ValueChanged -= Slider_ValueChanged;
}
And last of all, you can use WPF's Dispatcher to run events on the UI thread at a different DispatcherPriority. The default is Normal, which runs after Loaded, Render, and DataBind operations
Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
new Action(delegate() { counter.Text = e.NewValue.ToString(); }));
The true answer to this question is to use the MVVM pattern where window code behind files contain little to no initialization code.
In this pattern, the UI is connected to the rest of the code with data binding only. You write special view-model classes that implement INotifyPropertyChanged and take your business logic and expose it as a series of properties that UI binds to.
Naturally, you fully control how your view-models initialize.

Silverlight: How to Prevent Routing a MouseMove Event from a Child Canvas to Its Parent Canvas

I have my XAML code:
<Canvas x:Name="mainCanvas" Width="200" Height="150" Background="LightGray"
MouseLeftButtonUp="mainCanvas_MouseLeftButtonUp"
MouseMove="mainCanvas_MouseMove">
<Canvas x:Name="topCanvas" Width="200" Height="100" Background="LightBlue"
MouseLeftButtonUp="topCanvas_MouseLeftButtonUp"
MouseMove="topCanvas_MouseMove">
</Canvas>
</Canvas>
and its code behind:
private void topCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("topCanvas_MouseLeftButtonUp");
e.Handled = true; // This can prevent routing to the mainCanvas
}
private void mainCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("mainCanvas_MouseLeftButtonUp");
}
private void topCanvas_MouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("topCanvas_MouseMove");
// How to prevent routing to the mainCanvas?
// e.Handled = true does NOT exist in MouseEventArgs
}
private void mainCanvas_MouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("mainCanvas_MouseMove");
}
My question is already in the comments.
How to prevent routing the MouseMove event from the topCanvas (the child canvas) to the mainCanvas (parent canvas)?
Thanks.
Peter
Try setting the IsHitTestVisible property of your Canvas. With that property set accordingly mouse events will go either "through" your control or will be caught by it.
Hope this is what you need.
You can try comparing e.OriginalSource in mainCanvas's MouseMove Event and exit the Sub if it wasn't originated from the mainCanvas.
private void mainCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (sender != e.OriginalSource)
return;
}
In replying to your comment in a little more detail. According to the UIElement.MouseMove Event MSDN link.
Controls that inherit MouseMove can provide handling for the event
that acts as handler for all instances, by overriding the OnMouseMove
method. As with direct handling of the event, there is no Handled
property available, so OnMouseMove cannot be implemented in such a way
that it suppresses further handling of the event through the Handled
technique.
and this link states:
This event creates an alias for the Mouse.MouseMove attached event for
this class
Which brings us to this link on AttachedEvents which states.
Extensible Application Markup Language (XAML) defines a language
component and type of event called an attached event. The concept of
an attached event enables you to add a handler for a particular event
to an arbitrary element rather than to an element that actually
defines or inherits the event. In this case, neither the object
potentially raising the event nor the destination handling instance
defines or otherwise "owns" the event.
So as I see it, your only option is to code around it.
The functionality is called "Event Bubbling". You can stop it using below code:
jQuery:
event.stopPropagation();
Ref: http://api.jquery.com/event.stopPropagation/
You can also try below code:
e.stopPropagation(); //to prevent event from bubbling up
e.preventDefault(); //then cancel the event (if it's cancelable)
Ref: http://stackoverflow.com/questions/1967537/how-to-stop-event-bubbling-with-jquery-live
Thanks,
Ravi Verma

What's special about mouse capture and middle mouse button in WPF?

When I call CaptureMouse() in response to a MouseDown from the middle mouse button, it will capture and then release the mouse.
Huh?
I've tried using Preview events, setting Handled=true, doesn't make a difference. Am I not understanding mouse capture in WPF?
Here's some minimal sample code that reproduces the problem.
// TestListBox.cs
using System.Diagnostics;
using System.Windows.Controls;
namespace Local
{
public class TestListBox : ListBox
{
public TestListBox()
{
MouseDown += (_, e) =>
{
Debug.WriteLine("+MouseDown");
Debug.WriteLine(" Capture: " + CaptureMouse());
Debug.WriteLine("-MouseDown");
};
GotMouseCapture += (_, e) => Debug.WriteLine("GotMouseCapture");
LostMouseCapture += (_, e) => Debug.WriteLine("LostMouseCapture");
}
}
}
Generating a default WPF app that has this for its main window will use the test class:
<Window x:Class="Local.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Local"
Title="MainWindow" Height="350" Width="525">
<local:TestListBox>
<ListBoxItem>1</ListBoxItem>
<ListBoxItem>2</ListBoxItem>
<ListBoxItem>3</ListBoxItem>
<ListBoxItem>4</ListBoxItem>
</local:TestListBox>
</Window>
Upon clicking the middle button down, I get this output:
+MouseDown
GotMouseCapture
LostMouseCapture
Capture: True
-MouseDown
So I'm calling CaptureMouse, which in turn grabs and then releases capture, yet returns true that capture was successfully acquired.
What's going on here? Is it possible that this is something with my Logitech mouse driver doing something goofy, trying to initiate 'ultrascroll' or something?
This can be diagnosed by setting your debugger to break on UIElement.ReleaseMouseCapture() method and looking at the call stack. If you do this you will find that it is ListBox's OnMouseMove that is causing the problem.
So all you have to do to is override OnMouseMove and not call the base class if the middle button is down:
public class TestListBox : ListBox
{
protected override void OnMouseMove(MouseEventArgs e)
{
if(Mouse.MiddleButton!=MouseButtonState.Pressed)
base.OnMouseMove(e);
}
}
I found someone else had run into the same problem and narrowed it down to a specific issue with ListBox.
http://social.msdn.microsoft.com/Forums/en/wpf/thread/5487c21a-1527-4a4f-bdf5-62de921d2ae0?prof=required
If I switch to a Canvas then it works as I expect. So the ListBox is doing something with capture. Handling things via Previews with Handled=true and even overriding OnGotMouseCapture etc. without calling the base does not work around the issue.

Silverlight MouseLeftButtonDown event not firing

I can get MouseEnter, MouseLeave, and Click events to fire, but not MouseLeftButtonDown or MouseLeftButtonUp.
Here's my XAML
<UserControl x:Class="Dive.Map.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Canvas x:Name="LayoutRoot" MouseLeftButtonDown="LayoutRoot_MouseLeftButtonDown">
<Button x:Name="btnTest" Content="asdf" Background="Transparent" MouseLeftButtonDown="btnTest_MouseLeftButtonDown"></Button>
</Canvas>
</UserControl>
And here's my code
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void btnTest_MouseLeftButtonDown( object sender, MouseButtonEventArgs e )
{
btnTest.Content = DateTime.Now.ToString();
}
private void LayoutRoot_MouseLeftButtonDown( object sender, MouseButtonEventArgs e )
{
e.Handled = false;
}
}
What am I doing wrong?
The Button control (or more specifically the ButtonBase super-class from which it derives) handles the MouseLeftButtonDown event itself in order to generate the Click event. Hence you cannot get a MouseLeftButtonDown event from the standard Button.
Is there a reason you aren't using the Click event?
If you add the handlers with code like this:
dgv.AddHandler(DataGrid.MouseLeftButtonDownEvent, new MouseButtonEventHandler(dgv_MouseLeftButtonDown), true);
dgv.AddHandler(DataGrid.MouseLeftButtonUpEvent, new MouseButtonEventHandler(dgv_MouseLeftButtonUp), true);
and make sure you pass true in the last argument "HandledEventsToo" I think you will get it to work - I did...
Have you ever noticed that the MouseLeftButtonDown and MouseLeftButtonUp events are not fired when a Silverlight button is clicked? The reason for this is that the button handles these two events itself by overriding the OnMouseLeftButtonDown and the OnMouseLeftButtonUp handlers. In the OnMouseLeftButtonDown override, the Click event is raised and the MouseLeftButtonDown event is marked as handled so it couldn't bubble in the visual tree. The OnMouseLeftButtonUp override also marks the MouseLeftButtonUp as handled.
This thing can be changed using the ClickMode property of the Button control. It has the following values - Hover, Press, Release. The default one is Pressed and we have already explained it. When we have ClickMode set to Release, the Click event will be raised in the OnMouseLeftButtonUp override and the MouseLeftButtonDown and MouseLeftButtonUp events will be handled inside the button again. If we set the ClickMode to Hover, the Click event will be raised with the MouseEnter event and we will also be able to use the mouse button events.

Resources