首页 > 解决方案 > Avalondock - 某些面板的延迟布局恢复

问题描述

我正在使用类似于CodeProject上描述的加载/保存布局。捕获 LayoutSerializationCallback 事件并尝试为 LayoutItem 找到对应的 viewModel

private void LayoutSerializer_LayoutSerializationCallback(object sender, LayoutSerializationCallbackEventArgs e)
        {
            // This can happen if the previous session was loading a file
            // but was unable to initialize the view ...
            if (string.IsNullOrWhiteSpace(e.Model.ContentId) || (e.Content = ReloadItem(e.Model)) == null)
            {
                e.Cancel = true;
                return;
            }
        }

private object ReloadItem(object item)
{
    object ret = null;

    switch (item)
    {
        case LayoutAnchorable anchorable:
            //list of tools windows
            ret = Manager.Tools.FirstOrDefault(i => i.ContentId == anchorable.ContentId);
            if(ret == null && anchorable.ContentId.StartsWith(MapPanel.MapPanelPrefix))
            {

                MapPanels.Add(anchorable);
            }
            break;
        case LayoutDocument document:
            // list of restored documents
            ret = Manager.Documents.FirstOrDefault(i => i.ContentId == document.ContentId);
            break;
        default:
            throw new NotImplementedException("Not implemented type of AD item ");
    }

    return ret;

}

当我在反序列化/恢复布局时拥有所有可用的 ViewModel 时,这可以正常工作。

但我正在考虑延迟布局恢复之类的事情。就我而言,一开始我有一些文档和一些面板可用。但是可能有一些面板(称为 MapPanel)稍后会加载(视图模型会在将来的某个地方加载)。而且我不知道如何恢复这些面板的布局。

对于这种情况,我有 List MapPanels 来存储在 avalondock 布局加载时加载的可锚定对象,并尝试在 ILayoutUpdateStrategy 的 BeforeInsertAnchorable 中恢复它们。但是当我调试它时,存储的 LayoutAnchorable 有不同的父级存储一个。所以我假设在 LayoutSerializationCallback 中取消(e.Cancel = true)后,以某种方式修改了未恢复的可锚定。

public bool BeforeInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableToShow, ILayoutContainer destinationContainer)
        {
            if (anchorableToShow.Content is ToolPanel tool)
            {

                if(tool is MapPanel)
                {
                    anchorableToShow = LayoutSaveLoadUtil.Instance.MapPanels.FirstOrDefault(mp => mp.ContentId == anchorableToShow.ContentId);
                }

                var destPane = destinationContainer as LayoutAnchorablePane;
                if (destinationContainer != null && destinationContainer.FindParent<LayoutFloatingWindow>() != null)
                    return false;


                var dockLeftPane = layout.Descendents().OfType<LayoutAnchorablePane>().FirstOrDefault(d => d.Name == tool.PreferredLocation + "Pane");
                if (dockLeftPane != null)
                {
                    dockLeftPane.Children.Add(anchorableToShow);
                    return true;
                }

                return false;
            }

            return false;

        }

所以我很好奇实现这一目标的正确方法是什么。我也在考虑在加载 MapPanel 后(再次)恢复布局,但我不知道如何跳过所有其他 LayoutItems。那么是否有可能恢复单个Anchorable位置,浮动父级,停靠,大小等......?

标签: c#mvvmavalondock

解决方案


所以我可能想出了解决方案。

我有一些面板(称为 MapPanel),当从 XML 恢复布局时,它们的内容没有加载。在我的情况下,我有应用程序,它有一些文档和选项卡,另外,用户可以加载其他数据来显示地图。

当用户加载这些地图时,我需要恢复它们的布局。(单击按钮,选择加载地图的位置等)

当我有 LayoutAnchorable 类型的附加列表 MapPanelsStorage 时,我有名为 LayoutSaveLoadUtil 的静态类(如 codeproject 所述)。在此列表中,我存储在布局恢复时缺少内容的所有布局,并且 ContentId 具有特定前缀。这告诉我那是 MapPanel。然后我创建一个虚拟内容,将其分配给该面板并将其可见性设置为 false(因此面板不可见)

(这在 layoutSerializationCallback 中调用)

private object ReloadItem(object item)
{
    object ret = null;

    switch (item)
    {
        case LayoutAnchorable anchorable:
            //list of tools windows
            ret = Manager.Tools.FirstOrDefault(i => i.ContentId == anchorable.ContentId);
            if(ret == null && anchorable.ContentId.StartsWith(MapPanel.MapPanelPrefix))
            {
                //when layoutAnchorable is MapPanel (has MapPanel prefix) and its content is loaded yet
                //store its layout into list to restore it later (when content is loaded)
                MapPanelsStorage.Add(anchorable);
                //Set anchorable visibility to false 
                anchorable.IsVisible = false;
                //return dummy model for avalondock layout serialization
                ret = new EmptyMapViewModel();
            }
            break;
        case LayoutDocument document:
            // list of restored documents
            ret = Manager.Documents.FirstOrDefault(i => i.ContentId == document.ContentId);
            break;
        default:
            throw new NotImplementedException("Not implemented type of AD item ");
    }

    return ret;

}

然后在 BeforeInsertAnchorable 中(当新面板添加到布局中时调用)我检查面板内容是否为 MapPanel,查找存储的布局,尝试查找父级(LayoutAnchorablePane/LayoutDocumentPane),然后添加它而不是 DummyHidden 面板,然后将其从存储中删除.

//hacky hacky to restore map panel layout when map is opened after layout is loaded
//in layout deserialization, deserialize layout for MapPanels that hasnt DataModels yet with DummyModel to preserve their layout
if (anchorableToShow.Content is MapPanel mappanel)
{
    var storedMapsLayout = LayoutSaveLoadUtil.Instance.MapPanelsStorage;

    //check if Map panel has stored layout from previous layout deserialization
    var matchingAnchorable = storedMapsLayout.FirstOrDefault(m => m.ContentId == mappanel.ContentId);
    if (matchingAnchorable != null)
    {
        //make preserved layout visible, so its parent and etc is restored. Without this, correct parent LayoutGroup isnt found
        matchingAnchorable.IsVisible = true;

        LayoutAnchorablePane matchingAnchorablePane;
        LayoutDocumentPane matchingDocumentPane;

        //find parent layoutGroup. This can be LayoutAnchorablePane or LayoutDocumentPane
        if ((matchingAnchorablePane = matchingAnchorable.FindParent<LayoutAnchorablePane>()) != null)
        {
            //add new panel into layoutGroup with correct layout
            matchingAnchorablePane.Children.Add(anchorableToShow);
            //remove old dummy panel
            matchingAnchorablePane.RemoveChild(matchingAnchorable);
            //remove restored layout from storage
            storedMapsLayout.Remove(matchingAnchorable);
            return true;
        }
        else if ((matchingDocumentPane = matchingAnchorable.FindParent<LayoutDocumentPane>()) != null)
        {
            //add new panel into layoutGroup with correct layout
            matchingDocumentPane.Children.Add(anchorableToShow);
            //remove old dummy panel
            matchingDocumentPane.RemoveChild(matchingAnchorable);
            //remove restored layout from storage
            storedMapsLayout.Remove(matchingAnchorable);
            return true;
        }
        else
        {
            matchingAnchorable.IsVisible = false;
        }
    }
}

推荐阅读