为啥在使用 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 应用程序

使用 caliburn micro 打开另一个视图

Caliburn.Micro 能否很好地与用户控件配合使用?

来自用户控制的 Caliburn.Micro 导航对 ShellViewModel