C# 将窗口保持在顶部并在 WinForms 中窃取焦点

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/278237/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 21:15:44  来源:igfitidea点击:

Keep window on top and steal focus in WinForms

c#.netwinforms

提问by Adam Haile

I realize that this would be COMPLETELY bad practice in normal situations, but this is just for a test app that needs to be taking input from a bar code scanner (emulating a keyboard). The problem is that I need to start up some scripts while scanning, so I need the window to regain focus directly after I click the script to run it. I've tried using Activate(), BringToFront(), Focus() as well as some Win32 calls like SetForegroundWindow(), Setcapture() and SetActiveWindow()... however the best I can get any of them to do is to make the taskbar item start blinking to tell me that it wantsto have focus, but something is stopping it. BTW, I'm running this on XP SP2 and using .NET 2.0.

我意识到这在正常情况下是完全不好的做法,但这仅适用于需要从条形码扫描仪(模拟键盘)获取输入的测试应用程序。问题是我在扫描的时候需要启动一些脚本,所以我需要窗口在我点击脚本运行后直接重新获得焦点。我尝试过使用 Activate()、BringToFront()、Focus() 以及一些 Win32 调用,如 SetForegroundWindow()、Setcapture() 和 SetActiveWindow()……但是我可以让它们中的任何一个做的最好的是让任务栏项目开始闪烁,告诉我它想要获得焦点,但有些东西阻止了它。顺便说一句,我在 XP SP2 上运行它并使用 .NET 2.0。

Is this possible?

这可能吗?

Edit: To clarify, I am running the scripts by double-clicking on them in explorer. So I need it to steal focus back from explorer and to the test app.

编辑:为了澄清,我通过在资源管理器中双击脚本来运行脚本。所以我需要它从资源管理器和测试应用程序中窃取焦点。

采纳答案by Ray Hayes

Visibility

能见度

Make the window a "Top-Most" window. This is the way the Task-Manager can remain on top of other windows. This is a property of a Formand you make the form top-most (floating above other windows) by setting the value to true.

使窗口成为“最顶层”窗口。这是任务管理器可以保持在其他窗口之上的方式。这是 a 的一个属性,Form您可以通过将值设置为 来使表单位于最顶部(浮动在其他窗口上方)true

You shouldn't need to override any of the "Active window" behaviour with the top-most setting.

您不需要使用最顶层的设置覆盖任何“活动窗口”行为。

Focus

重点

I asked a similar question previously here on StackOverflowand the answer would solve your problem. You can make the application use a low-level input hook and get notification of the key-codes coming from the scanner. This way, your application always gets these keys even though the application does not have focus.

之前在 StackOverflow 上问过一个类似的问题,答案将解决您的问题。您可以让应用程序使用低级输入钩子,并从扫描仪获取键码通知。这样,即使应用程序没有焦点,您的应用程序也始终可以获得这些键。

You may need to enhance the solution to squash the key-codes so that they are not transmitted to the "in-focus" application (e.g. notepad).

您可能需要增强解决方案以压缩密钥代码,以便它们不会传输到“焦点”应用程序(例如记事本)。

Since Windows 2000, there is no official mechanism for an application to grab focus without direct intervention of the user. Peeking at the input streams through the RawInputDevices hook is the only sensible way to go.

从 Windows 2000 开始,应用程序没有官方机制可以在没有用户直接干预的情况下获取焦点。通过 RawInputDevices 挂钩查看输入流是唯一明智的方法。

A number of articles may help (C# implementations)

许多文章可能会有所帮助(C# 实现)

回答by Joel Coehoorn

You might try focusing on a specific input, or try the setting .TopMost property to true (and then unsetting it again).

您可以尝试关注特定输入,或尝试将 .TopMost 属性设置为 true(然后再次取消设置)。

But I suspect your problem is that these methods all just place messages in the windows event queue, and your program has to wait for all existing events to finish processing before it will handle that one and focus the app.

但我怀疑您的问题是这些方法都只是将消息放入 windows 事件队列中,并且您的程序必须等待所有现有事件完成处理,然后才能处理该事件并聚焦应用程序。

回答by Ken

I had a similar problem and found the following to do the trick. Adapted to C# from here

我遇到了类似的问题,并发现以下方法可以解决问题。从这里适应 C#

        // force window to have focus
        uint foreThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
        uint appThread = GetCurrentThreadId();
        const uint SW_SHOW = 5;
        if (foreThread != appThread)
        {
            AttachThreadInput(foreThread, appThread, true);
            BringWindowToTop(form.Handle);
            ShowWindow(form.Handle, SW_SHOW);
            AttachThreadInput(foreThread, appThread, false);
        }
        else
        {
            BringWindowToTop(form.Handle);
            ShowWindow(form.Handle, SW_SHOW);
        }
        form.Activate();

EDIT:Here are the necessary PInvoke definitions for C#:

编辑:以下是 C# 必要的 PInvoke 定义:

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

// When you don't want the ProcessId, use this overload and pass IntPtr.Zero for the second parameter
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();

/// <summary>The GetForegroundWindow function returns a handle to the foreground window.</summary>
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

[DllImport("user32.dll", SetLastError = true)]
static extern bool BringWindowToTop(IntPtr hWnd);

[DllImport("user32.dll", SetLastError = true)]
static extern bool BringWindowToTop(HandleRef hWnd);

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, uint nCmdShow);

回答by TallGuy

I struggled with a similar problem for quite a while. After much experimentation and guessing, this is how I solved it:

我在类似的问题上挣扎了很长一段时间。经过大量的实验和猜测,这就是我解决它的方法:

// Get the window to the front.
this.TopMost = true;
this.TopMost = false;

// 'Steal' the focus.
this.Activate();

回答by mxgg250

The way I approached this problem was to spawn another thread whose only purpose was to ensure the Form is TopMost and has focus at all times. This code will make all other applications unusable while it is running, which is what I needed for my specific applications. You can add in a Sleep in keepFocus or have some other event trigger it.

我解决这个问题的方法是产生另一个线程,其唯一目的是确保 Form 是 TopMost 并且始终具有焦点。此代码将使所有其他应用程序在运行时无法使用,这正是我的特定应用程序所需要的。您可以在 keepFocus 中添加 Sleep 或让其他事件触发它。

using System.Threading;          // be sure to include the System.Threading namespace

    //Delegates for safe multi-threading.
    delegate void DelegateGetFocus();
    private DelegateGetFocus m_getFocus;

    //Constructor.
    myForm()
    {
        m_getFocus = new DelegateGetFocus(this.getFocus);   // initialise getFocus
        InitializeComponent();
        spawnThread(keepFocus);                             // call spawnThread method
    }

    //Spawns a new Thread.
    private void spawnThread(ThreadStart ts)
    {
        try
        {
            Thread newThread = new Thread(ts);
            newThread.IsBackground = true;
            newThread.Start();
        }
        catch(Exception e)
        {
            MessageBox.Show(e.Message, "Exception!", MessageBoxButtons.OK, 
                MessageBoxIcon.Error);
        }
    }

    //Continuously call getFocus.
    private void keepFocus()
    {
        while(true)
        {
            getFocus();
        }
    }

    //Keeps Form on top and gives focus.
    private void getFocus()
    {
        //If we need to invoke this call from another thread.
        if (this.InvokeRequired)
        {
            try
            {
                this.Invoke(m_getFocus, new object[] { });
            }
            catch (System.ObjectDisposedException e)
            {
                // Window was destroyed. No problem but terminate application.
                Application.Exit();
            }
        }
        //Otherwise, we're safe.
        else
        {
            this.TopMost = true;
            this.Activate();
        }
    }       
}