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.
Related
question edited to provide a better explanation
I am using a treeview consisting of a stackpanel with a textblock and a textbox inside. What I would like to achieve is the total selection of the text when the textbox appears.
The textblock disappears by double-clicking or selecting an option from a context menu, giving visibility to the textbox to rename the item.
I'd like to have the selectall on the MouseLeftButtonDown on the textblock and also on click on a context menu option.
My treeview is contained in the MainWindow and, the stackpanel (with the text block and the textbox) is in a different file and I dynamically add it to the tree view depending on the user's action.
When I click on the StackPanel the first click highlights it, on double-click it opens a page and, on the MouseLeftButtonDown (and on click in a contextmenu option) I change the visibility of the textblock with the textbox and here I want the selectall() event to get fired.
I tried the following code and it only works halfway:
private void mniRename_Click(object sender, RoutedEventArgs e)
{
prevSelected.MyTextBlock.Visibility = Visibility.Collapsed;
prevSelected.MyTextBox.Visibility = Visibility.Visible;
prevSelected.MyTextBox.Focus();
if (prevSelected.MyTextBox.IsFocused)
{
prevSelected.MyTextBox.SelectAll();
}
prevSelected.MyTextBox.Text = prevSelected.MyTextBlock.Text;
}
The issue is that the SelectAll() event doesn't work on the first click while the Focus() works, then on the following clicks everything works fine.
The code is always executed in the same way.
Does anyone have any idea why this happens?
No really sure what you want to achieve. but what you describe can be achieved by the following code:
XAML
<StackPanel Orientation="Horizontal">
<TextBox x:Name="MyTextBox" LostFocus="MyTextBox_OnLostFocus" Width="100"/>
<TextBlock x:Name="MyTextBlock" Text="{Binding ElementName=MyTextBox, Path=Text}" MouseLeftButtonDown="MyTextBlock_OnMouseLeftButtonDown"/>
</StackPanel>
C#
private void MyTextBox_OnLostFocus(object sender, RoutedEventArgs e)
{
MyTextBox.Visibility = Visibility.Hidden;
MyTextBlock.Visibility = Visibility.Visible;
}
private void MyTextBlock_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MyTextBlock.Visibility = Visibility.Collapsed;
MyTextBox.Visibility = Visibility.Visible;
MyTextBox.Focus();
MyTextBox.SelectAll();
}
I currently have a program where you can load a text in it.
Now I created a button that Pops up a flyout/ContentDialog but Im not happy with it because Limits me of what Im trying to achieve.
When I click the button it opens a flyout, the flyout gets the full Focus. That means I cannot scroll to the text WHILE the flyout-box is open. And if I click outside the flyout-box the flyout-box disappears.
I have a similar Problem to the ContentDialog.
When I click the button and the ContentDialog Pops up, everything behind the ContentDialog goes a bit into White/Grey Color. Also the ContentDialog does not allow any Focus outside the ContentDialog itself.
So what do I want to have?
I want that when I click on the button that a Window appears. I should be able to customize the window (writing text in it and it should have a button).
While this Window is open I want to be able to do Actions outside that window without the window Closing. For example Scrolling through the text I loaded.
Is there something I can achieve this with?
Take a look at the Popup class. This will let you display content on top of other content within your app's window. It's similar to the Flyout but without all of the built-in Flyout behavior that you don't want. The Popup class documentation has more details and commentary on when and how to use it.
Here's a really bland example with no styling.
<Grid>
<Popup x:Name="popup">
<StackPanel>
<TextBlock Text="Poppity pop pop" />
<Button Click="ClosePopup_Click">Close</Button>
</StackPanel>
</Popup>
<Button Click="OpenPopup_Click">Open Popup</Button>
</Grid>
private void OpenPopup_Click(object sender, RoutedEventArgs e)
{
popup.IsOpen = true;
}
private void ClosePopup_Click(object sender, RoutedEventArgs e)
{
popup.IsOpen = false;
}
There is a slightly more complicated example in the Popup documentation
I just hide and show grids with whatever I want inside.
I've got a simple WPF dialog with these two controls:
<TextBox Text="{Binding MyText}"/>
<Button Command="{Binding MyCommand}" IsDefault="True"/>
Now, when I enter some text in the TextBox and click the button using the mouse, everything works like expected: the TextBox will set MyText and MyCommand is called.
But when I enter some text and hit enter to "click" the default button, it does not work. Since on hitting enter the focus does not leave the TextBox, the binding will not be refresh MyText. So when MyCommand is called (which works), MyText will contain old data.
How do I fix this in MVVM? In classic code-behind I probably just would call "MyButton.Focus()" in the MyCommand handler, but in MVVM the MyCommand handler does know nothing about the button.
So what now`?
Add the UpdateSourceTrigger to your TextBox with the value PropertyChanged. The default behavior of the Textbox is to update the source, when it´s lost focus.
<TextBox Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}"/>
Try this. This code moves focus on the button clicked. Thus binding completes before command processed.
public App()
{
EventManager.RegisterClassHandler(typeof(Button), Button.ClickEvent, new RoutedEventHandler(GenericButtonClickHandler));
}
void GenericButtonClickHandler(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button == null)
return;
if (button.IsDefault)
button.Focus();
}
One Solution ist, to create your own Class OKButton that calls Me.Focus in the OnClick-Method. This will be called before the Click_Event and before any Command that is bound to the button. You just have to remember to use an OKButton instead of setting IsDefault=True
Public Class OKButton
Inherits System.Windows.Controls.Button
Public Sub New()
MyBase.New()
Me.Content = "OK"
Me.IsDefault = True
End Sub
Protected Overrides Sub OnClick()
Me.Focus()
MyBase.OnClick()
End Sub
End Class
Simplest application possible:
<Page
x:Class="TestApp.MainPage"
...>
<Grid>
<TextBox />
</Grid>
</Page>
Question: is there any elegant way to prevent the cursor (focus) from being set in the TextBox on application start up?
To expand: My real issue is that I have a PopUp that is opened when the TextBox receives focus. If I click on an element in my PopUp it should close, but since the TextBox is the first focusable element in my page it automatically receives focus and thus the PopUp immediately opens again. The core of the problem I think is represented by the example above.
Focus is managed by various properties like IsTabStop, TabIndex, IsHitTestVisible, and the FocusManager class. There is built-in functionality to focus the first focusable element once the window is activated, and this behavior is generally not customizable.
We could designate a different element to be focused instead of the textbox like, say, the page itself:
<Page IsTabStop="True">
<TextBox/>
</Page>
This works in that the page gets initial focus instead of the textbox, but now the page participates in tabbing behavior, which is slightly undesirable.
Typically the framework will set focus to the RootScrollViewer when you click out of a focused control, even though the RootScrollViewer isn't a tab stop (so it can't receive focus by tabbing). If we can focus the RootScrollViewer upon page load, the framework will detect that something has focus and won't attempt to focus the first element.
<Page Loaded="onPageLoaded">
<TextBox/>
</Page>
private ScrollViewer getRootScrollViewer()
{
DependencyObject el = this;
while (el != null && !(el is ScrollViewer))
{
el = VisualTreeHelper.GetParent(el);
}
return (ScrollViewer)el;
}
private void onPageLoaded(object sender, RoutedEventArgs e)
{
getRootScrollViewer().Focus(FocusState.Programmatic);
}
This is the most "elegant" way that I know to prevent the textbox from getting focused automatically.
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.