diff --git a/PettingZoo/Images/Dock.svg b/PettingZoo/Images/Dock.svg
new file mode 100644
index 0000000..b1eabc4
--- /dev/null
+++ b/PettingZoo/Images/Dock.svg
@@ -0,0 +1,50 @@
+
+
+
+
diff --git a/PettingZoo/Images/Undock.svg b/PettingZoo/Images/Undock.svg
new file mode 100644
index 0000000..cdc1afb
--- /dev/null
+++ b/PettingZoo/Images/Undock.svg
@@ -0,0 +1,50 @@
+
+
+
+
diff --git a/PettingZoo/PettingZoo.csproj b/PettingZoo/PettingZoo.csproj
index b227252..6e599ee 100644
--- a/PettingZoo/PettingZoo.csproj
+++ b/PettingZoo/PettingZoo.csproj
@@ -18,13 +18,16 @@
+
+
+
@@ -42,6 +45,10 @@
+
+
+
+
True
@@ -91,6 +98,11 @@
True
True
+
+ True
+ True
+ UndockedTabHostStrings.resx
+
@@ -130,6 +142,10 @@
SubscriberViewStrings.Designer.cs
PublicResXFileCodeGenerator
+
+ PublicResXFileCodeGenerator
+ UndockedTabHostStrings.Designer.cs
+
diff --git a/PettingZoo/TODO.md b/PettingZoo/TODO.md
index c181109..450c51a 100644
--- a/PettingZoo/TODO.md
+++ b/PettingZoo/TODO.md
@@ -1,11 +1,10 @@
Must-have
---------
+- Option to not save password in profiles
Should-have
-----------
-- Support undocking tabs (and redocking afterwards)
-- Allow tab reordering
- Save / load publisher messages (either as templates or to disk)
- Export received messages to Tapeti JSON file / Tapeti.Cmd command-line
diff --git a/PettingZoo/UI/Connection/ConnectionViewModel.cs b/PettingZoo/UI/Connection/ConnectionViewModel.cs
index 55a02b8..37afe6e 100644
--- a/PettingZoo/UI/Connection/ConnectionViewModel.cs
+++ b/PettingZoo/UI/Connection/ConnectionViewModel.cs
@@ -235,7 +235,6 @@ namespace PettingZoo.UI.Connection
private bool SaveCanExecute()
{
- // TODO check changes in parameters (excluding password)
return SelectedStoredConnection != null &&
SelectedStoredConnection.Id != Guid.Empty &&
ValidConnection(false) &&
diff --git a/PettingZoo/UI/Main/ITabContainer.cs b/PettingZoo/UI/Main/ITabContainer.cs
new file mode 100644
index 0000000..4591fda
--- /dev/null
+++ b/PettingZoo/UI/Main/ITabContainer.cs
@@ -0,0 +1,8 @@
+namespace PettingZoo.UI.Main
+{
+ public interface ITabContainer
+ {
+ public double TabWidth { get; }
+ public double TabHeight { get; }
+ }
+}
diff --git a/PettingZoo/UI/Main/MainWindow.xaml b/PettingZoo/UI/Main/MainWindow.xaml
index 6194a98..b0fa1a7 100644
--- a/PettingZoo/UI/Main/MainWindow.xaml
+++ b/PettingZoo/UI/Main/MainWindow.xaml
@@ -6,6 +6,7 @@
xmlns:main="clr-namespace:PettingZoo.UI.Main"
xmlns:tab="clr-namespace:PettingZoo.UI.Tab"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
+ xmlns:ui="clr-namespace:PettingZoo.UI"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance main:DesignTimeMainWindowViewModel, IsDesignTimeCreatable=True}"
Width="800"
@@ -17,7 +18,11 @@
Closed="MainWindow_OnClosed">
+
+
+
+
+
@@ -70,28 +81,39 @@
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PettingZoo/UI/Main/MainWindow.xaml.cs b/PettingZoo/UI/Main/MainWindow.xaml.cs
index 10221a7..ea1b01d 100644
--- a/PettingZoo/UI/Main/MainWindow.xaml.cs
+++ b/PettingZoo/UI/Main/MainWindow.xaml.cs
@@ -1,15 +1,20 @@
using System;
+using System.Collections.ObjectModel;
using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
using PettingZoo.Core.Connection;
using PettingZoo.UI.Connection;
using PettingZoo.UI.Subscribe;
+using PettingZoo.UI.Tab;
// TODO improve readability of the connection status (especially when connecting/disconnected)
namespace PettingZoo.UI.Main
{
#pragma warning disable CA1001 // MainWindow can't be IDisposable, handled instead in OnDispatcherShutDownStarted
- public partial class MainWindow
+ public partial class MainWindow : ITabContainer
{
private readonly MainWindowViewModel viewModel;
@@ -18,7 +23,7 @@ namespace PettingZoo.UI.Main
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
- viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog);
+ viewModel = new MainWindowViewModel(connectionFactory, connectionDialog, subscribeDialog, this);
DataContext = viewModel;
InitializeComponent();
@@ -43,6 +48,74 @@ namespace PettingZoo.UI.Main
{
var _ = Application.Current.Windows;
}
+
+
+ private void TabItem_PreviewRightMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ var tabItem = GetParent(e.OriginalSource);
+ if (tabItem == null)
+ return;
+
+ var tabControl = GetParent(tabItem);
+ if (tabControl == null)
+ return;
+
+ tabControl.SelectedItem = tabItem.DataContext;
+ }
+
+
+ private void TabItem_PreviewMouseMove(object sender, MouseEventArgs e)
+ {
+ if (e.Source is not TabItem tabItem)
+ return;
+
+ if (Mouse.PrimaryDevice.LeftButton == MouseButtonState.Pressed)
+ DragDrop.DoDragDrop(tabItem, tabItem, DragDropEffects.All);
+ }
+
+
+ private void TabItem_Drop(object sender, DragEventArgs e)
+ {
+ var targetTab = GetParent(e.OriginalSource);
+ if (targetTab == null)
+ return;
+
+ var sourceTab = (TabItem?)e.Data.GetData(typeof(TabItem));
+ if (sourceTab == null || sourceTab == targetTab)
+ return;
+
+ var tabControl = GetParent(targetTab);
+ if (tabControl?.ItemsSource is not ObservableCollection dataCollection)
+ return;
+
+ if (sourceTab.DataContext is not ITab sourceData || targetTab.DataContext is not ITab targetData)
+ return;
+
+ var sourceIndex = dataCollection.IndexOf(sourceData);
+ var targetIndex = dataCollection.IndexOf(targetData);
+
+ dataCollection.Move(sourceIndex, targetIndex);
+ }
+
+
+ private static T? GetParent(object originalSource) where T : DependencyObject
+ {
+ var current = originalSource as DependencyObject;
+
+ while (current != null)
+ {
+ if (current is T targetType)
+ return targetType;
+
+ current = VisualTreeHelper.GetParent(current);
+ }
+
+ return null;
+ }
+
+
+ public double TabWidth => SubscriberTabs.ActualWidth;
+ public double TabHeight => SubscriberTabs.ActualHeight;
}
#pragma warning restore CA1001
}
diff --git a/PettingZoo/UI/Main/MainWindowStrings.Designer.cs b/PettingZoo/UI/Main/MainWindowStrings.Designer.cs
index 8b3f52f..a167f44 100644
--- a/PettingZoo/UI/Main/MainWindowStrings.Designer.cs
+++ b/PettingZoo/UI/Main/MainWindowStrings.Designer.cs
@@ -19,7 +19,7 @@ namespace PettingZoo.UI.Main {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class MainWindowStrings {
@@ -96,6 +96,15 @@ namespace PettingZoo.UI.Main {
}
}
+ ///
+ /// Looks up a localized string similar to Undock tab.
+ ///
+ public static string CommandUndock {
+ get {
+ return ResourceManager.GetString("CommandUndock", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Close tab.
///
@@ -105,6 +114,15 @@ namespace PettingZoo.UI.Main {
}
}
+ ///
+ /// Looks up a localized string similar to Undock.
+ ///
+ public static string ContextMenuUndockTab {
+ get {
+ return ResourceManager.GetString("ContextMenuUndockTab", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Non-persistent.
///
@@ -159,6 +177,15 @@ namespace PettingZoo.UI.Main {
}
}
+ ///
+ /// Looks up a localized string similar to No open tabs. Click on New Publisher or New Subscriber to open a new tab..
+ ///
+ public static string TabsEmptyText {
+ get {
+ return ResourceManager.GetString("TabsEmptyText", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Petting Zoo - a RabbitMQ live message viewer.
///
diff --git a/PettingZoo/UI/Main/MainWindowStrings.resx b/PettingZoo/UI/Main/MainWindowStrings.resx
index 6df2e25..6814dea 100644
--- a/PettingZoo/UI/Main/MainWindowStrings.resx
+++ b/PettingZoo/UI/Main/MainWindowStrings.resx
@@ -129,9 +129,15 @@
New Subscriber...
+
+ Undock tab
+
Close tab
+
+ Undock
+
Non-persistent
@@ -150,6 +156,9 @@
Error: {0}
+
+ No open tabs. Click on New Publisher or New Subscriber to open a new tab.
+
Petting Zoo - a RabbitMQ live message viewer
diff --git a/PettingZoo/UI/Main/MainWindowViewModel.cs b/PettingZoo/UI/Main/MainWindowViewModel.cs
index 5da1cdc..b16d70e 100644
--- a/PettingZoo/UI/Main/MainWindowViewModel.cs
+++ b/PettingZoo/UI/Main/MainWindowViewModel.cs
@@ -9,6 +9,7 @@ using PettingZoo.Core.Connection;
using PettingZoo.UI.Connection;
using PettingZoo.UI.Subscribe;
using PettingZoo.UI.Tab;
+using PettingZoo.UI.Tab.Undocked;
namespace PettingZoo.UI.Main
{
@@ -17,18 +18,21 @@ namespace PettingZoo.UI.Main
private readonly IConnectionFactory connectionFactory;
private readonly IConnectionDialog connectionDialog;
private readonly ISubscribeDialog subscribeDialog;
+ private readonly ITabContainer tabContainer;
private readonly ITabFactory tabFactory;
private SubscribeDialogParams? subscribeDialogParams;
private IConnection? connection;
private string connectionStatus;
private ITab? activeTab;
+ private readonly Dictionary undockedTabs = new();
private readonly DelegateCommand connectCommand;
private readonly DelegateCommand disconnectCommand;
private readonly DelegateCommand publishCommand;
private readonly DelegateCommand subscribeCommand;
private readonly DelegateCommand closeTabCommand;
+ private readonly DelegateCommand undockTabCommand;
public string ConnectionStatus
@@ -60,6 +64,7 @@ namespace PettingZoo.UI.Main
public ICommand PublishCommand => publishCommand;
public ICommand SubscribeCommand => subscribeCommand;
public ICommand CloseTabCommand => closeTabCommand;
+ public ICommand UndockTabCommand => undockTabCommand;
public IEnumerable ToolbarCommands => ActiveTab is ITabToolbarCommands tabToolbarCommands
? tabToolbarCommands.ToolbarCommands
@@ -68,13 +73,17 @@ namespace PettingZoo.UI.Main
public Visibility ToolbarCommandsSeparatorVisibility =>
ToolbarCommands.Any() ? Visibility.Visible : Visibility.Collapsed;
+ public Visibility NoTabsVisibility =>
+ Tabs.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
+
public MainWindowViewModel(IConnectionFactory connectionFactory, IConnectionDialog connectionDialog,
- ISubscribeDialog subscribeDialog)
+ ISubscribeDialog subscribeDialog, ITabContainer tabContainer)
{
this.connectionFactory = connectionFactory;
this.connectionDialog = connectionDialog;
this.subscribeDialog = subscribeDialog;
+ this.tabContainer = tabContainer;
connectionStatus = GetConnectionStatus(null);
@@ -83,9 +92,10 @@ namespace PettingZoo.UI.Main
disconnectCommand = new DelegateCommand(DisconnectExecute, IsConnectedCanExecute);
publishCommand = new DelegateCommand(PublishExecute, IsConnectedCanExecute);
subscribeCommand = new DelegateCommand(SubscribeExecute, IsConnectedCanExecute);
- closeTabCommand = new DelegateCommand(CloseTabExecute, CloseTabCanExecute);
+ closeTabCommand = new DelegateCommand(CloseTabExecute, HasActiveTabCanExecute);
+ undockTabCommand = new DelegateCommand(UndockTabExecute, HasActiveTabCanExecute);
- tabFactory = new ViewTabFactory(this, closeTabCommand);
+ tabFactory = new ViewTabFactory(this);
}
@@ -125,7 +135,16 @@ namespace PettingZoo.UI.Main
private async void DisconnectExecute()
{
Tabs.Clear();
-
+
+ var capturedUndockedTabs = undockedTabs.ToList();
+ undockedTabs.Clear();
+
+ foreach (var undockedTab in capturedUndockedTabs)
+ undockedTab.Value.Close();
+
+ RaisePropertyChanged(nameof(NoTabsVisibility));
+ undockTabCommand.RaiseCanExecuteChanged();
+
if (connection != null)
{
await connection.DisposeAsync();
@@ -170,24 +189,48 @@ namespace PettingZoo.UI.Main
private void CloseTabExecute()
{
- if (ActiveTab == null)
+ RemoveActiveTab();
+ }
+
+
+ private void UndockTabExecute()
+ {
+ var tab = RemoveActiveTab();
+ if (tab == null)
return;
+ var tabHostWindow = UndockedTabHostWindow.Create(this, tab, tabContainer.TabWidth, tabContainer.TabHeight);
+ undockedTabs.Add(tab, tabHostWindow);
+
+ tabHostWindow.Show();
+ }
+
+
+ private ITab? RemoveActiveTab()
+ {
+ if (ActiveTab == null)
+ return null;
+
var activeTabIndex = Tabs.IndexOf(ActiveTab);
if (activeTabIndex == -1)
- return;
+ return null;
+ var tab = Tabs[activeTabIndex];
Tabs.RemoveAt(activeTabIndex);
-
+
if (activeTabIndex == Tabs.Count)
activeTabIndex--;
ActiveTab = activeTabIndex >= 0 ? Tabs[activeTabIndex] : null;
closeTabCommand.RaiseCanExecuteChanged();
+ undockTabCommand.RaiseCanExecuteChanged();
+ RaisePropertyChanged(nameof(NoTabsVisibility));
+
+ return tab;
}
-
- private bool CloseTabCanExecute()
+
+ private bool HasActiveTabCanExecute()
{
return ActiveTab != null;
}
@@ -199,9 +242,25 @@ namespace PettingZoo.UI.Main
ActiveTab = tab;
closeTabCommand.RaiseCanExecuteChanged();
+ undockTabCommand.RaiseCanExecuteChanged();
+ RaisePropertyChanged(nameof(NoTabsVisibility));
}
-
+
+ public void DockTab(ITab tab)
+ {
+ if (undockedTabs.Remove(tab, out var tabHostWindow))
+ tabHostWindow.Close();
+
+ AddTab(tab);
+ }
+
+ public void UndockedTabClosed(ITab tab)
+ {
+ undockedTabs.Remove(tab);
+ }
+
+
private void ConnectionChanged()
{
disconnectCommand.RaiseCanExecuteChanged();
@@ -232,7 +291,7 @@ namespace PettingZoo.UI.Main
public class DesignTimeMainWindowViewModel : MainWindowViewModel
{
- public DesignTimeMainWindowViewModel() : base(null!, null!, null!)
+ public DesignTimeMainWindowViewModel() : base(null!, null!, null!, null!)
{
}
}
diff --git a/PettingZoo/UI/Tab/ITab.cs b/PettingZoo/UI/Tab/ITab.cs
index 5e650f7..2448b1f 100644
--- a/PettingZoo/UI/Tab/ITab.cs
+++ b/PettingZoo/UI/Tab/ITab.cs
@@ -23,7 +23,6 @@ namespace PettingZoo.UI.Tab
{
string Title { get; }
ContentControl Content { get; }
- ICommand CloseTabCommand { get; }
}
diff --git a/PettingZoo/UI/Tab/ITabHost.cs b/PettingZoo/UI/Tab/ITabHost.cs
index ec570df..e73ec07 100644
--- a/PettingZoo/UI/Tab/ITabHost.cs
+++ b/PettingZoo/UI/Tab/ITabHost.cs
@@ -3,5 +3,8 @@
public interface ITabHost
{
void AddTab(ITab tab);
+
+ void DockTab(ITab tab);
+ void UndockedTabClosed(ITab tab);
}
}
diff --git a/PettingZoo/UI/Tab/Publisher/PublisherView.xaml b/PettingZoo/UI/Tab/Publisher/PublisherView.xaml
index 81d25cc..394ed57 100644
--- a/PettingZoo/UI/Tab/Publisher/PublisherView.xaml
+++ b/PettingZoo/UI/Tab/Publisher/PublisherView.xaml
@@ -10,54 +10,57 @@
d:DesignWidth="800"
d:DataContext="{d:DesignInstance res:DesignTimePublisherViewModel, IsDesignTimeCreatable=True}"
Background="White">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs b/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs
index 9f88adf..595b91e 100644
--- a/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs
+++ b/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs
@@ -5,8 +5,6 @@ using System.Windows.Controls;
using System.Windows.Input;
using PettingZoo.Core.Connection;
-// TODO publish button in page instead of just in toolbar
-
namespace PettingZoo.UI.Tab.Publisher
{
public enum MessageType
diff --git a/PettingZoo/UI/Tab/Undocked/UndockedTabHostStrings.Designer.cs b/PettingZoo/UI/Tab/Undocked/UndockedTabHostStrings.Designer.cs
new file mode 100644
index 0000000..d7cd5ee
--- /dev/null
+++ b/PettingZoo/UI/Tab/Undocked/UndockedTabHostStrings.Designer.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace PettingZoo.UI.Tab.Undocked {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class UndockedTabHostStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal UndockedTabHostStrings() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Tab.Undocked.UndockedTabHostStrings", typeof(UndockedTabHostStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Dock tab.
+ ///
+ public static string CommandDock {
+ get {
+ return ResourceManager.GetString("CommandDock", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/Undocked/UndockedTabHostStrings.resx b/PettingZoo/UI/Tab/Undocked/UndockedTabHostStrings.resx
new file mode 100644
index 0000000..d987b57
--- /dev/null
+++ b/PettingZoo/UI/Tab/Undocked/UndockedTabHostStrings.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Dock tab
+
+
\ No newline at end of file
diff --git a/PettingZoo/UI/Tab/Undocked/UndockedTabHostViewModel.cs b/PettingZoo/UI/Tab/Undocked/UndockedTabHostViewModel.cs
new file mode 100644
index 0000000..ca07e96
--- /dev/null
+++ b/PettingZoo/UI/Tab/Undocked/UndockedTabHostViewModel.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace PettingZoo.UI.Tab.Undocked
+{
+ public class UndockedTabHostViewModel : BaseViewModel
+ {
+ private readonly ITabHost tabHost;
+ private readonly ITab tab;
+ private readonly DelegateCommand dockCommand;
+
+
+ public string Title => tab.Title;
+ public ContentControl Content => tab.Content;
+ public IEnumerable ToolbarCommands => tab.ToolbarCommands;
+
+ public Visibility ToolbarCommandsSeparatorVisibility =>
+ ToolbarCommands.Any() ? Visibility.Visible : Visibility.Collapsed;
+
+ public ICommand DockCommand => dockCommand;
+
+
+ public UndockedTabHostViewModel(ITabHost tabHost, ITab tab)
+ {
+ this.tabHost = tabHost;
+ this.tab = tab;
+
+ tab.PropertyChanged += (_, args) =>
+ {
+ RaisePropertyChanged(args.PropertyName);
+ if (args.PropertyName == nameof(ToolbarCommands))
+ RaisePropertyChanged(nameof(ToolbarCommandsSeparatorVisibility));
+ };
+
+ dockCommand = new DelegateCommand(DockCommandExecute);
+ }
+
+
+ private void DockCommandExecute()
+ {
+ tabHost.DockTab(tab);
+ }
+
+
+ public void WindowClosed()
+ {
+ tabHost.UndockedTabClosed(tab);
+ }
+ }
+
+
+ public class DesignTimeUndockedTabHostViewModel : UndockedTabHostViewModel
+ {
+ public DesignTimeUndockedTabHostViewModel() : base(null!, new DesignTimeTab())
+ {
+ }
+
+
+ private class DesignTimeTab : ITab
+ {
+ public event PropertyChangedEventHandler? PropertyChanged;
+ public IEnumerable ToolbarCommands { get; } = Array.Empty();
+
+ public string Title => "Design-time tab title";
+ public ContentControl Content => null!;
+
+
+ public void Activate()
+ {
+ // Just to prevent the "PropertyChanged is never used" message
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
+ }
+
+
+ public void Deactivate()
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml b/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml
new file mode 100644
index 0000000..bb5ba3e
--- /dev/null
+++ b/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml.cs b/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml.cs
new file mode 100644
index 0000000..52ede6b
--- /dev/null
+++ b/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml.cs
@@ -0,0 +1,34 @@
+using System.Windows;
+
+namespace PettingZoo.UI.Tab.Undocked
+{
+ ///
+ /// Interaction logic for UndockedTabHostWindow.xaml
+ ///
+ public partial class UndockedTabHostWindow
+ {
+ public static UndockedTabHostWindow Create(ITabHost tabHost, ITab tab, double width, double height)
+ {
+ var viewModel = new UndockedTabHostViewModel(tabHost, tab);
+ var window = new UndockedTabHostWindow(viewModel)
+ {
+ Width = width,
+ Height = height
+ };
+
+ return window;
+ }
+
+
+ public UndockedTabHostWindow(UndockedTabHostViewModel viewModel)
+ {
+ DataContext = viewModel;
+ InitializeComponent();
+
+ Closed += (_, _) =>
+ {
+ viewModel.WindowClosed();
+ };
+ }
+ }
+}
diff --git a/PettingZoo/UI/Tab/ViewTab.cs b/PettingZoo/UI/Tab/ViewTab.cs
index 9ec90b9..22950dc 100644
--- a/PettingZoo/UI/Tab/ViewTab.cs
+++ b/PettingZoo/UI/Tab/ViewTab.cs
@@ -12,7 +12,6 @@ namespace PettingZoo.UI.Tab
{
public string Title => getTitle(viewModel);
public ContentControl Content { get; }
- public ICommand CloseTabCommand { get; }
public IEnumerable ToolbarCommands => viewModel is ITabToolbarCommands tabToolbarCommands
? tabToolbarCommands.ToolbarCommands
@@ -25,14 +24,13 @@ namespace PettingZoo.UI.Tab
private readonly Func getTitle;
- public ViewTab(ICommand closeTabCommand, TView view, TViewModel viewModel, Expression> title)
+ public ViewTab(TView view, TViewModel viewModel, Expression> title)
{
if (title.Body is not MemberExpression titleMemberExpression)
throw new ArgumentException(@"Invalid expression type, expected viewModel => viewModel.TitlePropertyName", nameof(title));
var titlePropertyName = titleMemberExpression.Member.Name;
- CloseTabCommand = closeTabCommand;
this.viewModel = viewModel;
getTitle = title.Compile();
Content = view;
diff --git a/PettingZoo/UI/Tab/ViewTabFactory.cs b/PettingZoo/UI/Tab/ViewTabFactory.cs
index 608d835..80d90dc 100644
--- a/PettingZoo/UI/Tab/ViewTabFactory.cs
+++ b/PettingZoo/UI/Tab/ViewTabFactory.cs
@@ -8,13 +8,11 @@ namespace PettingZoo.UI.Tab
public class ViewTabFactory : ITabFactory
{
private readonly ITabHost tabHost;
- private readonly ICommand closeTabCommand;
- public ViewTabFactory(ITabHost tabHost, ICommand closeTabCommand)
+ public ViewTabFactory(ITabHost tabHost)
{
this.tabHost = tabHost;
- this.closeTabCommand = closeTabCommand;
}
@@ -22,7 +20,6 @@ namespace PettingZoo.UI.Tab
{
var viewModel = new SubscriberViewModel(tabHost, this, connection, subscriber);
return new ViewTab(
- closeTabCommand,
new SubscriberView(viewModel),
viewModel,
vm => vm.Title);
@@ -33,7 +30,6 @@ namespace PettingZoo.UI.Tab
{
var viewModel = new PublisherViewModel(tabHost, this, connection, fromReceivedMessage);
return new ViewTab(
- closeTabCommand,
new PublisherView(viewModel),
viewModel,
vm => vm.Title);