
Keyboard stack
Posted Saturday, 18 June, 2011 - 19:05 by james_lohr inHello,
I'm creating a game, and one of the issues I've encountered is with managing which handlers have been added to the keyboard events. For example, suppose you're in a menu and you've added a handler so that you can move up/down within the menu. Now suppose you select some control within the menu (e.g. a box which allows free text to be entered) which has its own handlers . Whilst the control is active, it is desirable that no other handlers are listening to keyboard events. What is the standard way for dealing with this?
In the end I went for a KeyboardStack, which has the following usage
var keyboardStack = new keyboardStack(Keyboard); keyboardStack.AddKeyDown( ControlA.KeyDown); keyboardStack.AddKeyDown( ControlB.KeyDown); //here, controlA, controlB will receive input keyboardStack.Push(); keyboardStack.AddKeyDown( ControlC.KeyDown); //here, only control C will receive input keyboardStack.Push(); keyboardStack.AddKeyDown( ControlD.KeyDown); //here, only control D will receive input keyboardStack.Pop(); //here, only control C again keyboardStack.Pop(); //here, only controlA, controlB again keyboardStack.Pop(); //nothing will receive input
This has certainly tidied up my code a lot, but I was just wondering if there a standard/better way of doing it.
Thanks,
James L.
If anyone wants to use my KeyboardStack class, then it's here:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using OpenTK.Input; namespace JOpenTKUtils { public class KeyboardStack { private KeyboardDevice keyboardDevice; private Stack<HandlerLists> handlerStack; private class HandlerLists { public List<EventHandler<KeyboardKeyEventArgs>> KeyDownList = new List<EventHandler<KeyboardKeyEventArgs>>(); public List<EventHandler<KeyboardKeyEventArgs>> KeyUpList = new List<EventHandler<KeyboardKeyEventArgs>>(); } public void AddKeyDown(EventHandler<KeyboardKeyEventArgs> keyDown) { if (handlerStack.Count == 0) Push(); handlerStack.Peek().KeyDownList.Add(keyDown); keyboardDevice.KeyDown += keyDown; } public void AddKeyUp(EventHandler<KeyboardKeyEventArgs> keyUp) { if (handlerStack.Count == 0) Push(); handlerStack.Peek().KeyUpList.Add(keyUp); keyboardDevice.KeyUp += keyUp; } public void RemoveKeyDown(EventHandler<KeyboardKeyEventArgs> keyDown) { if (handlerStack.Count == 0) return; handlerStack.Peek().KeyDownList.Remove(keyDown); keyboardDevice.KeyDown -= keyDown; } public void RemoveKeyUp(EventHandler<KeyboardKeyEventArgs> keyUp) { if (handlerStack.Count == 0) return; handlerStack.Peek().KeyUpList.Remove(keyUp); keyboardDevice.KeyUp -= keyUp; } private void RemoveAllHandlers(HandlerLists lists) { foreach (var handler in lists.KeyDownList) keyboardDevice.KeyDown -= handler; foreach (var handler in lists.KeyUpList) keyboardDevice.KeyUp -= handler; } private void AddAllHandlers(HandlerLists lists) { foreach (var handler in lists.KeyDownList) keyboardDevice.KeyDown += handler; foreach (var handler in lists.KeyUpList) keyboardDevice.KeyUp += handler; } public void Push() { if(handlerStack.Count != 0) RemoveAllHandlers(handlerStack.Peek()); var handlerLists = new HandlerLists(); handlerStack.Push(handlerLists); } public void Pop() { if (handlerStack.Count == 0) throw new Exception("Attempted to pop an empty KeyboardStack."); RemoveAllHandlers(handlerStack.Pop()); if (handlerStack.Count != 0) AddAllHandlers(handlerStack.Peek()); } public KeyboardStack(KeyboardDevice keyboard) { handlerStack = new Stack<HandlerLists>(); this.keyboardDevice = keyboard; } } }


Comments
Re: Keyboard stack
This is one of the issues with event-based input code (OpenTK trunk now offers an additional, polling-based API that does not suffer from this - components can now poll for input independently, on a need-by-need basis).
There are many possible solutions to this issue. Your approach is flexible and should work well. Operating systems use a similar approach, combining the concept of 'input focus' with a stack (the window at the top of the stack has input focus; close that and the focus moves to the next window in the stack).
Re: Keyboard stack
Thanks for the information Fiddler.
In the past I've always used polling-based APIs, and for this reason my original solution was to wrap the OpenTK event API with my own API I could then poll. The issue with this was that it introduced additional latency, and in fact it was less clean than using the event API directly.
Now that I've changed my code to use the events via my InputStack class (I've modified it to include mouse input now too), it feels much cleaner than even a polling-based API, so I think I shall stick to events for the time being.
Is the plan to support polling and events alongside each other indefinitely (both implemented to minimize latency), or is there the risk that one will become obsolete or have an additional latency attached?
Re: Keyboard stack
For me, its not only you that encounters that certain problem. Keyboard stack really exist. FXDD