Using AvalonDock, I am experiencing a problem that seems somewhat strange to me.
The situation
I have an ObservableCollection of ViewModels bound to the DocumentSource property of the DockingManager. I add items to this collection, and thus documents are added to the DockingManager: the correct View (UserControl) for the ViewModel is picked because I specified a DataTemplate for each ViewModel.
The problem
The above all works. However, when I change between tabs in AvalonDock, the View is recreated each time a tab is selected (InitializeComponent() gets called). This leads to problems cause selections are not persisted, and static UIElements cause exceptions when added somewhere while already in use.
I am unsure what I am doing wrong, even after spending many hours on this issue. Using a template selector instead of the current way of choosing the right view has no effect.
Any ideas are welcome!
Edit 1
Interestingly, I have better results with a version of AvalonDock I had lying around here. That is version 2.0.0 from some time in 2016, as opposed to the current version of 3.5.0. The exact same code works as I would expect with the old version, and does not with the latest version.
Implementation
Below is a somewhat abbreviated version of my implementation of AvalonDock.
The main view:
<Window ... >
<Window.Resources>
<DataTemplate DataType="{x:Type local:Workspace1}">
<local:Workspace1View />
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<Button Content="1" Command="{Binding Path=OpenWorkspace1Command}" Margin="2" />
</StackPanel>
<ad:DockingManager
Grid.Row="1"
DocumentsSource="{Binding Path=Workspaces}"
AllowMixedOrientation="True">
</ad:DockingManager>
</Grid>
</Window>
The main ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private RelayCommand _openWorkspace1Command;
public ObservableCollection<WorkspaceBase> Workspaces { get; } = new ObservableCollection<WorkspaceBase>();
public ICommand OpenWorkspace1Command => _openWorkspace1Command ?? (_openWorkspace1Command = new RelayCommand(() =>
{
Workspaces.Add(new Workspace1());
}));
}
Workspace View (note that when scrolling down in the ListView, when leaving the tab and returning, the scroll position is reset, cause the View is recreated):
<UserControl ...>
<Grid>
<ListView ItemsSource="{Binding Items}" />
</Grid>
</UserControl>
Workspace ViewModel:
public class Workspace1 : WorkspaceBase
{
public override void OnWorkspaceOpened() { }
public List<string> Items { get; }
public Workspace1()
{
Items = new List<string>();
for (int i = 0; i < 10000; i++) Items.Add("Item " + i.ToString());
}
}
And finally the abstract WorkspaceBase class:
public abstract class WorkspaceBase : ViewModelBase, IDisposable
{
RelayCommand _closeCommand;
public virtual string DisplayName { get; }
public virtual ImageSource Icon { get; }
public virtual bool HideOnClose { get; } = false;
public virtual bool IsCloseable { get; } = true;
protected WorkspaceBase() { }
public ICommand CloseCommand => _closeCommand ?? (_closeCommand = new RelayCommand(CloseCommand_executed, CloseCommand_canExecute));
public delegate void WorkspaceRequestCloseEventHandler(object sender, WorkspaceRequestCloseEventArgs e);
public event WorkspaceRequestCloseEventHandler RequestClose;
void CloseCommand_executed()
{
Dispose();
this.RequestClose?.Invoke(this, new WorkspaceRequestCloseEventArgs());
}
private bool CloseCommand_canExecute()
{
return IsCloseable;
}
public abstract void OnWorkspaceOpened();
public void Dispose()
{
this.OnDispose();
}
protected virtual void OnDispose()
{
}
}