简体   繁体   中英

How to prevent a popup Form from exceeding screen area?

I am building a two column custom context menu layout using Forms.
I named the class of the form of the custom context menu as ContextMenu .

I created a flag function to check whether it exceeds the device screen dimension upon invocation.
Single cases like exceeding only the length or only the height of device screen works.

However, when it comes to exceeding both the length and height of device screen, it somehow didn't work. I've tried to print the values in the console for checking purposes( example ). The values printed are correct, but for some reason, it didn't enter the switch case for it.

Did I missed something anywhere in the codes?

Below is the flag function. case 3 doesn't work( example ).

//exceed screen flag function
private Point processContextMenuFormLocation(ContextMenu theContextMenu, int screenExceedFlag, Point mouseCoor)
{
    //local form coordinate
    int formXCoor;
    int formYCoor;

    switch (screenExceedFlag)
    {
        case 1: //if exceed right boundary
            formXCoor = (mouseCoor.X) - (contextMenuObj.Width); //move context menu to the left
            formYCoor = mouseCoor.Y; //no need changes
            //after exceedFlag, set where context menu position is
            theContextMenu.Location = new Point(formXCoor, formYCoor);
            break;
        case 2: //if exceed bottom boundary
            formXCoor = (mouseCoor.X); //no need changes
            formYCoor = (mouseCoor.Y) - (contextMenuObj.Height); //move context menu to the top
            theContextMenu.Location = new Point(formXCoor, formYCoor);
            break;
        case 3: //if exceed right & bottom boundary
            formXCoor = (mouseCoor.X) - (contextMenuObj.Width); //move context menu to the left
            formYCoor = (mouseCoor.Y) - (contextMenuObj.Height); //move context menu to the top
            theContextMenu.Location = new Point(formXCoor, formYCoor);
            break;
        case -1: //if exceeded nothing
            theContextMenu.Location = new Point(mouseCoor.X, mouseCoor.Y);
            break;
    }
    //return the new location of context menu
    return theContextMenu.Location;
}

Below is the event handler, which calls the above function:

//mouse up event handler
private void richTextBox1_MouseUp(object sender, MouseEventArgs e)
{
    //set default CM closed so that won't open concurrently
    richContextStrip.Visible = false;

    if (e.Button == MouseButtons.Right)
    {
        IsKeyUp((int)MouseButtons.Right); //set rmbIsUp = true 

        //here because the rmb event handler is above this
        bool canDisplay = contextMenuDisplayFlag(ctrlIsDown, rmbIsUp); //get ctrl and rmb flag status 

        if (canDisplay)
        {
            //to process whether custom context menu was opened beyond screen area or not
            exceedScreenFlag = isBeyondScreen(Cursor.Position.X, Cursor.Position.Y, contextMenuObj.Width, contextMenuObj.Height);

            //obtain mouse coordinates
            Point theMouseCoor = new Point(Cursor.Position.X, Cursor.Position.Y);

            //exceed screen flag function
            Point formLocation = processContextMenuFormLocation(contextMenuObj, exceedScreenFlag, theMouseCoor);

            //center the cursor at context menu
            Point cursorLocation = processContextMenuCursorLocation(contextMenuObj, formLocation);

            displayCustomContextMenu(contextMenuObj, formLocation, cursorLocation);
            toolStripStatusLabel1.Text = "Custom context menu opened!";
        }
        else //if ctrl key is not pressed
        {
            richContextStrip.Visible = true;
            toolStripStatusLabel1.Text = "Default context menu opened!";
        }
    }
}

You may want to consider that the current mouse Pointer location is returned by MouseEventArgs.Location . You just need to convert this value to Screen coordinates using the [Control].PointToScreen() method.

Then compare the initial Location - plus the Width and Height of your Popup - with the value returned by Screen.FromControl([Control]).WorkingArea and verify whether the Popup is contained within this bounds.
If it's not, then subtract the Popup's Width and/or Height from the WorkingArea left and right bounds.

Something like this:
Note that these measures don't include the Form's invisible borders. The distance from the screen's sides will be 14~16 pixels. Adjust it if you prefer a tighter position

private void someControl_MouseUp(object sender, MouseEventArgs e)
{
    var f = new Form() { Width = 400, Height = 400, StartPosition = FormStartPosition.Manual };
    f.Location = SetPopupLocation(Screen.FromControl(this), f, (sender as Control).PointToScreen(e.Location));
    f.Show();
}

private Point SetPopupLocation(Screen screen, Form form, Point initPosition)
{
    var p = new Point();
    var wrkArea = screen.WorkingArea;
    p.X = wrkArea.Right - (initPosition.X + form.Width);
    p.Y = wrkArea.Bottom - (initPosition.Y + form.Height);
    p.X = p.X < 0 ? wrkArea.Right - form.Width : initPosition.X;
    p.Y = p.Y < 0 ? wrkArea.Bottom - form.Height : initPosition.Y;
    return p;
}

Your app needs to be DpiAware to receive reliable measures and coordinates.
See the notes in Using SetWindowPos with multiple monitors

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM