我在WPF项目中使用Blend SDK KeyTrigger,并且遇到每次按下指定键时多次触发事件的问题,这里是DeleteCommand。
<ei:KeyTrigger FiredOn="KeyDown" ActiveOnFocus="True" SourceName="repositoryPackages" Key="Delete">
<i:InvokeCommandAction Command="{Binding SelectedItem.DeleteCommand, repositoryPackages}" />
</ei:KeyTrigger>
此触发器位于ListView的触发器集合中,ListView本身位于用户控件内的网格上。
然后将用户控件嵌入到应用程序主窗口上的WPF TabControl的选项卡上。
每次我使用ListView切换回选项卡时,触发器会无限次地再次调用该命令。
我查看了KeyTrigger的源代码(在Microsoft.Expressions.Interactions中)并注意到以下几行:
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);
}
}
每次触发器的关联元素获取OnLoaded事件时,都会调用OnEvent方法一次。但每次激活选项卡时,TabControl上的元素都会获得OnLoaded事件。这意味着每次都向KeyDown / KeyUp添加相同的事件处理程序。
对我来说,这看起来像是Blend SDK KeyTrigger实施中的一个重大疏忽。
有没有人有想法阻止这种情况,或者可能是如何实现正确的KeyTrigger?
答案 0 :(得分:2)
您是否尝试过使用FiredOn="KeyUp"
? KeyDown也可以由操作系统重复,我认为你不想反复删除?
答案 1 :(得分:2)
KeyTrigger在Loaded事件上注册了KeyDown / Up事件。
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);
}
}
}
例如,在选项卡控件中,当您切换到另一个选项卡时,您将获得卸载事件,当您返回选项卡时,您将再次获得一个Loaded事件。这会导致再次注册keydown / up事件。
这似乎是微软的一个错误,因为我认为他们应该在卸载时取消注册事件!!!
我们之前已经看到KeyTrigger导致内存泄漏,因为主窗口可能引用了已加载的Tab,即使它已从Tab控件关闭/删除,它仍然被引用。
我的建议是将CallMethodAction与KeyDown事件一起使用。