How to override PreviewKeyDown on a TextBox? - wpf

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.

Related

How to set logical focus without giving keyboard focus?

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);
}

Explicit Binding UpdateSource() Doesn't Work Until Mouse Hovers Over TextBox

So I'm calling the UpdateSource() method on Text property of TextBox in code behind. The ErrorTemplate should come up, but it doesn't until I move my mouse over the TextBox.
Or maybe it does but doesn't get repainted? Any ideas how to fix this to update GUI instantly?
EDIT:
It is PropertyChanged. The problem is not with updating source. The problem is that when source updates, it causes validation and the ErrorTemplate should come up, but it doesn't until I move my mouse over validated TextBox.
EDIT:
Appearently it does update when I move my mouse over some other GUI elements as well (like radio button), which doesn't have anything to do with validation. This is definitely an issue of repainting or binding validation error check trigger. How can I trigger that in code behind?
Binding to Text property of 'TextBox' control is quite specific. UpdateSourceTrigger is set to "LostFocus" by default. It is for improving performance. Try to change it to PropertyChanged.
You may be able to get Validation_Error to fire.
<TextBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="fieldValue" BorderBrush="SteelBlue" BorderThickness="2" TextWrapping="Wrap"
Text="{Binding Path=DF.FieldValue, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True, UpdateSourceTrigger=Explicit}"
Validation.Error="Validataion_Error"
LostFocus="fieldValue_LostFocus" KeyUp="fieldValue_KeyUp"/>
private void Validataion_Error(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
{
MessageBox.Show(e.Error.ErrorContent.ToString(), "Fatal Update Error");
}
}
It may be that since that event is on the TextBox is only fires when the TextBox has focus. You may need to throw a custom event and handle it at the Page/Window.

AutoCompleteBox with TextChanged event not selecting properly

Hi I'm using an AutoCompleteBox like this
<!-- XAML Code -->
<sdk:AutoCompleteBox Grid.Row="2"
FilterMode="None"
ItemsSource="{Binding Customers}"
SelectedItem="{Binding Path=SelectedCustomer, Mode=TwoWay}"
Text="{Binding CustomerSearchString, Mode=TwoWay}"
ValueMemberBinding="{Binding Path=FullName}"
ValueMemberPath="FullName"
TextChanged="{ext:Invoke MethodName=Search, Source={Binding}}"/>
C# part:
// Search Method in the viewmodel
public void Search()
{
var customerOperation = _context.Load(_context.GetCustomerByNameQuery(CustomerSearchString));
customerOperation.Completed += (s, e) => Customers = new List<Customer>(customerOperation.Entities);
}
In my app to quick-search for customers for an fast and uncomplicated method to search. I get it to display everything correctly in the dropdown, and when I select with the mouse it works perfectly.
But when I press ArrowDown, you see the text come up for a split-second but then it reverts and puts the cursor back in the textbox instead of selecting the first entry down. I tried using the TextInput event, but that one won't fire.
How can I avoid this behaviour?
SOLUTION:
The problem was, that the TextChanged event got fired when the user selected an entry, creating some sort of race condition like behaviour where the Text got reset. The solution was to use the KeyUp event (don't use KeyDown, because the Text property won't be updated yet). This event doesn't get triggered when the user selects something, solving the problem.
Final code (ViewModel unchanged):
<!-- XAML Code -->
<sdk:AutoCompleteBox Grid.Row="2"
FilterMode="None"
ItemsSource="{Binding Customers}"
SelectedItem="{Binding Path=SelectedCustomer, Mode=TwoWay}"
Text="{Binding CustomerSearchString, Mode=TwoWay}"
ValueMemberBinding="{Binding Path=FullName}"
ValueMemberPath="FullName"
KeyUp="{ext:Invoke MethodName=Search, Source={Binding}}"/>
Thanks everyone!
Add a handler like this in code:
KeyEventHandler eventHandler = MyAutoCompleteBox_KeyDown;
MyAutoCompleteBox.AddHandler(KeyDownEvent, eventHandler, true);
I'm not understanding why you are using a TextChanged event...? What is that for? If you take that out, does it work? I use an autocomplete box in my project and I don't need a search method... all I do is just supply a list of objects to the autocompletebox and the it searches that list when the user types. I can select either by mouse or by up/down arrows. The only thing that I can think of is that each time you try to use the up/down arrow the text changes and fires off the search function and closes the selection option drop down...

