|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms.Design.Behavior;
namespace System.Windows.Forms.Design;
/// <summary>
/// This class handles all design time behavior for the SplitContainer class. This
/// draws a visible border on the splitter if it doesn't have a border so the
/// user knows where the boundaries of the splitter lie.
/// </summary>
internal partial class SplitContainerDesigner : ParentControlDesigner
{
private const string Panel1Name = "Panel1";
private const string Panel2Name = "Panel2";
private IDesignerHost? _designerHost;
private SplitContainer? _splitContainer;
private SplitterPanel? _selectedPanel;
private const int NumberOfSplitterPanels = 2;
private SplitterPanel? _splitterPanel1, _splitterPanel2;
// The Container Should not Show any GRIDs in the Splitter Region.
private bool _disableDrawGrid;
private bool _disabledGlyphs;
private bool _splitContainerSelected;
private int _initialSplitterDistance;
private bool _splitterDistanceException;
/// <summary>
/// Gets the design-time supported actions on the control.
/// </summary>
public override DesignerActionListCollection ActionLists
{
get
{
DesignerActionListCollection designerActionListCollection = new();
if (HasComponent)
{
designerActionListCollection.Add(new OrientationActionList(this));
}
return designerActionListCollection;
}
}
/// <summary>
/// The <see cref="SplitContainerDesigner"/> will not re-parent any controls
/// that are within it's lasso at creation time.
/// </summary>
protected override bool AllowControlLasso => false;
protected override bool DrawGrid => !_disableDrawGrid && base.DrawGrid;
/// <summary>
/// This property is used by deriving classes to determine if it returns the control being designed or
/// some other Container while adding a component to it.
/// e.g: When SplitContainer is selected and a component is being added,
/// the SplitContainer designer would return a SelectedPanel as the ParentControl
/// for all the items being added rather than itself.
/// </summary>
protected override Control? GetParentForComponent(IComponent component) => _splitterPanel1;
/// <summary>
/// Returns a list of SnapLine objects representing interesting alignment points for this control.
/// These SnapLines are used to assist in the positioning of the control on a parent's surface.
/// </summary>
public override IList SnapLines =>
// We don't want padding snaplines, so call directly to the internal method.
EdgeAndMarginSnapLines().Unwrap();
/// <summary>
/// Returns the number of internal control designers in the <see cref="SplitContainerDesigner"/>.
/// An internal control is a control that is not in the IDesignerHost.Container.Components collection.
/// We use this to get SnapLines for the internal control designers.
/// </summary>
public override int NumberOfInternalControlDesigners() => NumberOfSplitterPanels;
/// <summary>
/// Returns the internal control designer with the specified index in the <see cref="ControlDesigner"/>.
/// InternalControlIndex is zero-based.
/// </summary>
public override ControlDesigner? InternalControlDesigner(int internalControlIndex)
{
SplitterPanel panel;
switch (internalControlIndex)
{
case 0:
panel = _splitterPanel1!;
break;
case 1:
panel = _splitterPanel2!;
break;
default:
return null;
}
return _designerHost?.GetDesigner(panel) as ControlDesigner;
}
/// <summary>
/// This is the internal Property which stores the currently selected panel.
/// If the user double clicks a controls it is placed in the SelectedPanel.
/// </summary>
internal SplitterPanel? Selected
{
get => _selectedPanel;
set
{
if (_selectedPanel is not null)
{
if (_designerHost!.GetDesigner(_selectedPanel) is SplitterPanelDesigner panelDesigner1)
{
panelDesigner1.Selected = false;
}
_selectedPanel = null;
}
if (value is not null)
{
_selectedPanel = value;
if (_designerHost!.GetDesigner(value) is SplitterPanelDesigner panelDesigner)
{
panelDesigner.Selected = true;
}
}
}
}
/// <summary>
/// The ToolStripItems are the associated components. We want those to come with in any cut, copy operations.
/// </summary>
public override ICollection AssociatedComponents
{
get
{
List<Control> components = [];
foreach (SplitterPanel panel in _splitContainer!.Controls)
{
components.AddRange(panel.Controls.Cast<Control>());
}
return components;
}
}
protected override void OnDragEnter(DragEventArgs de) => de.Effect = DragDropEffects.None;
/// <summary>
/// This is the worker method of all CreateTool methods. It is the only one that can be overridden.
/// </summary>
protected override IComponent[]? CreateToolCore(ToolboxItem tool, int x, int y, int width, int height, bool hasLocation, bool hasSize)
{
// We invoke the drag drop handler for this. This implementation is shared between all designers that create components.
Selected ??= _splitterPanel1!;
var selectedPanelDesigner = (SplitterPanelDesigner)_designerHost!.GetDesigner(Selected)!;
InvokeCreateTool(selectedPanelDesigner, tool);
// Return Dummy null as the InvokeCreateTool of SplitterPanel would do the necessary hookups.
return null;
}
protected override void Dispose(bool disposing)
{
if (TryGetService(out ISelectionService? svc))
{
svc.SelectionChanged -= OnSelectionChanged;
}
if (_splitContainer is not null)
{
_splitContainer.MouseDown -= OnSplitContainer;
_splitContainer.SplitterMoved -= OnSplitterMoved;
_splitContainer.SplitterMoving -= OnSplitterMoving;
_splitContainer.DoubleClick -= OnSplitContainerDoubleClick;
}
base.Dispose(disposing);
}
protected override bool GetHitTest(Point point) => !(InheritanceAttribute == InheritanceAttribute.InheritedReadOnly) && _splitContainerSelected;
/// <summary>
/// Returns a 'BodyGlyph' representing the bounds of this control.
/// The BodyGlyph is responsible for hit testing the related CtrlDes and
/// forwarding messages directly to the designer.
/// </summary>
protected override ControlBodyGlyph GetControlGlyph(GlyphSelectionType selectionType)
{
if (TryGetService(out SelectionManager? selMgr))
{
Rectangle translatedBounds = BehaviorService?.ControlRectInAdornerWindow(_splitterPanel1!) ?? Rectangle.Empty;
var panelDesigner = _designerHost?.GetDesigner(_splitterPanel1!) as SplitterPanelDesigner;
OnSetCursor();
if (panelDesigner is not null)
{
// Create our glyph, and set its cursor appropriately.
ControlBodyGlyph bodyGlyph = new(translatedBounds, Cursor.Current, _splitterPanel1, panelDesigner);
selMgr.BodyGlyphAdorner.Glyphs.Add(bodyGlyph);
}
translatedBounds = BehaviorService?.ControlRectInAdornerWindow(_splitterPanel2!) ?? Rectangle.Empty;
panelDesigner = _designerHost?.GetDesigner(_splitterPanel2!) as SplitterPanelDesigner;
if (panelDesigner is not null)
{
// Create our glyph, and set its cursor appropriately.
ControlBodyGlyph bodyGlyph = new(translatedBounds, Cursor.Current, _splitterPanel2, panelDesigner);
selMgr.BodyGlyphAdorner.Glyphs.Add(bodyGlyph);
}
}
return base.GetControlGlyph(selectionType);
}
public override void Initialize(IComponent component)
{
base.Initialize(component);
AutoResizeHandles = true;
_splitContainer = component as SplitContainer;
Debug.Assert(_splitContainer is not null, $"Component must be a non-null SplitContainer, it is a: {component.GetType().FullName}");
_splitterPanel1 = _splitContainer.Panel1;
_splitterPanel2 = _splitContainer.Panel2;
EnableDesignMode(_splitContainer.Panel1, Panel1Name);
EnableDesignMode(_splitContainer.Panel2, Panel2Name);
_designerHost = (IDesignerHost?)component.Site?.GetService(typeof(IDesignerHost));
if (_selectedPanel is null)
{
Selected = _splitterPanel1;
}
_splitContainer.MouseDown += OnSplitContainer;
_splitContainer.SplitterMoved += OnSplitterMoved;
_splitContainer.SplitterMoving += OnSplitterMoving;
_splitContainer.DoubleClick += OnSplitContainerDoubleClick;
if (TryGetService(out ISelectionService? svc))
{
svc.SelectionChanged += OnSelectionChanged;
}
}
/// <summary>
/// Overrides our base class. We don't draw the Grids for this Control. Also we Select the Panel1 if nothing is still Selected.
/// </summary>
protected override void OnPaintAdornments(PaintEventArgs pe)
{
try
{
_disableDrawGrid = true;
// We don't want to do this for the tab control designer because you can't drag anything onto it anyway.
// So we will always return false for draw grid.
base.OnPaintAdornments(pe);
}
finally
{
_disableDrawGrid = false;
}
}
/// <summary>
/// Determines if the this designer can parent to the specified designer.
/// Generally this means if the control for this designer can parent the given ControlDesigner's designer.
/// </summary>
public override bool CanParent(Control control) => false;
private void OnSplitContainer(object? sender, MouseEventArgs e)
{
var svc = GetRequiredService<ISelectionService>();
svc.SetSelectedComponents(new object[] { Control });
}
private void OnSplitContainerDoubleClick(object? sender, EventArgs e)
{
if (_splitContainerSelected)
{
try
{
DoDefaultAction();
}
catch (Exception ex) when (!ex.IsCriticalException())
{
DisplayError(ex);
}
}
}
private void OnSplitterMoved(object? sender, SplitterEventArgs e)
{
if (InheritanceAttribute == InheritanceAttribute.InheritedReadOnly || _splitterDistanceException)
{
return;
}
try
{
RaiseComponentChanging(TypeDescriptor.GetProperties(_splitContainer!)["SplitterDistance"]);
RaiseComponentChanged(TypeDescriptor.GetProperties(_splitContainer!)["SplitterDistance"], oldValue: null, newValue: null);
// Enable all adorners except for BodyGlyph adorner. But only if we turned off the adorners.
if (_disabledGlyphs)
{
BehaviorService?.EnableAllAdorners(true);
var selMgr = GetService<SelectionManager>();
selMgr?.Refresh();
_disabledGlyphs = false;
}
}
catch (InvalidOperationException ex)
{
var uiService = (IUIService?)Component?.Site?.GetService(typeof(IUIService));
uiService?.ShowError(ex.Message);
}
catch (CheckoutException checkoutException) when (checkoutException == CheckoutException.Canceled)
{
try
{
_splitterDistanceException = true;
_splitContainer!.SplitterDistance = _initialSplitterDistance;
}
finally
{
_splitterDistanceException = false;
}
}
}
private void OnSplitterMoving(object? sender, SplitterCancelEventArgs e)
{
_initialSplitterDistance = _splitContainer!.SplitterDistance;
if (InheritanceAttribute == InheritanceAttribute.InheritedReadOnly)
{
return;
}
// We are moving the splitter via the mouse or key and not as a result of resize of the container itself
// (through the ResizeBehavior::OnMouseMove).
_disabledGlyphs = true;
// Find our BodyGlyph adorner offered by the behavior service. We don't want to disable
// the transparent body glyphs.
SelectionManager? selMgr = GetService<SelectionManager>();
Adorner? bodyGlyphAdorner = selMgr?.BodyGlyphAdorner;
// Disable all adorners except for BodyGlyph adorner.
if (BehaviorService is not null)
{
foreach (Adorner adorner in BehaviorService.Adorners)
{
if (bodyGlyphAdorner is not null && adorner.Equals(bodyGlyphAdorner))
{
continue;
}
adorner.EnabledInternal = false;
}
BehaviorService.Invalidate();
}
// From the BodyAdorners Remove all Glyphs Except the ones for SplitterPanels.
List<ControlBodyGlyph> glyphsToRemove = [];
foreach (ControlBodyGlyph g in bodyGlyphAdorner!.Glyphs)
{
if (g.RelatedComponent is not SplitterPanel)
{
glyphsToRemove.Add(g);
}
}
foreach (Glyph g in glyphsToRemove)
{
bodyGlyphAdorner.Glyphs.Remove(g);
}
}
/// <summary>
/// Called when the current selection changes. Here we check to see if the newly selected component
/// is one of our Panels. If it is, we make sure that the tab is the currently visible tab.
/// </summary>
private void OnSelectionChanged(object? sender, EventArgs e)
{
_splitContainerSelected = false;
if (!TryGetService(out ISelectionService? svc))
{
return;
}
ICollection selComponents = svc.GetSelectedComponents();
foreach (object comp in selComponents)
{
SplitterPanel? panel = CheckIfPanelSelected(comp);
if (panel is not null && panel.Parent == _splitContainer)
{
_splitContainerSelected = false;
Selected = panel;
return;
}
if (comp == _splitContainer)
{
_splitContainerSelected = true; // This is for HitTest purposes
break;
}
}
Selected = null;
}
/// <summary>
/// Given a component, this retrieves the splitter panel that it's parented to,
/// or null if it's not parented to any splitter panel.
/// </summary>
private static SplitterPanel? CheckIfPanelSelected(object comp) => comp as SplitterPanel;
/// <summary>
/// Called when one of the child splitter panels receives a MouseHover message.
/// Here, we will simply call the parenting SplitContainer.OnMouseHover so we can get a
/// grab handle for moving this thing around.
/// </summary>
internal void SplitterPanelHover() => OnMouseHover();
}
|