
I have a very difficult bug to produce when saving the docklayout for later use. I have hardcoded the ToolWindowsHaveCloseButtons="False" in my xaml which the issue arises. I have 2 layers deep of Docksites and a tab control in-between. On rare occasions, when saving off my Docksite, the required toolwindow which doesn't allow closing disappears. The backend code is a little crazy thanks to all the noise WPF generates when creating controls so I've had to add some wacky rules to get the state to save and load correctly. That all said, I can't get this bug to happen on my own so I can't create a sample app to expose the issue. Here's the inner Docksite code that is running within a tab control:
<local:BaseDockableControl x:Class="ServerManager.TemplateTestPropertiesControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:docking="http://schemas.actiprosoftware.com/winfx/xaml/docking"
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"
xmlns:lib="clr-namespace:ServerManagerLibrary;assembly=cbsm"
xmlns:local="clr-namespace:ServerManager"
mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<docking:DockSite x:Name="dockSite" Grid.Row="0" Margin="0" Padding="0"
CanToolWindowsClose="False"
CanToolWindowsFloat="False"
ToolWindowsHaveTabImages="True"
ToolItemContainerStyle="{StaticResource ToolWindowStyle}"
ToolItemTemplateSelector="{StaticResource ToolItemTemplateSelector}"
ToolItemsSource="{Binding ToolViewModels}"
ToolWindowsHaveCloseButtons="False">
<docking:SplitContainer Orientation="Vertical">
<docking:Workspace>
<ScrollViewer Grid.Row="0 " VerticalScrollBarVisibility="Auto" Background="{StaticResource White}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" Content="{Binding}" ContentTemplateSelector="{StaticResource TemplateTestPropertiesSelector}"/>
</Grid>
</ScrollViewer>
</docking:Workspace>
</docking:SplitContainer>
</docking:DockSite>
<!--Nasty hack to prevent Ctrl+Left Mouse Click from moving focus to another tab-->
<StackPanel Grid.Row="1" Focusable="True" />
</Grid>
</local:BaseDockableControl>
using System.Diagnostics;
using System.IO;
using System.Windows;
using ActiproSoftware.Windows.Controls.Docking;
using ServerManagerLibrary;
using XPlatformShared;
namespace ServerManager {
public abstract class BaseDockableControl : UserControl {
protected abstract DockSite _dockSite { get; }
//private DateTime? LastTimeLoaded;
private DateTime? LastTimeSaved;
int DuplicateWindowsOpeningCount;
private static int Id;
private readonly int _id;
public BaseDockableControl() {
_id = Id++;
Debug.WriteLine($"{GetType().Name} {_id}");
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
}
protected virtual void UserControl_Loaded(object? sender, RoutedEventArgs e) {
try {
Debug.WriteLine($"{GetType().Name} UserControl_Loaded {_id}");
LoadDockState();
}
catch { }
}
private void UserControl_Unloaded(object? sender, RoutedEventArgs e) {
try {
Debug.WriteLine($"{GetType().Name} UserControl_Unloaded {_id}");
SaveDockState();
}
catch { }
}
private void _dockSite_WindowsOpening(object? sender, DockingWindowsEventArgs e) {
try {
Debug.WriteLine($"{GetType().Name} _dockSite_WindowsOpening {_id} {DuplicateWindowsOpeningCount}");
if (DuplicateWindowsOpeningCount > 0)
return;
DuplicateWindowsOpeningCount++;
LoadDockState();
}
catch { }
}
private void _dockSite_WindowsClosing(object? sender, DockingWindowsEventArgs e) {
try {
Debug.WriteLine($"{GetType().Name} _dockSite_WindowsClosing {_id}");
SaveDockState();
}
catch { }
}
#region State Filename
private string StateFileName {
get {
if (DataContext != null)
return $"{DataContext.GetType().Name}.Dock.xml";
else
return $"{GetType().Name}.Dock.xml";
}
}
private string StateFilePath => Path.Combine(CBPath.UserApplicationDataPath, StateFileName);
#endregion
private void LoadDockState() {
//if (LastTimeLoaded != null && DateTime.Now - LastTimeLoaded < TimeSpan.FromSeconds(1))
// return;
if (_dockSite == null)
return;
bool stateIsLoadedFromFile = false;
try {
_dockSite.WindowsOpening -= _dockSite_WindowsOpening;
_dockSite.WindowsClosing -= _dockSite_WindowsClosing;
if (File.Exists(StateFilePath)) {
#if DEBUG
var contents = File.ReadAllText(StateFilePath);
#endif
App.DockSiteLayoutSerializerInstance.LoadFromFile(StateFilePath, _dockSite);
stateIsLoadedFromFile = true;
}
}
catch { }
finally {
_dockSite.WindowsOpening += _dockSite_WindowsOpening;
_dockSite.WindowsClosing += _dockSite_WindowsClosing;
}
if (!stateIsLoadedFromFile) {
if (_dockSite.ToolWindows.Count > 1)
_dockSite.ToolWindows[_dockSite.ToolWindows.Count - 1].Activate();
}
foreach (var toolWindow in _dockSite.ToolWindows) {
if (toolWindow.DataContext is BaseViewModel toolWindowVM)
toolWindowVM.InitializationComplete();
}
if (DataContext is TabItemViewModel tabItemVM)
tabItemVM.OnDockStateLoaded();
Debug.WriteLine($"{GetType().Name} LoadDockState {_id}");
//LastTimeLoaded = DateTime.Now;
}
public void SaveDockState() {
if (_dockSite == null)
return;
if (_dockSite.ToolWindows.Count == 0)
return;
if (LastTimeSaved != null && DateTime.Now - LastTimeSaved < TimeSpan.FromSeconds(1))
return;
var xml = App.DockSiteLayoutSerializerInstance.SaveToString(_dockSite);
XFile.StringToFile(xml, StateFilePath);
Debug.WriteLine($"{GetType().Name} SaveDockState {_id}");
LastTimeSaved = DateTime.Now;
}
protected void OnPreviewNumericText(object? sender, System.Windows.Input.TextCompositionEventArgs e) {
e.Handled = RegExUtil.IsNumeric(e.Text);
}
}
}
[Modified 7 months ago]