How to use the same eventhandler for different events - wpf

How can i create events for a click on a textbox, a checkbox and combobox and use the same eventhandler for all these events?

Depending on what you indend to do, you may for example handle the PreviewMouseLeftButtonDown event:
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement fe = (FrameworkElement)sender;
MessageBox.Show(fe.Name);
}
Sample XAML:
<TextBox x:Name="a" PreviewMouseLeftButtonDown="OnMouseLeftButtonDown" />
<CheckBox x:Name="b" PreviewMouseLeftButtonDown="OnMouseLeftButtonDown" />
<ComboBox x:Name="c" PreviewMouseLeftButtonDown="OnMouseLeftButtonDown">
<ComboBoxItem>1</ComboBoxItem>
</ComboBox>

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.

Programatically attach an event handler to a custom child element routed event

I have a Window that has a Button and a Popup. When the button is clicked, the event handler in code behind opens the Popup. On the Popup I have Button when clicked, the event handler in code behind closes the Popup. Simple. Crude.
I also have a UserControl that has a custom routed event and a Button that raises that event.
That UserControl has been placed on the Popup.
I have added an event handler in XAML on the Popup element for the UserControl custom event. In code behind I show a message box.
This all works fine and dandy.
This is an extremely boiled down example. My ultimate question is, how do I programmatically attach an event handler to the custom routed event at the Popup level?
The UserControl XAML:
<Grid>
<Button
Width="100"
Height="100"
Click="Button_Click"
Content="Event!" />
</Grid>
The UserControl code behind:
public static readonly RoutedEvent CustomEventEvent = EventManager.RegisterRoutedEvent(
nameof(CustomEvent), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl1));
public event RoutedEventHandler CustomEvent
{
add => this.AddHandler(CustomEventEvent, value);
remove => this.RemoveHandler(CustomEventEvent, value);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(CustomEventEvent));
}
Main window XAML:
<Grid>
<Button
Width="200"
Height="100"
Click="Button_Click"
Content="Popup" />
<Popup
x:Name="MyPopup"
local:UserControl1.CustomEvent="MyPopup_CustomEvent"
AllowsTransparency="True"
Loaded="MyPopup_Loaded"
Placement="Right">
<Border
Background="Azure"
BorderBrush="Gray"
BorderThickness="2"
CornerRadius="3">
<Grid Width="500" Height="300">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button
Grid.Row="0"
Width="100"
Height="100"
Click="Button_Click_1"
Content="Close" />
<local:UserControl1
Grid.Row="1"
Width="100"
Height="100" />
</Grid>
</Border>
</Popup>
</Grid>
The main window code behind (minus the boring popup open/close button clicks):
private void MyPopup_CustomEvent(object sender, RoutedEventArgs e)
{
MessageBox.Show("YAY");
}
private void MyPopup_Loaded(object sender, RoutedEventArgs e)
{
if (sender is Popup popup)
{
// Here is where the wheels fall off.
// How do I find/attach to the routed event after it has bubbled up to the popup?
// popup.CustomEvent += LocalMyPopup_CustomEvent;
}
}
private void LocalMyPopup_CustomEvent(object sender, RoutedEventArgs e)
{
}
Ultimately, there will be several of the UserControls in many popups and I'd rather not have to attach event handlers to each and every one of them. Especially considering I can get them all at the Popup level using MyPopup_CustomEvent. I just want to replicate that behavior.
Turns out the solution is as trivial as it is (in hindsight) obvious. The AddHandler has to be called directly on the Popup and passing in the static Routed Event reference.
private void MyPopup_Loaded(object sender, RoutedEventArgs e)
{
if (sender is Popup popup)
{
popup.AddHandler(UserControl1.CustomEventEvent, new RoutedEventHandler(LocalMyPopup_CustomEvent), true);
// popup.CustomEvent += LocalMyPopup_CustomEvent;
}
}

WPF RoutedEvent inconsistency: ListViewItem.MouseDoubleClick Not bubbling correct e.Source

Binding a RoutedEvent for the "Click" event of static buttons contained in a StackPanel is easy. The RoutedEventArgs will contain the button as the e.Source of the event.
XAML:
<StackPanel Grid.Column="1" Button.Click="RoutedEventHandler">
<Button Name="btn1" Content="btn1" />
<Button Name="btn2" Content="btn2" />
</StackPanel>
Code Behind:
private void RoutedEventHandler(object sender, RoutedEventArgs e)
{
MessageBox.Show(((FrameworkElement)e.Source).Name);
}
However - handling routed events with "ListViewItem.MouseDoubleClick" will result the ListView container object in the e.Source, instead of the expected ListViewItem object.
XAML:
<ListView Name="lbAnecdotes"
ListViewItem.MouseDoubleClick="RoutedEventHandler">
<ListView.ItemTemplate >
<DataTemplate>
<WrapPanel >
<TextBlock Text="{Binding Path=Name}" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView >
Can you explain the inconsistency?
Simply put, the ListViewItem.MouseDoubleClick event is not a RoutedEvent like the ButtonBase.Click event. It uses the MouseButtonEventHandler delegate and a Direct routing strategy where as the ButtonBase.Click event is a RoutedEvent with a Bubbling strategy. They are different types of events and are implemented differently.

