MessageBox and subtleties

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
Hello everybody,

My MessageBox displayed the message, but behind other windows, and with the mouse cursor in the middle of the message window instead of the middle of the button.
Furthermore, after I positioned the mouse cursor, the previous position was still visible, so I saw two mouse cursors on the screen.

So, I corrected that, it works good, but I wonder whether a much more simple solution does not exist.

And also, to evaluate the correct position of the cursor, on WinForms you have Control.PointToScreen, but I am not sure that can be used on a MessageBox. I put values hardcoded, that is not sure to adapt if I change the sizes of the fonts in Windows.
Manage a MessageBox:
       private void Button_Click(Object sender, EventArgs e)
       {
                if (fic.FullName.Equals(strPath))
                {
                    bFicPresent = true;
                    Form messageForm = new Form();
                    messageForm.TopMost = true;
                    strWindowToPosition = "File already present";
                    timer2.Enabled = true;
                    MessageBox.Show(messageForm, String.Format("{1} :\n{0}", fic.FullName, strWindowToPosition), strWindowToPosition);
                    messageForm.Close();
                    break;
                }
       }

        string strWindowToPosition;

        private void timer2_Tick(object sender, EventArgs e)
        {
            timer2.Enabled = false;
            //System.Threading.Thread.Sleep(1000);
            RECT rect = new RECT();
            IntPtr hWnd = FindWindow(null, strWindowToPosition);
            SetForegroundWindow(hWnd);
            SetActiveWindow(hWnd);
            GetWindowRect(hWnd, out rect);
            int x = rect.Right - (btnShortcut.Width + 60);
            int y = rect.Bottom - 70;
            //lblInfo.Text = x.ToString();
            SetCursorPos(x, y);
            #region mask old position of cursor
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(100);
                SetCursorPos(x--, y--);
            }
            #endregion
        }

The timer is also used at another place, where I do not use messageForm to manage the TopMost attribute. It some of works, but the MessageBox appears behind the calling form. I found a solution by hiding the calling form until the user has validated the message.

I repeat the title of the MessageBox at the top of the message, as during several months my Windows did not show the titles of the messages, either in C# or in Javascript. Having it repeated looks strange but works, if it is not displayed at all you miss some information.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
6,514
Location
Chesapeake, VA
Programming Experience
10+
First, it is against the Windows design guidelines to move the users cursor without their consent. What if they are in the middle of doing something with the mouse, and about ready to click when you suddenly move the mouse and they end up clicking on your button instead?

Next, what is the point of lines 32-36? Just move the cursor to the final position. Why try to do a second long animation of moving the cursor over 10 pixels? You are again moving the users' cursor? What if over the course of that 1 second that user also moves the cursor?

As an over arching question, why are you trying to override the default Windows behaviors. Those defaults follow the Windows design guidelines. By changing the behavior, you are giving all the Windows critics who say that Windows has an inconsistent UI look and feel more ammunition. When those critics are pushed for evidence, it turns out it is actually 3rd party apps that they were complaining about. Most of the core Windows OS apps, and the big MS products follow the guidelines. It was the 3rd party apps that was ruining the party. Alas, MS needs those 3rd party apps to remain relevant. It's ironic that those same apps also had Mac versions and they follow the UI guidelines for the Mac OS. When they port to Windows, they should also have taken time to port behaviors to follow Windows UI guidelines.

To address your actual questions, I believe that the default message box Win32 API has a way to make it a system dialog box instead of a just an app dialog box and there by push it all the way to the top of the Windows Z-order. It may not be exposed directly in the .NET Framework because at the time the framework was first being built lots of people were abusing that feature. You may need to use interop to get at that feature.

As for your message box not following the position of your form created on line 6: At that point in time, the form still doesn't have a window handle. Recall the WinForms life cycle. The underlying MessageBox API that is called by the .NET framework's MessageBox requires a Windows handle to refer to when passing in a parent form.

As for placing the mouse over a button, I believe that there is a Windows OS feature that lets you enable this. I could be mistaken that this maybe a mouse driver specific feature. When I get to my PC, I'll look around.

As an aside, what are those magic numbers on lines 27, 28, 32 and 34?
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
Hello,

Well, the default Windows behavior seems to be to display the message box behind the form that called it, so the user stays there wondering what happens.
My aim is to propose an application that can run, and if the message box is behind the form it is not visible, so the application cannot run.

That is for the z-order. Then, you are right, I should use the default tools, and put a default button in the buttons parameter, and check the Windows parameters box to position the cursor on the default button. Maybe that way the previous cursor disappears.

My point is not to violate the Windows UI guidelines, but to force Windows to respect them, which it obviously does not. A dialog box that disappears behind the application is not a normal thing. Either it does not respect the guidelines, or the guidelines are badly written, from a user point of view.

Hum, I did not know there existed a system dialog box, but I wonder whether it is a good idea, in this case. Supposing the user receives a mail at that moment, he cannot read it and come back to the dialog box later.

