tags:

views:

4481

answers:

9

Okay, so basically I want to be able to retrieve keyboard text. Like entering text into a text field or something. I'm only writing my game for windows. I've disregarded using Guide.BeginShowKeyboardInput because it breaks the feel of a self contained game, and the fact that the Guide always shows XBOX buttons doesn't seem right to me either. Yes it's the easiest way, but I don't like it.

Next I tried using System.Windows.Forms.NativeWindow. I created a class that inherited from it, and passed it the Games window handle, implemented the WndProc function to catch WM_CHAR (or WM_KEYDOWN) though the WndProc got called for other messages, WM_CHAR and WM_KEYDOWN never did. So I had to abandon that idea, and besides, I was also referencing the whole of Windows forms, which meant unnecessary memory footprint bloat.

So my last idea was to create a Thread level, low level keyboard hook. This has been the most successful so far. I get WM_KEYDOWN message, (not tried WM_CHAR yet) translate the virtual keycode with Win32 funcation MapVirtualKey to a char. And I get my text! (I'm just printing with Debug.Write at the moment)

A couple problems though. It's as if I have caps lock on, and an unresponsive shift key. (Of course it's not however, it's just that there is only one Virtual Key Code per key, so translating it only has one output) and it adds overhead as it attaches itself to the Windows Hook List and isn't as fast as I'd like it to be, but the slowness could be more due to Debug.Write.

Has anyone else approached this and solved it, without having to resort to an on screen keyboard? or does anyone have further ideas for me to try?

thanks in advance.

note: This is cross posted from the XNA Creators Forums, so if I get an answer there I'll post it here and Vice-Versa

Question asked by Jimmy

Maybe I'm not understanding the question, but why can't you use the XNA Keyboard and KeyboardState classes?

My comment:

It's because though you can read keystates, you can't get access to typed text as and how it is typed by the user.

So let me further clarify. I want to implement being able to read text input from the user as if they are typing into textbox is windows. The keyboard and KeyboardState class get states of all keys, but I'd have to map each key and combination to it's character representation. This falls over when the user doesn't use the same keyboard language as I do especially with symbols (my double quotes is shift + 2, while american keyboards have theirs somewhere near the return key).

A: 

Maybe I'm not understanding the question, but why can't you use the XNA Keyboard and KeyboardState classes?

Jimmy
It's because though you can read keystates, you can't get access to typed text as and how it is typed by the user.
Sekhat
Ah I see. In the past, I've written a utility class for this purpose, similar to the one at http://www.gamedev.net/community/forums/topic.asp?topic_id=457783
Jimmy
Thanks, it seems my window hook was the way to go, just the reason I wasn't getting WM_CHAR is because the XNA message pump doesn't do translate message. I'll have a fiddle around and see what else I can get :)
Sekhat
+1  A: 

it seems my window hook was the way to go, just the reason I wasn't getting WM_CHAR is because the XNA message pump doesn't do translate message.

Adding TranslateMessage in whenever I received a WM_KEYDOWN message meant I got my WM_CHAR message, I then used this to fire a character typed event in my MessageHook class which my KeyboardBuffer class had subscribed to, which then buffers that to a text buffer :D (or StringBuilder, but the result is the same)

So I have it all working as I want.

Many thanks to Jimmy for a link to a very informative thread.

Sekhat
A: 

hi! Can you post your solution? I am trying to override the WndProc method in my XNA game because I need to receive the signals sent to my program, but the class "Game" itself doesn't have that method at all. I can't find a way to override it in "Game.Window", but your solution may also work for me. Would be awesome if you could post it!!

see my newly added answer
Sekhat
+3  A: 

For adding a windows hook in XNA

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Reflection;

/* Author: Nicholas Radford
 * 
 * License: Public Domain.
 * 
 * Usage:
 *
 * Inherit from this class, and override the WndProc function in your derived class, 
 * in which you handle your windows messages.
 * 
 * To start recieving the message, create an instance of your derived class, passing in the
 * window handle of the window you want to listen for messages for.
 * 
 * in XNA: this would be the Game.Window.Handle property
 * in Winforms Form.Handle property
 */

namespace WindowsHookExample
{
    public abstract class WindowsHook : IDisposable
    {
        IntPtr hHook;
        IntPtr hWnd;
        // Stored here to stop it from getting garbage collected
        Win32.WndProcDelegate wndProcDelegate;

        public WindowsHook(IntPtr hWnd)
        {
            this.hWnd = hWnd;

            wndProcDelegate = WndProcHook;

            CreateHook();
        }

        ~WindowsHook()
        {
            Dispose(false);
        }

        private void CreateHook()
        {

            uint threadId = Win32.GetWindowThreadProcessId(hWnd, IntPtr.Zero);

            hHook = Win32.SetWindowsHookEx(Win32.HookType.WH_CALLWNDPROC, wndProcDelegate, IntPtr.Zero, threadId);

        }

        private int WndProcHook(int nCode, IntPtr wParam, ref Win32.Message lParam)
        {
            if (nCode >= 0)
            {
                Win32.TranslateMessage(ref lParam); // You may want to remove this line, if you find your not quite getting the right messages through. This is here so that WM_CHAR is correctly called when a key is pressed.
                WndProc(ref lParam);
            }

            return Win32.CallNextHookEx(hHook, nCode, wParam, ref lParam);
        }

        protected abstract void WndProc(ref Win32.Message message);

        #region Interop Stuff
        // I say thankya to P/Invoke.net.
        // Contains all the Win32 functions I need to deal with
        protected static class Win32
        {
            public enum HookType : int
            {
                WH_JOURNALRECORD = 0,
                WH_JOURNALPLAYBACK = 1,
                WH_KEYBOARD = 2,
                WH_GETMESSAGE = 3,
                WH_CALLWNDPROC = 4,
                WH_CBT = 5,
                WH_SYSMSGFILTER = 6,
                WH_MOUSE = 7,
                WH_HARDWARE = 8,
                WH_DEBUG = 9,
                WH_SHELL = 10,
                WH_FOREGROUNDIDLE = 11,
                WH_CALLWNDPROCRET = 12,
                WH_KEYBOARD_LL = 13,
                WH_MOUSE_LL = 14
            }

            public struct Message
            {
                public IntPtr hWnd;
                public uint msg;
                public IntPtr wparam;
                public IntPtr lparam;
            }

            /// <summary>
            ///  Defines the windows proc delegate to pass into the windows hook
            /// </summary>      
            public delegate int WndProcDelegate(int nCode, IntPtr wParam, ref Message m);

            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern IntPtr SetWindowsHookEx(HookType hook, WndProcDelegate callback,
                IntPtr hMod, uint dwThreadId);

            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern bool UnhookWindowsHookEx(IntPtr hhk);

            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref Message m);

            [DllImport("coredll.dll", SetLastError = true)]
            public static extern IntPtr GetModuleHandle(string module);

            [DllImport("user32.dll", EntryPoint = "TranslateMessage")]
            public extern static bool TranslateMessage(ref Message m);

            [DllImport("user32.dll")]
            public extern static uint GetWindowThreadProcessId(IntPtr window, IntPtr module);
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Free managed resources here
            }
            // Free unmanaged resources here
            if (hHook != IntPtr.Zero)
            {
                Win32.UnhookWindowsHookEx(hHook);
            }
        }

        #endregion
    }
}
Sekhat
A: 

