当在连续运行在不同线程中并由用户代码关闭的表单上使用时,TopMost不起作用

use*_*375 0 .net c# winforms

我有以下示例代码

   [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();
    }
Run Code Online (Sandbox Code Playgroud)

其中Form1定义为:

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
}
Run Code Online (Sandbox Code Playgroud)

这段代码很简单,我的生产代码提取,所以请不要告诉我它没用.

如果我将所有Application.Runs放到一个线程中,一切都会正常工作,所有三个表单都是TopMost.如果我运行代码,因为它是第一个窗体显示为TopMost,第二个窗体显示为TopMost.

但是:如果我通过显示表单上的红色X关闭按钮关闭表单(因此未调用计时器的tick事件处理程序中的Close方法),则下一个表单将显示为TopMost.在我看来,表单上的X Close按钮和代码中调用的Close方法之间必定存在一些区别.但我无法弄清楚有什么区别以及如何达到想要的行为:通过计时器关闭,所有窗口最顶层.

感谢帮助

Han*_*ant 5

    TopMost = true;
Run Code Online (Sandbox Code Playgroud)

这并不像你认为的那样.Windows有一个"温和"和"原始"版本的窗口最顶层.Winforms实现了它的温和版本,最不可能打乱用户.一般来说,当程序将窗口推入用户的脸部时,会感到非常沮丧.

Windows有这种防范措施,确保程序不能做到这一点,除非它是可能的,用户不会被它打乱.它是一种启发式方法,基于进程拥有的窗口何时获得用户输入.就像你点击关闭按钮一样.用户正在使用窗口的信号.它不仅可以点击关闭按钮,例如,您可以单击窗口或按光标键,您将看到您的程序设法保持前景爱.

程序中的主要问题是第一个窗口关闭时会发生什么.您的流程没有可以获得焦点的窗口.因此Windows窗口管理器被迫找到另一个窗口管理器,这将是一个完全不同的程序所拥有的窗口.像Visual Studio一样.现在启发式操作开始起作用,如果Windows不相信你应该得到它,你就无法获得前景.在另一个线程中显示另一个窗口并不足以说服它.

好吧,你做错了,从你创建的条件开始,你的程序暂时没有窗口.你可以调用TopMost的原始版本,这很容易做到:

protected override CreateParams CreateParams {
    get {
        var cp = base.CreateParams;
        cp.ExStyle |= 8;  // Turn on WS_EX_TOPMOST
        return cp;
    }
}
Run Code Online (Sandbox Code Playgroud)

而且你会发现你的窗口现在真的是最顶级的.原始类型,由完全依赖于最顶层窗口的程序使用,如osk.exe(屏幕键盘程序).由于显而易见的原因,这种方式也会让用户感到不安.

我确实需要发布一个关于你的方法的强烈警告,在不同的线程上显示UI在其他方面非常麻烦.非常糟糕的一种,你的程序随机挂起,没有明显的原因.SystemEvents类是一个主要的麻烦制造者,它试图在UI线程上触发事件,但当程序有多个时,当然不能可靠地执行此操作.在许多Winforms程序中,这往往会很糟糕,工具箱中有一堆控件在错误的线程上触发时无法处理UserPreferenceChanged事件.诊断挂起所需的调试会话类型如下所示.那是为了吓唬你,不要这样做.