Last active
August 12, 2019 15:30
-
-
Save ArtemAvramenko/e260420b86564cf13d2e to your computer and use it in GitHub Desktop.
WinForms Splitter without flickering
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.ComponentModel; | |
using System.Drawing; | |
using System.Globalization; | |
using System.Runtime.InteropServices; | |
using System.Security.Permissions; | |
namespace System.Windows.Forms | |
{ | |
/// <summary> | |
/// Represents a splitter control that enables the user to resize docked controls. | |
/// Fixes <see cref="T:System.Windows.Forms.Splitter"/> problems: | |
/// <list type="number"> | |
/// <item>removes Application.DoEvents;</item> | |
/// <item>avoids flickering;</item> | |
/// <item>sets ControlStyles.OptimizedDoubleBuffer.</item> | |
/// </list> | |
/// </summary> | |
[DefaultEvent("SplitterMoved"), DefaultProperty("Dock"), ClassInterface(ClassInterfaceType.AutoDispatch), ComVisible(true)] | |
public class SplitterEx : Control | |
{ | |
#region PInvoke | |
private const int WS_BORDER = 0x00800000; | |
private const int WS_EX_CLIENTEDGE = 0x00000200; | |
private const int DCX_CACHE = 2; | |
private const int DCX_LOCKWINDOWUPDATE = 0x400; | |
private const int PATINVERT = 0x5A0049; | |
[DllImport("user32.dll")] | |
private static extern IntPtr GetCapture(); | |
[DllImport("user32.dll")] | |
private static extern IntPtr SetCapture(IntPtr hWnd); | |
[DllImport("user32.dll")] | |
private static extern int ReleaseCapture(); | |
[DllImport("user32.dll")] | |
private static extern IntPtr GetDCEx(IntPtr hWnd, IntPtr hrgnClip, int flags); | |
[DllImport("user32.dll")] | |
private static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC); | |
[DllImport("user32.dll")] | |
private static extern bool SubtractRect(out RECT lprcDst, [In] ref RECT lprcSrc1, [In] ref RECT lprcSrc2); | |
[DllImport("gdi32.dll")] | |
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); | |
[DllImport("gdi32.dll")] | |
private static extern bool PatBlt(IntPtr hdc, int nXLeft, int nYLeft, int nWidth, int nHeight, int dwRop); | |
[DllImport("gdi32.dll")] | |
private static extern bool DeleteObject(IntPtr hObject); | |
[DllImport("gdi32.dll")] | |
private static extern IntPtr CreateBitmap(int nWidth, int nHeight, int cPlanes, int cBitsPerPel, byte[] lpvBits); | |
[DllImport("gdi32.dll")] | |
private static extern IntPtr CreateBrushIndirect(ref LOGBRUSH lplb); | |
[StructLayout(LayoutKind.Sequential)] | |
private struct RECT | |
{ | |
public int left; | |
public int top; | |
public int right; | |
public int bottom; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct LOGBRUSH | |
{ | |
public int lbStyle; | |
public int lbColor; | |
public int lbHatch; | |
} | |
#endregion PInvoke | |
private const int DRAW_START = 1; | |
private const int DRAW_MOVE = 2; | |
private const int DRAW_END = 3; | |
private const int defaultWidth = 3; | |
private static readonly object EVENT_MOVING = new object(); | |
private static readonly object EVENT_MOVED = new object(); | |
private BorderStyle borderStyle = BorderStyle.None; | |
private int minSize = 25; | |
private int minExtra = 25; | |
private Point anchor = Point.Empty; | |
private Control splitTarget; | |
private int splitSize = -1; | |
private int splitterThickness = 3; | |
private int initTargetSize; | |
private int lastDrawSplit = -1; | |
private int maxSize; | |
private SplitterMessageFilter splitterMessageFilter = null; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="T:System.Windows.Forms.SplitterEx"/> class. | |
/// </summary> | |
public SplitterEx() | |
{ | |
base.SetStyle(ControlStyles.Selectable, false); | |
base.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); | |
base.TabStop = false; | |
this.Dock = DockStyle.Left; | |
} | |
/// <summary> | |
/// Occurs when the splitter control is in the process of moving. | |
/// </summary> | |
public event SplitterEventHandler SplitterMoving | |
{ | |
add | |
{ | |
base.Events.AddHandler(EVENT_MOVING, value); | |
} | |
remove | |
{ | |
base.Events.RemoveHandler(EVENT_MOVING, value); | |
} | |
} | |
/// <summary> | |
/// Occurs when the splitter control is moved. | |
/// </summary> | |
public event SplitterEventHandler SplitterMoved | |
{ | |
add | |
{ | |
base.Events.AddHandler(EVENT_MOVED, value); | |
} | |
remove | |
{ | |
base.Events.RemoveHandler(EVENT_MOVED, value); | |
} | |
} | |
/// <summary> | |
/// Gets or sets the style of border for the control. | |
/// </summary> | |
/// <returns> | |
/// One of the <see cref="T:System.Windows.Forms.BorderStyle"/> values. The default is BorderStyle.None. | |
/// </returns> | |
/// <exception cref="T:System.ComponentModel.InvalidEnumArgumentException"> | |
/// The value of the property is not one of the | |
/// <see cref="T:System.Windows.Forms.BorderStyle"/> values. | |
/// </exception> | |
[DefaultValue(BorderStyle.None), DispId(-504)] | |
public BorderStyle BorderStyle | |
{ | |
get | |
{ | |
return this.borderStyle; | |
} | |
set | |
{ | |
if (value < BorderStyle.None || value > BorderStyle.Fixed3D) | |
{ | |
throw new InvalidEnumArgumentException("value", (int)value, typeof(BorderStyle)); | |
} | |
if (this.borderStyle != value) | |
{ | |
this.borderStyle = value; | |
base.UpdateStyles(); | |
} | |
} | |
} | |
/// <summary> | |
/// Gets or sets which <see cref="T:System.Windows.Forms.SplitterEx"/> borders are docked to | |
/// its parent control and determines how a <see cref="T:System.Windows.Forms.SplitterEx"/> is | |
/// resized with its parent. | |
/// </summary> | |
/// <returns> | |
/// One of the <see cref="T:System.Windows.Forms.DockStyle"/> values. The default is <see cref="F:System.Windows.Forms.DockStyle.Left"/>. | |
/// </returns> | |
[DefaultValue(DockStyle.Left), Localizable(true)] | |
public override DockStyle Dock | |
{ | |
get | |
{ | |
return base.Dock; | |
} | |
set | |
{ | |
if (value < DockStyle.Top || value > DockStyle.Right) | |
{ | |
throw new ArgumentException("Splitter control must be docked left, right, top, or bottom."); | |
} | |
int requestedSize = splitterThickness; | |
base.Dock = value; | |
switch (Dock) | |
{ | |
case DockStyle.Top: | |
case DockStyle.Bottom: | |
if (splitterThickness != -1) | |
{ | |
Height = requestedSize; | |
} | |
break; | |
case DockStyle.Left: | |
case DockStyle.Right: | |
if (splitterThickness != -1) | |
{ | |
Width = requestedSize; | |
} | |
break; | |
} | |
} | |
} | |
/// <summary> | |
/// Gets or sets the minimum distance that must remain between the splitter control and the | |
/// edge of the opposite side of the container (or the closest control docked to that side). | |
/// </summary> | |
/// <returns> | |
/// The minimum distance, in pixels, between the | |
/// <see cref="T:System.Windows.Forms.SplitterEx"/> control and the edge of the opposite side | |
/// of the container (or the closest control docked to that side). The default is 25. | |
/// </returns> | |
[DefaultValue(25), Localizable(true)] | |
public int MinExtra | |
{ | |
get | |
{ | |
return this.minExtra; | |
} | |
set | |
{ | |
if (value < 0) | |
{ | |
value = 0; | |
} | |
this.minExtra = value; | |
} | |
} | |
/// <summary> | |
/// Gets or sets the minimum distance that must remain between the splitter control and the | |
/// container edge that the control is docked to. | |
/// </summary> | |
/// <returns> | |
/// The minimum distance, in pixels, between the | |
/// <see cref="T:System.Windows.Forms.SplitterEx"/> control and the container edge that the | |
/// control is docked to. The default is 25. | |
/// </returns> | |
[DefaultValue(25), Localizable(true)] | |
public int MinSize | |
{ | |
get | |
{ | |
return this.minSize; | |
} | |
set | |
{ | |
if (value < 0) | |
{ | |
value = 0; | |
} | |
this.minSize = value; | |
} | |
} | |
/// <summary> | |
/// Gets or sets the distance between the splitter control and the container edge that the | |
/// control is docked to. | |
/// </summary> | |
/// <returns> | |
/// The distance, in pixels, between the <see cref="T:System.Windows.Forms.SplitterEx"/> | |
/// control and the container edge that the control is docked to. If the | |
/// <see cref="T:System.Windows.Forms.Splitter"/> control is not bound to a control, the | |
/// value is -1. | |
/// </returns> | |
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] | |
public int SplitPosition | |
{ | |
get | |
{ | |
if (this.splitSize == -1) | |
{ | |
this.splitSize = this.CalcSplitSize(); | |
} | |
return this.splitSize; | |
} | |
set | |
{ | |
SplitData splitData = this.CalcSplitBounds(); | |
if (value > this.maxSize) | |
{ | |
value = this.maxSize; | |
} | |
if (value < this.minSize) | |
{ | |
value = this.minSize; | |
} | |
this.splitSize = value; | |
this.DrawSplitBar(DRAW_END); | |
if (splitData.target == null) | |
{ | |
this.splitSize = -1; | |
return; | |
} | |
Rectangle bounds = splitData.target.Bounds; | |
switch (this.Dock) | |
{ | |
case DockStyle.Top: | |
bounds.Height = value; | |
break; | |
case DockStyle.Bottom: | |
bounds.Y += bounds.Height - this.splitSize; | |
bounds.Height = value; | |
break; | |
case DockStyle.Left: | |
bounds.Width = value; | |
break; | |
case DockStyle.Right: | |
bounds.X += bounds.Width - this.splitSize; | |
bounds.Width = value; | |
break; | |
} | |
splitData.target.Bounds = bounds; | |
this.OnSplitterMoved(new SplitterEventArgs(base.Left, base.Top, base.Left + bounds.Width / 2, base.Top + bounds.Height / 2)); | |
} | |
} | |
/// <summary> | |
/// Gets the default size of the control. | |
/// </summary> | |
/// <returns> | |
/// A <see cref="T:System.Drawing.Size"/> that represents the default size of the control. | |
/// </returns> | |
protected override Size DefaultSize | |
{ | |
get | |
{ | |
return new Size(defaultWidth, defaultWidth); | |
} | |
} | |
/// <summary> | |
/// Gets or sets the default cursor for the control. | |
/// </summary> | |
/// <returns> | |
/// An object of type <see cref="T:System.Windows.Forms.Cursor"/> representing the current | |
/// default cursor. | |
/// </returns> | |
protected override Cursor DefaultCursor | |
{ | |
get | |
{ | |
switch (this.Dock) | |
{ | |
case DockStyle.Top: | |
case DockStyle.Bottom: | |
return Cursors.HSplit; | |
case DockStyle.Left: | |
case DockStyle.Right: | |
return Cursors.VSplit; | |
default: | |
return base.DefaultCursor; | |
} | |
} | |
} | |
/// <summary> | |
/// Returns the parameters needed to create the handle. | |
/// </summary> | |
/// <returns> | |
/// A <see cref="T:System.Windows.Forms.CreateParams"/> that contains the required creation | |
/// parameters when the handle to the control is created. | |
/// </returns> | |
protected override CreateParams CreateParams | |
{ | |
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] | |
get | |
{ | |
CreateParams createParams = base.CreateParams; | |
createParams.ExStyle &= ~WS_EX_CLIENTEDGE; | |
createParams.Style &= ~WS_BORDER; | |
switch (this.borderStyle) | |
{ | |
case BorderStyle.FixedSingle: | |
createParams.Style |= WS_EX_CLIENTEDGE; | |
break; | |
case BorderStyle.Fixed3D: | |
createParams.ExStyle |= WS_BORDER; | |
break; | |
} | |
return createParams; | |
} | |
} | |
/// <summary> | |
/// Gets the default Input Method Editor (IME) mode supported by this control. | |
/// </summary> | |
/// <returns>One of the <see cref="T:System.Windows.Forms.ImeMode"/> values.</returns> | |
protected override ImeMode DefaultImeMode | |
{ | |
get | |
{ | |
return ImeMode.Disable; | |
} | |
} | |
private bool Horizontal | |
{ | |
get | |
{ | |
DockStyle dock = this.Dock; | |
return dock == DockStyle.Left || dock == DockStyle.Right; | |
} | |
} | |
private bool CaptureInternal | |
{ | |
get | |
{ | |
return base.IsHandleCreated && GetCapture() == base.Handle; | |
} | |
set | |
{ | |
if (this.CaptureInternal != value) | |
{ | |
if (value) | |
{ | |
SetCapture(base.Handle); | |
return; | |
} | |
ReleaseCapture(); | |
} | |
} | |
} | |
/// <summary> | |
/// Returns a string that represents the <see cref="T:System.Windows.Forms.Splitter"/> control. | |
/// </summary> | |
/// <returns>A string that represents the current <see cref="T:System.Windows.Forms.Splitter"/>.</returns> | |
public override string ToString() | |
{ | |
string text = base.ToString(); | |
return string.Concat(new string[] | |
{ | |
text, | |
", MinExtra: ", | |
this.MinExtra.ToString(CultureInfo.CurrentCulture), | |
", MinSize: ", | |
this.MinSize.ToString(CultureInfo.CurrentCulture) | |
}); | |
} | |
/// <summary> | |
/// Raises the <see cref="E:System.Windows.Forms.Control.MouseDown"/> event. | |
/// </summary> | |
/// <param name="e"> | |
/// A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data. | |
/// </param> | |
protected override void OnMouseDown(MouseEventArgs e) | |
{ | |
base.OnMouseDown(e); | |
if (e.Button == MouseButtons.Left && e.Clicks == 1) | |
{ | |
this.SplitBegin(e.X, e.Y); | |
} | |
} | |
/// <summary> | |
/// Raises the <see cref="E:System.Windows.Forms.Control.MouseMove"/> event. | |
/// </summary> | |
/// <param name="e"> | |
/// A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data. | |
/// </param> | |
protected override void OnMouseMove(MouseEventArgs e) | |
{ | |
base.OnMouseMove(e); | |
if (this.splitTarget != null) | |
{ | |
int x = e.X + base.Left; | |
int y = e.Y + base.Top; | |
var splitLine = this.CalcSplitLine(this.GetSplitSize(e.X, e.Y), 0); | |
int splitX = splitLine.left; | |
int splitY = splitLine.top; | |
this.OnSplitterMoving(new SplitterEventArgs(x, y, splitX, splitY)); | |
} | |
} | |
/// <summary> | |
/// Raises the <see cref="E:System.Windows.Forms.Control.MouseUp"/> event. | |
/// </summary> | |
/// <param name="e"> | |
/// A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data. | |
/// </param> | |
protected override void OnMouseUp(MouseEventArgs e) | |
{ | |
base.OnMouseUp(e); | |
if (this.splitTarget != null) | |
{ | |
this.CalcSplitLine(this.GetSplitSize(e.X, e.Y), 0); | |
this.SplitEnd(true); | |
} | |
} | |
/// <summary> | |
/// Raises the <see cref="E:System.Windows.Forms.Splitter.SplitterMoving"/> event. | |
/// </summary> | |
/// <param name="sevent"> | |
/// A <see cref="T:System.Windows.Forms.SplitterEventArgs"/> that contains the event data. | |
/// </param> | |
protected virtual void OnSplitterMoving(SplitterEventArgs sevent) | |
{ | |
SplitterEventHandler splitterEventHandler = (SplitterEventHandler)base.Events[EVENT_MOVING]; | |
if (splitterEventHandler != null) | |
{ | |
splitterEventHandler(this, sevent); | |
} | |
if (this.splitTarget != null) | |
{ | |
this.SplitMove(sevent.SplitX, sevent.SplitY); | |
} | |
} | |
/// <summary> | |
/// Raises the <see cref="E:System.Windows.Forms.Splitter.SplitterMoved"/> event. | |
/// </summary> | |
/// <param name="sevent"> | |
/// A <see cref="T:System.Windows.Forms.SplitterEventArgs"/> that contains the event data. | |
/// </param> | |
protected virtual void OnSplitterMoved(SplitterEventArgs sevent) | |
{ | |
SplitterEventHandler splitterEventHandler = (SplitterEventHandler)base.Events[EVENT_MOVED]; | |
if (splitterEventHandler != null) | |
{ | |
splitterEventHandler(this, sevent); | |
} | |
if (this.splitTarget != null) | |
{ | |
this.SplitMove(sevent.SplitX, sevent.SplitY); | |
} | |
} | |
/// <param name="x"> | |
/// The new <see cref="P:System.Windows.Forms.Control.Left"/> property value of the control. | |
/// </param> | |
/// <param name="y"> | |
/// The new <see cref="P:System.Windows.Forms.Control.Top"/> property value of the control. | |
/// </param> | |
/// <param name="width"> | |
/// The new <see cref="P:System.Windows.Forms.Control.Width"/> property value of the control. | |
/// </param> | |
/// <param name="height"> | |
/// The new <see cref="P:System.Windows.Forms.Control.Height"/> property value of the control. | |
/// </param> | |
/// <param name="specified"> | |
/// A bitwise combination of the <see cref="T:System.Windows.Forms.BoundsSpecified"/> values. | |
/// </param> | |
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) | |
{ | |
if (this.Horizontal) | |
{ | |
if (width < 1) | |
{ | |
width = defaultWidth; | |
} | |
this.splitterThickness = width; | |
} | |
else | |
{ | |
if (height < 1) | |
{ | |
height = defaultWidth; | |
} | |
this.splitterThickness = height; | |
} | |
base.SetBoundsCore(x, y, width, height, specified); | |
} | |
private void DrawSplitBar(int mode) | |
{ | |
var newRect = default(RECT); | |
if (mode != DRAW_END) | |
{ | |
newRect = CalcSplitLine(splitSize, defaultWidth); | |
} | |
if (lastDrawSplit >= 0 && mode != DRAW_START) | |
{ | |
var oldRect = CalcSplitLine(lastDrawSplit, defaultWidth); | |
if (mode != DRAW_END) | |
{ | |
var rect = newRect; | |
SubtractRect(out newRect, ref newRect, ref oldRect); | |
SubtractRect(out oldRect, ref oldRect, ref rect); | |
} | |
DrawSplitHelper(oldRect); | |
} | |
if (mode == DRAW_END) | |
{ | |
lastDrawSplit = -1; | |
} | |
else | |
{ | |
DrawSplitHelper(newRect); | |
lastDrawSplit = splitSize; | |
} | |
} | |
private RECT CalcSplitLine(int splitSize, int minWeight) | |
{ | |
Rectangle bounds = base.Bounds; | |
Rectangle targetBounds = this.splitTarget.Bounds; | |
switch (this.Dock) | |
{ | |
case DockStyle.Top: | |
if (bounds.Height < minWeight) | |
{ | |
bounds.Height = minWeight; | |
} | |
bounds.Y = targetBounds.Y + splitSize; | |
break; | |
case DockStyle.Bottom: | |
if (bounds.Height < minWeight) | |
{ | |
bounds.Height = minWeight; | |
} | |
bounds.Y = targetBounds.Y + targetBounds.Height - splitSize - bounds.Height; | |
break; | |
case DockStyle.Left: | |
if (bounds.Width < minWeight) | |
{ | |
bounds.Width = minWeight; | |
} | |
bounds.X = targetBounds.X + splitSize; | |
break; | |
case DockStyle.Right: | |
if (bounds.Width < minWeight) | |
{ | |
bounds.Width = minWeight; | |
} | |
bounds.X = targetBounds.X + targetBounds.Width - splitSize - bounds.Width; | |
break; | |
} | |
var result = default(RECT); | |
result.left = bounds.Left; | |
result.top = bounds.Top; | |
result.right = bounds.Right; | |
result.bottom = bounds.Bottom; | |
return result; | |
} | |
private int CalcSplitSize() | |
{ | |
Control control = this.FindTarget(); | |
if (control == null) | |
{ | |
return -1; | |
} | |
Rectangle bounds = control.Bounds; | |
switch (this.Dock) | |
{ | |
case DockStyle.Top: | |
case DockStyle.Bottom: | |
return bounds.Height; | |
case DockStyle.Left: | |
case DockStyle.Right: | |
return bounds.Width; | |
default: | |
return -1; | |
} | |
} | |
private SplitData CalcSplitBounds() | |
{ | |
SplitData splitData = new SplitData(); | |
Control targetControl = this.FindTarget(); | |
splitData.target = targetControl; | |
if (targetControl != null) | |
{ | |
switch (targetControl.Dock) | |
{ | |
case DockStyle.Top: | |
case DockStyle.Bottom: | |
this.initTargetSize = targetControl.Bounds.Height; | |
break; | |
case DockStyle.Left: | |
case DockStyle.Right: | |
this.initTargetSize = targetControl.Bounds.Width; | |
break; | |
} | |
Control parentInternal = this.Parent; | |
Control.ControlCollection controls = parentInternal.Controls; | |
int count = controls.Count; | |
int dockWidth = 0; | |
int dockHeight = 0; | |
for (int i = 0; i < count; i++) | |
{ | |
Control childControl = controls[i]; | |
if (childControl != targetControl) | |
{ | |
switch (childControl.Dock) | |
{ | |
case DockStyle.Top: | |
case DockStyle.Bottom: | |
dockHeight += childControl.Height; | |
break; | |
case DockStyle.Left: | |
case DockStyle.Right: | |
dockWidth += childControl.Width; | |
break; | |
} | |
} | |
} | |
Size clientSize = parentInternal.ClientSize; | |
if (this.Horizontal) | |
{ | |
this.maxSize = clientSize.Width - dockWidth - this.minExtra; | |
} | |
else | |
{ | |
this.maxSize = clientSize.Height - dockHeight - this.minExtra; | |
} | |
splitData.dockWidth = dockWidth; | |
splitData.dockHeight = dockHeight; | |
} | |
return splitData; | |
} | |
private void DrawSplitHelper(RECT rectangle) | |
{ | |
if (this.splitTarget == null) | |
{ | |
return; | |
} | |
IntPtr handle = Parent.Handle; | |
IntPtr dCEx = GetDCEx(handle, IntPtr.Zero, DCX_LOCKWINDOWUPDATE | DCX_CACHE); | |
var brush = default(LOGBRUSH); | |
brush.lbColor = 0x2A2A2A; // Invert alternate bits except highest: ..#.#.#. | |
//brush.lbColor = 0x555555; | |
IntPtr brushHandle = CreateBrushIndirect(ref brush); | |
IntPtr oldObject = SelectObject(dCEx, brushHandle); | |
PatBlt( | |
dCEx, | |
rectangle.left, | |
rectangle.top, | |
rectangle.right - rectangle.left, | |
rectangle.bottom - rectangle.top, | |
PATINVERT); | |
SelectObject(dCEx, oldObject); | |
DeleteObject(brushHandle); | |
ReleaseDC(handle, dCEx); | |
} | |
private Control FindTarget() | |
{ | |
Control parentInternal = Parent; | |
if (parentInternal == null) | |
{ | |
return null; | |
} | |
Control.ControlCollection controls = parentInternal.Controls; | |
int count = controls.Count; | |
DockStyle dock = this.Dock; | |
for (int i = 0; i < count; i++) | |
{ | |
Control control = controls[i]; | |
if (control != this) | |
{ | |
switch (dock) | |
{ | |
case DockStyle.Top: | |
if (control.Bottom == base.Top) | |
{ | |
return control; | |
} | |
break; | |
case DockStyle.Bottom: | |
if (control.Top == base.Bottom) | |
{ | |
return control; | |
} | |
break; | |
case DockStyle.Left: | |
if (control.Right == base.Left) | |
{ | |
return control; | |
} | |
break; | |
case DockStyle.Right: | |
if (control.Left == base.Right) | |
{ | |
return control; | |
} | |
break; | |
} | |
} | |
} | |
return null; | |
} | |
private int GetSplitSize(int x, int y) | |
{ | |
int delta; | |
if (this.Horizontal) | |
{ | |
delta = x - this.anchor.X; | |
} | |
else | |
{ | |
delta = y - this.anchor.Y; | |
} | |
int val = 0; | |
switch (this.Dock) | |
{ | |
case DockStyle.Top: | |
val = this.splitTarget.Height + delta; | |
break; | |
case DockStyle.Bottom: | |
val = this.splitTarget.Height - delta; | |
break; | |
case DockStyle.Left: | |
val = this.splitTarget.Width + delta; | |
break; | |
case DockStyle.Right: | |
val = this.splitTarget.Width - delta; | |
break; | |
} | |
return Math.Max(Math.Min(val, this.maxSize), this.minSize); | |
} | |
private void SplitBegin(int x, int y) | |
{ | |
SplitData splitData = this.CalcSplitBounds(); | |
if (splitData.target != null && this.minSize < this.maxSize) | |
{ | |
this.anchor = new Point(x, y); | |
this.splitTarget = splitData.target; | |
this.splitSize = this.GetSplitSize(x, y); | |
if (this.splitterMessageFilter != null) | |
{ | |
this.splitterMessageFilter = new SplitterMessageFilter(this); | |
} | |
Application.AddMessageFilter(this.splitterMessageFilter); | |
CaptureInternal = true; | |
this.DrawSplitBar(DRAW_START); | |
} | |
} | |
private void SplitEnd(bool accept) | |
{ | |
this.DrawSplitBar(DRAW_END); | |
this.splitTarget = null; | |
CaptureInternal = false; | |
if (this.splitterMessageFilter != null) | |
{ | |
Application.RemoveMessageFilter(this.splitterMessageFilter); | |
this.splitterMessageFilter = null; | |
} | |
if (accept) | |
{ | |
this.ApplySplitPosition(); | |
} | |
else if (this.splitSize != this.initTargetSize) | |
{ | |
this.SplitPosition = this.initTargetSize; | |
} | |
this.anchor = Point.Empty; | |
} | |
private void ApplySplitPosition() | |
{ | |
this.SplitPosition = this.splitSize; | |
} | |
private void SplitMove(int x, int y) | |
{ | |
int size = this.GetSplitSize(x - base.Left + this.anchor.X, y - base.Top + this.anchor.Y); | |
if (this.splitSize != size) | |
{ | |
this.splitSize = size; | |
this.DrawSplitBar(DRAW_MOVE); | |
} | |
} | |
private class SplitterMessageFilter : IMessageFilter | |
{ | |
private SplitterEx owner; | |
public SplitterMessageFilter(SplitterEx splitter) | |
{ | |
this.owner = splitter; | |
} | |
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] | |
public bool PreFilterMessage(ref Message m) | |
{ | |
if (m.Msg >= 256 && m.Msg <= 264) | |
{ | |
if (m.Msg == 256 && (int)((long)m.WParam) == 27) | |
{ | |
this.owner.SplitEnd(false); | |
} | |
return true; | |
} | |
return false; | |
} | |
} | |
private class SplitData | |
{ | |
public int dockWidth = -1; | |
public int dockHeight = -1; | |
internal Control target; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment