diff --git a/source/Components/AvalonDock/Controls/LayoutFloatingWindowControl.cs b/source/Components/AvalonDock/Controls/LayoutFloatingWindowControl.cs
index 7a4af7cd..b6805938 100644
--- a/source/Components/AvalonDock/Controls/LayoutFloatingWindowControl.cs
+++ b/source/Components/AvalonDock/Controls/LayoutFloatingWindowControl.cs
@@ -45,7 +45,13 @@ public abstract class LayoutFloatingWindowControl : Window, ILayoutControl
private DragService _dragService = null;
private bool _internalCloseFlag = false;
private bool _isClosing = false;
- #endregion fields
+
+ ///
+ /// Is false until the margins have been found once.
+ ///
+ ///
+ private bool _isTotalMarginSet = false;
+ #endregion fields
#region Constructors
@@ -181,6 +187,90 @@ protected override void OnStateChanged(EventArgs e)
#endregion IsMaximized
+ #region TotalMargin
+
+ private static readonly DependencyPropertyKey TotalMarginPropertyKey =
+ DependencyProperty.RegisterReadOnly(nameof(TotalMargin),
+ typeof(Thickness),
+ typeof(LayoutFloatingWindowControl),
+ new FrameworkPropertyMetadata(default(Thickness)));
+
+ public static readonly DependencyProperty TotalMarginProperty = TotalMarginPropertyKey.DependencyProperty;
+
+ ///
+ /// The total margin (including window chrome and title bar).
+ ///
+ /// The margin is queried from the visual tree the first time it is rendered, zero until the first call of FilterMessage(WM_ACTIVATE)
+ ///
+ public Thickness TotalMargin
+ {
+ get { return (Thickness) GetValue(TotalMarginProperty); }
+ protected set { SetValue(TotalMarginPropertyKey, value); }
+ }
+
+ #endregion
+
+ #region ContentMinHeight
+ public static readonly DependencyPropertyKey ContentMinHeightPropertyKey = DependencyProperty.RegisterReadOnly(
+ nameof(ContentMinHeight), typeof(double), typeof(LayoutFloatingWindowControl), new FrameworkPropertyMetadata(0.0));
+
+ public static readonly DependencyProperty ContentMinHeightProperty =
+ ContentMinHeightPropertyKey.DependencyProperty;
+
+ ///
+ /// The MinHeight of the content of the window, will be 0 until the window has been rendered, or if the MinHeight is unset for the content
+ ///
+ public double ContentMinHeight
+ {
+ get { return (double) GetValue(ContentMinHeightProperty); }
+ set { SetValue(ContentMinHeightPropertyKey, value); }
+ }
+ #endregion
+
+ #region ContentMinWidth
+ public static readonly DependencyPropertyKey ContentMinWidthPropertyKey = DependencyProperty.RegisterReadOnly(
+ nameof(ContentMinWidth), typeof(double), typeof(LayoutFloatingWindowControl), new FrameworkPropertyMetadata(0.0));
+
+ public static readonly DependencyProperty ContentMinWidthProperty =
+ ContentMinWidthPropertyKey.DependencyProperty;
+
+ ///
+ /// The MinWidth ocf the content of the window, will be 0 until the window has been rendered, or if the MinWidth is unset for the content
+ ///
+ public double ContentMinWidth
+ {
+ get { return (double) GetValue(ContentMinWidthProperty); }
+ set { SetValue(ContentMinWidthPropertyKey, value); }
+ }
+ #endregion
+
+ #region SetWindowSizeWhenOpened
+
+ public static readonly DependencyProperty SetWindowSizeWhenOpenedProperty = DependencyProperty.Register(
+ nameof(SetWindowSizeWhenOpened), typeof(bool), typeof(LayoutFloatingWindowControl), new PropertyMetadata(false,
+ (sender, args) =>
+ {
+ if (args.OldValue != args.NewValue &&
+ sender is LayoutFloatingWindowControl control)
+ {
+ // This will resize the window when this property is set to true and the window is open
+ control._isTotalMarginSet = false;
+ }
+ }));
+
+ ///
+ /// If true the MinHeight and MinWidth of the content will be used together with the margins to determine the initial size of the floating window
+ ///
+ ///
+ ///
+ ///
+ public bool SetWindowSizeWhenOpened
+ {
+ get { return (bool) GetValue(SetWindowSizeWhenOpenedProperty); }
+ set { SetValue(SetWindowSizeWhenOpenedProperty, value); }
+ }
+
+ #endregion
#endregion Properties
#region Internal Methods
@@ -248,7 +338,9 @@ protected virtual IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntP
handled = true;
}
}
- break;
+ UpdateWindowsSizeBasedOnMinSize();
+
+ break;
case Win32Helper.WM_EXITSIZEMOVE:
UpdatePositionAndSizeOfPanes();
@@ -283,7 +375,102 @@ protected virtual IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntP
return IntPtr.Zero;
}
- internal void InternalClose(bool closeInitiatedByUser = false)
+ ///
+ /// Set the margins of the window control (including the borders of the floating window and the title bar).
+ /// The result will be stored in _totalMargin.
+ ///
+ /// If the control is not loaded _totalMargin will not be set.
+ private void UpdateMargins()
+ {
+ // The grid with window bar and content
+ var grid = this.GetChildrenRecursive()
+ .OfType()
+ .FirstOrDefault(g => g.RowDefinitions.Count > 0);
+ ContentPresenter contentControl = this.GetChildrenRecursive()
+ .OfType()
+ .FirstOrDefault(c => c.Content is LayoutContent);
+ if (contentControl == null)
+ return;
+ // The content control in the grid, this has a different tree to walk up
+ var layoutContent = (LayoutContent)contentControl.Content;
+ if (grid != null && layoutContent.Content is FrameworkElement content)
+ {
+ var parents = content.GetParents().ToArray();
+ var children = this.GetChildrenRecursive()
+ .TakeWhile(c => c != grid)
+ .ToArray();
+ var borders = children
+ .OfType()
+ .Concat(parents
+ .OfType())
+ .ToArray();
+ var controls = children
+ .OfType()
+ .Concat(parents
+ .OfType())
+ .ToArray();
+ var frameworkElements = children
+ .OfType()
+ .Concat(parents
+ .OfType())
+ .ToArray();
+ var padding = controls.Sum(b => b.Padding);
+ var border = borders.Sum(b => b.BorderThickness);
+ var margin = frameworkElements.Sum(f => f.Margin);
+ margin = margin.Add(padding).Add(border).Add(grid.Margin);
+ margin.Top = grid.RowDefinitions[0].MinHeight;
+ TotalMargin = margin;
+ _isTotalMarginSet = true;
+ }
+ }
+
+ ///
+ /// Update the floating window size based on the MinHeight and MinWidth of the content of the control.
+ ///
+ /// This will only be run once, when the window is rendered the first time and _totalMargin is identified.
+ private void UpdateWindowsSizeBasedOnMinSize()
+ {
+ if (!_isTotalMarginSet)
+ {
+ UpdateMargins();
+ if(_isTotalMarginSet)
+ {
+ // The LayoutAnchorableControl is bound via the ContentPresenter, hence it is best to do below in code and not in a style
+ // See https://github.com/Dirkster99/AvalonDock/pull/146#issuecomment-609974424
+ var layoutContents = this.GetChildrenRecursive()
+ .OfType()
+ .Select(c => c.Content)
+ .OfType()
+ .Select(lc => lc.Content);
+ var contents = layoutContents.OfType();
+ foreach (var content in contents)
+ {
+ ContentMinHeight = Math.Max(content.MinHeight, ContentMinHeight);
+ ContentMinWidth = Math.Max(content.MinWidth, ContentMinWidth);
+ if (SetWindowSizeWhenOpened)
+ {
+ var parent = content.GetParents()
+ .OfType()
+ .FirstOrDefault();
+ // StackPanels among others have an ActualHeight larger than visible, hence we check the parent control as well
+ if (content.ActualHeight < content.MinHeight ||
+ parent != null && parent.ActualHeight < content.MinHeight)
+ {
+ Height = content.MinHeight + TotalMargin.Top + TotalMargin.Bottom;
+ }
+
+ if (content.ActualWidth < content.MinWidth ||
+ parent != null && parent.ActualWidth < content.MinWidth)
+ {
+ Width = content.MinWidth + TotalMargin.Left + TotalMargin.Right;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ internal void InternalClose(bool closeInitiatedByUser = false)
{
_internalCloseFlag = !closeInitiatedByUser;
if (_isClosing) return;
diff --git a/source/Components/AvalonDock/Extensions.cs b/source/Components/AvalonDock/Extensions.cs
index a861f6d9..b45fe5b5 100644
--- a/source/Components/AvalonDock/Extensions.cs
+++ b/source/Components/AvalonDock/Extensions.cs
@@ -8,37 +8,124 @@ This program is provided to you under the terms of the Microsoft Public
************************************************************************/
using System;
-using System.Collections.Generic;
using System.Collections;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Media;
namespace AvalonDock
{
- internal static class Extensions
- {
- public static bool Contains(this IEnumerable collection, object item)
- {
- foreach (var o in collection)
- if (o == item) return true;
- return false;
- }
-
-
- public static void ForEach(this IEnumerable collection, Action action)
- {
- foreach (var v in collection) action(v);
- }
-
-
- public static int IndexOf(this T[] array, T value) where T : class
- {
- for (var i = 0; i < array.Length; i++)
- if (array[i] == value) return i;
- return -1;
- }
-
- public static V GetValueOrDefault(this WeakReference wr)
- {
- return wr == null || !wr.IsAlive ? default : (V) wr.Target;
- }
- }
+ internal static class Extensions
+ {
+ public static bool Contains(this IEnumerable collection, object item)
+ {
+ foreach (var o in collection)
+ if (o == item) return true;
+ return false;
+ }
+
+
+ public static void ForEach(this IEnumerable collection, Action action)
+ {
+ foreach (var v in collection) action(v);
+ }
+
+
+ public static int IndexOf(this T[] array, T value) where T : class
+ {
+ for (var i = 0; i < array.Length; i++)
+ if (array[i] == value) return i;
+ return -1;
+ }
+
+ public static V GetValueOrDefault(this WeakReference wr)
+ {
+ return wr == null || !wr.IsAlive ? default : (V)wr.Target;
+ }
+
+ ///
+ /// Recursively get the visual tree children of a dependency object.
+ ///
+ /// This function is recursive and will return all children.
+ /// The object to find the children of
+ /// An enumerable with all the children of the dependency object
+ public static IEnumerable GetChildrenRecursive(this DependencyObject dependencyObject)
+ {
+ var children = dependencyObject.GetChildren();
+ foreach (var child in children)
+ {
+ yield return child;
+ foreach (var c in GetChildrenRecursive(child))
+ {
+ yield return c;
+ }
+ }
+ }
+
+ ///
+ /// Get the visual tree children of a dependency object.
+ ///
+ /// This function is not recursive and will only return the first level of children.
+ /// The object to find the children of
+ /// An enumerable with all the first level children of the dependency object
+ public static IEnumerable GetChildren(this DependencyObject dependencyObject)
+ {
+ int n = VisualTreeHelper.GetChildrenCount(dependencyObject);
+ for (int i = 0; i < n; i++)
+ {
+ yield return VisualTreeHelper.GetChild(dependencyObject, i);
+ }
+ }
+
+ ///
+ /// Get the visual tree parents of a dependency object,
+ ///
+ /// The object to find the parents of
+ /// An enumerable with the parents of the dependencyObject, the first parent returned will be
+ /// the direct parent of dependencyObject
+ public static IEnumerable GetParents(this DependencyObject dependencyObject)
+ {
+ while (dependencyObject != null)
+ {
+ dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
+ if (dependencyObject != null)
+ yield return dependencyObject;
+ }
+ }
+
+ ///
+ /// Calculate the sum multiple thicknesses in an enumerable
+ ///
+ /// The type in the enumerable
+ /// The enumerable with thicknesses
+ /// A function returning the thickness from T
+ /// The total thickness
+ public static Thickness Sum(this IEnumerable enumerable, Func func)
+ {
+ double top = 0, bottom = 0, left = 0, right = 0;
+ foreach (var e in enumerable)
+ {
+ var t = func(e);
+ left = t.Left;
+ top += t.Top;
+ right = t.Right;
+ bottom += t.Bottom;
+ }
+ return new Thickness(left, top, right, bottom);
+ }
+
+ ///
+ /// Add two thicknesses, each individual component will be added independently of the others.
+ ///
+ /// The first thickness
+ /// The second thickness
+ /// The total thickness
+ public static Thickness Add(this Thickness thickness, Thickness other)
+ {
+ return new Thickness(thickness.Left + other.Left,
+ thickness.Top + other.Top,
+ thickness.Right + other.Right,
+ thickness.Bottom + other.Bottom);
+ }
+ }
}
diff --git a/source/TestApp/MainWindow.xaml b/source/TestApp/MainWindow.xaml
index ac4c440c..1df19a59 100644
--- a/source/TestApp/MainWindow.xaml
+++ b/source/TestApp/MainWindow.xaml
@@ -54,6 +54,7 @@
@@ -108,7 +109,7 @@
Title="Tool Window 1"
ContentId="toolWindow1"
Hiding="OnToolWindow1Hiding">
-
+
diff --git a/source/TestApp/MainWindow.xaml.cs b/source/TestApp/MainWindow.xaml.cs
index a8c559cf..dc8e1b21 100644
--- a/source/TestApp/MainWindow.xaml.cs
+++ b/source/TestApp/MainWindow.xaml.cs
@@ -236,5 +236,17 @@ private void OnShowHeader(object sender, RoutedEventArgs e)
{
//// LayoutDocumentPane.ShowHeader = !LayoutDocumentPane.ShowHeader;
}
- }
+
+ private void OnNewFloatingWindow(object sender, RoutedEventArgs e)
+ {
+ var view = new TestUserControl();
+ var anchorable = new LayoutAnchorable()
+ {
+ Title = "New floating window",
+ Content = view
+ };
+ anchorable.AddToLayout(dockManager,AnchorableShowStrategy.Most);
+ anchorable.Float();
+ }
+ }
}
diff --git a/source/TestApp/TestUserControl.xaml b/source/TestApp/TestUserControl.xaml
index b164e968..cd1fe972 100644
--- a/source/TestApp/TestUserControl.xaml
+++ b/source/TestApp/TestUserControl.xaml
@@ -13,8 +13,11 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
- Height="300" Width="300">
+ MinHeight="300" MinWidth="300">
-
+
+ A label:
+ Some input
+