简体   繁体   中英

Cross-platform textbox input in MonoGame (Windows/Ubuntu)

I'm working on a game for desktop platforms that uses MonoGame. Previously, it was targeted only at Windows and used pure XNA. I'm exploring options to make it support other platforms using MonoGame and my current alternate test platform is Ubuntu 15.04.

One of the features of the game is support for text boxes that behave similarly to the standard windows UI textbox controls. In order to accomplish this, I used code from this StackOverflow answer .

My question is: How can I write a portable version of the functionality from the linked answer in such a way that it will work on alternate desktop platforms (in this case, Ubuntu)? I'm looking for something that will capture keypress events from the game window on both Windows and Ubuntu.

Additional info:

The linked answer overrides the WndProc of the game's window and passes input into the current textbox by firing events ( CharEntered , KeyDown , KeyUp ). I've refactored the answer's code to a point where I can encapsulate all the Windows functionality in a single "events" class:

internal class Win32KeyboardEvents : IKeyboardEvents
{
    public event CharEnteredHandler CharEntered;
    public event KeyEventHandler KeyDown;
    public event KeyEventHandler KeyUp;

    private readonly IntPtr _prevWndProc;
    private readonly NativeMethods.WndProc _hookProcDelegate;

    public Win32KeyboardEvents(GameWindow window)
    {
        _hookProcDelegate = HookProc;
        _prevWndProc = (IntPtr)NativeMethods.SetWindowLong(window.Handle, NativeMethods.GWL_WNDPROC,
            (int)Marshal.GetFunctionPointerForDelegate(_hookProcDelegate));
    }

    private IntPtr HookProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        IntPtr returnCode = NativeMethods.CallWindowProc(_prevWndProc, hWnd, msg, wParam, lParam);

        switch (msg)
        {
            case NativeMethods.WM_GETDLGCODE:
                returnCode = (IntPtr)(returnCode.ToInt32() | NativeMethods.DLGC_WANTALLKEYS);
                break;

            case NativeMethods.WM_KEYDOWN:
                if (KeyDown != null) //fire events that are subscribed to by textbox object based on which windows message is received
                    KeyDown(null, new XNAKeyEventArgs((Keys)wParam));
                break;

            case NativeMethods.WM_KEYUP:
                if (KeyUp != null)
                    KeyUp(null, new XNAKeyEventArgs((Keys)wParam));
                break;

            case NativeMethods.WM_CHAR:
                if (CharEntered != null)
                    CharEntered(null, new Win32CharEnteredEventArgs((char)wParam, lParam.ToInt32()));
                break;
        }

        return returnCode;
    }
}

My first attempt for something that would work on Ubuntu was creating a GameComponent -derived object that listened for keyboard input in the component's Update() and fired events appropriately, but this quickly got too complicated.

I've investigated using GTK#, but there are quite a few dependencies that I don't want to install unless GTK# is the best way to go. Here is my GTK# attempt, which is currently untested due to a missing dependency:

internal class CrossPlatformKeyboardEvents : DrawingArea, IKeyboardEvents
{
    public event CharEnteredHandler CharEntered = delegate { };
    public event KeyEventHandler KeyDown = delegate { };
    public event KeyEventHandler KeyUp = delegate { };

    public CrossPlatformKeyboardEvents()
    {
        Application.Init();
        Application.Run();

        AddEvents((int)EventMask.KeyPressMask);
        AddEvents((int)EventMask.KeyReleaseMask);
    }

    [ConnectBefore]
    protected override bool OnKeyPressEvent(EventKey evnt)
    {
        if (IsPastEvent(evnt))
        {
            CharEntered(null, new CharEnteredEventArgs(KeyboardDispatcher.CHAR_PASTE_CODE));
            return base.OnKeyPressEvent(evnt);
        }

        switch (evnt.Key)
        {
            case Key.Return:
                CharEntered(null, new CharEnteredEventArgs(KeyboardDispatcher.CHAR_RETURNKEY_CODE));
                break;
            case Key.BackSpace:
                CharEntered(null, new CharEnteredEventArgs(KeyboardDispatcher.CHAR_BACKSPACE_CODE));
                break;
            case Key.Tab:
                CharEntered(null, new CharEnteredEventArgs(KeyboardDispatcher.CHAR_TAB_CODE));
                break;
        }

        var keyCode = GetXNAKey(evnt.Key);
        if (keyCode != Keys.None)
            KeyDown(null, new XNAKeyEventArgs(keyCode));

        return base.OnKeyPressEvent(evnt);
    }

    [ConnectBefore]
    protected override bool OnKeyReleaseEvent(EventKey evnt)
    {
        var keyCode = GetXNAKey(evnt.Key);
        if (keyCode != Keys.None)
            KeyUp(null, new XNAKeyEventArgs(keyCode));
        return base.OnKeyReleaseEvent(evnt);
    }

    private bool IsPastEvent(EventKey evnt)
    {
        return (evnt.State & ModifierType.ControlMask) > 0 && (evnt.Key == Key.V || evnt.Key == Key.v);
    }

    private Keys GetXNAKey(Key key)
    {
        if ((key >= Key.Key_0 && key <= Key.Key_9) ||
            (key >= Key.A && key <= Key.Z))
        {
            return (Keys) key;
        }
        if (key >= Key.a && key <= Key.z)
        {
            return (Keys) (key - 32);
        }

        return Keys.None;
        //switch (key)
        //{

        //}
    }

I got around this by using the TextInput event on GameWindow in MonoGame.

This is an extension that is part of MonoGame but not part of Xna. Currently, I have 2 projects - one for standard Xna and one for Monogame. The Monogame project has links to all the files in my Xna project so they share the same code.

Because of the way my projects are set up, I also had to add a compiler flag (MONO) that excludes this code from being compiled for standard XNA.

Full class (replaces CrossPlatformKeyboardEvents posted in the question):

internal sealed class MonoGameKeyboardEvents : IKeyboardEvents
{
    public event CharEnteredHandler CharEntered;

    private readonly GameWindow _window;

    public MonoGameKeyboardEvents(GameWindow window)
    {
        _window = window;
#if !MONO
        if (CharEntered != null) //hide warning for "member is not used"
            CharEntered(null, null);
#else
        _window.TextInput += GameWindow_TextInput;
#endif
    }

#if MONO
    private void GameWindow_TextInput(object sender, TextInputEventArgs e)
    {
        if (CharEntered != null)
        {
            CharEntered(null, new CharEnteredEventArgs(e.Character));
        }
    }

    ~MonoGameKeyboardEvents()
    {
        Dispose(false);
    }
#endif

    public void Dispose()
    {
#if !MONO
    }
#else
        Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (!disposing) return;

        _window.TextInput -= GameWindow_TextInput;
    }
#endif
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM