How to set logical focus without giving keyboard focus? - wpf

How can I in code define the logical focus of a container, or focused child, WITHOUT giving it keyboard focus ?
I just want to control which child will get the focus when the control will get focus through Tab key or clicking on part of the container that does not hit a child, but not give it (or steal) actual focus if it doesn't already have it.
And I also want that selecting a specific child through a keyboard gesture or clicking on it with the mouse is still possible.
I understand the difference in WPF between Keyboard Focus and Logical Focus, and the fact that for an element, having Keyboard Focus implies also having logical focus, but having logical focus does not imply that the element have keyboard focus.
I also understand attached property FocusManager.FocusedElement defines which element has logical focus in the visual tree starting at the element defining this property.
I’ve realized that this property is not used only when FocusManager.IsFocusScope is set to true, but also for containers such as GroupBox.
I’ve made many attempts, tons of searches and reading on WPF focus topic, but with no success so far, I don't understand what I'm missing:
Calling FocusManager.SetFocusedElement also give keyboard focus, and it I temporarily change the property Focusable of my child element to false just before, it only works the very first time when no child had focus before, but not after a child got focus
Handling events GotKeyboardFocus and PreviewGotKeyboardFocus at element or container to override initial focused element doesn’t work either, since I cannot tell whether the focus was obtained through the mouse or the keyboard, and whether focus got directly set to a child element or indirectly through the container.
An example to illustrate what I’m trying to achieve: I’ve a simple group of RadioButtons, and I want to control dynamically in code which option will get focus when user will “tab” to move focus to this GroupBox (typically the option that has isChecked=true).
<GroupBox Header="Options" Name="myGroupBox"
KeyboardNavigation.TabNavigation="Once"
KeyboardNavigation. DirectionalNavigation="Cycle" >
<StackPanel>
<RadioButton GroupName="g1" Name="opt1" Content="Option _1"/>
<RadioButton GroupName="g1" Name="opt2" Content="Option _2"/>
<RadioButton GroupName="g1" Name="opt3" Content="Option _3"/>
</StackPanel>
</GroupBox>
A final comment, I know how to implement a dynamic list of options using a ListBox, binding selectedItem of the list to a property of the data context, and then through styles and templates on ListBoxItem bind IsChecked property of the RadioButton in item template to IsSelcted property of its parent ListBoxItem, and it works, but for my specific case, I need to keep my RadioButtons directly bound to properties of my data context, I can’t bind them to IsSelected property of the list at the same time.

I know this is an old question but hopefully this answer will help others with a similar problem.
If I understand the problem correctly, you can achieve the desired behaviour by setting FocusManager.IsFocusScope="True" on the GroupBox, and hook up an event handler for the RadioButton.Checked event that sets the Logical Focus to the sender of the event:
Xaml:
<GroupBox Header="Options" Name="myGroupBox"
FocusManager.IsFocusScope="True"
RadioButton.Checked="MyGroupBox_OnChecked"
KeyboardNavigation.TabNavigation="Once"
KeyboardNavigation.DirectionalNavigation="Cycle">
<StackPanel>
<RadioButton GroupName="g1" Name="opt1" Content="Option _1"/>
<RadioButton GroupName="g1" Name="opt2" Content="Option _2"/>
<RadioButton GroupName="g1" Name="opt3" Content="Option _3"/>
</StackPanel>
</GroupBox>
Code behind:
private void MyGroupBox_OnChecked(object sender, RoutedEventArgs e)
{
var radioB = sender as RadioButton;
if (radioB != null)
FocusManager.SetFocusedElement(myGroupBox, radioB);
}

As the Document remarked, FocusManager.SetFocusedElement will give the specified element logical focus in the specified focus scope and will attempt to give the element keyboard focus.
I encountered the same problem, and I feel this attempt to give the element keyboard focus is rather not predictable. In some cases, the element which gets the logical focus will also get the keyboard focus. But in some other cases, it won't. I have not yet figured out the exact mechanism behind.
If the official documents say so, I reckon there may not be an "official" way to implement what you want. For now I am doing this by just invoking Keyboard.Focus upon the element you would like to maintain the keyboard focus after setting the logical focus.
An illustration of what I am explaining is as follows:
private void SomeMember(){
FocusManager.SetFocusedElement(focusScope, logicalFocus);
Keyboard.Focus(controlMaintainingKeyboardFocus);
}