WPF AutoCompleteBox in DataGridTemplateColumn keyboard navigation issues

I'm using an AutoCompleteBox from the WPF Toolkit inside a DataGridTemplateColumn's editing template in WPF4. It works well enough for the most part once I sorted out all the niggling binding issues around DataGrid binding and also AutoCompleteBox's own gotchas and incompleteness. So far so good. The problem is keyboard navigation.
This is the scenario: there's a DataGrid with two columns. The first is a DataGridTemplateColumn which has an AutoCompleteBox in its editing template. The second is just an ordinary DataGridTextColumn.
If I invoke editing of a row, I can choose an item in the AutoCompleteBox. I press tab to move to the next column, but instead the row edit gets committed, and the keyboard focus doesn't move to the next column. If this was a DataGridTextColumn, it would stay in edit mode and let me edit the next column. This also happens for new rows.
To my mind it seems like there's something wrong with where WPF decides to send the keyboard focus when it comes out of the Autocompletebox, but I can't figure out what I can do about it, and I also haven't been able to find anybody talking about the same problem, which suggests either I'm doing something wrong or nobody else cares about keyboard navigation through their grids. I have been using a TemplateColumn subclass which overrides PrepareCellForEditing to ensure the focus lands in the AutoCompleteBox immediately on editing the cell (as per other answers here), but the problem persists if I disable all that code so it's not an effect of that bit of trickery as far as I can tell.
Any ideas?
The XAML looks more or less like this (simplified, of course, the grid has a lot more than two columns and some rather complicated data binding going on - I've left out the bindings and kept it to the overall structure).
<DataGrid>
<DataGridTemplateColumn Header="AutoCompleteBox">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate><TextBlock /></DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<toolkit:AutoCompleteBox>
<!-- autocompletebox's item template etc. -->
</toolkit:AutoCompleteBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" />
</DataGrid>
For moving focus to the next column I've made extended class (tab works fine for me):
public class ExAutoCompleteBox : AutoCompleteBox
{
public ExAutoCompleteBox()
{
PreviewKeyUp += (o, e) =>
{
if (e.Key == Key.Enter)
{
((UIElement)Keyboard.FocusedElement).MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
};
}
}

How do I set focus to a control within the ItemTemplate when the ListBoxItem is selected?

I have a ListBox which presents objects using a DataTemplate. The DataTemplate contains a TextBox. When the user selects an item in the ListBox, I would like to set focus to the TextBox for the selected item.
I have been able to partially achieve this by handling ListBox.SelectionChanged, but it only works when the user clicks on the ListBox to select the item - it does not work if the user tabs into the ListBox and uses the arrow keys to select the item even though TextBox.Focus() is invoked.
How can I set focus to the TextBox when the user uses the keyboard to select an item?
Here is the markup for the ListBox:
<ListBox Name="lb1" SelectionChanged="ListBox_SelectionChanged" ItemsSource="{Binding Items}" >
<ListBox.ItemTemplate>
<DataTemplate >
<TextBox></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is the handling code:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBoxItem lbi = (ListBoxItem)this.lb1.ItemContainerGenerator.ContainerFromItem(this.lb1.SelectedItem);
Visual v = GetDescendantByType<TextBox>(lbi);
TextBox tb = (TextBox)v;
tb.Focus();
}
One way to do this is to replace the tb.Focus() from your SelectionChanged event handler with:
tb.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(delegate()
{
tb.Focus();
}));
This works because calling BeginInvoke on the dispatcher causes the specified code to run when the dispatcher is available - i.e. after WPF has finished handling the events internally.
The catch is that, after you first press down arrow when a list item has focus, the next list item will became selected, its textbox will become focused and you will not be able to move selection again with the down arrow. You'll probably want to write some code to handle this case too.

Resources