为啥在使用 Caliburn Micro Conductor.OneActive 时,Blend Interaction 事件触发器会多次触发?
Posted
技术标签:
【中文标题】为啥在使用 Caliburn Micro Conductor.OneActive 时,Blend Interaction 事件触发器会多次触发?【英文标题】:Why do Blend Interaction event triggers fire multiple times when using a Caliburn Micro Conductor.OneActive?为什么在使用 Caliburn Micro Conductor.OneActive 时,Blend Interaction 事件触发器会多次触发? 【发布时间】:2014-12-19 14:34:29 【问题描述】:[请注意,我在第一次尝试诊断时提出了错误的问题 - 现在已更正。]
我有一个 WPF 应用程序,其中一个主窗口继承自 Conductor.Collection.OneActive。它处理导航请求并保留视图模型的缓存,以便维护状态。此私有集合几乎与 base.Items 集合相同,但并非所有视图模型都是 IScreen。
当我们从一个活动项目移动到另一个活动项目时,一切正常并且状态保持不变。但是,交互触发器存在错误。当活动项目是一个 IScreen 时,触发器每次导航都会触发一次额外的时间,就好像它们每次都重新连接一样;普通触发器不这样做,只有来自交互库的触发器。如果活动项不是 IScreen - 只是从 PropertyChangedBase 继承 - 我们看不到这个问题,而且我们在导航期间丢失了视图的状态。
如果您导航到一个视图四次,事件将触发四次、五次、五次等等。
这似乎与as this question 相同,但我无法使用他的解决方案,因为我不知道具体的视图模型是什么并且无法为它们创建公共属性。
我的主窗口类如下所示:
public sealed class MainWindowViewModel : Conductor<object>.Collection.OneActive, IHandle<NavigateToUriMessage>
public void Handle(NavigateToUriMessage message)
var ignoredUris = RibbonUri.GetItems().Where(t => t.SubTabs.Count > 0).Select(t => t.Uri);
Func<string, bool> isMatch = uri => uri == message.Uri;
if (isMatch(RibbonUri.BookkeepingBatchView.Uri))
GetAndActivateViewModel<BatchPanelViewModel>(message);
...
private T GetAndActivateViewModel<T>(NavigateToUriMessage message) where T : class
var vm = GetViewModel<T>(message.Uri);
ActivateItem(vm);
return (T) vm;
private object GetViewModel<T>(string uri) where T : class
if (viewModelCache.ContainsKey(uri))
return viewModelCache[uri];
var vm = viewModelFactory.Create<T>();
viewModelCache.Add(uri, vm);
return vm;
我的 MainWindow XAML 如下所示:
<Window x:Class="Ui.Views.MainWindowView"
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"
d:DataContext="d:DesignInstance Type=viewModels:MainWindowViewModel, IsDesignTimeCreatable=False"
mc:Ignorable="d"
WindowState="Maximized" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1"/>
<RowDefinition/>
<RowDefinition Height="1"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<ContentControl x:Name="MainRibbon" Grid.Row="0" Margin="0" />
<Grid Background="#FFBBBBBB" Grid.Row="1" />
<ContentControl Grid.Row="2" x:Name="ActiveItem" Background="White" />
<Grid Grid.Row="3" Background="#FFBBBBBB"/>
<ContentControl x:Name="BottomStatusBar" Grid.Row="4"/>
</Grid>
</Window>
活动项视图中的 XAML 如下所示:
<DataGrid Focusable="True" FocusVisualStyle="x:Null" SelectionMode="Single" HeadersVisibility="None" IsReadOnly="True" GridLinesVisibility="None" AutoGenerateColumns="False" Background="Transparent" BorderThickness="0" Grid.Row="3" Grid.RowSpan="2">
<i:Interaction.Triggers>
<ei:KeyTrigger Key="Left" FiredOn="KeyUp" ActiveOnFocus="True" >
<cal:ActionMessage MethodName="TryCollapseSelectedItem"/>
</ei:KeyTrigger>
<ei:KeyTrigger Key="Right" FiredOn="KeyUp" ActiveOnFocus="True" >
<cal:ActionMessage MethodName="TryExpandSelectedItem"/>
</ei:KeyTrigger>
</i:Interaction.Triggers>
....
【问题讨论】:
借助此处对这个问题的回答,我能够更准确地诊断这一点:***.com/a/8835349/81317 当 ActiveItem 设置时,子控件的 Loaded 事件会触发。 KeyTrigger 在 Load 上连接,所以要多次加载。如果我创建自己的 KeyTrigger 类并从 KeyTrigger 继承,我可以覆盖 OnEvent 方法并停止多个附件。但是,我想知道每次我们激活项目时触发的 Load 事件会影响多少其他事情?我做错了什么还是这只是 KeyTrigger 的一个问题,我可以规避。 使用反编译器的进一步调查表明,KeyTrigger 在加载期间附加了一个按键事件处理程序,但在卸载 UserControl 时它没有获得 OnDetaching 调用,因此它是不对称的。因此,UserControl 每次激活时都会收到一个 Load 事件,并且 KeyTrigger 已连接。 【参考方案1】:似乎this question here has a better answer,尽管隐藏在接受答案的 cmets 中。 KeyTrigger 类是有问题的,并且每次将子控件重新加载到 ContentControl 时都会附加事件。 @gunter 说,“真正的问题似乎是 OnLoaded 上的钩子,并且选项卡控件上的元素会获得多个 OnLoaded 事件。”然后@dain 说:“好吧,我明白了。好吧,KeyTrigger 是一个公共类,所以你可以扩展它并覆盖 OnEvent 以防止多个事件处理程序附加?”
所以我实现了@dain 的建议,覆盖了 KeyTrigger 类并停止了多次连接事件:
public class MyKeyTrigger : KeyTrigger
private bool eventAttached;
protected override void OnEvent(EventArgs eventArgs)
if (eventAttached) return;
base.OnEvent(eventArgs);
eventAttached = true;
protected override void OnDetaching()
eventAttached = false;
base.OnDetaching();
【讨论】:
以上是关于为啥在使用 Caliburn Micro Conductor.OneActive 时,Blend Interaction 事件触发器会多次触发?的主要内容,如果未能解决你的问题,请参考以下文章
使用 WPF 和 Caliburn.Micro 在视图中添加多个视图
从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器
使用 Caliburn.Micro 的单实例 WPF 应用程序