Related

Refresh Scroll position of WPF ComboxBox on ItemSource's reload

I have a ComboBox which uses ListBox to show items. ListBox’s ItemSource is bound with a CollectionViewSource.
Issue: Once I open ComboBox and scroll through the items and leave it in middle or at bottom. Once I reopen ComboBox, even though I refresh or reload ItemSource (CollectionViewSource), the Scrollbar remains at the same place where I left it last time. I want it to be as default (at top) each and every time I reload ItemSource.
Is there is any way of doing this in XAML itself? Another thing, I cannot use Behavior or Attach property. I want any template or style for this.
In order for a Style on a ListBox to embody some behavior for the <ScrollViewer>, you would need to use an Attached Property / Attached Behavior to control the ScrollViewer's "grabber" position. This is because your collection is bound to your ListBox and notifying when it is updated needs to drive a behavior that isn't natively on the ListBox. It may be possible to reset the scroll position with a <ControlTemplate> for the <ScrollViewer> itself, but I imagine it would be difficult as it would likely involve manipulating Transforms / StoryBoards based on DataTriggering with your ItemsSource, but again that may cause a dependency on needing to use an Attached property, which for some reason you can't use...
If you simply want to get a result now, and you don't care about testability or re-usability, I would handle the TargetUpdated event in the code-behind. It's ultimately what the Attached Behavior would end up doing. On the other hand, if you do care about re-usability then you need to evaluate and challenge why you can't use an Attached Behavior (they are also testable, too); an Attached Behavior would be also easier than trying to edit a ControlTemplate.
Here is the code-behind approach:
.xaml:
<ListBox x:Name="myListBox"
ItemsSource="{Binding MyItemsSource, NotifyOnTargetUpdated=True}"
TargetUpdated="ListBox_TargetUpdated"/>
.xaml.cs:
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
if (myListBox.Items.Count > 0)
myListBox.ScrollIntoView(myListBox.Items[0]);
}
Edit: On the flip-side, if you are using MVVM, you can do something like this SO post suggests and set IsSynchronizationWithCurrentItem="True" and when you refresh your ItemsSource, simply set your SelectedItem to the first in the list and handle the SelectionChanged event in your vm.

How to override PreviewKeyDown on a TextBox?

I have a handler for the PreviewKeyDown event on a TextBox inside a control I made, which checks to see if the user has pressed the down key. The event handler correctly handles the key press when the control sits inside a layout container like a grid. If however, I place the control inside a DataGrid's DataGridTempalteColumn, the control does not do what I need it to do.
I think the issue is that because the PreviewKeyDown is on a Tunneling strategy, the host DataGrid gets to handle the down arrow key press before my control does. For the down arrow the DataGrid moves the focus to the next row. The DataGrid doesn't seem to be setting the IsHandled to true, because it the event eventually gets down to my control, but it does nevertheless do its own thing on the event, which breaks things for me.
The issue isn't really with the DataGrid, but with the fact that my control has a tunneling PreviewKeyDown event from the TextBox. I'm looking for a way to override this default event on the TextBox. Perhaps there's something I can do with attached behaviors? Maybe I need to inherit from the TextBox and then override? So far I've not found anything that indicates how to handle a situation like this.
Below is the original text for this question that didn't yield any answers
I'm having difficulty using a custom autocomplete text box I made as a DataTemplate in a DataGridTemplateColumn.
<DataGrid.Columns>
<DataGridTemplateColumn Header="Material" Width="300">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<local:actextbox Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
MatchList="{Binding Path=DataContext.LaborTemplatesList, RelativeSource={RelativeSource AncestorType=UserControl, AncestorLevel=2}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
The actextbox class derives from user control and has event handlers to respond to certain key presses like so
private void myTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down & myPopup.IsOpen == true)
{
myPopUpList.SelectedIndex = 0;
ListBoxItem lbi = (ListBoxItem)myPopUpList.ItemContainerGenerator.ContainerFromItem(myPopUpList.SelectedItem);
lbi.Focus();
e.Handled = true;
}
}
The intent is that when an autocomplete popup is displayed, pressing down and up allows the user to navigate its contents. This works as expected when the control is placed in a hierarchy of layout containers; however, when it is part of the cell in a datagrid the expected behaviour is lost. Looks like the previewKeyDown is used by the DataGrid to apply its own interpretation of the down or up arrows, and while it does not set the event as handled, by the time the event gets down to my control focus is lost, and different row is selected.
I've looked online all over, and wasn't able to find any clues on how to handle this. Certainly, I've seen controls inside DataGridTemplateColumns handle all sorts of inputs, but how they accomplish this is lost on me.
OK, following some advice to use Snoop I figured out what was happening. In fact the issue was not that the DataGrid was doing something with the PreviewKEyDown event, but that I was moving focus away from the data grid cell that was presently being edited triggering a CellEditEnding event. This resulted in the behaviour I was observing.