The first plan for the application seems to be the good level.
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
As an aside, what are those magic numbers on lines 27, 28, 32 and 34?
This is the reason why I asked the question :
And also, to evaluate the correct position of the cursor, on WinForms you have Control.PointToScreen, but I am not sure that can be used on a MessageBox. I put values hardcoded, that is not sure to adapt if I change the sizes of the fonts in Windows.
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
As for your message box not following the position of your form created on line 6: At that point in time, the form still doesn't have a window handle. Recall the WinForms life cycle. The underlying MessageBox API that is called by the .NET framework's MessageBox requires a Windows handle to refer to when passing in a parent form.
Sorry you sent a rich message, not sure I answered everything yet.

messageForm is only used in line 10, it is never displayed, its only aim is to provide a z-order thanks to its TopMost property. And it appears that in fact the message box appeared on top of the application when doing that.

By curiosity I displayed it at a moment, it was empty and not at all at the place I should have foreseen. Well, compared to the code I found I added messageForm.Close(), but I am not sure it was useful, as I am not sure it is even opened.

I shall try to put "this" as the owner of the messagebox, but I should pretty guess it is the default value ?
 

JohnH

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
1,528
Location
Norway
Programming Experience
10+
Either set Owner property or use Show(owner) method.
Owned forms are also never displayed behind their owner form.

@Skydiver is correct there is Windows setting to automatically move cursor to the default button of a dialog, it's in mouse settings:
1657893966155.png

To enable the default button in a form assign Form.AcceptButton Property (System.Windows.Forms)
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
Either set Owner property or use Show(owner) method.
Oh, going to dig that ...
@Skydiver is correct there is Windows setting to automatically move cursor to the default button of a dialog, it's in mouse settings:
View attachment 2217
To enable the default button in a form assign Form.AcceptButton Property (System.Windows.Forms)

Yes I recognized that in message #3, second paragraph.
True that it would be cleaner as I guess Windows does not let two mouse cursors on screen when displaying a message box, so it would avoid moving the cursor ten more times.
And ... It is right that I emulated this option without verifying the user had checked it.
I used the default button parameter in MsgBox in Access twenty years ago, this is a good revision.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
6,514
Location
Chesapeake, VA
Programming Experience
10+
Well, the default Windows behavior seems to be to display the message box behind the form that called it
That's because the form never existed from the point of view of Windows. It never ever got created because nothing between lines 6-11 actually causes the low level window handle to be created.
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
No, lines 6 to 11 were an invention to force Windows to display the dialog box, instead of opening it behind the application.
And it worked.

But if MessageBox is called without any owner parameter, I should await that it appears on the screen, as there is no owner to allow it to do anyhow else. Alas, it is not the case, you have to reduce the application to see it.

I should say this has not always been the case ?
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
6,514
Location
Chesapeake, VA
Programming Experience
10+
Unfortunately, your invention failed to provide the crucial Window handle.

