Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gathering keyboard input doesn't work (winui 3) #7330

Open
1 of 2 tasks
jtorjo opened this issue Jul 6, 2022 · 34 comments
Open
1 of 2 tasks

Gathering keyboard input doesn't work (winui 3) #7330

jtorjo opened this issue Jul 6, 2022 · 34 comments
Labels
feature proposal New feature proposal product-winui3 WinUI 3 issues team-Rendering Issue for the Rendering team

Comments

@jtorjo
Copy link

jtorjo commented Jul 6, 2022

Describe the bug

I simply can't believe that this is an issue in 2022:

There's simply no reliable way to capture keyboard in winui -- I have no idea what to do.

I won't reiterate what Gavin Williams already said in #3986 -- we don't have a CoreWindow in WinUI 3, and I don't see any way to reliably capture keys.

Steps to reproduce the bug

I have a DataGrid and I want to capture keyboard input for it.
Obviously, since I don't have access to CoreWindow, I tried to capture its KeyDown or PreviewKeyDown
Of course it doesn't work.

Then I tried to capture the parent's Keydown:

var grid = Parent as Grid;
Debug.Assert(grid != null);

grid.IsTabStop = true;
grid.IsHitTestVisible = true;
grid.Background = new SolidColorBrush(Colors.Transparent);
grid.PreviewKeyDown += DecentDataGrid_KeyDown;

This doesn't work either. There's simply no way to capture this. How am I supposed to create a half decent app when I can't even handle an INS hotkey?

LATER EDIT: Turns out the issue with the datagrid not handling KeyDown happens when it's placed on a pivot.

Expected behavior

  1. There should be a way to globally capture keyboard, like in UWP
  2. When capturing KeyDown, this should work reliably (see below, when Datagrid is on a Pivot)

Screenshots

No response

NuGet package version

WinUI 3 - Windows App SDK 1.0.4

Windows app type

  • UWP
  • Win32

Device form factor

Desktop

Windows version

Windows 10 (21H1): Build 19043

Additional context

No response

@jtorjo jtorjo added the bug Something isn't working label Jul 6, 2022
@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Jul 6, 2022
@castorix
Copy link

castorix commented Jul 6, 2022

KeyDown works for me, with CommunityToolkit.WinUI.UI.Controls.DataGrid
(Windows 10 21H1, test with Windows App SDK 1.1.0, C#)

@jtorjo
Copy link
Author

jtorjo commented Jul 7, 2022

@castorix Thanks for that! I just tested on a dummy app, and indeed, that works.

I dug a bit deeper. The problem happens when the datagrid is part of a Pivot control (as is in my case).

Here's the simplest example I came up with:
DatagridBadKeydown.zip

Long story short:

<Grid>
    <Pivot Grid.Row="1" x:Name="main_grid" KeyDown="pivot_key_down">
        <PivotItem Header="Tab one" KeyDown="pivot_item1_key_down">
            <controls:DataGrid x:Name="data_grid" Background="Transparent" PreviewKeyDown="Data_grid_OnKeyDown"/>
        </PivotItem>
        <PivotItem Header="Tab two" KeyDown="pivot_item2_key_down">
            <controls:DataGrid x:Name="data_grid2" Background="Transparent" PreviewKeyDown="Data2_grid_OnKeyDown"/>
        </PivotItem>
    </Pivot>
</Grid>
  1. By default, when i start the app, only the pivot receives keydown.
  2. If I manually select an item from the datagrid, then the datagrid will receive keydown as well -- this is NOT what a user would expect.
  3. If I go from tab one to tab two (by clicking tab two), once again, only the pivot will receive key down.
  4. If I then move focus to the datagrid, only then will the second datagrid receive key down (once again, the default behavior should be that the hovered control should receive keyboard)
  5. Even worse, if I switch to first tab (where the first grid HAD focus), now it's lost it, and only the pivot will receive key down. Same, if I switch to the second tab (where the second grid HAD focus), now it's lost it and only the pivot will receive key down.

The current behavior is beyond horrible.

@castorix
Copy link

castorix commented Jul 7, 2022

There should be a way to globally capture keyboard, like in UWP

A classic way in Win32 is with Hooks
For example, in this test, a Beep is generated on [Ctrl + P] , which should work with the focus in any control :

       private static int m_hHook = 0;
       private HookProc m_HookProcedure;
       
       public MainCtrl()
       {
           this.InitializeComponent();

           m_HookProcedure = new HookProc(HookProcedure);
           m_hHook = SetWindowsHookEx(WH_KEYBOARD, m_HookProcedure, (IntPtr)0, (int)GetCurrentThreadId());
       }

       private int HookProcedure(int nCode, IntPtr wParam, IntPtr lParam)
       {
           if ((nCode >= 0))
           {
               bool bCtrlDown = (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down));
               var nUp = (HIWORD(lParam) & KF_UP);
               if (wParam == (IntPtr)(int)Windows.System.VirtualKey.P && nUp == 0)
               {
                   if (bCtrlDown)
                       Console.Beep(1000, 10);
               }
           }
           return nCode < 0 ? CallNextHookEx(m_hHook, nCode, wParam, lParam) : 0;
       }

with declarations :

        public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern uint GetCurrentThreadId();
        
        public const int WH_MIN = (-1);
        public const int WH_MSGFILTER = (-1);
        public const int WH_JOURNALRECORD = 0;
        public const int WH_JOURNALPLAYBACK = 1;
        public const int WH_KEYBOARD = 2;
        public const int WH_GETMESSAGE = 3;
        public const int WH_CALLWNDPROC = 4;
        public const int WH_CBT = 5;
        public const int WH_SYSMSGFILTER = 6;
        public const int WH_MOUSE = 7;
        public const int WH_HARDWARE = 8;
        public const int WH_DEBUG = 9;
        public const int WH_SHELL = 10;
        public const int WH_FOREGROUNDIDLE = 11;
        public const int WH_CALLWNDPROCRET = 12;
        public const int WH_KEYBOARD_LL = 13;
        public const int WH_MOUSE_LL = 14;
        public const int WH_MAX = 14;
        public const int WH_MINHOOK = WH_MIN;
        public const int WH_MAXHOOK = WH_MAX;

        public const int KF_EXTENDED = 0x0100;
        public const int KF_DLGMODE = 0x0800;
        public const int KF_MENUMODE = 0x1000;
        public const int KF_ALTDOWN = 0x2000;
        public const int KF_REPEAT = 0x4000;
        public const int KF_UP = 0x8000;

        internal static int HIWORD(IntPtr wParam)
        {
            return (int)((wParam.ToInt64() >> 16) & 0xffff);
        }

        internal static int LOWORD(IntPtr wParam)
        {
            return (int)(wParam.ToInt64() & 0xffff);
        }

@MYPOSConnect
Copy link

This is a big issue for us, we can't listen for key presses on specific controls because keyboard input can come at any time (regardless of what the app is doing) from keyboard wedge barcode scanners.

I then notify the relevant areas of the app using MessagingCenter that a barcode has been scanned. Have I really got to dig into Win32 code?

@jtorjo
Copy link
Author

jtorjo commented Jul 7, 2022

A classic way in Win32 is with Hooks
For example, in this test, a Beep is generated on [Ctrl + P] , which should work with the focus in any control :

This is pretty hard core. And I still 99% think it won't work for me.
The reason is I only want to process the INS hotkey is on a specific scenario (like, when user is not editing my datagrid). In the other cases, the INS should be processed by the default event processing.

Anyway, I will give it a try, thanks!

@castorix
Copy link

castorix commented Jul 7, 2022

... when user is not editing my datagrid). In the other cases, the INS should be processed by the default event processing.

You could add a test with Microsoft.UI.Xaml.Input.FocusManager.GetFocusedElement
(with XamlRoot)

@MYPOSConnect
Copy link

MYPOSConnect commented Jul 7, 2022

I got it going, thanks to Castorix and some digging around on Google. It needs some tidying up after being hacked together but seems to work ok.

  1. Put this class WinAPI.cs in Platforms/Windows folder
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace YourApp { 

    public class WinAPI
    {
        public delegate void HookProc(int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern uint GetCurrentThreadId();

        [DllImport("user32.dll")]
        public static extern int MapVirtualKey(uint uCode, uint uMapType);

     [DllImport("user32.dll")]
        public static extern int ToUnicode(uint virtualKeyCode, uint scanCode,
    byte[] keyboardState,
    [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
    StringBuilder receivingBuffer,
    int bufferSize, uint flags);

        public const int WH_MIN = (-1);
        public const int WH_MSGFILTER = (-1);
        public const int WH_JOURNALRECORD = 0;
        public const int WH_JOURNALPLAYBACK = 1;
        public const int WH_KEYBOARD = 2;
        public const int WH_GETMESSAGE = 3;
        public const int WH_CALLWNDPROC = 4;
        public const int WH_CBT = 5;
        public const int WH_SYSMSGFILTER = 6;
        public const int WH_MOUSE = 7;
        public const int WH_HARDWARE = 8;
        public const int WH_DEBUG = 9;
        public const int WH_SHELL = 10;
        public const int WH_FOREGROUNDIDLE = 11;
        public const int WH_CALLWNDPROCRET = 12;
        public const int WH_KEYBOARD_LL = 13;
        public const int WH_MOUSE_LL = 14;
        public const int WH_MAX = 14;
        public const int WH_MINHOOK = WH_MIN;
        public const int WH_MAXHOOK = WH_MAX;

        public const int KF_EXTENDED = 0x0100;
        public const int KF_DLGMODE = 0x0800;
        public const int KF_MENUMODE = 0x1000;
        public const int KF_ALTDOWN = 0x2000;
        public const int KF_REPEAT = 0x4000;
        public const int KF_UP = 0x8000;

        internal static int HIWORD(IntPtr wParam)
        {
            return (int)((wParam.ToInt64() >> 16) & 0xffff);
        }

        internal static int LOWORD(IntPtr wParam)
        {
            return (int)(wParam.ToInt64() & 0xffff);
        }

    }
}

  1. Add this using to Platform/Windows/App.xaml.cs:
using static YourApp.WinAPI;
  1. Add these as properties in Platforms/Windows/App.xaml.cs
 private static int m_hHook = 0;
 private HookProc m_HookProcedure;
  1. Add these methods to same file
  protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs e)
    {
        base.OnLaunched(e);
        m_HookProcedure = new HookProc(HookProcedure);
        m_hHook = SetWindowsHookEx(WH_KEYBOARD, m_HookProcedure, (IntPtr)0, (int)GetCurrentThreadId());

    }
    
    private void HookProcedure(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode < 0) return;

        bool shift = (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down));

        string s = GetCharFromKey((uint)wParam, shift, false);

        if (s == "") return;

        Debug.WriteLine(s);

    }

    static string GetCharFromKey(uint key, bool shift, bool altGr)
    {
        var buf = new StringBuilder(256);
        var keyboardState = new byte[256];
        if (shift)
            keyboardState[(int)VirtualKey.Shift] = 0xff;
        if (altGr)
        {
            keyboardState[(int)VirtualKey.Control] = 0xff;
            keyboardState[(int)VirtualKey.Menu] = 0xff;
        }
        WinAPI.ToUnicode(key, 0, keyboardState, buf, 256, 0);

        if (buf.Length == 0) return "";
        return buf[0].ToString();
    }
    

@MYPOSConnect
Copy link

For the specific case of the INS key, check wParam == (IntPtr)(int)Windows.System.VirtualKey.Insert.

Then you could just send that event using MessagingCenter to your code that checks if the DataGrid is being edited.

@MikeHillberg
Copy link
Contributor

I think what you're seeing is that input events stop bubbling up the tree once they've been handled. If you click on a row in the DataGrid, that row gets keyboard focus, so KeyDown bubbles up from there. If the DataGrid sets KeyRoutedEventArgs.Handled to true, the Pivot's KeyDown event won't get raised.

You can override this for input events using UIElement.AddHandler (passing true for handledEventsToo), or you can listen for "preview" (tunneling) input events by listening to the UIElement.PreviewKeyDown event.

@ojhad ojhad added the team-Rendering Issue for the Rendering team label Jul 11, 2022
@jtorjo
Copy link
Author

jtorjo commented Jul 16, 2022

@castorix Thanks, so the workaround works. I'm not happy I have to resort to this, but at least it works.

@jcady
Copy link

