Set focus to a usercontrol when it is made visible - wpf

I am showing a MessageBox and want the user to be able to copy the contents of the message using CTRL+C. The problem is that I can't seem to set focus to the dialog.
The MessageBox is implemented in MVVM. To show it I just make a usercontrol visible (centre screen) and disable the main view.
The copy command is implemented using a Prism DelegateCommand:
<UserControl.InputBindings>
<KeyBinding Key="C" Modifiers="Control" Command="{Binding CopyCommand}"/>
</UserControl.InputBindings>
If I tab onto on of the message box button the CopyCommand fires. However I cannot get it to work initially when the dialog is shown.
How do I get the usercontrol to accept focus or to attach the KeyBinding to the whole of the usercontrol?
Note: I need an MVVM solution as don't want any code in the code behind file.

In situations when using MVVM pattern and need to interact with the user interface, I always try to implement this solution through an attached behavior. Attached behavior is very powerful and convenient solution that fully satisfies the MVVM pattern, which can also be used in the Blend (with a pre-defined interface).
In this case, I created an attached behavior VisibleFocusBehavior, which set a IsVisibleChanged event handler, wherein the focus is set in the case of the visibility of the element.
To avoid the appearance of dotted box, when the control gets focus, I set FocusVisualStyle="{x:Null} for UserControl.
VisibleFocusBehavior
public class VisibleFocusBehavior
{
#region IsFocusEnabled Dependency Property
public static readonly DependencyProperty IsFocusEnabledProperty;
public static void SetIsFocusEnabled(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsFocusEnabledProperty, value);
}
public static bool GetIsFocusEnabled(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsFocusEnabledProperty);
}
#endregion
#region BringToFrontBehavior Constructor
static VisibleFocusBehavior()
{
IsFocusEnabledProperty = DependencyProperty.RegisterAttached("IsFocusEnabled",
typeof(bool),
typeof(VisibleFocusBehavior),
new UIPropertyMetadata(false, IsFocusTurn));
}
#endregion
#region IsFocusTurn
private static void IsFocusTurn(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UIElement element = sender as UIElement;
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
if (element != null)
{
element.IsVisibleChanged += new DependencyPropertyChangedEventHandler(ElementIsVisibleChanged);
}
}
}
#endregion
#region ElementIsVisibleChanged Handler
private static void ElementIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
UIElement visibilityElement = sender as UIElement;
if (visibilityElement.IsVisible == true)
{
visibilityElement.Focus();
}
}
#endregion
}
Example of using
<UserControl x:Class="UserControlFocusHelp.TestUserControl"
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:local="clr-namespace:UserControlFocusHelp"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
xmlns:AttachedBehaviors="clr-namespace:UserControlFocusHelp.AttachedBehaviors"
AttachedBehaviors:VisibleFocusBehavior.IsFocusEnabled="True"
FocusVisualStyle="{x:Null}">
<UserControl.InputBindings>
<KeyBinding Key="C"
Modifiers="Control"
Command="{Binding CopyCommand}" />
</UserControl.InputBindings>
Test window
XAML
<Grid>
<local:TestUserControl x:Name="TestUserControl"
Width="300"
Height="300"
Focusable="True"
Visibility="Collapsed" />
<Button Width="100"
Height="30"
Content="Visible"
HorizontalAlignment="Left"
Click="Button_Click" />
</Grid>
Code-behind
private void Button_Click(object sender, RoutedEventArgs e)
{
TestUserControl.Visibility = Visibility.Visible;
}
Full example is available at this link.

There is a simple way to set focus to a control. Here's an example:
<UserControl
FocusManager.FocusedElement="{Binding ElementName=txtNickname}">
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="txtNickname" Grid.Row="1" />
</Grid>
</Grid>

Related

Return keyboard focus