How can I tell when the default textbox context menu is about to open or close?

How can I tell when the default textbox context menu is about to open (before it opens) or after it has closed (not before, after)? The ContextMenuOpening and ContextMenuClosing events don't seem to fire when I'm using the standard, built-in menu. I'm guessing I could simply recreate the menu and populate it with standard commands, but that does seem to be a bit overkill.
The reason for this specifically, is I have a templated control that has a textbox swapped in when in 'Edit' mode. That control automatically drops out of edit mode when the textbox loses focus. Problem is when the context menu pops up, the textbox loses focus, and thus it drops out of edit mode, and the context menu disappears instantly.
What I want to do is just before the context menu opens, set a flag to short-circuit LostFocus event code on the textbox. Then after the context menu closes, I need to clear that flag but I also need to detect if the control that now has the focus is still the textbox, and if not, then process the code as if it did lose focus. (Alternately I could test an event prior to it closing if I knew which control will have the focus once it does close. It would achieve the same effect.)
This is needed to handle the specific case if someone shows the context menu (and as such the textbox technically doesn't have focus anymore) but then clicks elsewhere in the UI which dismisses the context menu, because I then need to detect that the textbox has in fact lost focus and as such the control should drop out of edit mode. But if the user dismisses the context menu by clicking back in the textbox, then I don't want that LostFocus event to fire.
Make sense?
M
UPDATE: Technically this question wasn't answered although I marked it as such since the responders did help me solve my problem. But as for the actual question here, it looks like the answer is 'You can't'.
The good news is since the default textbox context menu is just three standard items, it's easy to duplicate by adding this to the resources somewhere...
<ContextMenu x:Key="DefaultTextBoxContextMenu">
<MenuItem Command="ApplicationCommands.Cut" />
<MenuItem Command="ApplicationCommands.Copy" />
<MenuItem Command="ApplicationCommands.Paste" />
</ContextMenu>
...and attach it like this...
<TextBox x:Name="EditTextBox"
ContextMenu="{StaticResource DefaultTextBoxContextMenu}"
ContextMenuOpening="EditTextBox_ContextMenuOpening"
ContextMenuClosing="EditTextBox_ContextMenuClosing" />
...then your eventing works as you would expect. Still odd if you ask me, but a trivial work-around anyway so I won't complain.
M
When the textbox's context menu opens the textbox loses keyboard focus, but retains logical focus. In other words, its LostKeyboardFocus event will fire but its LostFocus event will not. The textbox's IsFocused property remains true when the context menu is open. Based on your description of the problem, you should be able to rely on logical focus to determine when to end "edit mode" in your control.
By default a TextBox still reports true for IsFocused while the default context menu is open but reports false for IsKeyboardFocused. That is to say, LostFocus is not raised on the TextBox when the context menu is opened but will be raised if the context menu is closed by selecting some other control. This sounds like precisely the behavior you are looking for.
You can show this default behavior with a small test program:
<Grid>
<StackPanel>
<TextBox Text="Some text one"
GotFocus="TextBox_GotFocus"
LostFocus="TextBox_LostFocus"/>
<TextBox Text="Some text two"/>
</StackPanel>
</Grid>
and the code-behind:
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
Debug.WriteLine("GotFocus");
}
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
Debug.WriteLine("LostFocus");
}
If you are not getting this default behavior in the context of your larger application, then there might be a focus scope issue interfering.

