1

I want to program an on-screen keyboard.

To send keys to another application, I need to store the current foreground window (=the window with the focus) when my app is about to get the focus, and when the user presses a button on my form to send the key, I will SetForegroundWindow to this previous window handle.

This way the text will be sent to the previously focussed window.

However, I don't find the correct event for that.

Form_Activated is too late.

Using a timer to constantly check for the current foreground window seems a bit rude.

Is there an "official" event that occurs before my app gets the focus?

tmighty
  • 10,734
  • 21
  • 104
  • 218
  • You need a Window that is not activated (so it doesn't become the ForegroundWindow) when one of its child Controls is interacted with and these Controls also don't get the focus when selected. E.g.,: [How to keep a Form always on top without stealing focus from the active Window?](https://stackoverflow.com/a/65439642/7444103) and [Use a virtual Keyboard on focused Textboxes and DataGridView Cells](https://stackoverflow.com/a/64228742/7444103) – Jimi Feb 20 '21 at 12:05
  • Can also be combine with, e.g., [Move window when external application's window moves](https://stackoverflow.com/a/48812831/7444103) – Jimi Feb 20 '21 at 12:10
  • @Jimi Thank you, but that is too complicated. I know that the "official" on-screen keyboard in Windows XP / 7 worked the "focus" way, not the GWL_EXSTYLE way. I would like to re-create that. – tmighty Feb 20 '21 at 12:14
  • Why would you want to try to do it the bad way? I suggest that you check [this](https://www.vbforums.com/showthread.php?459890) out. It provides some ready-made classes that you can simply add to your project and then use like any other controls and you can inherit your form from the `UnselectableForm` class instead of the standard `Form` class. I used them myself to create an onscreen keyboard and it worked exactly as intended. It would be a good idea to do so but there's no requirement for you to understand any of the code. – jmcilhinney Feb 20 '21 at 12:17
  • *"Is there an "official" event"*. No there isn't. The XP/Win7 OSK was not a WinForms app. – jmcilhinney Feb 20 '21 at 12:18
  • This is what an OSK needs to do: it doesn't steal the Focus from other applications, so it doesn't cause `Activated` events, which may cause the Focus on a Control to switch to another or other unwanted effects. Constantly setting and re-setting the ForegroundWindow is not what you want. -- I don't see how that's *complicated*, it's just a few lines of code to enable the base functionality. – Jimi Feb 20 '21 at 12:21
  • @Jimi I develop such an on-screen keyboard since 15 years, and I have spent really many months with all the pitfalls. I can assure you that it IS complicated and comes with really many downsides that you need to handle. I wanted to send a friend an easy example instead, that is why I asked about this event. – tmighty Feb 20 '21 at 12:49
  • I'm not sure I follow. The hard part of an OSK is handling different languages with a single application that needs to work in different Systems, to redirect (or generate, depending on the implementation) the input to applications that may monitor these messages and react in different manners (or don't *react* at all). The Focus part is the trivial one. An OSK tool simply doesn't take focus. – Jimi Feb 20 '21 at 13:06

1 Answers1

1

Impoerant:, I agree with Jimi's and jmcilhinney's comments and I also believe it's not the right way of implementing an on-screen keyboard, but this post it just trying to help you on:

  • Finding handle of the deactivated window when this window has been activated.

Find handle of the deactivated window when this window has been activated

You can use SetWinEventHook you can to listen to some events from other processes and register a WinEventProc callback method to receive the event when the event raised.

Here we are interested in EVENT_SYSTEM_FOREGROUND. Every time that we receive this event, if the activated window is not our form, we track the window which has been activated, then when our window is activated we look into the value of tracked window which is now the previous window which has lost its focus.

C#

Here is the code that I tried and worked well for me:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyForm : Form
{
    public const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
    public const uint EVENT_OBJECT_DESTROY = 0x8001;
    public const uint WINEVENT_OUTOFCONTEXT = 0;

    public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, 
        IntPtr hwnd, int idObject, int idChild, 
        uint dwEventThread, uint dwmsEventTime);
    
    [DllImport("user32.dll")]
    public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, 
        IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, 
        uint idProcess, uint idThread, uint dwFlags);
    
    [DllImport("user32.dll")]
    public static extern bool UnhookWinEvent(IntPtr hWinEventHook);

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    IntPtr hook = IntPtr.Zero;
    protected override void OnLoad(EventArgs e)
    {
        previous = GetForegroundWindow();
        hook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, 
        EVENT_SYSTEM_FOREGROUND,
        IntPtr.Zero, new WinEventDelegate(WinEventProc),
        0, 0, WINEVENT_OUTOFCONTEXT);
        base.OnLoad(e);
    }
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        UnhookWinEvent(hook);
        base.OnFormClosing(e);
    }
    IntPtr? previous = null;
    void WinEventProc(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread,
        uint dwmsEventTime)
    {
        if (hwnd != this.Handle)
        {
            previous = hwnd;
        }
        else
        {
            if (previous.HasValue)
                this.Text = $"Previous window: {(int)previous:X}";
            else
                this.Text = $"No idea about previous window.";
        }
    }
}