I have an application where the main window contains a user control, and inside that user control are items stored in an ItemsControl. Each item can be removed by clicking an 'x' button.
The problem I am facing is that although the Keyboard focus is initially set to the user control, when you remove an item, focus is then transferred to the main window, instead of back to the user control?
Is there a way I can fix this without having to add code behind to manually store/retrieve/set focus after the click?
I have lots of these buttons within my application and I'm trying to avoid having to add code all over the place to manage returning the Focus.
I have created a very simple example to show the issue :
<UserControl x:Class="WpfApp28.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Width="300">
<StackPanel>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}" />
<Button Content="x"
Width="20"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="Button_Click" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
Focusable = true;
Loaded += MyControl_Loaded;
}
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
Keyboard.Focus(this);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement fe && fe.DataContext is string item)
{
(DataContext as ObservableCollection<string>).Remove(item);
}
}
}
<Window x:Class="WpfApp28.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp28"
Title="MainWindow" Height="450" Width="800">
<local:MyControl DataContext="{Binding Items}" />
public partial class MainWindow : Window
{
public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();
public MainWindow()
{
Items.Add("hello");
Items.Add("there");
Items.Add("world");
DataContext = this;
InitializeComponent();
DispatcherTimer t = new DispatcherTimer();
t.Interval = TimeSpan.FromMilliseconds(250);
t.Tick += T_Tick;
t.Start();
}
private void T_Tick(object? sender, EventArgs e)
{
Title = Keyboard.FocusedElement?.GetType().ToString() ?? "NULL";
}
}
The reason that the keyboard focus moves to the hosting Window is obvious once you understand how WPF handles focus. It's important to know that WPF uses scopes in which the focus traverses the elements.
There can be multiple focus scopes allowing multiple elements to remain focused simultaneously.
By default, the hosting Window defines a focus scope. Since it is the only focus scope, it is global (the scope of the complete visual tree).
What happens in your code in short:
The Button receives the focus via mouse click
The click handler removes the clicked item and therefore the clicked Button from the visual tree
WPF moves focus back to the focus scope root, which is the MainWindow in your case
You have multiple options to prevent the focus from being moved back to the focus root. Some involve code-behind.
The following examples show how to move the focus back to the parent UserControl. But it could be any element as well:
You can configure the Button (the element that "steals" the current focus) to be not focusable. This only works if the UserControl is already focused:
<DataTemplate>
<Button Content="x"
Focusable="False" />
</DataTemplate>
You can introduce a new focus scope. Since you want the UserControl itself to be focused, you must choose the root element of the UserControl. You can achieve this by using the FocusManager helper class:
<UserControl>
<Grid x:Name="RootPanel"
FocusManager.IsFocusScope="True"
Width="300">
</Grid>
</UserControl>
You can of course register a Button.Click handler or preferably a routed command to move the focus back to the UserControl explicitly. A routed command can be more convenient in most cases. It allows to send a command parameter that makes the code-behind simpler.
Note, since Button.Click is a routed event, you can simply register a Button.Click event handler on the UserControl. This example uses the existing click handler that is used to remove the item from the ItemsControl:
UserControl.xaml
<DataTemplate>
<Button Content="x"
Click="OnButtonClick" />
</DataTemplate>
UserControl.xaml.cs
private void OnButtonClick(object sender, RoutedEventArgs)
{
/* Delete the item */
Keyboard.Focus(this);
}
Final suggested solution
To improve your code and handling of the UserControl you must definitely implement an ItemsSource dependency property and use a routed command to delete the items.
The following example uses the predefined ApplicationCommands.Delete routed command. You will notice how simple the code has become:
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public IList ItemsSource
{
get => (IList)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource",
typeof(IList),
typeof(UserControl4), new PropertyMetadata(default));
public MyControl()
{
InitializeComponent();
this.Focusable = true;
}
public override void OnApplyTemplate()
=> Keyboard.Focus(this);
private void DeleteItemCommand_Executed(object sender, ExecutedRoutedEventArgs e)
=> this.ItemsSource.Remove(e.Parameter);
private void DeleteItemCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = this.ItemsSource.Contains(e.Parameter);
}
MyControl.xaml
<UserControl>
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Delete}"
Executed="DeleteItemCommand_Executed"
CanExecute="DeleteItemCommand_CanExecute" />
</UserControl.CommandBindings>
<Grid x:Name="RootPanel"
FocusManager.IsFocusScope="True">
<StackPanel>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:UserControl4}, Path=ItemsSource}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}" />
<Button Content="x"
Command="{x:Static ApplicationCommands.Delete}"
CommandParameter="{Binding}"
Width="20"
HorizontalAlignment="Right"
VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
MainWindow.xaml
<Window>
<MyControl ItemsSource="{Binding Items}" />
</Window>
Remarks
You should consider to use a ListBox instead of the pure ItemsControl.
ListBox is an extended ItemsControl. It will significantly improve performance and provides a ScrollViewer by default.

