Lose focus on textbox when enter is pressed or clicked away - wpf

I have a textbox on a canvas. How do I lose the focus (so the caret goes away) when I press enter and/or click on the canvas? My textbox is in a template for a button.
I've tried pretty much everything and it doesn't work:
FocusManager.SetIsFocusScope(mainCanvas, true)
mainCanvas.Focus();
FocusManager.SetFocusedElement(child, parent);
it is interesting that if I use a button it works with KeyBoard.Focus(button) but it doesn't work with a canvas, does anyone know why or have any other suggestion?

The problem with Canvas is that when you click on it, you don't actually get the click event to occur unless you have a background that is not white.
One trick if you want white is to use white -1 or #FFFFFE or if the parent is also white use Transparent. So no one can tell it isn't white.
Now your click event can occur.
Also you need to make it focusable.
MainWindow.xaml
<Window x:Class="TextBoxInCanvas.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">
<Grid Name="MainGrid">
<Canvas Name="canvas1" Focusable="True" Background="#FFFFFE" MouseDown="canvas1_MouseDown">
<TextBox Height="23" Name="textBox1" Width="120" IsEnabled="True"
Canvas.Left="81" Canvas.Top="115" PreviewKeyDown="textBox1_PreviewKeyDown"/>
</Canvas>
</Grid>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Input;
namespace TextBoxInCanvas
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void canvas1_MouseDown(object sender, MouseButtonEventArgs e)
{
Keyboard.Focus(canvas1);
}
private void textBox1_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (Key.Enter == e.Key)
Keyboard.Focus(canvas1);
}
}
}
How to make the WPF Canvas mouse click event work?
I made a more complete post on my blog.

Related

Control Focus in dialog box with Prism 7.x and 8.x?

I need to set a TextBox being focused when the dialog box was opened. It was working with Prism 6.x; but is no longer working since 7.x and 8.x. The framework difference is that Prism 6.x uses InteractionRequest for dialog box; while Prism 7/8 uses the dialog service. Does Prism 7 and 8 also introduce some new approaches related to dialog box? Following is the code snippet of XAML setting FocusManager:
<UserControl x:Class="FeatureModule.Views.MyDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
FocusManager.FocusedElement="{Binding ElementName=edit}"
mc:Ignorable="d" d:DesignHeight="140" d:DesignWidth="400">
It does not function any more. Does it related to the dialog service?
The dialog service starting from Prism 7.2.x is completely different from the legacy interaction requests in prior versions, so it is hard to tell what exactly was the breaking change. Setting the focus manager property on the root element of the UserControl instead of on the UserControl could solve this the issue, but it might not work under other circumstances, read below why.
<UserControl x:Class="FeatureModule.Views.MyDialogView" ...>
<Grid FocusManager.FocusedElement="{Binding ElementName=edit}">
<!-- ...your markup -->
</Grid>
</UserControl>
I think the issue is more general. WPF has two different focus concepts, logical and keyboard focus. What you want to set on the TextBox is keyboard focus, but the FocusManager only sets logical focus.
Logical focus pertains to the FocusManager.FocusedElement within a specific focus scope.
The important part is the relationship between logical and keyboard focus.
An element with logical focus does not necessarily have keyboard focus, but an element with keyboard focus will have logical focus.
This means, even if you set the logical focus on a different element it might not work.
Keyboard focus pertains to the element that is currently receiving keyboard input. There can be only one element with keyboard focus.
Unfortunately, the Keyboard.FocusedElement property has only a getter and is not a dependency property, so you cannot bind to it, but you can call the Keyboard.Focus method instead. In order to avoid code-behind, you can either create a small behavior or simply an attached property like this:
public static class KeyboardFocus
{
public static readonly DependencyProperty ElementProperty = DependencyProperty.RegisterAttached(
"Element", typeof(FrameworkElement), typeof(KeyboardFocus), new PropertyMetadata(null, OnElementChanged));
public static FrameworkElement GetElement(DependencyObject dependencyObject)
{
return (FrameworkElement)dependencyObject.GetValue(ElementProperty);
}
public static void SetElement(DependencyObject dependencyObject, FrameworkElement value)
{
dependencyObject.SetValue(ElementProperty, value);
}
private static void OnElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is FrameworkElement frameworkElement)
frameworkElement.Loaded += OnLoaded;
}
private static void OnLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
frameworkElement.Loaded -= OnLoaded;
Keyboard.Focus(frameworkElement);
}
}
This attached property can be used on any FrameworkElement, not only TextBox. You can customize it to fit your needs. In XAML apply it to the root element of your UserControl, not the UserControl itself, otherwise the binding element might not be found e.g.:
<UserControl x:Class="FeatureModule.Views.MyDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:local="clr-namespace:FeatureModule.Views"
prism:ViewModelLocator.AutoWireViewModel="True"
FocusManager.FocusedElement="{Binding ElementName=edit}"
mc:Ignorable="d" d:DesignHeight="140" d:DesignWidth="400">
<Grid local:KeyboardFocus.Element="{Binding ElementName=edit}">
<!-- ...your markup. -->
</Grid>
</UserControl>
Alternatively, you can set the property on the TextBox itself.
<TextBox local:KeyboardFocus.Element="{Binding RelativeSource={RelativeSource Self}}"/>

