C# 为什么使用Caliburn Micro Conductor.OneActive时,混合交互事件会触发多次触发?
[请注意,我在第一次尝试诊断时提出了错误的问题-现在已更正。] 我有一个WPF应用程序,它有一个从Conductor.Collection.OneActive继承的主窗口。它处理导航请求并保留viewmodels的缓存,以便保持状态。此私有集合与基本集合几乎相同。Items集合但并非所有viewmodels都是IScreen 当我们从一个活动项移动到另一个活动项时,一切正常,状态保持不变。然而,交互触发器有一个bug。当活动项为IScreen时,触发器会在每次导航时额外触发一次,就好像每次都重新连接一样;普通触发器不这样做,只有来自交互库的触发器才这样做。如果活动项不是IScreen-只是从PropertyChangedBase继承-我们看不到这个问题,但是在导航期间我们也会丢失视图的状态 如果导航到视图四次,事件将触发四次、五次、五次,依此类推 这看起来是一样的,但我不能使用他的解决方案,因为我不知道具体的viewmodels是什么,也不能为它们创建公共属性 我的主窗口类如下所示:C# 为什么使用Caliburn Micro Conductor.OneActive时,混合交互事件会触发多次触发?,c#,wpf,mvvm,caliburn.micro,blend,C#,Wpf,Mvvm,Caliburn.micro,Blend,[请注意,我在第一次尝试诊断时提出了错误的问题-现在已更正。] 我有一个WPF应用程序,它有一个从Conductor.Collection.OneActive继承的主窗口。它处理导航请求并保留viewmodels的缓存,以便保持状态。此私有集合与基本集合几乎相同。Items集合但并非所有viewmodels都是IScreen 当我们从一个活动项移动到另一个活动项时,一切正常,状态保持不变。然而,交互触发器有一个bug。当活动项为IScreen时,触发器会在每次导航时额外触发一次,就好像每次都重新
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;
}
}
<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>
<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>
....
公共密封类主窗口视图模型:Conductor.Collection.OneActive,IHandle
{
公共无效句柄(NavigateToUriMessage消息)
{
var ignoredUris=RibbonUri.GetItems()。其中(t=>t.SubTabs.Count>0)。选择(t=>t.Uri);
Func isMatch=uri=>uri==message.uri;
if(isMatch(RibbonUri.BookkeepingBatchView.Uri))
GetAndActivateViewModel(消息);
...
}
private T GetAndActivateViewModel(NavigateToUriMessage消息),其中T:class
{
var vm=GetViewModel(message.Uri);
激活肽(vm);
返回(T)vm;
}
私有对象GetViewModel(字符串uri),其中T:class
{
if(viewModelCache.ContainsKey(uri))
返回viewModelCache[uri];
var vm=viewModelFactory.Create();
添加(uri,vm);
返回虚拟机;
}
}
我的主窗口XAML如下所示:
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;
}
}
<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>
<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>
....
活动项视图中的XAML如下所示:
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;
}
}
<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>
<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>
....
....
似乎,尽管隐藏在对公认答案的评论中。KeyTrigger类有问题,每次将子控件重新加载到ContentControl时都会附加事件@gunter说,“真正的问题似乎是OnLoaded上的钩子和tab控件上的元素得到多个OnLoaded事件。”然后@dain说,“好的,我明白了。KeyTrigger是一个公共类,所以你可以扩展它并覆盖OneEvent以防止多个事件处理程序连接?”
因此,我实现了@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();
}
我已经能够更准确地诊断这个问题,因为这里有一个问题的答案:当设置ActiveItem时,子控件加载的事件将触发。KeyTrigger是有线加载的,所以要多加载。如果我创建自己的KeyTrigger类并从KeyTrigger继承,我可以重写OnEvent方法并停止多个附件。但是,我想知道每次激活它们时,有多少其他事情会受到加载事件触发的影响?是我做错了什么,还是这只是我可以绕过的KeyTrigger的一个问题。使用反编译器进行的进一步调查表明,KeyTrigger在加载期间附加了一个按键事件处理程序,但当UserControl卸载时,它不会得到一个OnDetaching调用,因此它是不对称的。因此,UserControl每次被激活并连接KeyTrigger时都会获得一个加载事件。