Avalondock - 某些面板的延迟布局恢复
Avalondock - Delayed layout restore for some panels
我正在使用 Load/Save 与 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;
}
当我在 deserializing/restoring 布局时所有 ViewModel 都可用时,这很好用。
但我正在考虑延迟布局恢复之类的方法。就我而言,一开始我有一些文档和一些面板可用。但是可能会有一些面板(称为 MapPanel)稍后加载(viewModels 会在将来的某个地方加载)。我不知道如何恢复这些面板的布局。
对于这种情况,我有 List MapPanels 来存储在 avalondock 布局加载时加载的可锚定,并尝试在 ILayoutUpdateStrategy 的 BeforeInsertAnchorable 中恢复它们。但是当我调试它时,存储的 LayoutAnchorable 与存储的 parent 不同。所以我假设在 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位置,浮动parent,停靠,大小等...?
所以我可能找到了解决方案。
我有一些面板(称之为 MapPanel),当从 XML 恢复布局时,它们的内容没有加载。在我的例子中,我有一个应用程序,它有一些文档和选项卡,此外,用户可以加载其他数据来显示地图。
我需要在用户加载这些地图时恢复它们的布局。 (点击按钮,选择在哪里加载地图等)
当我有 LayoutAnchorable 类型的附加列表 MapPanelsStorage 时,我有静态 class 调用 LayoutSaveLoadUtil(如代码项目中所述)。在这个列表中,我存储了所有在布局还原时缺少内容且 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;
}
}
}
我正在使用 Load/Save 与 CodeProject 中描述的布局类似的方式。捕获 LayoutSerializationCallback 事件并尝试为 LayoutItem
找到相应的 viewModelprivate 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;
}
当我在 deserializing/restoring 布局时所有 ViewModel 都可用时,这很好用。
但我正在考虑延迟布局恢复之类的方法。就我而言,一开始我有一些文档和一些面板可用。但是可能会有一些面板(称为 MapPanel)稍后加载(viewModels 会在将来的某个地方加载)。我不知道如何恢复这些面板的布局。
对于这种情况,我有 List MapPanels 来存储在 avalondock 布局加载时加载的可锚定,并尝试在 ILayoutUpdateStrategy 的 BeforeInsertAnchorable 中恢复它们。但是当我调试它时,存储的 LayoutAnchorable 与存储的 parent 不同。所以我假设在 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位置,浮动parent,停靠,大小等...?
所以我可能找到了解决方案。
我有一些面板(称之为 MapPanel),当从 XML 恢复布局时,它们的内容没有加载。在我的例子中,我有一个应用程序,它有一些文档和选项卡,此外,用户可以加载其他数据来显示地图。
我需要在用户加载这些地图时恢复它们的布局。 (点击按钮,选择在哪里加载地图等)
当我有 LayoutAnchorable 类型的附加列表 MapPanelsStorage 时,我有静态 class 调用 LayoutSaveLoadUtil(如代码项目中所述)。在这个列表中,我存储了所有在布局还原时缺少内容且 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;
}
}
}