So in short i'm simply trying to move a rectangle around a Canvas object in a WPF application. What i have here is my KeyDown event function. The problem is, when i hold a key down for long, it launches this function over and over again rapidly and screws up my rectangle location code.
My theory/logic behind it:
BECAUSE WHEN YOU HOLD A BUTTON DOWN ON A KEYBOARD IT DOES NOT MOVE SMOOTHLY (TEST IT ON THE SCROLL BAR IN YOUR BROWSER, IT STARTS, pauses, THEN CONTINUES SMOOTHLY), i want it to start a forms timer that moves the object in the UI. Then when the KeyUp event happens, the timer STOPS.
public void Window_KeyDown(object sender, KeyEventArgs e)
{
string msg;
string keystr = e.Key.ToString();
Key keyval = e.Key;
switch (keystr)
{
case "Down":
Console.WriteLine("Case 1");
Display.Content = "Down";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Down");
}
break;
case "Up":
Console.WriteLine("Case 2");
Display.Content = "Up";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Up");
}
break;
case "Left":
Console.WriteLine("Case 3");
Display.Content = "Left";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Left");
}
break;
case "Right":
Display.Content = "Right";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Right");
}
break;
}
}
public void Window_KeyUp(object sender, KeyEventArgs e)
{
Display.Content = "No key is pressed.";
foreach (Character character in creatures)
{
if (e.Key == Key.Right)
{
character.StopIt();
}
if (e.Key == Key.Left)
{
character.StopIt();
}
if (e.Key == Key.Up)
{
character.StopIt();
}
if (e.Key == Key.Down)
{
character.StopIt();
}
}
}
and just for reference if you need my rectangle class code i'll post what happens if the RIGHT arrow key is pressed:
Position is called
public void Position(String Direction)
{
if (Direction == "Right")
{
tmr = new System.Windows.Forms.Timer();
tmr.Interval = this.waitTime;
tmr.Tick += new EventHandler(GoRight);
tmr.Start();
}
}
GoRight is called:
public void GoRight(object sender, System.EventArgs e)
{
if (x < Background.ActualWidth - CharacterWidth)
{
if (goRight)
{
x += incrementSize;
CharacterImage.SetValue(Canvas.LeftProperty, x);
}
if (x > Background.ActualWidth - CharacterWidth)
{
goRight = false;
tmr.Stop();
}
}
}
Finally, StopIt is called in the KeyUp event:
public void StopIt()
{
tmr.Stop();
goRight = true;
goLeft = true;
goUp = true;
goDown = true;
}
I've only been learning c# for a couple months now so i'm trying to keep it relatively simple if possible, and only use .net.
Any help would be appreciated!!
EDIT:: MY SOLUTION:
I simply made a while(flag) loop around my switch case. Then i set flag = false within the cases. When Key UP is pressed i set flag equal to true again. YAY
I assume that you want your character to move on the initial KeyDown event. Then you want to ignore any subsequent KeyDown events until you get a KeyUp event.
So you can ignore the subsequent KeyDown events by checking e.IsRepeat e.g.
public void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.IsRepeat) return;
// rest of your code...
BTW, the non-smooth movement that you observe when scrolling an application is caused by the keyboard repeat delay. You can set this in the keyboard properties or though http://msdn.microsoft.com/en-us/library/system.windows.systemparameters.keyboarddelay.aspx
I don't know enough about WPF to tell you what is going on, though you may be correct about the push button/pause thing. It depends on how WPF treats keypresses. My guess would be that it does so the same way most Microsoft forms work; it has a pause to keep you from typing multiple characters at a time. There may be a way around this but I'm not sure.
What I will say though is that you should use something designed for games. When I first tried creating games I did so in a style similar to what you are doing and it doesn't work. What you are using is designed for office software and will not give you access to what you need; at least not without fighting and workarounds. As was suggested by Alex Beisley look into XNA. It's a dead language unfortunately but it died fairly recently. It uses c#, was made by Microsoft, and is powerful enough to do what you want without fighting you and easy enough to use once you get the hang of it. It's a shame to see it killed off.
If you want to torture yourself then I'd suggest going the route I've been trying which is to learn C++ and DirectX. It is not easy and you will need to be patient and go through multiple tutorials (no one tutorial seems to do a good job explaining anything). DirectX and C++ are not going anywhere soon, so they are a safe bet if you are looking to get into a long term language.
Related
I'm creating a Space Invaders game with C# WinForms and when coding the movement of the player's cannon, I create this event handler:
private void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
for (int i = 0; i < 500; i++)
{
if (e.KeyCode == Keys.Left)
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
Application.DoEvents();
System.Threading.Thread.Sleep(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Right)
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
Application.DoEvents();
System.Threading.Thread.Sleep(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Up)
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
}
But going on different sites concerning how this is unreliable in C#, I am going to make sure to not use it from thereon. What other alternatives are there to use instead of Application.DoEvents within this instance?
I'd suggest to make that event handler async and use await Task.Delay() instead of Thread.Sleep():
private async void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
for (int i = 0; i < 500; i++)
{
if (e.KeyCode == Keys.Left)
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
await Task.Delay(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Right)
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
await Task.Delay(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Up)
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
}
This way, the control flow is returned to the caller and your UI thread has time to handle the other events (so no need for Application.DoEvents()). Then after (about) the 10ms, the control is returned and execution of that handler resumed.
There may more fine-tuning be necessary, because now of course you could manage to hit more keys while the method has not finished. How to handle that depends on the surroundings. You could declare a flag that signals current execution and refuses further method entries (no thread safety needed here as it's all happening sequentially on the UI thread).
Or instead of refusing re-entrance queue the keystrokes and handle them in another event, e.g. "idle" events (like Lasse suggested in the comments).
Note that an event handlers is one of the rare occasions where using async without returning a Task is ok.
Use a timer that will call the game processing each 20 milliseconds.
Within the KeyDown/KeyUp events just change the current state which is used by the game processing.
Sample code:
[Flags]
public enum ActionState
{
MoveLeft,
MeveRight,
FireLaser,
}
// stores the current state
private ActionState _actionState;
// set action state
private void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
switch ( e.KeyCode )
{
case Keys.Left:
_actionState |= ActionState.MoveLeft;
break;
case Keys.Right:
_actionState |= ActionState.MoveRight;
break;
case Keys.Up:
_actionState |= ActionState.FireLaser;
break;
default:
break;
}
}
// remove action state
private void Game_Screen_KeyUp(object sender, KeyEventArgs e)
{
switch ( e.KeyCode )
{
case Keys.Left:
_actionState &= ~ActionState.MoveLeft;
break;
case Keys.Right:
_actionState &= ~ActionState.MoveRight;
break;
case Keys.Up:
_actionState &= ~ActionState.FireLaser;
break;
default:
break;
}
}
// called from a timer every 20 milliseconds
private void Game_Screen_LoopTimer_Tick(object sender, EventArgs e)
{
if ( _actionState.HasFlag( ActionState.MoveLeft ) && !_actionState.HasFlag( ActionState.MoveRight ) )
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
}
if ( _actionState.HasFlag( ActionState.MoveRight ) && !_actionState.HasFlag( ActionState.MoveLeft ) )
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
}
if ( _actionState.HasFlag( ActionState.FireLaser ) )
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
Application.DoEvents() interupts the execution of your method and the UI thread will process its events (including redrawing of the UI). From my experience there is nothing wrong with using it in the right places...
Using the 'async' pattern (as René Vogt suggested) is best practice to make reponsive UI's.
However. You have to ask yourself if you need a loop that checks 500 times, if the key down is left, right or up. Especially as it seems this loop is triggerd by a key down event...
It would be maybe easier if you make a 'while(true)' loop in the main and call Application.DoEvents from there.
Or you react on the key_down event and do on action at a time. => press left -> move one left -> press left again -> move one left more... and so on.
https://msdn.microsoft.com/en-us//library/system.windows.forms.application.doevents(v=vs.110).aspx
I am developing a simple Universal Windows App using C#. I have a RichEditBox and I found a weird behavior when using Control+I key combination, which for some reason inserts a Tab (is that expected?). Because I wanted the combination of keys to toggle Italic font style, I thought the best way was through the KeyDown event.
So, this is my code:
private void richbox_KeyDown(object sender, KeyRoutedEventArgs e)
{
System.Diagnostics.Debug.Write("\nKeyDown : " + e.Key.ToString());
if (e.Key == VirtualKey.Tab)
{
richbox.Document.Selection.TypeText("\t");
e.Handled = true;
}
else if (Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) == Windows.UI.Core.CoreVirtualKeyStates.Down)
{
//If Control is pressed down, check if current key is B,I,U...
System.Diagnostics.Debug.Write(" => Control is down!");
switch (e.OriginalKey)
{
case VirtualKey.B:
toogleBold();
e.Handled = true;
break;
case VirtualKey.I:
e.Handled = true;
toogleItalic();
break;
case VirtualKey.U:
toogleUnderline();
e.Handled = true;
break;
}
}
}
My problem is, the condition on the Else If is not always true when I press the Control key. I would like to understand why and what could I do to fix it.
If I run the code and I press the control key a few times, this is the output:
KeyDown : Control => Control is down!
KeyDown : Control
KeyDown : Control => Control is down!
KeyDown : Control
...
Thanks in advance :)
I tried your code and used debugger output to see what the actual state of Ctrl is in those situations:
var state = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control);
Debug.WriteLine(state);
What I found out is that the second time you press the key, its state is not Down, but Down|Locked, more specifically Windows.UI.Core.CoreVirtualKeyStates.Down | Windows.UI.Core.CoreVirtualKeyStates.Locked. It turns out CoreVirtualKeyStates is a flag enum and it can have multiple values at the same time. In this case you are comparing with == which means you don't get a match. You can first use the HasFlag method or bitwise AND (&) to get the right value out and then compare and you will be good to go!
That means either this:
else if ( Window.Current.CoreWindow.GetKeyState(VirtualKey.Control).
HasFlag( CoreVirtualKeyStates.Down ) )
{
//rest of the code
}
Or this:
else if (
( Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) &
Windows.UI.Core.CoreVirtualKeyStates.Down )
== CoreVirtualKeyStates.Down )
{
//rest of the code
}
So in short i'm simply trying to move a rectangle around a Canvas object in a WPF application. What i have here is my KeyDown event function. The problem is, when i hold a key down for long, it launches this function over and over again rapidly and screws up my rectangle location code.
My theory/logic behind it:
BECAUSE WHEN YOU HOLD A BUTTON DOWN ON A KEYBOARD IT DOES NOT MOVE SMOOTHLY (TEST IT ON THE SCROLL BAR IN YOUR BROWSER, IT STARTS, pauses, THEN CONTINUES SMOOTHLY), i want it to start a forms timer that moves the object in the UI. Then when the KeyUp event happens, the timer STOPS.
public void Window_KeyDown(object sender, KeyEventArgs e)
{
string msg;
string keystr = e.Key.ToString();
Key keyval = e.Key;
switch (keystr)
{
case "Down":
Console.WriteLine("Case 1");
Display.Content = "Down";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Down");
}
break;
case "Up":
Console.WriteLine("Case 2");
Display.Content = "Up";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Up");
}
break;
case "Left":
Console.WriteLine("Case 3");
Display.Content = "Left";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Left");
}
break;
case "Right":
Display.Content = "Right";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Right");
}
break;
}
}
public void Window_KeyUp(object sender, KeyEventArgs e)
{
Display.Content = "No key is pressed.";
foreach (Character character in creatures)
{
if (e.Key == Key.Right)
{
character.StopIt();
}
if (e.Key == Key.Left)
{
character.StopIt();
}
if (e.Key == Key.Up)
{
character.StopIt();
}
if (e.Key == Key.Down)
{
character.StopIt();
}
}
}
and just for reference if you need my rectangle class code i'll post what happens if the RIGHT arrow key is pressed:
Position is called
public void Position(String Direction)
{
if (Direction == "Right")
{
tmr = new System.Windows.Forms.Timer();
tmr.Interval = this.waitTime;
tmr.Tick += new EventHandler(GoRight);
tmr.Start();
}
}
GoRight is called:
public void GoRight(object sender, System.EventArgs e)
{
if (x < Background.ActualWidth - CharacterWidth)
{
if (goRight)
{
x += incrementSize;
CharacterImage.SetValue(Canvas.LeftProperty, x);
}
if (x > Background.ActualWidth - CharacterWidth)
{
goRight = false;
tmr.Stop();
}
}
}
Finally, StopIt is called in the KeyUp event:
public void StopIt()
{
tmr.Stop();
goRight = true;
goLeft = true;
goUp = true;
goDown = true;
}
I've only been learning c# for a couple months now so i'm trying to keep it relatively simple if possible, and only use .net.
Any help would be appreciated!!
EDIT:: MY SOLUTION:
I simply made a while(flag) loop around my switch case. Then i set flag = false within the cases. When Key UP is pressed i set flag equal to true again. YAY
I assume that you want your character to move on the initial KeyDown event. Then you want to ignore any subsequent KeyDown events until you get a KeyUp event.
So you can ignore the subsequent KeyDown events by checking e.IsRepeat e.g.
public void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.IsRepeat) return;
// rest of your code...
BTW, the non-smooth movement that you observe when scrolling an application is caused by the keyboard repeat delay. You can set this in the keyboard properties or though http://msdn.microsoft.com/en-us/library/system.windows.systemparameters.keyboarddelay.aspx
I don't know enough about WPF to tell you what is going on, though you may be correct about the push button/pause thing. It depends on how WPF treats keypresses. My guess would be that it does so the same way most Microsoft forms work; it has a pause to keep you from typing multiple characters at a time. There may be a way around this but I'm not sure.
What I will say though is that you should use something designed for games. When I first tried creating games I did so in a style similar to what you are doing and it doesn't work. What you are using is designed for office software and will not give you access to what you need; at least not without fighting and workarounds. As was suggested by Alex Beisley look into XNA. It's a dead language unfortunately but it died fairly recently. It uses c#, was made by Microsoft, and is powerful enough to do what you want without fighting you and easy enough to use once you get the hang of it. It's a shame to see it killed off.
If you want to torture yourself then I'd suggest going the route I've been trying which is to learn C++ and DirectX. It is not easy and you will need to be patient and go through multiple tutorials (no one tutorial seems to do a good job explaining anything). DirectX and C++ are not going anywhere soon, so they are a safe bet if you are looking to get into a long term language.
You may find if the CapsLock key has been pressed subscribing to the KeyDown/KeyUp event. And then toggle the state of the CapsLock based on that input. The problem with this approach is that you need the initial state of the CapsLock key to start toggling that.
One application of this could be giving the user a notification on a Login Page (this is what i need).
By the way i'm using Silverlight 5.
EDIT
The solution posted here says:
You can however find out if Capslock is on by making use of
KeyEventArgs.PlatformKeyCode that's actually send at onKeyDown.You can
look up the Virtual Key-code for capslock in here:
http://msdn.microsoft.com/en-us/library/ms927178.aspx
With this solution you can't determine the CapsLock state, because KeyEventArgs.PlatformKeyCode returns "an integer value that represents the key that is pressed or released (depending on which event is raised)". So if CapsLock is On and Key A is pressed then KeyEventArgs.PlatformKeyCode = 65, and on the other hand if CapsLock is off and Key A is pressed then KeyEventArgs.PlatformKeyCode = 65.
In other words you can't determine if the CapsLock is enabled or not based on the KeyEventArgs.PlatformKeyCode property.
The answer to this question also seems to have a solution, it checks two things:
the letter typed is Upper Case and Shift isn't pressed
the letter typed is Lower Case and Sift is pressed
Both of this cases implies that the CapsLock is On, but there is also a problem with this solution, given a KeyEventArgs you can know the pressed key in the keyboard but can't know the Char outputted by that key.
I'd suggest using a Behavior for this detection since you can hook into the PasswordChanged and KeyDown events to determine if the Caps Lock is on. Here is a quick behavior I wrote to detect if the Caps Lock is on. You can bind to the CapsLockOn behavior and use something like a data state behavior to hide/show your warning message.
public class DetectCapsLockBehavior : Behavior<PasswordBox>
{
private int _lastKey;
private ModifierKeys _modifiers;
[Category("Settings")]
public bool CapsLockOn
{
get { return (bool)GetValue(CapsLockOnProperty); }
set { SetValue(CapsLockOnProperty, value); }
}
public static readonly DependencyProperty CapsLockOnProperty = DependencyProperty.Register("CapsLockOn", typeof(bool), typeof(DetectCapsLockBehavior), new PropertyMetadata(null));
protected override void OnAttached()
{
AssociatedObject.PasswordChanged += new RoutedEventHandler(AssociatedObject_PasswordChanged);
AssociatedObject.KeyDown += new KeyEventHandler(AssociatedObject_KeyDown);
}
void AssociatedObject_KeyDown(object sender, KeyEventArgs e)
{
_lastKey = e.PlatformKeyCode;
_modifiers = Keyboard.Modifiers;
}
void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e)
{
if (_lastKey >= 0x41 && _lastKey <= 0x5a)
{
var lastChar = AssociatedObject.Password.Last();
if (_modifiers != ModifierKeys.Shift)
{
CapsLockOn = char.ToLower(lastChar) != lastChar;
}
else
{
CapsLockOn = char.ToUpper(lastChar) != lastChar;
}
}
}
}
NOTE: This is sample code, so there could be bugs. Just trying to demonstrate how it could be done.
region KeysDetection
bool bCaps = false;
bool bIns = false;
bool bNum = false;
public void FloatableWindow_KeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.CapsLock:
bCaps = !bCaps;
lbl_caps.Opacity = (bCaps) ? 1 : 0.5;
break;
case Key.Insert:
bIns = !bIns;
lbl_ins.Opacity = (bIns) ? 1 : 0.5;
break;
case Key.Unknown:
{
if (e.PlatformKeyCode == 144)
{
{
bNum = !bNum;
lbl_num.Opacity = (bNum) ? 1 : 0.5;
}
}
break;
}
}
}
#endregion
In this application, I need to be able to stop the response from a key which is held down in order to prevent unnessecary data from entering the output. The problem I'm having is, using the methods in my code below does prevent the keys from repeating, but it also stops them from being responsive enough - as the users are hitting the keys very quickly.
I'm not sure if it's my hardware, api restriction or a problem with my code, but the routines I have below do not simply come round fast enough to work without making the program impossible to use. A way of identifying if a key is being actively held down (and for how long) would also help another feature for the program and solve this current issue.
Any ideas?
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
e.SuppressKeyPress = isKeyDown;
isKeyDown = true;
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
isKeyDown = false;
}
private void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
if (!isStreamPlaying) return;
if (e.KeyChar.Equals('d') || e.KeyChar.Equals('j'))
{
//red hit
SoundPlayer hitSounds = new SoundPlayer(taikoLiveMapper.Properties.Resources.normal_hitnormal);
hitSounds.Play();
outputlist.Add(string.Format("320,240,{0},1,{1}", ms, 0));
lastms = ms;
}
else if (e.KeyChar.Equals('s') || e.KeyChar.Equals('k'))
{
//blue hit
SoundPlayer hitSounds = new SoundPlayer(taikoLiveMapper.Properties.Resources.normal_hitclap);
hitSounds.Play();
outputlist.Add(string.Format("320,240,{0},1,{1}", ms, 8));
lastms = ms;
}
}
You can use GetKeyState to find out if a key is down and use that to track the keys:
[DllImport("user32.dll")]
static extern short GetKeyState(int key);
static bool IsKeyPressed(Keys key)
{
short state = GetKeyState((int)key);
return ((state & 128) != 0);
}
int i = 0;
Dictionary<Keys, DateTime> downSince = new Dictionary<Keys, DateTime>();
private void UpdateKeyStates()
{
foreach (var entry in downSince.ToArray())
{
if (!IsKeyPressed(entry.Key))
downSince.Remove(entry.Key);
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
UpdateKeyStates();
if (!downSince.ContainsKey(e.KeyCode))
{
downSince.Add(e.KeyCode, DateTime.UtcNow);
i++;
}
Text = i.ToString() + " " +(int)(DateTime.UtcNow - downSince[e.KeyCode]).TotalMilliseconds;
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
UpdateKeyStates();
}
This example counts i up every time a key is pressed, and shows for how long it has been pressed. It uses GetKeyState instead of tracking KeyDown/KeyUp since you might miss those messages if something else has focus.
According to the documentation, "[d]uplicate KeyDown events occur each time the key repeats, if the key is held down, but only one KeyUp event is generated when the user releases the key."
So the simplest solution is to ignore a repeated KeyDown event unless its corresponding KeyUp event has been seen.
Just worked for me.
Use Timers instead: initialize timers, one for each "action" (e.g. pressing d/j or s/k) move the red hit/blue hit code inside the timer and instead of your current code, have this:
if (e.KeyChar.Equals('d') || e.KeyChar.Equals('j'))
{
//red hit
if (!tmrRedHit.Enabled)
tmrRedHit.Enabled = true;
}
else if (e.KeyChar.Equals('s') || e.KeyChar.Equals('k'))
{
//blue hit
if (!tmrBlueHit.Enabled)
tmrBlueHit.Enabled = true;
}
And in the timers Elpased event also set their Enabled to false after the code is executed.