jcady commented Jul 22, 2022

I was in a similar boat, needing to capture characters entered on the keyboard. I preferred to use CharacterReceived, as I didn't want to reinvent the wheel of tracking shift key presses, etc. With the absence of CoreWindow, I was stuck with attaching the event to specific controls (Pages, or otherwise). But this would lead to poor behavior where the event wouldn't fire as soon as the control lost focus (even if the event was attached to the Page and even if it was attached in code with handledEventsToo property set).

Sadly, I couldn't figure out a good clean solution, so I ended up reverting the app to UWP to use CoreWindow and it works great now. Perhaps there's a way I just couldn't find, but it seems like it should be easier than this.

@bpulliam bpulliam added the product-winui3 WinUI 3 issues label Oct 12, 2022
@bpulliam bpulliam removed the needs-triage Issue needs to be triaged by the area owners label Dec 7, 2022
@lavinders
Copy link
Member

I think what you're seeing is that input events stop bubbling up the tree once they've been handled. If you click on a row in the DataGrid, that row gets keyboard focus, so KeyDown bubbles up from there. If the DataGrid sets KeyRoutedEventArgs.Handled to true, the Pivot's KeyDown event won't get raised.

You can override this for input events using UIElement.AddHandler (passing true for handledEventsToo), or you can listen for "preview" (tunneling) input events by listening to the UIElement.PreviewKeyDown event.

Listening to PreviewKeyDown works for me. KeyDown is indeed getting stopped from being bubbled up once handled. Maybe, this should be documented on KeyDown for apps which have need for a global "key handler" irrespective of any input element which has focus.

@applefanbois
Copy link

Can't believe we are here because Microsoft forgot that a PC use a mouse and ... a keyboard.

@JJBrychell JJBrychell added feature proposal New feature proposal and removed bug Something isn't working labels Aug 30, 2023
@JJBrychell
Copy link

WinUI3 has no current mechanism to allow applications to process keyboard input prior to it being handled in the Xaml tree. Converting this to a feature proposal.

@applefanbois
Copy link

Hey Microsoft!
Computers have a keyboard.

Please, Please allow us to use our beloved keyboard.

(Typed this message on a Mac where Keyboards are supported)

@applefanbois
Copy link

I will add that it used to work.
But in the presence of a Webview2 all the keyboard events are absorbed by it.
Webview2 does not constrain itself to its rectangle.

No matter that the webview2 is 1x1 pixel and there are tons of GUI components around it.
The keyboard events stop working everywhere if a webview2 is present.

@JJBrychell
Copy link

@applefanbois I am sorry, I missed the part about WebView2 in the initial postings on this issue. I was responding to the point:

There should be a way to globally capture keyboard, like in UWP

