Blaato
Blaato

Reputation: 129

How to check if mouse is in ContextMenuStrip or in its items

I´m using ContextMenuStrip in my Form app as my drop down menu. Exactly, when I click on button, ContextMenuStrip shows right under it. Everithing is OK, but I really want to auto-close ContextMenuStrip after mouse leave its area. Ok, so I´m try to use MouseLeave event. Once again, everything is OK, but when I add dropdown items to some ToolStripItem in ContextMenuStrip, the mouseLeave event donť recognize this new area as a part of ContextMenuStrip. This is my newest attempt, but it is not finished. Any idea, how to resolve this problem?

    private void ContextMenuStrip_MouseLeave(object sender, EventArgs e)
    {
        ContextMenuStrip cms = (sender is ContextMenuStrip) ? sender as ContextMenuStrip : null;

        if (cms != null)
        {
            //List<Rectangle> cmsFullArea = new List<Rectangle>();
            //cmsFullArea.Add(new Rectangle(cms.Bounds.Location, cms.Bounds.Size));

            bool itemIsPressed = false;
            for (int i = 0; i < cms.Items.Count; i++)
            {
                if (cms.Items[i].Pressed) { itemIsPressed = true; break; }
            }

            if (!itemIsPressed) { cms.Close(); }
        }
    }

This works fine, when I leave CMS to dropDown items, but it is not working, when I leave them too after. I need to close whole CMS, when I leave any of his areas.

Upvotes: 1

Views: 360

Answers (2)

Blaato
Blaato

Reputation: 129

As I promise in comments, I want to post another solution for this problem.

Firstly there is MouseLeave method for event handle. This method is common to ContextMenuStrip (CMS) and ToolStripDropDownMenu (TSDDM).

    private void ContextMenuStrip_MouseLeave(object sender, EventArgs e)
    {
        ContextMenuStrip cms = (sender is ContextMenuStrip) ? sender as ContextMenuStrip : null;

        //Recognize CMS or TSDDM, in this case we dont need anything else
        if (cms != null)
        {
            //Check, if mouse position is on any of CMS DropDownMenus. 
            //If false, close CMS. If true, we dont want to close it - CMS is actively in use
            if (!IsMouseOnDropDown(cms.Items)) { cms.Close(); }
        }
        else
        {
            ToolStripDropDownMenu ddm = (sender is ToolStripDropDownMenu) ? sender as ToolStripDropDownMenu : null;

            if (ddm != null)
            {
                //As above, check mouse position against items DropDownMenus
                if (IsMouseOnDropDown(ddm.Items)) { return; }

                //Declare our CMS
                cms = GetPrimaryOwner(ddm);

                //Get TSDDM owner
                //var is important here, because we dont know if it is CMS or another TSDDM!!!
                //Also TSDDM and CMS have the same properties for our purpose, so var is OK
                var owner = ddm.OwnerItem.Owner;

                Point pnt = Cursor.Position;

                //If owner doesn't contains mouse position, close whole CMS
                if (!owner.Bounds.Contains(pnt))
                {
                    cms.Close();
                }
                else
                {
                    //If does, we need to check if mouse position is exactly on parent item, 
                    //because its prevent to TSDDM unnecessary close/open 
                    //(explanation: Mouse leave TSDDM -> TSDDM close; 
                                  //Mouse is on parent item -> TSDDM open)
                    for (int i = 0; i < owner.Items.Count; i++)
                    {
                        //Define own rectangle, because item has its own bounds against the owner
                        //so we need to add up their X and Y to get the real one
                        int x = owner.Bounds.X + (owner.Items[i] as ToolStripMenuItem).Bounds.X;
                        int y = owner.Bounds.Y + (owner.Items[i] as ToolStripMenuItem).Bounds.Y;
                        Rectangle rect = new Rectangle(new Point(x, y), (pupik.Items[i] as ToolStripMenuItem).Bounds.Size);

                        //If its our DropDownMenu and mouse position is in there,
                        //we dont want to close ddm
                        if ((owner.Items[i] as ToolStripMenuItem).DropDown == ddm
                            && rect.Contains(pnt))
                        {
                            return;
                        }
                    }

                    ddm.Close();
                }
            }
        }
    }