How to programmatically set UserControl to Topmost?

How to programmatically set UserControl to Topmost of pc screen. I have multiple usercontrol in my wpf application, when I resize any usercontrol, I want to show this usercontrol top of the screen.
i want to show this usercontrol top of the screen.
If by that you mean "top of the screen" inside your application, then that is achieved by using Panel's ZIndex attached property.
Xaml :
<Grid x:Name="LayoutRoot">
<UserControl x:Name="TopMostUserControl"
Margin="10,140,106,48"
Panel.ZIndex="1"
Background="Green" />
<UserControl x:Name="SecondUserControl"
Margin="39,50,37,87"
Panel.ZIndex="0"
Background="red" />
</Grid>
C# :
public MainWindow()
{
InitializeComponent();
Panel.SetZIndex(TopMostUserControl, 1);
Panel.SetZIndex(SecondUserControl, 0);
}
<Grid x:Name="LayoutRoot">
<UserControl x:Name="TopMostUserControl"
Margin="10,140,106,48"
Background="Green" />
<UserControl x:Name="SecondUserControl"
Margin="39,50,37,87"
Background="red" />
</Grid>
Result :
However, if you mean to topmost that UserControl on the entire screen, then that would be something different, you should create another Window that hosts your topmost UserControl and you should change its TopMost property to true when you resize your other UserControls.
MainWindow :
<Grid x:Name="LayoutRoot">
<UserControl x:Name="FirstUserControl"
Margin="10,140,106,48"
Background="Green"
MouseDown="FirstUserControl_OnMouseDown" />
<UserControl x:Name="SecondUserControl"
Margin="39,50,37,87"
Background="red" />
</Grid>
Code Behind :
public partial class MainWindow : Window
{
public TopMostWindow TopMostWindow;
public MainWindow()
{
InitializeComponent();
TopMostWindow = new TopMostWindow();
TopMostWindow.Show();
}
private void FirstUserControl_OnSizeChanged(object sender, SizeChangedEventArgs e)
{
TopMostWindow.Topmost = true;
}
private void MainWindow_OnContentRendered(object sender, EventArgs e)
{
FirstUserControl.SizeChanged += FirstUserControl_OnSizeChanged;
SecondUserControl.SizeChanged += FirstUserControl_OnSizeChanged;
}
//This is to simulate the resizing
private void FirstUserControl_OnMouseDown(object sender, MouseButtonEventArgs e)
{
FirstUserControl.Width = 400;
}
}
TopMostWindow :
<Window x:Class="MvvmLight1.TopMostWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TopMostWindow"
Width="300"
Height="300"
Topmost="False"
WindowStyle="None">
<Grid>
<UserControl x:Name="TopMostUserControl" Background="Blue" />
</Grid>
</Window>
It depends in which container you are using it. Or in what scenario. Basically in Grid you just need to specify it as a last element under Grid container. Otherwise use Panel.ZIndex="1" on the UserControl declaration in XAML

How to show a warning window before opening a combobox items list?