Why is RoutedEventArgs.Handled ignored between TouchDown and Click

I am trying to support both TouchDown and Click with the same event.
You will notice that the following code, instead of toggling between green and red, just flashes red and goes back to green. As far as I can tell, this is because the click event is ignoring the Handled property of the RoutedEvent. I need this to work with both Touch and Mouse.
XAML:
<Window x:Class="CodeSpace.WPF.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" Background="LightGreen">
<Grid>
<Button Content="Touch Me" HorizontalAlignment="Center" VerticalAlignment="Center" Height="75" Width="75" Click="OnClick" TouchDown="OnClick"/>
</Grid>
</Window>
Code behind:
using System.Windows;
using System.Windows.Media;
namespace CodeSpace.WPF
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnClick(object sender, RoutedEventArgs e)
{
Background = (Background.Equals(Brushes.LightGreen)) ? Brushes.LightCoral : Brushes.LightGreen;
e.Handled = true;
}
}
}
Note: the reason I am not just using Click alone (which is supposed to work with Touch) is because with my specific case when I do that, the first time the button is touched nothing happens. All subsequent touches work just fine. This is a separate issue that I can't ask about because I cannot reproduce it in a simple code example. Also, this behavior only happens when windows is set to 120 DPI. Everything works just fine in 96 DPI. I have no idea!
The click event comes as a result of MouseUp/TouchUp rather than *Down (unless you change the ClickBehavior of the button). This answer has a good approach to globally blocking the promotion of mouse events to touch events for an element: https://stackoverflow.com/a/19540120/518955

Odd Loaded behavior in markup versus code

I'm currently very confused by the differing behavior regarding the FrameworkElement.Loaded event. I've put together a small example application that demonstrates this.
Xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Loaded="Window_Loaded">
<Grid>
<TabControl>
<TabItem Header="Tab 1" />
<TabItem Header="Tab 2" >
<WindowsFormsHost Name="formHost" Loaded="formHost_Loaded" />
</TabItem>
</TabControl>
</Grid>
</Window>
Code:
using System.Windows;
namespace WpfApplication1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
formHost.Loaded += delegate
{
MessageBox.Show("Delegate");
};
}
private void formHost_Loaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("Markup");
}
}
}
As it is, when I run the application I get two immediate MessageBoxes - "Markup" and "Delegate". If, however, I remove Loaded="formHost_Loaded" from the WindowsFormsHost, I get neither on startup. It obviously makes sense why I don't get the "Markup" dialog, but why does this also remove the "Delegate"? I would imagine it has to do with the order in which the events are called (Window versus its children), but I'm having a tough time figuring it out.
Note: You can replace the WindowsFormsHost with other controls, it really shouldn't matter - I was just using it for another few tests.
The Loaded event handlers are determined before any of them are called, as the Loaded event is effectively broadcast all at once starting at the top of the tree. Since you added a handler after WPF has decided what handlers need to be called, it is ignored.
This can be verified using Reflector. Specifically, the BroadcastEventHelper.BroadcastLoadedSynchronously method will call the BroadcastEventHelper.BroadcastEvent method with the LoadedEvent routed event.
BroadcastEvent method gathers all the objects in the visual tree that have a Loaded event handler first, then loops through and raises the events on those objects.

Create a custom click event handler for a WPF usercontrol which contains a button?

