I am using the Blend SDK KeyTrigger in a WPF project and have the problem that the event is fired multiple times each time I press the assigned key, here the DeleteCommand.
<ei:KeyTrigger FiredOn="KeyDown" ActiveOnFocus="True" SourceName="repositoryPackages" Key="Delete">
<i:InvokeCommandAction Command="{Binding SelectedItem.DeleteCommand, repositoryPackages}" />
</ei:KeyTrigger>
This trigger is in the trigger collection of a ListView which itself is on a grid inside a user control.
The user control is then embedded on a tab of a WPF TabControl on the application main window.
Each time I switch away and back to the tab with my ListView the trigger invokes the command one more time ad infinitum.
I looked at the source of KeyTrigger (in Microsoft.Expressions.Interactions) and noticed the following lines:
protected override void OnEvent(EventArgs eventArgs)
{
if (this.ActiveOnFocus)
{
this.targetElement = base.Source;
}
else
{
this.targetElement = GetRoot(base.Source);
}
if (this.FiredOn == KeyTriggerFiredOn.KeyDown)
{
this.targetElement.KeyDown += new KeyEventHandler(this.OnKeyPress);
}
else
{
this.targetElement.KeyUp += new KeyEventHandler(this.OnKeyPress);
}
}
The OnEvent method gets called once for each time the associated element of the trigger gets an OnLoaded event. But elements on a TabControl get an OnLoaded event each time you activate a tab. Which means you add the same event handler to KeyDown/KeyUp each time.
For me this really looks like a big oversight in the implementation of Blend SDK KeyTrigger.
Does anyone have an idea to prevent this or probably how to implement a correct KeyTrigger?
Have you tried using FiredOn="KeyUp" instead? KeyDown can be repeated by the OS too and I presume you don't want repeated delete anyway?
KeyTrigger registers te KeyDown/Up events on the Loaded event.
public class KeyTrigger : EventTriggerBase<UIElement>
{
// Fields
public static readonly DependencyProperty ActiveOnFocusProperty = DependencyProperty.Register("ActiveOnFocus", typeof(bool), typeof(KeyTrigger));
public static readonly DependencyProperty FiredOnProperty = DependencyProperty.Register("FiredOn", typeof(KeyTriggerFiredOn), typeof(KeyTrigger));
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register("Key", typeof(Key), typeof(KeyTrigger));
public static readonly DependencyProperty ModifiersProperty = DependencyProperty.Register("Modifiers", typeof(ModifierKeys), typeof(KeyTrigger));
private UIElement targetElement;
// Methods
private static ModifierKeys GetActualModifiers(Key key, ModifierKeys modifiers)
{
if ((key == Key.LeftCtrl) || (key == Key.RightCtrl))
{
modifiers |= ModifierKeys.Control;
return modifiers;
}
if (((key == Key.LeftAlt) || (key == Key.RightAlt)) || (key == Key.System))
{
modifiers |= ModifierKeys.Alt;
return modifiers;
}
if ((key == Key.LeftShift) || (key == Key.RightShift))
{
modifiers |= ModifierKeys.Shift;
}
return modifiers;
}
protected override string GetEventName()
{
return "Loaded";
}
private static UIElement GetRoot(DependencyObject current)
{
UIElement element = null;
while (current != null)
{
element = current as UIElement;
current = VisualTreeHelper.GetParent(current);
}
return element;
}
protected override void OnDetaching()
{
if (this.targetElement != null)
{
if (this.FiredOn == KeyTriggerFiredOn.KeyDown)
{
this.targetElement.KeyDown -= new KeyEventHandler(this.OnKeyPress);
}
else
{
this.targetElement.KeyUp -= new KeyEventHandler(this.OnKeyPress);
}
}
base.OnDetaching();
}
protected override void OnEvent(EventArgs eventArgs)
{
if (this.ActiveOnFocus)
{
this.targetElement = base.Source;
}
else
{
this.targetElement = GetRoot(base.Source);
}
if (this.FiredOn == KeyTriggerFiredOn.KeyDown)
{
this.targetElement.KeyDown += new KeyEventHandler(this.OnKeyPress);
}
else
{
this.targetElement.KeyUp += new KeyEventHandler(this.OnKeyPress);
}
}
private void OnKeyPress(object sender, KeyEventArgs e)
{
if ((e.Key == this.Key) && (Keyboard.Modifiers == GetActualModifiers(e.Key, this.Modifiers)))
{
base.InvokeActions(e);
}
}
// Properties
public bool ActiveOnFocus
{
get
{
return (bool)base.GetValue(ActiveOnFocusProperty);
}
set
{
base.SetValue(ActiveOnFocusProperty, value);
}
}
public KeyTriggerFiredOn FiredOn
{
get
{
return (KeyTriggerFiredOn)base.GetValue(FiredOnProperty);
}
set
{
base.SetValue(FiredOnProperty, value);
}
}
public Key Key
{
get
{
return (Key)base.GetValue(KeyProperty);
}
set
{
base.SetValue(KeyProperty, value);
}
}
public ModifierKeys Modifiers
{
get
{
return (ModifierKeys)base.GetValue(ModifiersProperty);
}
set
{
base.SetValue(ModifiersProperty, value);
}
}
}
In tab controls for example, when you switch to another tab, you get unloaded event, and when you get back to your tab you get a Loaded event again. This causes the registeration of the keydown/up events again.
This seem to be a bug of microsoft since I would thing that they should unregister the events on unload!!!
We have seen it before that KeyTrigger caused Memory Leaks since the main window might have a reference to the Tab that was loaded, and even when it was closed/removed from the tab control then it is still being referenced.
My suggest is to use CallMethodAction with KeyDown event.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With