| File: System\Windows\Forms\Design\SplitContainerDesigner.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms.Design\src\System.Windows.Forms.Design.csproj (System.Windows.Forms.Design) |
// 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(); }