pkNX/pkNX.WinForms/Utils/DockPanelSplitter.cs
2023-09-03 20:39:59 +02:00

322 lines
9.9 KiB
C#

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace OpenSourceControls;
/// <summary>
/// Code Project article
/// http://www.codeproject.com/KB/WPF/DockPanelSplitter.aspx
///
/// CodePlex project
/// http://wpfcontrols.codeplex.com
///
/// DockPanelSplitter is a splitter control for DockPanels.
/// Add the DockPanelSplitter after the element you want to resize.
/// Set the DockPanel.Dock to define which edge the splitter should work on.
///
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:OsDps="clr-namespace:DockPanelSplitter"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:DockPanelSplitter;assembly=DockPanelSplitter"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:DockPanelSplitter/>
///
/// </summary>
public class DockPanelSplitter : Grid
{
static DockPanelSplitter()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DockPanelSplitter),
new FrameworkPropertyMetadata(typeof(DockPanelSplitter)));
// override the Background property
BackgroundProperty.OverrideMetadata(typeof(DockPanelSplitter), new FrameworkPropertyMetadata(Brushes.Transparent));
// override the Dock property to get notifications when Dock is changed
DockPanel.DockProperty.OverrideMetadata(typeof(DockPanelSplitter),
new FrameworkPropertyMetadata(Dock.Left, new PropertyChangedCallback(DockChanged)));
}
/// <summary>
/// Resize the target element proportionally with the parent container
/// Set to false if you don't want the element to be resized when the parent is resized.
/// </summary>
public bool ProportionalResize
{
get { return (bool)GetValue(ProportionalResizeProperty); }
set { SetValue(ProportionalResizeProperty, value); }
}
public static readonly DependencyProperty ProportionalResizeProperty =
DependencyProperty.Register("ProportionalResize", typeof(bool), typeof(DockPanelSplitter),
new UIPropertyMetadata(true));
/// <summary>
/// Height or width of splitter, depends of orientation of the splitter
/// </summary>
public double Thickness
{
get { return (double)GetValue(ThicknessProperty); }
set { SetValue(ThicknessProperty, value); }
}
public static readonly DependencyProperty ThicknessProperty =
DependencyProperty.Register("Thickness", typeof(double), typeof(DockPanelSplitter),
new UIPropertyMetadata(4.0, ThicknessChanged));
#region Private fields
private FrameworkElement element; // element to resize (target element)
private double width; // current desired width of the element, can be less than minwidth
private double height; // current desired height of the element, can be less than minheight
private double previousParentWidth; // current width of parent element, used for proportional resize
private double previousParentHeight; // current height of parent element, used for proportional resize
#endregion
public DockPanelSplitter()
{
Loaded += DockPanelSplitterLoaded;
Unloaded += DockPanelSplitterUnloaded;
UpdateHeightOrWidth();
}
void DockPanelSplitterLoaded(object sender, RoutedEventArgs e)
{
Panel dp = Parent as Panel;
if (dp == null) return;
// Subscribe to the parent's size changed event
dp.SizeChanged += ParentSizeChanged;
// Store the current size of the parent DockPanel
previousParentWidth = dp.ActualWidth;
previousParentHeight = dp.ActualHeight;
// Find the target element
UpdateTargetElement();
}
void DockPanelSplitterUnloaded(object sender, RoutedEventArgs e)
{
Panel dp = Parent as Panel;
if (dp == null) return;
// Unsubscribe
dp.SizeChanged -= ParentSizeChanged;
}
private static void DockChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DockPanelSplitter)d).UpdateHeightOrWidth();
}
private static void ThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DockPanelSplitter)d).UpdateHeightOrWidth();
}
private void UpdateHeightOrWidth()
{
if (IsHorizontal)
{
Height = Thickness;
Width = double.NaN;
}
else
{
Width = Thickness;
Height = double.NaN;
}
}
public bool IsHorizontal
{
get
{
Dock dock = DockPanel.GetDock(this);
return dock == Dock.Top || dock == Dock.Bottom;
}
}
/// <summary>
/// Update the target element (the element the DockPanelSplitter works on)
/// </summary>
private void UpdateTargetElement()
{
Panel dp = Parent as Panel;
if (dp == null) return;
int i = dp.Children.IndexOf(this);
// The splitter cannot be the first child of the parent DockPanel
// The splitter works on the 'older' sibling
if (i > 0 && dp.Children.Count > 0)
{
element = dp.Children[i - 1] as FrameworkElement;
}
}
private void SetTargetWidth(double newWidth)
{
if (newWidth < element.MinWidth)
newWidth = element.MinWidth;
if (newWidth > element.MaxWidth)
newWidth = element.MaxWidth;
// todo - constrain the width of the element to the available client area
Panel dp = Parent as Panel;
Dock dock = DockPanel.GetDock(this);
MatrixTransform t = element.TransformToAncestor(dp) as MatrixTransform;
if (dock == Dock.Left && newWidth > dp.ActualWidth - t.Matrix.OffsetX - Thickness)
newWidth = dp.ActualWidth - t.Matrix.OffsetX - Thickness;
element.Width = newWidth;
}
private void SetTargetHeight(double newHeight)
{
if (newHeight < element.MinHeight)
newHeight = element.MinHeight;
if (newHeight > element.MaxHeight)
newHeight = element.MaxHeight;
// todo - constrain the height of the element to the available client area
Panel dp = Parent as Panel;
Dock dock = DockPanel.GetDock(this);
MatrixTransform t = element.TransformToAncestor(dp) as MatrixTransform;
if (dock == Dock.Top && newHeight > dp.ActualHeight - t.Matrix.OffsetY - Thickness)
newHeight = dp.ActualHeight - t.Matrix.OffsetY - Thickness;
element.Height = newHeight;
}
private void ParentSizeChanged(object sender, SizeChangedEventArgs e)
{
if (!ProportionalResize) return;
DockPanel dp = Parent as DockPanel;
if (dp == null) return;
double sx = dp.ActualWidth / previousParentWidth;
double sy = dp.ActualHeight / previousParentHeight;
if (!double.IsInfinity(sx))
SetTargetWidth(element.Width * sx);
if (!double.IsInfinity(sy))
SetTargetHeight(element.Height * sy);
previousParentWidth = dp.ActualWidth;
previousParentHeight = dp.ActualHeight;
}
double AdjustWidth(double dx, Dock dock)
{
if (dock == Dock.Right)
dx = -dx;
width += dx;
SetTargetWidth(width);
return dx;
}
double AdjustHeight(double dy, Dock dock)
{
if (dock == Dock.Bottom)
dy = -dy;
height += dy;
SetTargetHeight(height);
return dy;
}
Point StartDragPoint;
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
if (!IsEnabled) return;
Cursor = IsHorizontal ? Cursors.SizeNS : Cursors.SizeWE;
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
if (!IsEnabled) return;
if (!IsMouseCaptured)
{
StartDragPoint = e.GetPosition(Parent as IInputElement);
UpdateTargetElement();
if (element != null)
{
width = element.ActualWidth;
height = element.ActualHeight;
CaptureMouse();
}
}
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (IsMouseCaptured)
{
Point ptCurrent = e.GetPosition(Parent as IInputElement);
Point delta = new Point(ptCurrent.X - StartDragPoint.X, ptCurrent.Y - StartDragPoint.Y);
Dock dock = DockPanel.GetDock(this);
if (IsHorizontal)
delta.Y = AdjustHeight(delta.Y, dock);
else
delta.X = AdjustWidth(delta.X, dock);
bool isBottomOrRight = (dock == Dock.Right || dock == Dock.Bottom);
// When docked to the bottom or right, the position has changed after adjusting the size
if (isBottomOrRight)
StartDragPoint = e.GetPosition(Parent as IInputElement);
else
StartDragPoint = new Point(StartDragPoint.X + delta.X, StartDragPoint.Y + delta.Y);
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
if (IsMouseCaptured)
{
ReleaseMouseCapture();
}
base.OnMouseUp(e);
}
}