user2126375
user2126375

Reputation: 1624

TopMost does not work when used on Forms running in consecutively in different threads and closed by user code

I have following sample code

   [STAThread]
    static void Main(string[] args)
    {
        Thread thread = new Thread(() =>
        {
            using (var mww = new Form1())
            {
                Application.Run(mww);
            }
        });
        thread.Start();
        thread.Join();

        thread = new Thread(() =>
        {
            using (var mww = new Form1())
            {
                Application.Run(mww);
            }
        });
        thread.Start();
        thread.Join();

        thread = new Thread(() =>
        {
            using (var mww = new Form1())
            {
                Application.Run(mww);
            }
        });
        thread.Start();
        thread.Join();
    }

Which shows Form1 defined as:

public partial class Form1 : Form
{
    private readonly Timer _myTimer = new Timer();

    public Form1()
    {
        InitializeComponent();
        _myTimer.Interval = 10000;
        _myTimer.Tick += TOnTick;
        TopMost = true;
    }

    private void TOnTick(object sender, EventArgs eventArgs)
    {
        _myTimer.Stop();
        Close();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        _myTimer.Start();
    }
}

partial class Form1
{
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (_myTimer != null)
        {
            _myTimer.Dispose();
            _myTimer = null;
        }
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.SuspendLayout();
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(284, 262);
        this.Name = "Form1";
        this.Text = "Form1";
        this.Load += new System.EventHandler(this.Form1_Load);
        this.ResumeLayout(false);

    }

    #endregion
}

This code is very simple extraction of my production code, so please do not tell me it is useless.

If I would put all Application.Runs to one thread everything would be working and all three forms would be TopMost. If I run code as it is the first form is shown as TopMost and second and third does not.

BUT: If I close form by red X Close button on shown form (thus Close method in timer's tick eventhandler is not called), next form will be shown as TopMost. It seems to me that there must be some difference between X Close button on form and Close method called in code. But I cannot figure out what is the difference and how to reach wanted behavior: closing by timer, all windows top most.

Thanks for help

Upvotes: 0

Views: 2433

Answers (3)

Hans Passant
Hans Passant

Reputation: 942518

    TopMost = true;

That doesn't quite do what you think it does. Windows has a "mild" and "crude" version of making a window top-most. Winforms implements the mild version of it, the one least likely to upset a user. Whom, in general, do get quite upset when a program shoves a window into the user's face.

Windows has counter-measures for this, ensuring that programs cannot do this unless it is likely that a user won't be upset by it. It is a heuristic, based on when a window owned by a process got user input. Like you clicking the Close button. A signal that the user is actively using the window. It doesn't just take clicking the Close button, you can for example click the window or press a cursor key and you'll see that your program manages to keep the foreground love.

The principal problem in your program is what happens when the first window closes. Your process has no windows left that can get the focus. So the Windows window manager is forced to find another one, that is going to be a window owned by an entirely different program. Like Visual Studio. Now the heuristic starts to act up, you can't gain the foreground back if Windows isn't convinced that you deserve it. Displaying another window in a different thread is not good enough to convince it.

Well, you are doing it wrong, starting with the condition you created where your program temporarily has no window. You can invoke the crude version of TopMost, that's pretty easy to do:

protected override CreateParams CreateParams {
    get {
        var cp = base.CreateParams;
        cp.ExStyle |= 8;  // Turn on WS_EX_TOPMOST
        return cp;
    }
}

And you'll see that your window now is truly top-most. The crude kind, used by programs that completely depend on having a topmost window, like osk.exe (the On Screen Keyboard program). The kind that tends to get users upset as well, for obvious reasons.

I do need to post a strong warning about your approach, displaying UI on different threads is very troublesome in other ways. The really bad kind, where your program randomly hangs for no discernible reason. The SystemEvents class is a major trouble-maker, it tries to fire events on the UI thread but of course cannot do this reliably when your program has more than one. Which tends to end quite poorly in many Winforms programs, there are a bunch of controls in the toolbox that cannot deal with the UserPreferenceChanged event when it is fired on the wrong thread. The kind of debug session you need to diagnose the hang looks like this. That was meant to scare you, don't do it.

Upvotes: 6

DatRid
DatRid

Reputation: 1169

Every form has a OnClosed & OnClosing event. What you do is explicit closing the form.

If you want to to stop the Timer when you click on the red X you need to use OnClosed or OnClosing.

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
    _myTimer.Stop();
    _myTimer.Dispose();
}

private void TOnTick(object sender, EventArgs eventArgs)
{
    //_myTimer.Stop();
    //_myTimer.Dispose();  No need to call, as it will be redundant
    Close();   // Will call FormClosed
}

What you actually do now is to stop the timer on every tick of the timer, dispose it and then close the form.


EDIT:

I actually can reproduce your problem now if I try to move some other window over the form1, then the next one is not topmost.

I made some research then, and Raymond Chen explained perfectly why it doesn't work.

The question "What if two programs did this?" is also helpful in evaluating a feature or a design request. Combining this with "Imagine if this were possible" leads to an impressive one-two punch. Here are a few examples:

"How do I create a window that is never covered by any other windows, not even other topmost windows?"

Imagine if this were possible and imagine if two programs did this. Program A creates a window that is "super-topmost" and so does Program B. Now the user drags the two windows so that they overlap. What happens? You've created yourself a logical impossibility. One of those two windows must be above the other, contradicting the imaginary "super-topmost" feature.

I found this at this answer, which says:

Form.TopMost will work unless the other program is creating topmost windows.

There is no way to create a window that is not covered by new topmost windows of another process.

Warning: The text beneath is pure speculation, but it seems to make the most sense from what I experienced now.

So it seems like the first form is TopMost first, then you move another Window, which get's queued as "you-are-the-next-TopMost". Then you create the next form, which is also TopMost, but it is in the "you-are-TopMost"-queue behind the window you selected between those two.

Upvotes: 1

Dibran
Dibran

Reputation: 1555

Closing a form programmatically by calling close is somewhat different from closing it using the red cross. Using the red cross with a mouse click will result in additional windows messages which will activate the "next" window of your process. Your application is still active and it will therefore reactivate the next form. Have a look at the windows messages, you can use the pinvokes. For more information about these messages please visit pinvoke.net.

protected override void WndProc(ref Message m)
{
    Console.WriteLine(m.Msg);
    base.WndProc(ref m);
}

To solve your problem you might want to call activate when the form is shown. This will make the new form active and therefore TopMost. Keep in mind that calling active also claims the focus. You should use the ShowWindow pinvoke if you want to activate the form without hijacking the focus.

Upvotes: 0

Related Questions