0

Brief

I am trying to create a on-screen keypad app in C#. This keypad should act like a on-screen keyboard to emulate keyboard input as the user clicks a button. For example, when user clicks the "A" button, it should type a in an input box (in another application).

As I only had this idea last night and started to develop it now, I have only made a really simple test ui:

enter image description here

It only has one button "A".

The Problem

One of the problems is to set the window non-activating on mouse click. Thanks to a comment in an earlier question I posted and this answer, I now have the following code:

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        var winH = (HwndSource) PresentationSource.FromVisual(this);
        // set mouse activate false
        winH.AddHook(WndProc);
    }

    #region WndProc

    private const int WM_MOUSEACTIVATE = 0x0021;
    private const int MA_NOACTIVATE = 0x0003;

    private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg != WM_MOUSEACTIVATE)
            return IntPtr.Zero;

        handled = true;
        return new IntPtr(MA_NOACTIVATE);
    }

    #endregion WndProc

However, the code does not work well. Mouse activating is only disabled when the background (empty space) of the MainWindow is clicked. If the Button is clicked, the window will get activated. Since a wpf control does not have HWND, I can't AddHook to it.

In addition, although clicking on the empty space of the MainWindow does not bring the application to the front, but seems activate the application nonetheless as it gets highlighted on the taskbar.

Appendix - Full code

MainWindow.xaml:

<Window x:Class="OnScreenKeyPad.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OnScreenKeyPad"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="200"
        WindowStyle = "None"
        AllowsTransparency="True"            >
    <StackPanel>
        <Button x:Name="KeyA" Content="A" Width="50" Margin="10"/>
    </StackPanel>
</Window>

MainWindow.xaml.cs:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace OnScreenKeyPad
{
    public partial class MainWindow
    {

        public MainWindow()
        {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            var winH = (HwndSource) PresentationSource.FromVisual(this);
            // set mouse activate false
            winH.AddHook(WndProc);
        }

        #region WndProc

        private const int WM_MOUSEACTIVATE = 0x0021;
        private const int MA_NOACTIVATE = 0x0003;

        private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg != WM_MOUSEACTIVATE)
                return IntPtr.Zero;

            handled = true;
            return new IntPtr(MA_NOACTIVATE);
        }

        #endregion WndProc
    }
}

Update 1

I have logged a list of WindowProc messages:

ID   Event

33   WM_MOUSEACTIVATE 
32   WM_SETCURSOR
513  WM_LBUTTONDOWN
NA   OnMouseDownPreviewEvent
70   WM_WINDOWPOSCHANGING
71   WM_WINDOWPOSCHANGED
28   WM_ACTIVATEAPP
134  WM_NCACTIVATE
6    WM_ACTIVATE 
641  WM_IME_SETCONTEXT 
642  WM_IME_NOTIFY
7    WM_SETFOCUS 
8    WM_KILLFOCUS 
641  WM_IME_SETCONTEXT 
641  WM_IME_SETCONTEXT 
7    WM_SETFOCUS 
641  WM_IME_SETCONTEXT 
641  WM_IME_SETCONTEXT 
132  WM_NCHITTEST 
512  WM_NCHITTEST 
132  WM_MOUSEFIRST
514  WM_LBUTTONUP 
533  WM_CAPTURECHANGED
132  WM_NCHITTEST 
NA   ButtonOnClickEvent

Now I probably understand why the code doesn't work. MOUSEACTIVATE is fired right after the moust button is clicked, which is before the actual mouse button event (ID 513). Although the MOUSEACTIVATE is handled and window activating prevented, the mouse button event will activate the window again with ID 28 WM_ACTIVATEAPP. I have checked the documentation of these events and none of them can be blocked.

Maybe I can use a label and make use of its PreviewMouseDown event.