VB.NET

Imports System.Runtime.InteropServices

Public Class MyForm
    Inherits Form

    Public Const EVENT_SYSTEM_FOREGROUND As UInteger = &H3
    Public Const EVENT_OBJECT_DESTROY As UInteger = &H8001
    Public Const WINEVENT_OUTOFCONTEXT As UInteger = 0
    Public Delegate Sub WinEventDelegate(ByVal hWinEventHook As IntPtr,
                                         ByVal eventType As UInteger,
                                         ByVal hwnd As IntPtr,
                                         ByVal idObject As Integer,
                                         ByVal idChild As Integer,
                                         ByVal dwEventThread As UInteger,
                                         ByVal dwmsEventTime As UInteger)
    <DllImport("user32.dll")>
    Public Shared Function SetWinEventHook(ByVal eventMin As UInteger,
                                           ByVal eventMax As UInteger,
                                           ByVal hmodWinEventProc As IntPtr,
                                           ByVal lpfnWinEventProc As WinEventDelegate,
                                           ByVal idProcess As UInteger,
                                           ByVal idThread As UInteger,
                                           ByVal dwFlags As UInteger) As IntPtr
    End Function
    <DllImport("user32.dll")>
    Public Shared Function UnhookWinEvent(ByVal hWinEventHook As IntPtr) As Boolean
    End Function
    <DllImport("user32.dll")>
    Private Shared Function GetForegroundWindow() As IntPtr
    End Function
    Private hook As IntPtr = IntPtr.Zero
    Protected Overrides Sub OnLoad(ByVal e As EventArgs)
        previous = GetForegroundWindow()
        hook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND,
                               EVENT_SYSTEM_FOREGROUND, IntPtr.Zero,
                               New WinEventDelegate(AddressOf WinEventProc),
                               0, 0, WINEVENT_OUTOFCONTEXT)
        MyBase.OnLoad(e)
    End Sub

    Protected Overrides Sub OnFormClosing(ByVal e As FormClosingEventArgs)
        UnhookWinEvent(hook)
        MyBase.OnFormClosing(e)
    End Sub

    Private previous As IntPtr? = Nothing

    Private Sub WinEventProc(ByVal hWinEventHook As IntPtr,
                             ByVal eventType As UInteger,
                             ByVal hwnd As IntPtr,
                             ByVal idObject As Integer,
                             ByVal idChild As Integer,
                             ByVal dwEventThread As UInteger,
                             ByVal dwmsEventTime As UInteger)
        If hwnd <> Me.Handle Then
            previous = hwnd
        Else

            If previous.HasValue Then
                Me.Text = $"Previous window: {CInt(previous):X}"
            Else
                Me.Text = $"No idea about previous window."
            End If
        End If
    End Sub
End Class
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398