Thanks for your answer, I found this tutorial already: http://www.nuclex.org/articles/capturing-keyboard-input-in-xna It is working perfectly for me!

That URL gives a 404 error for me.
Foole
A: 

All,

This post gives me a lot of faith.

I am relatively new to the topic of Windows programming, but am an old hand at C/C++.

I am curious on your thoughts about using XNA to receive other events such as WM_TOUCH (new for Windows 7 and Multi-touch devices). Any thoughts on where I could go to learn more about this?

Best,

Evan

If you've looked at windows code before, my above code basically hooks into the windows life cycle and the WndProc method gets called whenever the C++ callback for WndProc would get called. So you can check against any message. You'll have to fiddle with the above code mind to make it work. But it will certainly be possible to handle them.
Sekhat
A: 

Hi Sekhat, I tried to use your code to receive WM_TOUCH messages as you suggested to Evan, but it doesn't work. Looks like I can see all the messages except those related to touch. Either I'm doing something wrong or Windows treats WM_TOUCH messages differently and doesn't pass them to WndProcHook procedure. Any ideas? Has anyone succeeded with this approach?

speedstone
Check this article out, about half-way down is a section on raw touch messages.http://msdn.microsoft.com/en-us/magazine/ee336016.aspx
Sekhat
A: 

This page is on top of google result about WM_CHAR interception in xna, so i leave here some note. Maybe this will be useful for other (if they will be able to understand my English =)) ).

I try code with windowshook from Sekhat, but seems there should be WH_GETMESSAGE passed to SetWindowsHookEx instead Win32.HookType.WH_CALLWNDPROC (only with WH_GETMESSAGE code lparaw will point to Win32.Message ).

Also there is sometime duplicated messages (with wparam 0). (look here - http://msdn.microsoft.com/en-us/library/ms644981%28v=VS.85%29.aspx something about PM_NOREMOVE/PM_REMOVE in WPARAM )

When i add something like this

    if (nCode >= 0 && wParam == 1)
    {
        Win32.TranslateMessage(ref lParam); 
        WndProc(ref lParam);
    }

wm_keypress wm_char duplication stopped (i supose 1 is PM_NOREMOVE or PM_REMOVE).

P.S. nuclex variant now show 404 page, but can be viewed with webarchive. nuclex variant works, but it cause broken mouseWheel processing from native XNA MouseState (on XNA 3.1) =(

Zakus
A: 

I used the solution from this gamedev.net post and it works great :)

nornagon