Anthony
  • 3,595
  • 2
  • 29
  • 38
  • Just in case you didn't know, there is an onscreen keyboard built in to Windows, that will handle all of these problem and many you have yet to encounter (like accents, and non-European characters). https://www.windowscentral.com/auto-display-touch-keyboard-windows-10-desktop-mode – Neil Jun 05 '18 at 13:17
  • 1
    If your question specifically says "problem 1", "problem 2", you're inviting a "too broad" closure. One question per question please. – spender Jun 05 '18 at 13:18
  • @Neil Yes, similar but not the same. I do not want so many keys but only WASD, arrow keys, esc etc. – Anthony Jun 05 '18 at 13:26
  • @spender didn't realise it and now it's fine I suppose :) – Anthony Jun 05 '18 at 13:26
  • *"The first problem is to set the window non-activating on mouse click."* Why? How should your application respond to button clicks if the window is not activated? – Dan Wilson Jun 05 '18 at 13:28
  • @DanWilson I had the same thought as you do, then a commenter reminded me I can make use of [WM_MOUSEACTIVATE message](https://msdn.microsoft.com/en-us/library/windows/desktop/ms645612(v=vs.85).aspx) – Anthony Jun 05 '18 at 13:30
  • Yes, but I fail to underatand *why* you would want to do that if your goal is to populate a TextBox in your application. – Dan Wilson Jun 05 '18 at 13:32
  • @DanWilson the TextBox is only present while testing the application. The desired functionality of this application is NOT to type letters in the TextBox but an external application for example, Notepad, and this external application must remain activated. *This text has also been added to the question* – Anthony Jun 05 '18 at 13:38
  • I have made a similar application, not keyboard but a window that ignores clicks and doesn't activate. In WPF all elements share the same handle and it is there for the main window. Try using ```IntPtr hWnd = new WindowsInteropHelper(mainWindow).Handle;``` and see how far you get. If you don't I'll pull up my code and post what I did but it's been a while. – Michael Puckett II Jun 05 '18 at 13:45
  • @DanWilson I just realise the `TextBox` is now out of scope (there was another question related to it but it has been deleted otherwise too broad), so anything related to `TextBox` has been also deleted. – Anthony Jun 05 '18 at 13:47
  • @MichaelPuckettII I think I have probably made a false statement in the question, but `AddHook` only works with an `HwndSource`. I'm not sure how to make use of the `hWnd` :( – Anthony Jun 05 '18 at 15:01
  • Just keep in mind that ```Preview``` events in WPF, although fired before the actual event, and can cancel the other event with ```Handled = true;``` it's not denying the event from happening. It's main purpose is to help you work with routed events. Preview events happen at the parent before tunneling to the children and the opposite for the actual event. It happens before bubbling up to the parents and can be canceled also. In other words, whether you're capturing Preview or the actual click on the button you're not preventing anything. You're only preventing the bubbling or tunneling. – Michael Puckett II Jun 05 '18 at 16:28
  • @MichaelPuckettII Thanks for your comment, but I'm not sure if I have understood it entirely :-|. From my test, clicking on a label control does not activate the application, thus does not bring the app to the front. What I intend to do with the `PreviewMouseDown` is to capture the mouse click and send the keyboard input. I will try it first. – Anthony Jun 06 '18 at 09:32
  • @Anthony I guess all I'm saying is, for what you're doing, choosing the preview event isn't any different than choosing the actual event. It's easier to picture the WPF windows as one single control with one hwnd (which is essentially what it is). Preview events basically start at the window level and tunnel down to the lowest to control that should see it. The actual event starts at the actual lowest control and bubbles upwards to the parent. These can be canceled at any point but all you're canceling is the tunneling or bubbling. – Michael Puckett II Jun 06 '18 at 14:56

1 Answers1

0

You need to set the WS_EX_NOACTIVATE window style rather than trying to eat messages yourself:

private const int WS_EX_NOACTIVATE = 0x08000000;
private const int GWL_EXSTYLE = -20;

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowLong(IntPtr hwnd, int index);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

protected override void OnSourceInitialized(EventArgs e)
{
  var hWnd = new WindowInteropHelper(this).Handle;
  int style = GetWindowLong(hWnd, GWL_EXSTYLE);
  SetWindowLong(hWnd, GWL_EXSTYLE, style | WS_EX_NOACTIVATE);

  base.OnSourceInitialized(e);
}
GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103