Skip to content

Commit

Permalink
Merge pull request #146 from eriove/AutoResizeFloatingWindow
Browse files Browse the repository at this point in the history
Auto resizing floating window on startup
  • Loading branch information
Dirkster99 authored Apr 9, 2020
2 parents da2c59b + a5c663e commit c48ed5d
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 36 deletions.
193 changes: 190 additions & 3 deletions source/Components/AvalonDock/Controls/LayoutFloatingWindowControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ public abstract class LayoutFloatingWindowControl : Window, ILayoutControl
private DragService _dragService = null;
private bool _internalCloseFlag = false;
private bool _isClosing = false;
#endregion fields

/// <summary>
/// Is false until the margins have been found once.
/// </summary>
/// <see cref="TotalMargin"/>
private bool _isTotalMarginSet = false;
#endregion fields

#region Constructors

Expand Down Expand Up @@ -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;

/// <summary>
/// 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)
/// </summary>
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;

/// <summary>
/// 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
/// </summary>
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;

/// <summary>
/// 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
/// </summary>
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;
}
}));

/// <summary>
/// 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
/// </summary>
/// <seealso cref="TotalMargin"/>
/// <seealso cref="ContentMinWidth"/>
/// <seealso cref="ContentMinHeight"/>
public bool SetWindowSizeWhenOpened
{
get { return (bool) GetValue(SetWindowSizeWhenOpenedProperty); }
set { SetValue(SetWindowSizeWhenOpenedProperty, value); }
}

#endregion
#endregion Properties

#region Internal Methods
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -283,7 +375,102 @@ protected virtual IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntP
return IntPtr.Zero;
}

internal void InternalClose(bool closeInitiatedByUser = false)
/// <summary>
/// Set the margins of the window control (including the borders of the floating window and the title bar).
/// The result will be stored in <code>_totalMargin</code>.
/// </summary>
/// <remarks>If the control is not loaded <code>_totalMargin</code> will not be set.</remarks>
private void UpdateMargins()
{
// The grid with window bar and content
var grid = this.GetChildrenRecursive()
.OfType<Grid>()
.FirstOrDefault(g => g.RowDefinitions.Count > 0);
ContentPresenter contentControl = this.GetChildrenRecursive()
.OfType<ContentPresenter>()
.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<Border>()
.Concat(parents
.OfType<Border>())
.ToArray();
var controls = children
.OfType<Control>()
.Concat(parents
.OfType<Control>())
.ToArray();
var frameworkElements = children
.OfType<FrameworkElement>()
.Concat(parents
.OfType<FrameworkElement>())
.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;
}
}

/// <summary>
/// Update the floating window size based on the <code>MinHeight</code> and <code>MinWidth</code> of the content of the control.
/// </summary>
/// <remarks>This will only be run once, when the window is rendered the first time and <code>_totalMargin</code> is identified.</remarks>
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<ContentPresenter>()
.Select(c => c.Content)
.OfType<LayoutContent>()
.Select(lc => lc.Content);
var contents = layoutContents.OfType<FrameworkElement>();
foreach (var content in contents)
{
ContentMinHeight = Math.Max(content.MinHeight, ContentMinHeight);
ContentMinWidth = Math.Max(content.MinWidth, ContentMinWidth);
if (SetWindowSizeWhenOpened)
{
var parent = content.GetParents()
.OfType<FrameworkElement>()
.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;
Expand Down
145 changes: 116 additions & 29 deletions source/Components/AvalonDock/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(this IEnumerable<T> collection, Action<T> action)
{
foreach (var v in collection) action(v);
}


public static int IndexOf<T>(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<V>(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<T>(this IEnumerable<T> collection, Action<T> action)
{
foreach (var v in collection) action(v);
}


public static int IndexOf<T>(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<V>(this WeakReference wr)
{
return wr == null || !wr.IsAlive ? default : (V)wr.Target;
}

/// <summary>
/// Recursively get the visual tree children of a dependency object.
/// </summary>
/// <remarks>This function is recursive and will return all children.</remarks>
/// <param name="dependencyObject">The object to find the children of</param>
/// <returns>An enumerable with all the children of the dependency object</returns>
public static IEnumerable<DependencyObject> 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;
}
}
}

/// <summary>
/// Get the visual tree children of a dependency object.
/// </summary>
/// <remarks>This function is not recursive and will only return the first level of children.</remarks>
/// <param name="dependencyObject">The object to find the children of</param>
/// <returns>An enumerable with all the first level children of the dependency object</returns>
public static IEnumerable<DependencyObject> GetChildren(this DependencyObject dependencyObject)
{
int n = VisualTreeHelper.GetChildrenCount(dependencyObject);
for (int i = 0; i < n; i++)
{
yield return VisualTreeHelper.GetChild(dependencyObject, i);
}
}

/// <summary>
/// Get the visual tree parents of a dependency object,
/// </summary>
/// <param name="dependencyObject">The object to find the parents of</param>
/// <returns>An enumerable with the parents of the <code>dependencyObject</code>, the first parent returned will be
/// the direct parent of <code>dependencyObject</code></returns>
public static IEnumerable<DependencyObject> GetParents(this DependencyObject dependencyObject)
{
while (dependencyObject != null)
{
dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
if (dependencyObject != null)
yield return dependencyObject;
}
}

/// <summary>
/// Calculate the sum multiple thicknesses in an enumerable
/// </summary>
/// <typeparam name="T">The type in the enumerable</typeparam>
/// <param name="enumerable">The enumerable with thicknesses</param>
/// <param name="func">A function returning the thickness from <code>T</code></param>
/// <returns>The total thickness</returns>
public static Thickness Sum<T>(this IEnumerable<T> enumerable, Func<T, Thickness> 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);
}

/// <summary>
/// Add two thicknesses, each individual component will be added independently of the others.
/// </summary>
/// <param name="thickness">The first thickness</param>
/// <param name="other">The second thickness</param>
/// <returns>The total thickness</returns>
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);
}
}
}
3 changes: 2 additions & 1 deletion source/TestApp/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<MenuItem Header="Tools">
<MenuItem Click="OnShowWinformsWindow" Header="WinForms Window" />
<MenuItem Click="OnShowToolWindow1" Header="Tool Window1" />
<MenuItem Click="OnNewFloatingWindow" Header="New floating window"/>
</MenuItem>
</Menu>

Expand Down Expand Up @@ -108,7 +109,7 @@
Title="Tool Window 1"
ContentId="toolWindow1"
Hiding="OnToolWindow1Hiding">
<StackPanel>
<StackPanel MinHeight="1500">
<local:TestUserControl />
<TextBox Text="{Binding TestTimer, Mode=OneWay, StringFormat='Tool Window 1 Attached to Timer ->\{0\}'}" />
</StackPanel>
Expand Down
Loading

0 comments on commit c48ed5d

Please sign in to comment.