| File: System\Windows\Forms\ContextMenuStrip.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Drawing; using Windows.Win32.UI.Accessibility; namespace System.Windows.Forms; /// <summary> /// This class is just a conceptual wrapper around ToolStripDropDownMenu. /// </summary> [DefaultEvent(nameof(Opening))] [SRDescription(nameof(SR.DescriptionContextMenuStrip))] public class ContextMenuStrip : ToolStripDropDownMenu { public ContextMenuStrip(IContainer container) : base() { // this constructor ensures ContextMenuStrip is disposed properly since its not parented to the form. ArgumentNullException.ThrowIfNull(container); container.Add(this); } public ContextMenuStrip() { } protected override void Dispose(bool disposing) { base.Dispose(disposing); } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [SRDescription(nameof(SR.ContextMenuStripSourceControlDescr))] public Control? SourceControl { get { return SourceControlInternal; } } // minimal Clone implementation for DGV support only. internal ContextMenuStrip Clone() { // VERY limited support for cloning. ContextMenuStrip contextMenuStrip = new(); // copy over events contextMenuStrip.Events.AddHandlers(Events); contextMenuStrip.AutoClose = AutoClose; contextMenuStrip.AutoSize = AutoSize; contextMenuStrip.Bounds = Bounds; contextMenuStrip.ImageList = ImageList; contextMenuStrip.ShowCheckMargin = ShowCheckMargin; contextMenuStrip.ShowImageMargin = ShowImageMargin; // copy over relevant properties for (int i = 0; i < Items.Count; i++) { ToolStripItem item = Items[i]; if (item is ToolStripSeparator) { contextMenuStrip.Items.Add(new ToolStripSeparator()); } else if (item is ToolStripMenuItem toolStripMenuItem) { contextMenuStrip.Items.Add(toolStripMenuItem.Clone()); } } return contextMenuStrip; } // internal overload so we know whether or not to show mnemonics. internal void ShowInternal(Control source, Point location, bool isKeyboardActivated) { Show(source, location); // if we were activated by keyboard - show mnemonics. if (isKeyboardActivated) { ToolStripManager.ModalMenuFilter.Instance.ShowUnderlines = true; } } internal void ShowInTaskbar(int x, int y) { // we need to make ourselves a topmost window WorkingAreaConstrained = false; Rectangle bounds = CalculateDropDownLocation(new Point(x, y), ToolStripDropDownDirection.AboveLeft); Rectangle screenBounds = Screen.FromRectangle(bounds).Bounds; if (bounds.Y < screenBounds.Y) { bounds = CalculateDropDownLocation(new Point(x, y), ToolStripDropDownDirection.BelowLeft); } else if (bounds.X < screenBounds.X) { bounds = CalculateDropDownLocation(new Point(x, y), ToolStripDropDownDirection.AboveRight); } bounds = WindowsFormsUtils.ConstrainToBounds(screenBounds, bounds); Show(bounds.X, bounds.Y); } protected override void SetVisibleCore(bool visible) { if (!visible) { WorkingAreaConstrained = true; } base.SetVisibleCore(visible); // There are two problems we're facing when trying to scale ContextMenuStrip: // 1. ContextMenuStrip is a top level window and thus only receive "WM_DPICHANGED" message // but not WM_DPICHANGED_BEFOREPARENT/WM_DPICHANGED_AFTERPARENT. // Unfortunately, we do not handle scaling of controls in "WM_DPICHANGED" message. // In WinForms, "WM_DPICHANGED" message is intended for Top-level Forms/ContainerControls. // As a result, the ContextMenuStrip window doesn't scale itself when moved from one monitor // to another on the "PerMonitorV2" process. // 2. When ContextMenuStrip changes to invisible(with "visible" set to false), owner of the window // is changed to a ParkingWindow that may possibly parked on primary monitor. // The "GetDpiForWindow()" API on ContextMenuStrip thus returns the DPI of the primary monitor. // Because of this inconsistency, we intentionally recreate the handle that triggers scaling according // to the new DPI, after setting the "visible" property. if (visible && IsHandleCreated && ScaleHelper.IsThreadPerMonitorV2Aware && DeviceDpi != (int)PInvoke.GetDpiForWindow(this)) { RecreateHandle(); } } protected override void OnOpened(EventArgs e) { if (IsHandleCreated && !IsInDesignMode) { AccessibilityNotifyClients(AccessibleEvents.SystemMenuPopupStart, -1); if (IsAccessibilityObjectCreated) { AccessibilityObject.RaiseAutomationEvent(UIA_EVENT_ID.UIA_MenuOpenedEventId); } } base.OnOpened(e); } protected override void OnClosed(ToolStripDropDownClosedEventArgs e) { base.OnClosed(e); if (IsHandleCreated && !IsInDesignMode) { if (IsAccessibilityObjectCreated) { AccessibilityObject.RaiseAutomationEvent(UIA_EVENT_ID.UIA_MenuClosedEventId); } AccessibilityNotifyClients(AccessibleEvents.SystemMenuPopupEnd, -1); } } }