You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于ViewModel优先MVVM模式在AvalonDock中添加AnchorablesSource遇阻

问题:Stylet框架结合AvalonDock使用AnchorablesSource时加载View失败

我在用Stylet框架的ViewModel优先MVVM模式开发时,给AvalonDock的AnchorablesSource绑定数据遇到了问题。

相关代码

AvalonDock的XAML代码

<DockingManager 
        Grid.Row="1"
        DocumentsSource="{Binding Scl.Documents}"
        AnchorablesSource="{Binding Scl.DocumentsAnchorable}"            
        x:Name="dockManager"
        AllowMixedOrientation="True"
        AutoWindowSizeWhenOpened="True"
        IsVirtualizingAnchorable="True"
        IsVirtualizingDocument="True"
        ActiveContent="{Binding Scl.ActiveDocument, Mode=TwoWay}"
        DocumentClosed="{s:Action DocumentClosed}"             
        >
        <DockingManager.LayoutItemContainerStyle>
            <Style TargetType="{x:Type LayoutItem}">
                <Setter Property="Title" Value="{Binding Model.Title}"/>
                <Setter Property="Height" Value="Auto"/>
                <Setter Property="IconSource" Value="{Binding Model.IconSource}" />
            </Style>
        </DockingManager.LayoutItemContainerStyle>
        <DockingManager.LayoutItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>
                    <ContentControl s:View.Model="{Binding Content , FallbackValue=#ERROR Content.title#}"></ContentControl>
                </Grid>
            </DataTemplate>
        </DockingManager.LayoutItemTemplate>
        <LayoutRoot x:Name="root">
            <LayoutPanel Orientation="Horizontal">
                <LayoutAnchorablePane x:Name="LayoutAnchorablePane" DockWidth="50">
                </LayoutAnchorablePane>
                <LayoutDocumentPaneGroup>
                    <LayoutDocumentPane x:Name="LayoutDocumentPane">
                        <!--  This is where the new windows are typically added -->
                    </LayoutDocumentPane>
                </LayoutDocumentPaneGroup>
                <LayoutAnchorablePaneGroup DockWidth="250">
                    <LayoutAnchorablePane x:Name="LayoutAnchorablePane1">
                    </LayoutAnchorablePane>
                </LayoutAnchorablePaneGroup>
            </LayoutPanel>

            <LayoutRoot.LeftSide>
                <LayoutAnchorSide>
                    <LayoutAnchorGroup>
                    </LayoutAnchorGroup>
                </LayoutAnchorSide>
            </LayoutRoot.LeftSide>
        </LayoutRoot>
    </DockingManager>

ViewModel代码

public ObservableCollection<LayoutDocument> Documents {get; set;} = new ObservableCollection<LayoutDocument>();
public ObservableCollection<LayoutAnchorable> DocumentsAnchorable { get; set; } = new ObservableCollection<LayoutAnchorable>();

添加普通文档的方法(可正常工作)

public void NewLayout(Screen viewModel, string title)
{
    if (IsOpen(title) == true)
    {
        return;
    }

    LayoutDocument layout = new LayoutDocument
    {
        Title = title,
        Content = viewModel
    };

    Documents.Add(layout);
    Documents.Move(Documents.Count - 1, 0);
    
    ActiveDocument = layout;

} 

添加可锚定文档的方法(报错)

public void NewLayoutAnchorable(Screen viewModel, string title)
{
    if (IsOpen(title) == true)
    {
        return;
    }
    LayoutAnchorable layout = new LayoutAnchorable
    {
        Title = title,
        Content = viewModel
    };

    DocumentsAnchorable.Add(layout);
    //DocumentsAnchorable.Move(Documents.Count - 1, 0);
    //ActiveDocument = layout;

}

错误信息

Exception thrown: 'Stylet.StyletViewLocationException' in Stylet.dll
Exception thrown: 'System.Windows.Markup.XamlParseException' in PresentationFramework.dll
Unable to transform ViewModel name AvalonDock.Layout.LayoutAnchorable into a suitable View name

排查过程

  1. 编辑1:把可锚定文档的ViewModel加到ObservableCollection<LayoutDocument>里能正常加载View,只有加到ObservableCollection<LayoutAnchorable>时才报错,说明问题出在AvalonDock的XAML配置上。
  2. 编辑2:移除XAML里的<ContentControl s:View.Model="{Binding Content , FallbackValue=#ERROR Content.title#}"></ContentControl>后,两个集合都能添加ViewModel,窗口也能显示,但内容为空。推测需要给可锚定窗口单独配置LayoutItemTemplate,但找不到示例。
  3. 编辑3:已制作极简复现项目。

我想知道为什么Stylet处理LayoutDocument时能找到对应View,但LayoutAnchorable不行?


解决方法

问题根源是AvalonDock对文档和可锚定面板的模板上下文处理有差异:

  • 绑定DocumentsSource时,LayoutItemTemplate的上下文是LayoutDocument,{Binding Content}能直接拿到你的ViewModel;
  • 绑定AnchorablesSource时,LayoutItemTemplate的上下文是LayoutItem容器,此时直接绑定Content会指向容器本身,Stylet误把LayoutAnchorable当成ViewModel去匹配View,所以报错。

有两种修复方式:

方式一:分别指定文档和可锚定面板的模板

放弃共用LayoutItemTemplate,给DockingManager单独设置LayoutDocumentTemplate和LayoutAnchorableTemplate,确保各自的上下文正确:

<DockingManager ...>
    <!-- 保留原有的LayoutItemContainerStyle -->
    <DockingManager.LayoutDocumentTemplate>
        <DataTemplate>
            <Grid>
                <ContentControl s:View.Model="{Binding Content}" />
            </Grid>
        </DataTemplate>
    </DockingManager.LayoutDocumentTemplate>
    <DockingManager.LayoutAnchorableTemplate>
        <DataTemplate>
            <Grid>
                <ContentControl s:View.Model="{Binding Content}" />
            </Grid>
        </DataTemplate>
    </DockingManager.LayoutAnchorableTemplate>
    <!-- 其余布局代码不变 -->
</DockingManager>

方式二:修正共用模板的绑定路径

如果想继续用统一模板,需要把绑定路径改成Model.Content,因为LayoutItem的Model属性才是真正的LayoutDocument/LayoutAnchorable实例:

<DockingManager.LayoutItemTemplate>
    <DataTemplate>
        <Grid>
            <ContentControl s:View.Model="{Binding Model.Content, FallbackValue=#ERROR Content.title#}" />
        </Grid>
    </DataTemplate>
</DockingManager.LayoutItemTemplate>

你的LayoutItemContainerStyle里已经用了{Binding Model.Title},说明你清楚Model属性的作用,只是模板里没补上这一层。两种方式都能解决问题,选哪种看你项目的模板复用需求。


内容的提问来源于stack exchange,提问作者Richard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 07:25:17