1

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!";
        }
    }
}
Jimi
  • 29,621
  • 8
  • 43
  • 61
ewu11
  • 65
  • 9

1 Answers1

2

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

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • OMG! Never expected to code it in that way. That fixed the whole problem. Also, since I am quite new to this, I've never known how I could make full use of the ```sender``` and ```e``` parameters on the event handler. Also, it is still unclear to me how and when should I use methods like ```PointToScreen()```. Thank you very much! :D – ewu11 Feb 10 '22 at 06:35
  • Sorry @Jimi..I'm so new to this stuff especially regarding .xml..but..about the enabling DpiAwareness..do I just need to edit and uncomment lines of codes in the .config and the .manifest files and then the popup location on another monitor will work..? The reason I ask is 'cuz I think I have done that..but the popup location is still only on the main monitor, although the invocation is on the second monitor. Do I need to write down any other things like additional codes? – ewu11 Feb 16 '22 at 12:06
  • I've changed the code to help you with that. Replace everything. -- The DpiAwareness depends on the .Net version in use and the DpiAware mode you want to activate. I suggest to test `PerMonitoV2` (in .Net Framework 4.7.2+ is set in `App.config`, in .Net 5+ is set in `Program.cs` calling `Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);`, but it should be already there in new projects created using the .Net Template) – Jimi Feb 16 '22 at 12:18