Can/Should MouseEnter event bubble?

Is it possible under any circumstances to get MouseEnter event bubbling?
MSDN says it's an attached event with Direct routing strategy, which technically excludes such possibility. I've a fairly complex control (in essence a hierarchy consisting of grids, stackpanels and content controls). I seem to get MouseEnter event propagated from bottom up, here's the debug dump taken from OnMouseEnter handler (I've the same custom control included at different levels of the hierarchy, which handles MouseEnter, so I have a central place for listening that event):
In: parent:s7b, timestamp:37989609
In: parent:s2, timestamp:37989609
In: parent:Root, timestamp:37989609
s7b, s2 and Root are FrameworkElement names and timestamp is e.Timestamp from MosueEnter event.
Provided that the Routing Strategy is Direct, how does WPF decide on event originator? Does it traverse the visual tree until the first FrameworkElement with attached MouseEnter event is found?
While I'm working on a minimalistic repro set for the problem, could anyone suggest what could cause the behaviour?
And here's the repro:
Create two custom controls, one is a contant control, another is event receiver.
1.1. MyContentControl
Code:
public class MyContentControl : ContentControl
{
static MyContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyContentControl),
new FrameworkPropertyMetadata(typeof(MyContentControl)));
}
protected override void OnMouseEnter(MouseEventArgs e)
{
if (e.Source == e.OriginalSource
&& e.Source is MyContentControl)
{
Debug.Write(string.Format("mouseenter:{0}, timestamp:{1}\n",
(e.Source as MyContentControl).Name,
e.Timestamp));
}
base.OnMouseEnter(e);
}
}
XAML:
<Style TargetType="{x:Type local:MyContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyContentControl}">
<StackPanel Orientation="Horizontal">
<local:MouseEventReceiver />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
1.2 MouseEventReceiver
Code:
public class MouseEventReceiver : Control
{
static MouseEventReceiver()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MouseEventReceiver),
new FrameworkPropertyMetadata(typeof(MouseEventReceiver)));
}
}
XAML:
<Style TargetType="{x:Type local:MouseEventReceiver}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="LightGray" Width="20" Height="20" Margin="5"></Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Finally the markup of my test harness:
XAML:
<Window x:Class="MouseTricks.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MouseTricks"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:MyContentControl x:Name="c1">
<local:MyContentControl x:Name="c2">
<local:MyContentControl x:Name="c3" />
</local:MyContentControl>
</local:MyContentControl>
</Grid>
</Window>
In order to reproduce the problem just hover over the right most gray square and watch the Debug Output window, you'll see three entries in there, while I'm expecting just one.
Cheers.
Perhaps a more detailed description will help. In the MSDN article on Mouse.MouseEnter the following quote is made:
Although this event is used to track when the mouse enters an element, it is also reporting the IsMouseOver property has changed from false to true on this element
MSDN says that Mouse.MouseEnter fires when IsMouseOver changes from false to true. Looking at the MSDN article for IsMouseOver the following quote is made:
Gets a value that indicates whether the mouse pointer is located over this element (including visual children elements that are inside its bounds)
As we both agree, the null background does not support interaction. There are a lot of caveats to the null background issue with regards to IsMouseOver, but it is obvious from practical application that this value does not get switched for a null background. However, the definition does say that if the mouse is "located over" any visual child within the bounds of the element then IsMouseOver will change barring several strange caveats. However, the null background is not one of these caveats.
A quick look at the visual tree of your control using the snoop utility or VisualTreeHelper shows that all three gray grids are the visual children of c1, the two rightmost grids are visual children of c2, and the rightmost grid is a visual child of c3. This is as would be expected since all of your content controls are nested within each other.
By monitoring the IsMouseOver for c1 property you can easily see that when the mouse touches a gray square the property value changes to true. You can verify this by adding a callback to the main window's mouse move event. I used the following callback:
private void MouseMove_Callback(Object sender, MouseEventArgs e)
{
if (c1.IsMouseOver)
MessageBox.Show("Mouse is Over c1!");
}
You will notice that no matter which of the three gray squares that you are over the IsMouseOver for c1 is set to true. This indicates that IsMouseOver changes to true for c1 when it is over any of the three squares and so the claims that MSDN makes are true. MouseEnter should and does get fired for c1 no matter which gray square you touch since all three gray squares are in c1s visual tree and are not eliminated from mouse hit testing by a caveat (such as the null background caveat).
The MouseEnter event is responding as a direct event in your application just as MSDN claims.
Since this is a complex control it seems likely that when you are entering the Root element with the mouse you are also entering s7b and s2 at the same time. Since all three elements are registered for the MouseEnter event they should all respond at exactly the same time if it is possible for the mouse to enter all three elements simultaneously.
It probably appears that the event is bubbling up the visual tree because you happen to have MouseEnter registered for a line of visual parents of similar size. If I define a Button in a StackPanel with the button stretching to fill the StackPanel and register both for the MouseEnter event then whenever the mouse enters the Button it will by default also be entering the parent (the StackPanel). In this case it may look like the event is bubbling up the visual tree when in fact it is just a direct event for two separate elements that is occurring simultaneously.
If you are creating a complex control then usually you would want one MouseEnter callback for the entire control or specific MouseEnter callbacks for specific pieces of the control. Are you sure that you need callbacks for the entire control as well as individual parts of the control?
-Edit
Just saw your new post. I tried your code and I noticed that the content MyContentControl instances are all nested. Since the MyContentControl class derives from content control the controls are being stretched to fit the available space. You can see this by adding a border property to the MyContentControl class. Since the background of MyContentControl is null by default MouseEnter only gets fired when one of the gray boxes is touched.
The first MyContentControl creates a horizontal Stackpanel and adds the gray box and then a content presenter. Anything to the right of the grid with the first gray box will automatically be in c2 and/or c3 because the content presenter from c1 will be stretched to fit the size of the window which has a fixed height and width. This is why when you hover over c2 you get the MouseEnter for c1 and c2 because when the gray box is touched the mouse has entered the content presenter of c1 and the mouse has also entered the gray box of c2. Similar logic can be used to understand the case for c3.
Mouse transparent controls (MTC) (I'd tend to call them layout controls) having mouse opaque children (MOC) can't handle mouse events correctly.
I could be wrong, but it looks like a bug to me. I can guess that the culprit is the fact that MTCs are incapable of handling mouse input but pretend to do so rather inconsistantly.
Due to the virtue of attached events, MTCs become Source & OriginalSource of mouse events, also their IsMouseOver gets set to true, which doesn't get on well with other parts of the system.
The workaround is - do subscribe only mouse opaque parts of your controls to mouse events. Sounds horrible at first glance, but at you shouldn't lose much of flexibility provided you use commands.
Any suggestions are highly appreciated.

WPF Button, Command Target, Binding Question

I have a DesignerCanvas (derived from canvas) that I can add UIElements to, then drag\drop\move\group and move them around.
On the toolbar I have a button that is bound to a group command.
<Button Margin="0,3,0,3" Padding="5" HorizontalContentAlignment="Left"
Command="{x:Static s:DesignerCanvas.Group}"
CommandTarget="{Binding ElementName=DesignerCanvas}">
The problem that I have is that I can have a control that also contains a DesignerCanvas. So there are nested canvas's, and I want the GroupCommand on the toolbar to apply to the canvas that is in focus. In the above binding it is binding only to the root canvas.
I suppose I could track the current canvas and expose it in the viewmodel for the binding, but I'd prefer to avoid tracking the activecanvas.
Any suggestions for a creative binding here?
Thanks,
jeff
Is GroupCommand a RoutedCommand? Assuming it is, I would expect that you would get the behavior you want by removing the CommandTarget property assignment.

Resources