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.