This is not a point that I necessarily disagree with, but the fact remains that there is no provision for it currently in Xaml and its work would be on the level of a Feature to implement it. I am only surmising (since I wasn't here at the time), but I guess that that wasn't built into Xaml because it originated in UWP and there was a Core Window available that could provide that capability. However, when moving this from UWP to desktop, the new windowing infrastructure does not provide that and hence there is no easy way to do it in Xaml. Again, I don't disagree that it would be useful, but at this point it would be a feature request. We have had a number of discussions about achieving this capability, but there is no work currently planned to do so.

As to the Webview issues (I am still not seeing it in the original posts, but can response to it). For the most part from my testing today, things are working as expected, although not necessarily as we want. If you have a webview in your scene and focus is anywhere other than the Webview, then keys event routing works as designed. The issue is that WebView, as I am sure you are aware, is an open source component and so we (Microsoft) do not have as much control over it as we did when we were using IE. WebView is currently really bad at cooperating with its host; It is mostly just a black box that Xaml pushes data (like urls) into and then it does its own thing. Hence, if the Webview has focus, the key input will go (as it should) to the Webview first. But the WebView doesn't tell Xaml whether or not it handled it. We have similar issues with scroll chaining and pointer events as well. Ideally, WebView would be able to tell its host whether it did anything with an input and whether the host should try to do something. Currently that doesn't happen, but we do have some people who have been looking at this. Now, if you are saying that WebView2 is eating keyboard input when it doesn't have the focus, that would definitely be a bug and if you provide a repro we can look at it.

@jtorjo Thanks for the repro application. I am not familiar with the community toolkit, so it helped let me look at it. The behavior you speak about is probably more about the controls than it is input. When keyboard input comes in, we attempt to deliver it first to the element with keyboard focus and then each ancestor above it until someone tells us they handled it.

So, when you first fire up you application keyboard focus is on the first pivot tab and any keyboard input would go first to the pivot control where your event handler fires. Since it does not mark the event as handle, we attempt to fire it on each ancestor as well (but they don't do anything with it).

Once you select an item, you have move keyboard focus to that item. Keyboard input first goes to that item (which does nothing) then works its way up the tree where (because it is never marked as handled) it goes through both the DataGrid and the Pivot control event handlers.

Tapping on the "Tab Two" (as you probably now have guessed) moves keyboard focus back to the pivot before it changes tabs.

And so on. From input's perspective, this is how it is supposed to work.

Is it unfortunate behavior (especially for this particular scenario), probably, but there are ways to work around it. I am not a controls person, but I suspect that you could listen for the selection changed event and force focus into the first item of the list whenever a tab changed. You could probably even listen to focus events and remember which element had focus when the tab was last active and set focus back into it.

Or you could certainly request this feature be added to the Pivot control itself, but again, I am not a controls person so I don't know how that would fit into their plans or whether there would be a generic enough implementation that it would make sense to add it.

@mmarinchenko
Copy link

@JJBrychell, very deep and detailed explanation. Thanks a lot!👍

@applefanbois
Copy link

Example of the BUG :

Inside all my ScrollViewer, I do a "scroll.KeyDown += scroll_KeyDown;

Everything is contained in a ScrollViewer + Canvas for hierarchy.

the KeyDown event always works, but as soon as a Webview2 is present, even at 1x1 in the bottom right of the window, the KeyDown event will stop working. Even when the focus is not on the Webview2.

@JJBrychell
Copy link

@applefanbois I apologize, but I still cannot repro your issue: Given the following Xaml:

<Window
    x:Class="App3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App3"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch" KeyDown="Scroll_KeyDown">
        <Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
            <WebView2 Source="http://www.microsoft.com" Height="200" Width="200" Canvas.Top="100" Canvas.Left="100"/>
        </Canvas>
    </ScrollViewer>
</Window>

I see the expected (although agreed - not optimal) behavior. If focus in on the button, then the Scroll_KeyDown event gets called, but if focus is in WebView, it does not.

@applefanbois
Copy link

@JJBrychell I don't know what is that, I do everything dynamically and programmatically.

@applefanbois
Copy link

applefanbois commented Oct 12, 2023

Sadly your amazing fix has caused other problems.
I had to de-activate it.

When using a French Canadian keyboard, we can type é & ç as long as it is only 1 key press.
For other accents à, è, ù, â, ê, î, ô, û where we have to press the accent key then the letter key, it does not work.
Instead of fusing the accent and the letter together, the accent press will type the accent twice.

Pressing the ^ key will produce ^^ everywhere.
Although your function will only trigger once.

Not only the problem is in everything WinUI3, but also Webview2.

So the final answer is :

You cannot intercept keyboard events on a WinUI3 win32 app.

Sadly Microsoft does not know that desktop computers use a device called Keyboard.

@JJBrychell
Copy link

@applefanbois I again apologize as I have no idea what "amazing fix" you are referring too. The only workaround I gave (with a caveat I am not a controls person) had to do with setting focus. Cleary, I am not understanding your issue and so will refrain from any additional suggestions until we have a repro that I/We can actually look at to understand what is going on.

@applefanbois
Copy link

I was talking about the code that use "GetCharFromKey"

Simple to reproduce, just set your keyboard to French-Canadian and try to type è, ï, ê ... anywhere.
You won't be able to. I don't understand why.

@castorix
Copy link

For other accents à, è, ù, â, ê, î, ô, û where we have to press the accent key then the letter key, it does not work.

Those keys are called "dead keys" (ToUnicode(Ex) returns < 0)

@applefanbois
Copy link

I don't need to intercept those keys.
I only need F11.

But the problem is that those dead keys will stop working normally in webview2 and in all WinUI3 input text.

@castorix
Copy link

castorix commented Oct 19, 2023

It works fine for me with a test with 2 TextBoxes, the second one to display the typed keys by adding GetKeyNameText in the test code I posted, with a french keyboard :

Keyboard_hook

@applefanbois
Copy link

And you are using that code from "WinAPI.cs" and

m_HookProcedure = new HookProc(HookProcedure);
m_hHook = SetWindowsHookEx(WH_KEYBOARD, m_HookProcedure, (IntPtr)0, (int)GetCurrentThreadId());

As soon as I call the SetWindowsHookEx I won't be able to type those characters in webview2 and text controls.

When I press on a dead keys, it will do this ^^

@castorix
Copy link

Do you return, in the hook procedure :
return nCode < 0 ? CallNextHookEx(m_hHook, nCode, wParam, lParam) : 0;

@applefanbois
Copy link

Well I copy the code back then from a little further down.
This version has HookProcedures as returning void.
The first version at the to des return an int.

I modified the code, but same problem.
I never receive a nCode that is < 0 so I never call the callnexthookex

Are you using a French-Canadian setting for the keyboard?

@castorix
Copy link

A "Français (France)" setting, but accents work normally...
The test with F11 (beep) :

    private static int m_hHook = 0;
private HookProc m_HookProcedure;
			
// In Constructor				
m_HookProcedure = new HookProc(HookProcedure);
m_hHook = SetWindowsHookEx(WH_KEYBOARD, m_HookProcedure, (IntPtr)0, (int)GetCurrentThreadId());
    
// Hook procedure
    private int HookProcedure(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if ((nCode >= 0))
        {
            var nUp = (HIWORD(lParam) & KF_UP);
            if (nUp == 0)
            {
                if (wParam == (IntPtr)(int)Windows.System.VirtualKey.F11)
                    Console.Beep(7000, 10);
                System.Text.StringBuilder sb = new System.Text.StringBuilder(260);
                GetKeyNameText(lParam, sb, sb.Capacity);
                /*
                 * in XAML :
                   <TextBox x:Name ="tb1" Width="200" Height="50"></TextBox>
                   <TextBox x:Name ="tb2" Width="200" Height="50"></TextBox>                      
                 */
                tb2.Text = sb.ToString();
            }
        }
        return nCode < 0 ? CallNextHookEx(m_hHook, nCode, wParam, lParam) : 0;
    }

// Declarations
public const int WH_KEYBOARD = 2;
public const int WH_KEYBOARD_LL = 13;

    public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern bool UnhookWindowsHookEx(int idHook);

    [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
    
    public const int KF_EXTENDED = 0x0100;
    public const int KF_DLGMODE = 0x0800;
    public const int KF_MENUMODE = 0x1000;
    public const int KF_ALTDOWN = 0x2000;
    public const int KF_REPEAT = 0x4000;
    public const int KF_UP = 0x8000;

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern uint GetCurrentThreadId();
    
   	public static int HIWORD(IntPtr wParam)
   	{
    	return (int)((wParam.ToInt64() >> 16) & 0xffff);
   	}

   	public static int LOWORD(IntPtr wParam)
   	{
    	return (int)(wParam.ToInt64() & 0xffff);
   	}

   	[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
   	public static extern int GetKeyNameText(IntPtr lParam, System.Text.StringBuilder lpString, int cchSize);

@applefanbois
Copy link

My project is a winui3 + win32 with a lot of obscure stuff to support only one single instance and multiple windows.

Maybe something is making my stuff behaving differently?
Maybe it is the French-Canadien that does not work?

@castorix
Copy link

castorix commented Oct 20, 2023

My project is a winui3 + win32 with a lot of obscure stuff to support only one single instance and multiple windows.

Maybe something is making my stuff behaving differently?
Maybe it is the French-Canadien that does not work?

Did you try the last test code above in a blank WinUI 3 project with 2 TextBoxes ?

@applefanbois
Copy link

I have the exact same code.
As soon as I attach the proc.
The dead keys will start pumping 2 accents in webview2 and in all text field.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature proposal New feature proposal product-winui3 WinUI 3 issues team-Rendering Issue for the Rendering team
Projects
None yet
Development

No branches or pull requests