My requirement is to show a warning window (in some specific conditions) when clicking on a WPF combobox, just before it shows a list of available items to choose from. The window asks the user if he proceeds or not.
The problem is that after showing this warning window, the combobox popup that should appear to select an item is not open, no matter if I set the property IsDropDownOpen to do so. See the code for details.
<Window x:Class="ComboBoxTester.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">
<StackPanel>
<ComboBox Height="20" PreviewMouseDown="ComboBox_PreviewMouseDown">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
<ComboBoxItem>Item 3</ComboBoxItem>
</ComboBox>
<CheckBox x:Name="warningConditionCheckBox" >Is warning condition?</CheckBox>
</StackPanel>
</Window>
and the code behind contains:
namespace ComboBoxTester {
using System.Windows.Input;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void ComboBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (warningConditionCheckBox.IsChecked == true)
{
// Warn about this situation
var window = new MyDialog { Owner = GetWindow(this) };
// Confirm to proceed
if (window.ShowDialog() != true) {
e.Handled = true;
}
else {
comboBox.IsDropDownOpen = true;
}
}
}
}
}
MyDialog is just a Dialog Window:
<Window x:Class="ComboBoxTester.MyDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyDialog" Height="150" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Border Background="Silver">
<TextBlock Text="Warning! Sure to proceed?" TextAlignment="Center"/>
</Border>
<StackPanel Grid.Row="1">
<Button Width="100" Content="OK" IsDefault="True" Click="ButtonOkClick"/>
<Button Width="100" Content="Cancel" IsCancel="True"/>
</StackPanel>
</Grid>
</Window>
namespace ComboBoxTester {
using System.Windows;
/// <summary>
/// Interaction logic for MyDialog.xaml
/// </summary>
public partial class MyDialog
{
public MyDialog()
{
InitializeComponent();
}
private void ButtonOkClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
}
My idea is to use the WPF combobox to deal with this requirement. And to not create another control, if this is possible. So... How can I see the list of items from this combobox after showing a window? Any suggestion helps.
This is often an issue in WPF that while you are in an event handler you can set other UI controls' properties but they are not processed properly.
But you can invoke your change via the dispatcher, which will queue your request in the UI Message Queue, where it is properly processed after your event handler:
Instead of
comboBox.IsDropDownOpen = true;
use:
Action action = () => comboBox.IsDropDownOpen = true;
Application.Current.Dispatcher.BeginInvoke(action);

Is there a generic way to set the DataContext of the ItemContainer underneath the mouse to a property of some other control?

When hovering over an item in a list, how can I set a property on another element to the DataContext of the list item?
I'm trying to make an area where I can display a preview of the item currently underneath the mouse cursor. I'm able to do this using code-behind, but I'd like to find an alternative way which could use EventSetters/Binding/Triggers/AttachedProperties or any other means.
The aim is to apply the solution in a more loosely coupled scenario where the ListView control could be in separate resource file and the PreviewControl might be shared by a number of ListViews to show previews of different types.
The following piece of code works, but requires code-behind:
<Window x:Class="Previewer.PreviewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="PreviewWindow" Height="300" Width="300">
<Window.Resources>
<x:Array x:Key="Data" Type="sys:String">
<sys:String>First</sys:String>
<sys:String>Second</sys:String>
</x:Array>
<CollectionViewSource x:Key="DataSource" Source="{StaticResource Data}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" x:Name="PreviewControl"/>
<ListView Grid.Row="1" ItemsSource="{Binding Source={StaticResource DataSource}}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseEnter" Handler="ListViewItem_MouseEnter"/>
<EventSetter Event="MouseLeave" Handler="ListViewItem_MouseLeave"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
The code-behind which at the moment does the trick:
public partial class PreviewWindow : Window
{
public PreviewWindow()
{
InitializeComponent();
}
private void ListViewItem_MouseEnter(object sender, MouseEventArgs e)
{
var listViewItem = (ListViewItem)sender;
PreviewControl.Content= listViewItem.DataContext;
}
private void ListViewItem_MouseLeave(object sender, MouseEventArgs e)
{
var listViewItem = (ListViewItem)sender;
PreviewControl.Content= null;
}
}
Solution 1 (not that generic but simple and works):
encapsulate the logic you already implemented into a custom control, which has a new dependency property (typeof(object)), which represents the HoveredItemContext. In the constructor of your custom list view, you can create the ContainerStyle and attach the events. Set the HoveredItemContext in this EventHandlers and you can bind to this property from the outside:
<ContentControl Grid.Row="0" x:Name="PreviewControl"
Content="{Binding ElementName=MyListView, Path=HoveredItemContext}"/>
<local:MyListView Grid.Row="1" x:Name="MyListView"
ItemsSource="{Binding Source={StaticResource DataSource}}" />
And here the custom control (works):
public class MyListView : ListView
{
public static readonly DependencyProperty HoveredItemContextProperty = DependencyProperty.Register(
"HoveredItemContext",
typeof(object),
typeof(MyListView),
new PropertyMetadata(null));
public object HoveredItemContext
{
get { return GetValue(HoveredItemContextProperty); }
set { SetValue(HoveredItemContextProperty, value); }
}
public MyListView()
{
this.ItemContainerStyle = new Style()
{
TargetType = typeof(ListViewItem),
};
this.ItemContainerStyle.Setters.Add(new EventSetter(ListViewItem.MouseEnterEvent,
(MouseEventHandler)((s, e) =>
{
this.HoveredItemContext = (s as ListViewItem).DataContext;
})));
this.ItemContainerStyle.Setters.Add(new EventSetter(ListViewItem.MouseLeaveEvent,
(MouseEventHandler)((s, e) =>
{
this.HoveredItemContext = null;
})));
}
}
Solution 2 (more generic):
I am still working on it on a similar problem but its not yet finished ;) If i bring it to an end, i will post this here.