Above you can see IsMouseOnDropDown and GetPrimaryOwner methods, so there is the code:

    private static bool IsMouseOnDropDown(ToolStripItemCollection itemCollection)
    {
        Point pnt = Cursor.Position;

        //All what we do is check, if some of DropDownMenus from input collection is active (Visible) 
        //and if mouse position is in it
        for (int i = 0; i < itemCollection.Count; i++)
        {
            if ((itemCollection[i] as ToolStripMenuItem).DropDown.Visible
                && (itemCollection[i] as ToolStripMenuItem).DropDown.Bounds.Contains(pnt))
            {
                return true;
            }
        }

        return false;
    }

    private static ContextMenuStrip GetPrimaryOwner(ToolStripDropDownMenu dropDownMenu)
    {
        //All what we do is take owner by owner until we find our CMS,
        //which is the last one -> primary owner
        object cmsItems = dropDownMenu;

        while (!(cmsItems is ContextMenuStrip))
        {
            cmsItems = (cmsItems as ToolStripDropDownMenu).OwnerItem.Owner;
        }

        return cmsItems as ContextMenuStrip;
    }

The last thing what we need to do, is handle the mouseLeave event for every DropDownMenu an ContextmenuStrip

   this.ContextMenuStrip1.MouseLeave += new System.EventHandler(ContextMenuStrip_MouseLeave);
   this.StripMenuItem1.DropDown.MouseLeave += new System.EventHandler(ContextMenuStrip_MouseLeave);

With this example everything works fine to me, so if you find an error, please let me know. It is just an example, so I dont write Try/Catch there..

Upvotes: 0

Add a region variable which will be ContextMenuStrip plus the DropDownMenus.

private Region rgn = new Region();

Initialize region:

public Form1() {
    InitializeComponent();

    rgn.MakeEmpty();
}

When ContextMenuStrip opens update region:

private void contextMenuStrip1_Opened( object sender, EventArgs e ) {
    rgn.Union( contextMenuStrip1.Bounds );
}

In leave event check if mouse is inside this region:

private void contextMenuStrip1_MouseLeave( object sender, EventArgs e ) {
    Point pnt = Cursor.Position;

    if( rgn.IsVisible( pnt ) == false ) {
        rgn.MakeEmpty();

        contextMenuStrip1.Close();
    }
}

When you create a new ToolStripDropDownMenu adding items to eg toolStripMenuItem0, add these event handlers:

//toolStripMenuItem0 is an item of your ContextMenuStrip
toolStripMenuItem0.DropDown.MouseLeave += DropDown_MouseLeave;
toolStripMenuItem0.DropDown.Opened += DropDown_Opened;
toolStripMenuItem0.DropDown.Closed += DropDown_Closed;

private void DropDown_Closed( object sender, ToolStripDropDownClosedEventArgs e ) {
    ToolStripDropDownMenu tsddm = (ToolStripDropDownMenu)sender;

    rgn.Exclude( tsddm.Bounds ); //remove rect from region
}

private void DropDown_Opened( object sender, EventArgs e ) {
    ToolStripDropDownMenu tsddm = (ToolStripDropDownMenu)sender;

    rgn.Union( tsddm.Bounds ); //add rect to region
}

private void DropDown_MouseLeave( object sender, EventArgs e ) {
    Point pnt = Cursor.Position;

    if( rgn.IsVisible( pnt ) == false ) {
        rgn.MakeEmpty();

        contextMenuStrip1.Close();
    }
}

Do the same for every DropDownMenu you create.

Upvotes: 1

Related Questions