15

さて、基本的にはキーボード テキストを取得できるようにしたいと考えています。テキストフィールドなどにテキストを入力するようなものです。私は自分のゲームを Windows 用に書いているだけです。私は Guide.BeginShowKeyboardInput の使用を無視しました。これは自己完結型のゲームの感覚を壊すためです。また、ガイドが常に XBOX ボタンを表示するという事実も、私には正しくないように思えます。はい、それは最も簡単な方法ですが、私はそれが好きではありません。

次にSystem.Windows.Forms.NativeWindowを使ってみました。それを継承するクラスを作成し、それをゲーム ウィンドウ ハンドルに渡し、WndProc 関数を実装して WM_CHAR (または WM_KEYDOWN) をキャッチしましたが、WndProc は他のメッセージに対して呼び出されましたが、WM_CHAR と WM_KEYDOWN は呼び出されませんでした。そのため、そのアイデアを放棄する必要がありました。また、Windows フォーム全体も​​参照していたため、不要なメモリ フットプリントが肥大化していました。

そこで私の最後のアイデアは、スレッド レベルの低レベル キーボード フックを作成することでした。これはこれまでで最も成功しています。WM_KEYDOWN メッセージが表示されます (WM_CHAR はまだ試していません)。仮想キーコードを Win32 関数 MapVirtualKey で char に変換します。そして、私は私のテキストを手に入れました!(現時点では Debug.Write で印刷しています)

ただし、いくつかの問題があります。Caps Lock がオンになっていて、Shift キーが反応しないかのようです。(もちろんそうではありませんが、キーごとに仮想キー コードが 1 つしかないため、翻訳すると出力が 1 つしかないだけです)、Windows フック リストにアタッチするためオーバーヘッドが追加され、私ほど速くはありません。そうであってほしいのですが、Debug.Write が原因で速度がさらに低下する可能性があります。

画面上のキーボードに頼ることなく、他の誰かがこれに取り組み、解決しましたか? または、私が試してみたいアイデアが他にありますか?

前もって感謝します。

ジミーからの質問

質問の意味がよくわからないかもしれませんが、XNA Keyboard および KeyboardState クラスを使用できないのはなぜですか?

私のコメント:

これは、キーステートを読み取ることはできますが、入力されたテキストにアクセスして、ユーザーがどのように入力したかを知ることができないためです。

それでは、さらに明確にしましょう。ユーザーがテキストボックスに入力しているかのように、ユーザーからのテキスト入力を読み取れるように実装したいと考えています。キーボードと KeyboardState クラスはすべてのキーの状態を取得しますが、各キーと組み合わせをその文字表現にマップする必要があります。これは、特に記号を使用する場合と同じキーボード言語をユーザーが使用しない場合に失敗します (私の二重引用符はシフト + 2 ですが、アメリカのキーボードはリターン キーの近くにあります)。


XNAメッセージポンプがメッセージを変換しないため、WM_CHARを取得できなかったのは、私のウィンドウフックがうまくいったようです。

WM _KEYDOWN メッセージを受信するたびに TranslateMessage を追加すると、WM_CHAR メッセージを取得したことになり、これを使用して、KeyboardBuffer クラスがサブスクライブしていた MessageHook クラスで文字型イベントを起動し、それをテキスト バッファーにバッファーします:D (または StringBuilder を使用しますが、結果は同じです)

だから私はそれをすべて私が望むように機能させています。

非常に有益なスレッドへのリンクを提供してくれた Jimmy に感謝します。

4

5 に答える 5

9

XNA で Windows フックを追加する場合

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