Dependency propery Binding Problem

Main WINDOW
<Window x:Class="dep2.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:dep2"
Title="Window1" Height="300" Width="381">
<Grid>
<local:UserControl1></local:UserControl1>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,77,36" Name="button1" VerticalAlignment="Bottom" Width="75" Click="button1_Click">Button</Button>
</Grid>
</Window>
public partial class Window1 : Window
{
UserControl1 uc = new UserControl1();
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
uc.InfoText = "SAMPLE";
}
}
My User CONTROL
<UserControl x:Class="dep2.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="32" Width="300">
<Grid Height="30">
<StackPanel Background="LightCyan">
<TextBox Height="21" Name="textBlock1" Width="120" Text="{Binding Text}" />
</StackPanel>
</Grid>
</UserControl>
public partial class UserControl1 : UserControl
{
public string InfoText
{
get
{
return (string)GetValue(InfoTextProperty);
}
set
{
SetValue(InfoTextProperty, value);
}
}
public static readonly DependencyProperty InfoTextProperty =
DependencyProperty.Register(
"InfoText",
typeof(string),
typeof(UserControl1),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(ChangeText)));
private static void ChangeText(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as UserControl1).UpdateText(e.NewValue.ToString());
}
private void UpdateText(string NewText)
{
textBox1.Text = NewText;
}
public UserControl1()
{
InitializeComponent();
DataContext = this;
}
}
Am getting my value in user control dependency property, but i cant able to bind my value to the text box.am using like this to bind Text="{Binding Text}" is it right,or how to bind my value in user control
i have attached my sample project here,
http://cid-08ec3041618e8ee4.skydrive.live.com/self.aspx/.SharedFavorites/dep2.rar
Can any one look and tell whats wrong in that,
everythng working well, but i cant bind the value in text box,
when u click the button u can see the passed value to usercontrol in message box, but i cant bind that value in text box.
Why????
Your code handles the callback from the dependency property and sets the text box value directly. This is not the role of this callback.
And by setting the Text property, you have lost the binding. Local property setting has a higher priority than bindings. See this blog

Resources