3

I'm looking for a cross platform (Win & MacOS) method to detect keypresses in C# for an OpenGL application.

The following works, but for alphanumeric characters only.

protected override void OnKeyPress(OpenTK.KeyPressEventArgs e)
{
    if (e.KeyChar == 'F' || e.KeyChar == 'f')
        DoWhatever();
}

Currently, I'm hacking it, detecting when the user releases a key:

void HandleKeyUp (object sender, KeyboardKeyEventArgs e)
{
    if (e.Key == Key.Tab)
        DoWhatever();
}

Using OpenTK.Input, I can test if a key is being held, but I'm after the initial keypress only.

var state = OpenTK.Input.Keyboard.GetState();

if (state[Key.W])
    DoWhatever();

So, what approaches are OpenTK users taking to register keypresses?

SOLUTION

As suggested by Gusman, simply by comparing the current keyboard state to that of the previous main loop.

KeyboardState keyboardState, lastKeyboardState;

protected override void OnUpdateFrame(FrameEventArgs e)
{
    // Get current state
    keyboardState = OpenTK.Input.Keyboard.GetState();

     // Check Key Presses
    if (KeyPress(Key.Right))
            DoSomething();

    // Store current state for next comparison;
    lastKeyboardState = keyboardState;
}

public bool KeyPress(Key key)
{
    return (keyboardState [key] && (keyboardState [key] != lastKeyboardState [key]) );
}
livin_amuk
  • 1,285
  • 12
  • 26

1 Answers1

4

Change a bit your mind and you will see it clear.

You're programming an OpenGL application, so you have a main loop where you update and render your objects.

If in that loop you get the keyboard state and compare to the previous stored state you have all the keyboard changes, then it's very easy to fire your own events (or just call functions) with the key changes.

In this way you will use the inherent mechanism to OpenTK and will be 100% portable.

Gusman
  • 14,905
  • 2
  • 34
  • 50
  • While this is a working solution for basic use this won't work when you want to offer unicode or non-english keyboard typing inside your opengl application :/ – Tyron Sep 28 '17 at 12:20
  • @Tyron Not sure about unicode, but it works with other languages, I use it with spanish all the time and never failed, you only need to know the real mapping between the enum and the key. – Gusman Sep 28 '17 at 12:33
  • Oh, where do you get the mapping from enum to key? – Tyron Sep 28 '17 at 12:48
  • @Tyron If I don't recall it wrong it was mapping the virtual key code to the unicode char with the `ToUnicode` function. – Gusman Sep 28 '17 at 12:57
  • Some googling only turned up a ToUnicode method via p/invoke on user32.dll that won't work on macos. Plus Keyboard.GetState() seems to return some proprietary key code – Tyron Sep 28 '17 at 14:03
  • Yes, it's only Windows, and the key code enum has the same values as the virtual keycodes on Windows. – Gusman Sep 28 '17 at 14:12
  • this solution doesn't work anymore with the current OpenTK version. Sadly, I found that the current replacement method to address this need (namely, you have to use built-in properties IsKeyPressed and IsKeyRelased) is flickering, and with flickering, I mean that sometimes it detects a keypress twice in a row messing things up... Did anyone have the same issue? – MaxC Jul 27 '22 at 21:59
  • It seems that the KeyboardState update is not synchronous with the OnRenderFrame events, so if you have a very high framerate the keyboard hit is detected several times. Actually, if you process your keypress test in the OnUpdateFrame loop with a reasonably low framerate, the problem is solved. But I think this is just a workaround. – MaxC Jul 27 '22 at 22:13
  • @MaxC KeyboardState is updated when you request it, it's not related at all with the render loop. Sincerely what you describe seems more a bug in your code than anything else. Are you using any type of multithreading? Is it possible that two threads/tasks are competing for using/updating the stored keyboard state? – Gusman Jul 28 '22 at 08:57
  • @Gusman I'm not using any multithreading, I simply test with an if statement the NativeWindows.KeyboardState.IsKeyPressed(Keys k) within OnRenderFrame(FrameEventArgs e) event. This property should be true for just one single frame, but there is a caveat because the property description states --"Frame" refers to invocation of NativeWindows.ProcessEvent() here-- I don't deeply understand this statement, but my feeling is that ProcessEvent is not synchronous with the frame rendering and might occur less frequently. – MaxC Jul 29 '22 at 10:40
  • @MaxC I think you don't understand how keyboard works in OpenTK, it never has been synchronous with anything and will never be, when you retrieve the KeyboardState it gets a snapshot of the key states, if the key is held by the user then "IsKeyPressed" will return true until the user releases that key, no matter how many frames are rendered, that's why in the solution livin_amuk kept a copy of the keyboard state, to avoid triggering the event more than once comparing to the previous state. Read the solution as it's what you need. – Gusman Jul 29 '22 at 15:39
  • @MaxC Also, the proposed solutin still works in the current version, instead of using the `OpenTK.Input.Keyboard.GetState()` you use `NativeWindows.KeyboardState.GetSnapshot()`, it will create an immutable copy and then the rest of the code is the same. https://opentk.net/api/OpenTK.Windowing.GraphicsLibraryFramework.KeyboardState.html – Gusman Jul 29 '22 at 15:45
  • I was missing the GetSnapshot() method. In this way detecting key transient (up or down) works the same as the previous version and certainly solves the problem. I still don't understand why the description says that the flag is true when the key is pressed at the current frame and was not present in the previous. And yes, I was sticking with the previous way to manage the keyboard I was used to and that worked perfectly fine in the past. – MaxC Jul 29 '22 at 16:44