Any way for a ToolTip to trigger a MouseEnter event?

I have a control with a tooltip. What i want is that when I hover over the control and the tooltip opens: if I then enter the tooltip with the mouse - this will trigger a mouseEnter event in order to trigger some other action. The closest I am to finding a solution to this is adding a ToolTipClosing event on the control with the trigger ... but this will fire as soon as I leave the control - even if my mouse doesn't enter the tooltip.
(Triggering a MouseEnter event on the tooltip itself doesn't seem to get fired at all)
Here's an example: (where I want to change the background of the border if I enter the tooltip)
XAML
<Border Height="300" Name="dummyBorder"
Width="200"
Background="Red" />
<Label ToolTipService.InitialShowDelay="3000"
Content="Hover over here"
ToolTipService.ShowDuration="4000"
ToolTipService.Placement="Right"
ToolTipClosing="Label_ToolTipClosing"
Width="100"
HorizontalAlignment="Center"
Margin="10">
<Label.ToolTip>
<ToolTip Name="tt" MouseEnter="ttBorder_MouseEnter">
<Border Background="Brown"
Name="ttBorder"
MouseEnter="ttBorder_MouseEnter"
Width="100"
Height="50">
<TextBlock Text="This is a tool tip." />
</Border>
</ToolTip>
</Label.ToolTip>
</Label>
CodeBehind: (neither of these work)
private void Label_ToolTipClosing(object sender, ToolTipEventArgs e)
{
if (tt.IsMouseDirectlyOver)
{
dummyBorder.Background = Brushes.Aqua;
}
}
private void ttBorder_MouseEnter(object sender, MouseEventArgs e)
{
dummyBorder.Background = Brushes.Aqua;
}
I specifically want to use a tooltip and not a popup. Is this possible?
Any help will be greatly appreciated!
you will want to try something like this:
<Window.CommandBindings>
<CommandBinding Command="ChangeColour"
CanExecute="ChangeCanExecute"
Executed="ChangeExecuted" />
</Window.CommandBindings>
inside your tooltip tag:
<MouseBinding Gesture="LeftClick" Command="{Binding ChangeColour}"/>
then in your codebehind:
private void ChangeCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void ChangeExecuted(object sender, ExecutedRoutedEventArgs e)
{
dummyBorder.Background = Brushes.Aqua;
e.Handled = true;
}
Well (after 9 months and no answer) I guess that there is no way then. (Unless proven otherwise)

Drag and Drop WPF ComboBox, Buttons, Radio Buttons, etc

I have a problem, i've already read tutorials, blogs, etc about drag and drop on WPF (i'm using VS10).
The problem is I need to have a toolbox with buttons,combobox, radio button,etc sothe user can drag it and drop it(copy) on a work space (canvas or whatever).
I managed to do drag and drop from textbox and images but that doesn't work for me, when i tried on buttons or combobox it just doesnt work, i assume it is cause of the click event by default, i don't know what the problem is tho. Here is what i've tried with a button.
<Window x:Class="WpfApplication1.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>
<TextBox Height="22" HorizontalAlignment="Left" Margin="84,36,0,0" Name="textBox1" VerticalAlignment="Top" Width="103" Text="Drag" />
<TextBox Height="40" HorizontalAlignment="Left" Margin="225,136,0,0" Name="textBox3" VerticalAlignment="Top" Width="124" Text="Drop" />
<Label Content="DragLabel" Height="26" HorizontalAlignment="Left" Margin="284,36,0,0" Name="label1" VerticalAlignment="Top" Width="80" MouseDown="label1_MouseDown" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="84,122,0,0" Name="button1" VerticalAlignment="Top" Width="75" MouseDown="button1_MouseDown" AllowDrop="True" IsEnabled="True" Click="button1_Click" />
<Rectangle Height="100" HorizontalAlignment="Left" Margin="149,199,0,0" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Width="200" AllowDrop="True" Fill="#FFDCA1A1" />
</Grid>
My Code Behind ...
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void label1_MouseDown(object sender, MouseButtonEventArgs e)
{
Label lbl = (Label)sender;
DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Copy);
}
private void button1_MouseDown(object sender, MouseButtonEventArgs e)
{
var dependencyObject = (Button)sender;
DragDrop.DoDragDrop(dependencyObject, dependencyObject, DragDropEffects.Move);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
return;
}
}
Thank You in advance guys. Btw sry about my english :s...
Thx again!
Luis
Have you tried using the PreviewMouseDown event instead of MouseDown? Your code will get called before the Button can capture the click.
WPF elements normally use RoutedEvents which often have a corresponding "Preview" event that uses the Tunneling Routing Strategy, which will be sent to all parents before the element that actually raised the event. This allows you to perform your operation in response to the MouseDown before the Button gets a chance to try to execute a click action.
private void button1_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var dependencyObject = (Button)sender;
DragDrop.DoDragDrop(dependencyObject, dependencyObject, DragDropEffects.Move);
}
will work as mentioned by Abe

Resources