have you ever found a problem when assigning a click event handler for your custom WPF usercontrol with a nested button control? I do.
When you put such user control in a main window, let's say Main.xaml, the MouseLeftButtonDown doesn't work, but the PreviewMouseLeftButtonDown works like a charm.
But imagine yourself telling each developer in your team to use this event when using your usercontrol... Some usercontrols in you library has MouseLeftButtonDown, others PreviewMouseLeftButtonDown.... It's a mess don't you agree?
So I've got a solution but I want someone to see if there's some elegant way to create your custom event handler called "Click".
In my usercontrol called CustomButton.xaml.cs, I have so far:
public partial class CustomButton: UserControl
{
public CustomButton()
: base()
{
this.InitializeComponent();
}
public delegate void ClickHandler(object sender, EventArgs e);
public event EventHandler Click;
public void Button_Click(object sender, RoutedEventArgs e) {//execute daddy's button click
(((sender as Button).Parent as Grid).Parent as CustomButton).Click(sender, e);
e.Handled = false;
}
In my CustomButton.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="YourCompany.UI.Controls.CustomButton" d:DesignHeight="72.5" d:DesignWidth="200">
<UserControl.Resources>
blablabla
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Button Style="{DynamicResource CustomButton}"
Width="{Binding ElementName=CustomButton, Path=ActualWidth}"
Cursor="Hand" Foreground="#ffffff" FontSize="28" Margin="8,8,0,12"
HorizontalAlignment="Left"
Content="Custom Button" Click="Button_Click" />
</Grid>
Now in my Main.xaml, the caller, I have:
<Window x:Class="YourCompany.MyProject.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyProject!" Height="600" Width="800"
MinWidth="800" MinHeight="600" WindowState="Maximized" WindowStartupLocation="CenterScreen"
xmlns:bigbola="clr-namespace:YourCompany.UI.Controls;assembly=YourCompany.UI.Controls">
<mycontrols:CustomButton Name="test" MyImage=".\Images\btnOptions.png" Cursor="Hand"
Texto="See options" Click="test_Click"
Margin="168.367,176.702,253.609,0" ToolTip="See all options" Height="76.682"
VerticalAlignment="Top"></mycontrols:CustomButton>
Explanation:
in the usercontrol, when you click the nested button, it executes its parent custom "Click" handler.
Is there a elegant way to accomplish the same effect?
Going off of what mdm20 was saying... Why are you creating a UserControl (a collection of controls grouped into 1) when you could much more easily create a CustomControl (a control that extends the functionality of an existing control, such as a Button)? Assuming a Button is the only control you'd like in CustomButton, I'd highly recommend a CustomControl over what you have (a UserControl).
Example of UserControl vs CustomControl here
Hope this helps!
If your implementing a button, why not just derive from button?
To answer your question though, all you need it this.
if (Click != null) Click(this, EventArgs.Empty);
Couldn't this line:
(((sender as Button).Parent as Grid).Parent as CustomButton).Click(sender, e);
be replaced by
this.Click(sender, e);
?
Other than that though the answer depends on the exact behaviour that you want. If you want to click event of your user control to only trigger when you click on the inner button then I think you are handling it the right way. On the other hand if you want the click event to trigger whenever you click anywhere within the bounds of the user control then you are probably best styling or inheriting from the standard button control. Remember that in WPF the button's content can be any other element including another button.

How to get a WPF window's ClientSize?

In WinForms, Form had a ClientSize property (inherited from Control), which returns the size of its client area, i.e., the area inside the title bar and window borders.
I'm not seeing anything similar in WPF: there's no ClientSize, ClientWidth, ClientHeight, GetClientSize(), or anything else that I can think to guess the name of.
How do I go about getting the client size of a WPF Window?
One way you could do it is to take the top most child element, cast this.Content to its type, and call .RenderSize on it, which will give you its size.
<Window x:Class="XML_Reader.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="600" WindowStyle="SingleBorderWindow">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</Grid>
</Window>
((Grid)this.Content).RenderSize.Height
((Grid)this.Content).RenderSize.Width
edit:
as Trent said, ActualWidth and ActualHeight are also viable solutions. Basically easier methods of getting what I put above.
var h = ((Panel)Application.Current.MainWindow.Content).ActualHeight;
var w = ((Panel)Application.Current.MainWindow.Content).ActualWidth;
One way to do it is with the code below. XAML:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
<Canvas>
</Canvas>
</Window>
C#:
using System.Windows;
using System.IO;
using System.Xml;
using System.Windows.Controls;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
double dWidth = -1;
double dHeight = -1;
FrameworkElement pnlClient = this.Content as FrameworkElement;
if (pnlClient != null)
{
dWidth = pnlClient.ActualWidth;
dHeight = pnlClient.ActualHeight;
}
}
}
}
I used a Grid with VerticalAlignment=Top. As a result the Grid unfortunately didn't fill the parent Window anymore (which is its default behaviour, but the VerticalAligment property spoils it).
I solved it by putting an empty Border around the Grid. This border fills the complete content of the window, it has the same dimensions as the default border that a wpf window has anyways.
To get the Grid to fill the main window, I used the binding:
<Border BorderThickness="0" x:Name=Main>
<Grid VerticalAlignment="Top" Height="{Binding ElementName=Main, Path=ActualHeight}"> ...
</Grid>
</Border>
All the suggested solutions are based on the idea to use the size of Windows.Content to know what is the actual size available within the window, like this:
var h = ((Panel)Application.Current.MainWindow.Content).ActualHeight;
This of course only works if Window.Content is not null. Which is a problem if you want to set Window.Content from your code and you already then need to know exactly how much space is available.
The other problem is that the above code only provides the available space once a first layout cycle has completed (i.e. in the Window_Loaded event). But what do you do if you need to know the available space during the first layout cycle, for example because you draw to the window during Windows.OnRender() ?
The first control in the visual tree of any Window is always a Border, even if Window.Content is null. Interestingly, Border.RenderSize has already a value, even when RenderSize.ActualSize might still be zero. I guess the reason is that the size of the Border does not depend on Window.Content, but only on the size of the window (unless, of course, if Window.SizeToContent is used).
I recommend to place your code into the Window.SizeChanged event. Because each time the Window size changes, your content needs to change too. You cannot use the size provided in the event parameters, which gives you the size of the complete window, but you can get the the available size within the window like this:
var h = ((Border)GetVisualChild(0)).RenderSize.Height;
You can use that line of code also if you override Windows.OnRender().

Resources