| File: System\Windows\Forms\Design\Behavior\BehaviorService.AdornerWindow.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.Drawing; namespace System.Windows.Forms.Design.Behavior; public sealed partial class BehaviorService { /// <summary> /// The AdornerWindow is a transparent window that resides ontop of the Designer's Frame. This window is used /// by the BehaviorService to intercept all messages. It also serves as a unified canvas on which to paint Glyphs. /// </summary> private partial class AdornerWindow : Control { private readonly BehaviorService _behaviorService; private static MouseHook? s_mouseHook; private static readonly List<AdornerWindow> s_adornerWindowList = []; /// <summary> /// Constructor that parents itself to the Designer Frame and hooks all /// necessary events. /// </summary> internal AdornerWindow(BehaviorService behaviorService, DesignerFrame designerFrame) { _behaviorService = behaviorService; DesignerFrame = designerFrame; Dock = DockStyle.Fill; AllowDrop = true; Text = "AdornerWindow"; SetStyle(ControlStyles.Opaque, true); } /// <summary> /// The key here is to set the appropriate TransparentWindow style. /// </summary> protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.Style &= ~(int)(WINDOW_STYLE.WS_CLIPCHILDREN | WINDOW_STYLE.WS_CLIPSIBLINGS); cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_TRANSPARENT; return cp; } } internal bool ProcessingDrag { get; set; } /// <summary> /// We'll use CreateHandle as our notification for creating our mouse attacher. /// </summary> [MemberNotNull(nameof(s_mouseHook))] protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); s_adornerWindowList.Add(this); s_mouseHook ??= new MouseHook(); } /// <summary> /// Unhook and null out our mouseHook. /// </summary> protected override void OnHandleDestroyed(EventArgs e) { s_adornerWindowList.Remove(this); // Unregister the mouse hook once all adorner windows have been disposed. if (s_adornerWindowList.Count == 0 && s_mouseHook is not null) { s_mouseHook.Dispose(); s_mouseHook = null; } base.OnHandleDestroyed(e); } /// <summary> /// Null out our mouseHook and unhook any events. /// </summary> protected override void Dispose(bool disposing) { if (disposing && DesignerFrame is not null) { DesignerFrame = null!; } base.Dispose(disposing); } internal DesignerFrame DesignerFrame { get; private set; } /// <summary> /// Returns the display rectangle for the adorner window /// </summary> internal Rectangle DesignerFrameDisplayRectangle => DesignerFrameValid ? DesignerFrame.DisplayRectangle : Rectangle.Empty; /// <summary> /// Returns true if the DesignerFrame is created and not being disposed. /// </summary> internal bool DesignerFrameValid => DesignerFrame is not null && !DesignerFrame.IsDisposed && DesignerFrame.IsHandleCreated; /// <summary> /// Ultimately called by ControlDesigner when it receives a DragDrop message - here, we'll exit from 'drag mode'. /// </summary> internal void EndDragNotification() => ProcessingDrag = false; /// <summary> /// Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate. /// Note the they use of the .Update() call for perf. purposes. /// </summary> internal void InvalidateAdornerWindow() { if (DesignerFrameValid) { DesignerFrame.Invalidate(true); DesignerFrame.Update(); } } /// <summary> /// Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate. /// Note the they use of the .Update() call for perf. purposes. /// </summary> internal void InvalidateAdornerWindow(Region region) { if (DesignerFrameValid) { // Translate for non-zero scroll positions Point scrollPosition = DesignerFrame.AutoScrollPosition; region.Translate(scrollPosition.X, scrollPosition.Y); DesignerFrame.Invalidate(region, true); DesignerFrame.Update(); } } /// <summary> /// Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate. /// Note the they use of the .Update() call for perf. purposes. /// </summary> internal void InvalidateAdornerWindow(Rectangle rectangle) { if (DesignerFrameValid) { // Translate for non-zero scroll positions Point scrollPosition = DesignerFrame.AutoScrollPosition; rectangle.Offset(scrollPosition.X, scrollPosition.Y); DesignerFrame.Invalidate(rectangle, true); DesignerFrame.Update(); } } /// <summary> /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate /// Behavior via the BehaviorService. /// </summary> protected override void OnDragDrop(DragEventArgs e) { try { _behaviorService.OnDragDrop(e); } finally { ProcessingDrag = false; } } private static bool IsLocalDrag(DragEventArgs e) { if (e.Data is DropSourceBehavior.BehaviorDataObject) { return true; } else { // Gets all the data formats and data conversion formats in the data object. string[] allFormats = e.Data!.GetFormats(); for (int i = 0; i < allFormats.Length; i++) { if (string.Equals(ToolboxFormat, allFormats[i])) { return true; } } } return false; } /// <summary> /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate /// Behavior via the BehaviorService. /// </summary> protected override void OnDragEnter(DragEventArgs e) { ProcessingDrag = true; // Determine if this is a local drag, if it is, do normal processing otherwise, force a // PropagateHitTest. We need to force this because the OLE D&D service suspends mouse messages // when the drag is not local so the mouse hook never sees them. if (!IsLocalDrag(e)) { _behaviorService._validDragArgs = e; PInvoke.GetCursorPos(out Point point); point = PointToClient(point); _behaviorService.PropagateHitTest(point); } _behaviorService.OnDragEnter(null, e); } /// <summary> /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate /// Behavior via the BehaviorService. /// </summary> protected override void OnDragLeave(EventArgs e) { // set our dragArgs to null so we know not to send drag enter/leave events when we re-enter the dragging area _behaviorService._validDragArgs = null; try { _behaviorService.OnDragLeave(null, e); } finally { ProcessingDrag = false; } } /// <summary> /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate /// Behavior via the BehaviorService. /// </summary> protected override void OnDragOver(DragEventArgs e) { ProcessingDrag = true; if (!IsLocalDrag(e)) { _behaviorService._validDragArgs = e; PInvoke.GetCursorPos(out Point point); point = PointToClient(point); _behaviorService.PropagateHitTest(point); } _behaviorService.OnDragOver(e); } /// <summary> /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate /// Behavior via the BehaviorService. /// </summary> protected override void OnGiveFeedback(GiveFeedbackEventArgs e) => _behaviorService.OnGiveFeedback(e); /// <summary> /// The AdornerWindow hooks all Drag/Drop notification so they can be forwarded to the appropriate /// Behavior via the BehaviorService. /// </summary> protected override void OnQueryContinueDrag(QueryContinueDragEventArgs e) => _behaviorService.OnQueryContinueDrag(e); /// <summary> /// Called by ControlDesigner when it receives a DragEnter message - we'll let listen to all Mouse /// Messages so we can send drag notifications. /// </summary> internal void StartDragNotification() => ProcessingDrag = true; /// <summary> /// The AdornerWindow intercepts all designer-related messages and forwards them to the BehaviorService /// for appropriate actions. Note that Paint and HitTest messages are correctly parsed and translated /// to AdornerWindow coords. /// </summary> protected override unsafe void WndProc(ref Message m) { // special test hooks if (m.Msg == (int)WM_GETALLSNAPLINES) { _behaviorService.TestHook_GetAllSnapLines(ref m); } else if (m.Msg == (int)WM_GETRECENTSNAPLINES) { _behaviorService.TestHook_GetRecentSnapLines(ref m); } switch (m.MsgInternal) { case PInvokeCore.WM_PAINT: { // Stash off the region we have to update. using RegionScope hrgn = new(0, 0, 0, 0); PInvoke.GetUpdateRgn(m.HWND, hrgn, true); // The region we have to update in terms of the smallest rectangle that completely encloses // the update region of the window gives us the clip rectangle. RECT clip = default; PInvoke.GetUpdateRect(m.HWND, &clip, true); Rectangle paintRect = clip; using Region region = hrgn.ToRegion(); // Call the base class to do its painting. DefWndProc(ref m); // Now do our own painting. using Graphics g = Graphics.FromHwnd(m.HWnd); using PaintEventArgs pevent = new(g, paintRect); g.Clip = region; _behaviorService.PropagatePaint(pevent); break; } case PInvokeCore.WM_NCHITTEST: Point pt = PARAM.ToPoint(m.LParamInternal); Point pt1 = PointToClient(default); pt.Offset(pt1.X, pt1.Y); m.ResultInternal = _behaviorService.PropagateHitTest(pt) && !ProcessingDrag ? (LRESULT)PInvoke.HTTRANSPARENT : (LRESULT)(int)PInvoke.HTCLIENT; break; case PInvokeCore.WM_CAPTURECHANGED: base.WndProc(ref m); _behaviorService.OnLoseCapture(); break; default: base.WndProc(ref m); break; } } /// <summary> /// Called by our mouseHook when it spies a mouse message that the adornerWindow would be interested in. /// Returning 'true' signifies that the message was processed and should not continue to child windows. /// </summary> private bool WndProcProxy(ref Message m, int x, int y) { Point mouseLoc = new(x, y); _behaviorService.PropagateHitTest(mouseLoc); switch (m.MsgInternal) { case PInvokeCore.WM_LBUTTONDOWN: if (_behaviorService.OnMouseDown(MouseButtons.Left, mouseLoc)) { return false; } break; case PInvokeCore.WM_RBUTTONDOWN: if (_behaviorService.OnMouseDown(MouseButtons.Right, mouseLoc)) { return false; } break; case PInvokeCore.WM_MOUSEMOVE: if (_behaviorService.OnMouseMove(MouseButtons, mouseLoc)) { return false; } break; case PInvokeCore.WM_LBUTTONUP: if (_behaviorService.OnMouseUp(MouseButtons.Left)) { return false; } break; case PInvokeCore.WM_RBUTTONUP: if (_behaviorService.OnMouseUp(MouseButtons.Right)) { return false; } break; case PInvokeCore.WM_MOUSEHOVER: if (_behaviorService.OnMouseHover(mouseLoc)) { return false; } break; case PInvokeCore.WM_LBUTTONDBLCLK: if (_behaviorService.OnMouseDoubleClick(MouseButtons.Left, mouseLoc)) { return false; } break; case PInvokeCore.WM_RBUTTONDBLCLK: if (_behaviorService.OnMouseDoubleClick(MouseButtons.Right, mouseLoc)) { return false; } break; } return true; } } }