The native Win32 MessageBox()( has always taken an optional window owner parameter:
Good Win32 programmers have known to always pass a valid window handle to ensure that the message box shows up with the correct windows hierarchy so that minimizing and restoring windows, as well as activating the correct application window will work correctly.

Basically, if you want a MessageBox to show up on top of your application, pass in your main form as the owner. As noted make sure that the window handle for this main form has been created, and that the form is visible. It possible to pass in a main form that is not visible, but the behavior can be erratic. Also as noted in the Win32 documentation, if you have an active dialog box, you'll want to ensure that the owner of the message box is that dialog box.
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
Well, it is no problem if the owner does not exist in reality, as we do not need to display it.
Only the dialog box, is displayed.
In the front.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
6,514
Location
Chesapeake, VA
Programming Experience
10+
You are effectively passing in a NULL hwnd if the owner doesn't exist.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
6,514
Location
Chesapeake, VA
Programming Experience
10+
Try playing with the following code:
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace WinForms
{
    class MainForm : Form
    {
        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

        MainForm()
        {
            Text = "Main";
            Size = new Size(800, 600);
            CenterToScreen();
        }

        protected override void OnShown(EventArgs e)
        {
            base.OnShown(e);
            var timer = new Timer { Interval = 500 };
            timer.Tick += (o, te) => {
                ((Timer)o).Stop();

                IntPtr hWnd = IntPtr.Zero;
                hWnd = Handle;
                MessageBox(hWnd, "This is a messagebox", "Message box", 0);
            };
            timer.Start();
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}

Run it first and the try to activate the main form. Notice that you can't get back to that owner form until the message box is dismissed.

Next, comment out line 31 so that hWnd remains set to IntPtr.Zero (e.g. a null HWND). When you run it this time around, notice that you can active the main form and interact with it even if the message box is up.
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
Perhaps we have to find another explanation.
When it is the only line of code in a button, I obtain the same results if I put no owner, a null owner, or this. And the display is correct, with the display of the message box in the front.

The problem appears with my button that has a little more complex code, but I have difficulties to copy it to the forum, did you already experiment such a thing?

Are there any limitations to the quote utility?
 
Last edited:

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
6,514
Location
Chesapeake, VA
Programming Experience
10+
Are you sure all your forms are being created and shown on the UI thread? Are you sure that you trying to show the message box while running on the UI thread? Are you sure that all your windows are owned by your main form?

If you could post a minimally complete code that demonstrates the problem, it would be a better way to try to figure out what is happening.
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
Are you sure all your forms are being created and shown on the UI thread? Are you sure that you trying to show the message box while running on the UI thread? Are you sure that all your windows are owned by your main form?
No, I am just sure I cannot post my code, and I wonder why.
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
Let us try from Notepad2...
Code of a button:
        private void btnPlus_Click(object sender, EventArgs e)
        {
            bool bRepPresent = false;
            bool bFicPresent = false;
            FileInfo fi = new FileInfo(strFileName);
            string[] splContenu = { "" };
            int i = 0;
            StringBuilder sbContenu = new StringBuilder();
            btnShortcut_Click(btnShortcut, new EventArgs());
            string strPath = Clipboard.GetText();
            FileInfo fic;
            try
            {
                fic = new FileInfo(strPath);
            }
            catch(Exception ex)
            {
                return;
            }
            if (fic.Exists)
            {
                using (StreamReader sr = new StreamReader(strFileName))
                {
                    splContenu = File.ReadAllLines(strFileName, Encoding.GetEncoding("Utf-32"));
                    for (int i1 = 0; i1 < splContenu.Count(); i1++)
                    {
                        if (splContenu[i1].Replace("[", "").Replace("]", "").Equals(fic.DirectoryName))
                        {
                            bRepPresent = true;
                        }
                        if (fic.FullName.Equals(strPath))
                        {
                            bFicPresent = true;
                            Form messageForm = new Form();
                            messageForm.TopMost = true;
                            strTitreFenetreAPositionner = "Fichier déjà présent";
                            timer2.Enabled = true;
                            MessageBox.Show(messageForm, String.Format("{1} :\n{0}", fic.FullName, strTitreFenetreAPositionner), "Fichier déjà présent");
                            messageForm.Close();
                            break;
                        }
                    }
                }
                if (i > splContenu.Count())
                {
                    while (splContenu[i].StartsWith("["))
                    {
                        sbContenu.AppendLine(splContenu[i]);
                        i++;
                    }
                }
            }
            if(!fic.Exists)
            {
                MessageBox.Show(String.Format("{0} : fichier non trouvé", strPath));
            }
            else
            {
                if (!bRepPresent)
                {
                    sbContenu.AppendLine(String.Format("[{0}]", fic.DirectoryName));
                }
                for(int ln=i; ln<splContenu.Count();ln++)
                {
                    if (!String.IsNullOrEmpty(splContenu[ln]))
                    {
                        sbContenu.AppendLine(splContenu[ln]);
                    }
                }
                if (!bFicPresent)
                {
                    sbContenu.AppendLine(fic.Name);
                }
                //using (StreamWriter sr = new StreamWriter(strFileName, true, Encoding.GetEncoding("utf-32")))
                //{
                //    string input = sbContenu.ToString();
                //    System.Diagnostics.Debug.Print("«{0}»",input);
                //    byte[] utfBytes = Encoding.UTF32.GetBytes(input);
                //    StringBuilder builder = new StringBuilder();
                //    for (int ib = 0; ib < utfBytes.Length; ib+=4)
                //    {
                //        builder.Append(utfBytes[ib].ToString());
                //    }
                //    Console.WriteLine(builder.ToString());
                //}
                using (StreamWriter sr = new StreamWriter(strFileName, false, Encoding.UTF8))
                {
                    string input = sbContenu.ToString();
                    input = ConvertEncoding(Encoding.Default, Encoding.UTF8, input);
                    sr.WriteLine(input);
                }
            }
            Point pt = new Point(btnPlus.Left + (btnPlus.Width / 2), btnPlus.Top + (btnPlus.Height / 2));
            pt = btnPlus.PointToScreen(pt);
            int x = pt.X;
            int y = pt.Y - 40;
            SetCursorPos(x,y);
            #region     hide previous position of cursor
            for (int i1 = 0; i1 < 10; i1++)
            {
                System.Threading.Thread.Sleep(100);
                SetCursorPos(x++, y--);
            }
            #endregion
        }

Oh, this time it went through, by pack of 6 or 7 lines ...
I did not even try and understand what I was copying.
 

Gloops

Well-known member
Joined
Jun 30, 2022
Messages
137
Programming Experience
10+
So, that code displays the message boxes OK, but with a code that is not very clean.
As I remember, if there is only a MessageBox in the code of a button, it is displayed OK in the front, either it declares an owner for the MessageBox or not.
But at line 38 here above, if there is no owner declared, the message box is only displayed if I execute step by step.
It is so laborious that I was not really thinking while doing it.
 
Top Bottom