/* Author: Sekhat
 * 
 * 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 lparam;
                public IntPtr wparam;
                public uint msg;
                public IntPtr hWnd;
            }

            /// <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
    }
}
于 2009-03-15T04:08:23.697 に答える
1

このgamedev.net投稿のソリューションを使用しましたが、うまく機能します:)

于 2010-05-16T22:45:16.380 に答える
1

spaceこれは、 IMO back、、、A-Zおよび特殊文字を持つ簡単な方法!,@,#,$,%,^,&,*,(,)です。(注意: インポートする必要がありますSystem.Linq) フィールドは次のとおりです。

Keys[] keys;
bool[] IskeyUp;
string[] SC = { ")" , "!", "@", "#", "$", "%", "^", "&", "*", "("};//special characters

コンストラクタ:

keys = new Keys[38];
Keys[] tempkeys;
tempkeys = Enum.GetValues(typeof(Keys)).Cast<Keys>().ToArray<Keys>();
int j = 0;
for (int i = 0; i < tempkeys.Length; i++)
{
    if (i == 1 || i == 11 || (i > 26 && i < 63))//get the keys listed above as well as A-Z
    {
        keys[j] = tempkeys[i];//fill our key array
        j++;
    }
}
IskeyUp = new bool[keys.Length]; //boolean for each key to make the user have to release the key before adding to the string
for (int i = 0; i < keys.Length; i++)
    IskeyUp[i] = true;

最後に、更新方法:

string result = "";

public override void Update(GameTime gameTime)
{
    KeyboardState state = Keyboard.GetState();
    int i = 0;
    foreach (Keys key in keys)
    {
        if (state.IsKeyDown(key))
        {
            if (IskeyUp[i])
            {
                if (key == Keys.Back && result != "") result = result.Remove(result.Length - 1);
                if (key == Keys.Space) result += " ";
                if (i > 1 && i < 12)
                {
                    if (state.IsKeyDown(Keys.RightShift) || state.IsKeyDown(Keys.LeftShift))
                        result += SC[i - 2];//if shift is down, and a number is pressed, using the special key
                    else result += key.ToString()[1];
                }
                if (i > 11 && i < 38)
                {
                    if (state.IsKeyDown(Keys.RightShift) || state.IsKeyDown(Keys.LeftShift))
                       result += key.ToString();
                    else result += key.ToString().ToLower(); //return the lowercase char is shift is up.
                }
            }
            IskeyUp[i] = false; //make sure we know the key is pressed
        }
        else if (state.IsKeyUp(key)) IskeyUp[i] = true;
        i++;
    }
    base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
}

これがうまくいくことを願っています。個人的には、フックよりも使いやすいと思いました。これも、特別な結果 (キーのシャッフルなど) のために簡単に変更できます。

于 2014-02-18T02:47:53.347 に答える
0

このページは、xna での WM_CHAR インターセプトに関する Google の結果の上にあるので、ここにメモを残します。多分これは他の人に役立つでしょう(彼らが私の英語を理解できるなら=)))。

Sekhat の windowshook でコードを試してみましたが、Win32.HookType.WH_CALLWNDPROC の代わりに WH_GETMESSAGE を SetWindowsHookEx に渡す必要があるようです (WH_GETMESSAGE コード lparaw のみで Win32.Message を指します)。

また、メッセージが重複する場合もあります (wparam 0 を使用)。(ここを見てください - http://msdn.microsoft.com/en-us/library/ms644981%28v=VS.85%29.aspx WPARAM の PM_NOREMOVE/PM_REMOVE に関する何か)

このようなものを追加すると

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

wm_keypress wm_char の複製が停止しました (1 は PM_NOREMOVE または PM_REMOVE であると仮定します)。

PS nuclex バリアントは 404 ページを表示するようになりましたが、webarchive で表示できます。nuclex バリアントは機能しますが、ネイティブ XNA MouseState (XNA 3.1 上) からの mouseWheel 処理が壊れます =(

于 2010-05-03T02:11:47.867 に答える
0

質問の意味がよくわからないかもしれませんが、XNA Keyboard および KeyboardState クラスを使用できないのはなぜですか?

于 2008-12-17T17:27:38.090 に答える