File: System\Windows\Forms\ContextMenuStrip.cs
Web Access
Project: src\src\System.Windows.Forms\src\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);
        }
    }
}