Best approach to add keyboard "Shortcuts/Hotkeys" to control my application - c#

I'm working on a desktop application in which I want to allow the user to control it's functionalities via Keyboard, also allow him to change the default controllers to a custom controllers according to his point of view.
My question is, what is the best approach to tackle this problem and provide an appropriate solution to this problem?

You can use a Dictionary to bound each key to an action
My approach here is to use a Dictionary where the Key is actual Keybord Keys and value is an int? representing one of your function that can be bound to a custom input.
Dictionnary<Keys, int?> shortcutDictionnary = new Dictionary<Keys, int?>();
// Add a new Keys
shortcutDictionary.Add(Keys.A, 1);
// Change a Keys value (change shortcut bounded to it)
shortcutDictionary[Keys.A] = 4;
To match those int? with these functions you only have to use a switch :
int? num = null;
if (this.shortcutDictionary.TryGetValue(keyPressed, out num))
{
switch (num)
{
case 1:
attack();
break;
case 2:
defend();
break;
case 3:
hide();
break;
case 4:
dance();
break;
default:
Console.WriteLine("Key not bounded");
break;
}
}
I also in my code below use an enum instead of direct use of Keys for my Dictionary. This way, I can choose which Keys can be bounded and which one can't.
My code made from a Winform app, as an exemple I only used 4 Keys (A,B,C,D) which can be bound and one to easily change bound (L), but I'm sure you can figure out how to change bound easily with any other method. Also as I'm working with a WindowsForm, I had to set KeyPreview = true.
Here's my code :
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Project
{
public enum UsableKeys
{
A = Keys.A,
B = Keys.B,
C = Keys.C,
D = Keys.D,
}
public partial class Form1 : Form
{
Dictionary<UsableKeys, int?> shortcutDictionary = new Dictionary<UsableKeys, int?>();
public Form1()
{
InitializeComponent();
foreach (UsableKeys key in Enum.GetValues(typeof(UsableKeys)))
{
// You may add default shortcut here
this.shortcutDictionary.Add(key, null);
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
UsableKeys keyPressed = (UsableKeys)e.KeyCode;
if (this.shortcutDictionary.ContainsKey(keyPressed))
{
executeAction(keyPressed);
e.Handled = true;
e.SuppressKeyPress = true;
}
else if (e.KeyCode == Keys.L)
{
switch (this.shortcutDictionary[UsableKeys.A])
{
case 1:
this.shortcutDictionary[UsableKeys.A] = 4;
this.shortcutDictionary[UsableKeys.B] = 3;
this.shortcutDictionary[UsableKeys.C] = 2;
this.shortcutDictionary[UsableKeys.D] = 1;
break;
case null:
this.shortcutDictionary[UsableKeys.A] = 1;
this.shortcutDictionary[UsableKeys.B] = 2;
this.shortcutDictionary[UsableKeys.C] = 3;
this.shortcutDictionary[UsableKeys.D] = 4;
break;
case 4:
this.shortcutDictionary[UsableKeys.A] = null;
this.shortcutDictionary[UsableKeys.B] = null;
this.shortcutDictionary[UsableKeys.C] = null;
this.shortcutDictionary[UsableKeys.D] = null;
break;
}
e.Handled = true;
e.SuppressKeyPress = true;
}
}
private void executeAction(UsableKeys keyPressed)
{
int? num = null;
if (this.shortcutDictionary.TryGetValue(keyPressed, out num))
{
switch (num)
{
case 1:
attack();
break;
case 2:
defend();
break;
case 3:
hide();
break;
case 4:
dance();
break;
default:
Console.WriteLine("Key not bounded");
break;
}
}
}
private void attack()
{
Console.WriteLine("Player swing his word");
}
private void defend()
{
Console.WriteLine("Player raise his shield");
}
private void hide()
{
Console.WriteLine("Player sneak around");
}
private void dance()
{
Console.WriteLine("Player start to dance");
}
}
}
With this code, output will be like :
// Press A, B, C or D
"Key not bounded"
// Press L
// Press A
"Player swing his word"
// Press B
"Player raise his shield"
// Press C
"Player sneak around"
// Press D
"Player start to dance"
// Press L
// Press A
"Player start to dance"
// Press B
"Player sneak around"
// Press C
"Player raise his shield"
// Press D
"Player swing his sword"
// Press L
// Press A, B, C or D
"Key not bounded"
Exemple to change key binding in run time :
// Create a new Dictionary for shortcuts
Dictionary<UsableKeys, int?> shortcutDictionary = new Dictionary<UsableKeys, int?>();
// Add a pair key/value that bind A to attack()
shortcutDictionary.Add(UsableKey.A, 1);
// Add a pair Key/value that bind B to defend()
shortcutDictionary.Add(UsableKey.B, 2);
// Now, if you press A, attack() will be called
shortcutDictionary[UsableKey.A] = 2;
// Now if you press A or B, defend() will be called
shortcutDictionary[UsableKey.B] = null;
// Now B isn't bind to any function, so only A is binded to defend();
With this method, you can't bind multiple functions to one Keys while you can bind multiple Keys to one function (If you want to inverse that, just swap key/value of Dictionary and adjust the code to match this).
I don't know if it's the optimal way to do this, but it isn't spaghetti code and it works well.

Related

The conditions for when user wins for slot machine isnt working properly

I am trying to make a slot machine game in c# where if all 3 slots match, the user wins 2x their bet and if 2 of the slots match, then the user wins their bet back and if none of the slots match then the user losses their bet but sometimes when there are 2 slots that match, the user doesnt win their money back and sometimes when there are no slots that match, the user wins their money back and sometimes when the user gets 3 matches, they win their money back but not double. How do I also make it so that if all 3 slots have a jackpot image, the user wins 5x their bet
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlTypes;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace Slot_Machine
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Random rnd = new Random();
int a, b, c, move, wins,losses, bid;
int balance = 100;
private void QuitBtn_Click(object sender, EventArgs e)
{
this.Close();
}
private void ResetBtn_Click(object sender, EventArgs e)
{
Application.Restart();
}
void Before_Game_Result()
{
bid = Convert.ToInt32(BidAmountTxt.Text);
balance = balance - bid;
BalanceLbl.Text = "Balance: $" + Convert.ToString(balance);
}
void Game_Result()
{
if (System.Convert.ToInt32(a) == b && System.Convert.ToInt32(b) == c)
{
wins++;
WinLbl.Text = "Wins: " + wins;
bid = Convert.ToInt32(BidAmountTxt.Text);
balance = balance + (bid * 2);
BalanceLbl.Text = "Balance: $" + Convert.ToString(balance);
BidBtn.Enabled = true;
BidAmountTxt.Enabled = true;
BidAmountTxt.BackColor = Color.White;
}
else
{
if ((System.Convert.ToInt32(a) == b && System.Convert.ToInt32(b) != c) || (System.Convert.ToInt32(a) == c && System.Convert.ToInt32(b) != c) ||
(System.Convert.ToInt32(b) == c && System.Convert.ToInt32(a) != c))
{
wins++;
WinLbl.Text = "Wins: " + wins;
bid = Convert.ToInt32(BidAmountTxt.Text);
balance = balance + bid;
BalanceLbl.Text = "Balance: $" + Convert.ToString(balance);
BidBtn.Enabled = true;
BidAmountTxt.Enabled = true;
BidAmountTxt.BackColor = Color.White;
}
else
{
losses++;
LossesLbl.Text = "Losses: " + losses;
BalanceLbl.Text = "Balance: $" + Convert.ToString(balance);
BidBtn.Enabled = true;
BidAmountTxt.Enabled = true;
BidAmountTxt.BackColor = Color.White;
}
}
if (balance <= 0)
{
MessageBox.Show("You don't have any money!! SCRAM");
this.Close();
}
}
private void BidBtn_Click(object sender, EventArgs e)
{
if(BidAmountTxt.Text =="")
{
MessageBox.Show("input a bid first!!");
}
else
{
int x = Convert.ToInt32(BidAmountTxt.Text);
bool success = false;
if(x <= balance)
{
success = true;
}
if(success)
{
Before_Game_Result();
timer1.Enabled = true;
BidAmountTxt.Enabled = false;
BidBtn.Enabled = false;
BidAmountTxt.BackColor = Color.Black;
}
else
{
MessageBox.Show("INVALID - please enter a bid lower or equal to your balance");
BidAmountTxt.Clear();
}
}
}
private void timer1_Tick(object sender, EventArgs e)
{
move++;
if (move < 30)
{
a = rnd.Next(5);
b = rnd.Next(5);
c = rnd.Next(5);
switch(a)
{
case 0:
Slot1.Image = Properties.Resources.basketball3;
break;
case 1:
Slot1.Image = Properties.Resources.soccer_ball2;
break;
case 2:
Slot1.Image = Properties.Resources.volleyball;
break;
case 3:
Slot1.Image = Properties.Resources.hockey_puck;
break;
case 4:
Slot1.Image = Properties.Resources.Jackpot;
break;
}
switch (b)
{
case 0:
Slot2.Image = Properties.Resources.basketball3;
break;
case 1:
Slot2.Image = Properties.Resources.soccer_ball2;
break;
case 2:
Slot2.Image = Properties.Resources.volleyball;
break;
case 3:
Slot2.Image = Properties.Resources.hockey_puck;
break;
case 4:
Slot2.Image = Properties.Resources.Jackpot;
break;
}
switch (c)
{
case 0:
Slot3.Image = Properties.Resources.basketball3;
break;
case 1:
Slot3.Image = Properties.Resources.soccer_ball2;
break;
case 2:
Slot3.Image = Properties.Resources.volleyball;
break;
case 3:
Slot3.Image = Properties.Resources.hockey_puck;
break;
case 4:
Slot3.Image = Properties.Resources.Jackpot;
break;
}
}
else
{
timer1.Enabled = false;
move = 0;
Game_Result();
}
}
}
}
The problem is probably you are ignoring one valid return value of rnd.Next(5). Ie, you ignore, that Next may also return 0. Thus, if for one of a, b or c you have a value of 0, you don't change the image. Thus on the screen you may see a winning situation where there is none or vice versa ...
So change your switches for a, b and c to
switch(a) {
case 0: ...
case 1: ...
case 2: ...
...
}
to also deal with a value of 0. And either introduce an additional symbol or change your random function to rnd.Next(4) to return values from 0..3
But there are also some other remarks about your code
System.Convert.ToInt32(a) == b does not make any sense, as all of a, b and c are already integers. You can simply compare if (a == b)
When checking your "2-matches" winning situation (System.Convert.ToInt32(a) == b && System.Convert.ToInt32(b) != c) you don't need to check the inequality of b and c because at this position in the code you already know, that not all three values are equal (because otherwise your code would have entered the a==b && b==c branch. Thus if a == b, c must be different. No need for checking it again. Same of course for all other "2-matches" checks.

How can l change PictureBox when user does not estimate a letter correct?

I'm trying to make a hangman game in C#. I've done most of the work. I use dynamic buttons as keyboard. My problem is, that when user clicks on a letter on keyboard that is not present in the word that user should estimate, picturebox should replaced with picturebox2, and if his estimation is again wrong, replace with picturebox3, go on until final picturebox. However, if user estimates the letter correctly, picturebox should not replaced. I tried to do this, but my code does not work, as when I click on a button, picturebox keep changing until the last picture even if my estimation is correct or not. where is my mistake?
Here is the part of my code:
void btn_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
btn.BackColor = Color.Aqua;
btn.ForeColor = Color.Red;
int a = 0;
// xy is the word that user should estimate
for (int i = 0; i < xy.Length; i++)
{
if (this.Controls.Find("txt" + i, true)[0].Text == btn.Text)
{
this.Controls.Find("txt" + i, true)[0].Text = btn.Text;
this.Controls.Find("txt" + i, true)[0].BackColor = Color.White;
}
else
{
a++;
switch(a)
{
case 1:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton1.png");
break;
case 2:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton2.png");
break;
case 3:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton3.png");
break;
case 4:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton4.png");
break;
case 5:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton5.png");
break;
default:
break;
}
}
}
}
First mistake is what Hans mentioned in comments: your variable must be a field of the class and not a local variable on the function, and must reset on each new game.
Second mistake: you check the letter the player clicked against every letter on the word, and for everytime it doesn't match, you add one to your error counter. You must add 1 only after you have finished checking all the letters in your word.
void btn_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
btn.BackColor = Color.Aqua;
btn.ForeColor = Color.Red;
bool found = false;
// xy is the word that user should estimate
for (int i = 0; i < xy.Length; i++)
{
if (this.Controls.Find("txt" + i, true)[0].Text == btn.Text)
{
this.Controls.Find("txt" + i, true)[0].Text = btn.Text;
this.Controls.Find("txt" + i, true)[0].BackColor = Color.White;
found = true;
}
}
if (!found)
{
a++;
switch(a)
{
case 1:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton1.png");
break;
case 2:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton2.png");
break;
case 3:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton3.png");
break;
case 4:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton4.png");
break;
case 5:
pictbx.Image = Image.FromFile("D:/Csharp_Pro/Games/Mine/hangman/hangman/skeleton5.png");
break;
default:
break;
}
}
}
It still has a lot of room for improvement, buy I decided to stop here so you can eventually find it out by yourself while you're learning.

Radiobuttons, how to replace all those if statements

Several places in my program, the RadioButton matching the selected item has to be checked, and I have a lot of if statements like so:
DataRowView TempRow = (DataRowView)ScheduleDataGrid.SelectedItem;
if (Convert.ToString(TempRow["Bio"]) == "Bio1")
{
BioRB1.IsChecked = true;
}
if (Convert.ToString(TempRow["Bio"]) == "Bio2")
{
BioRB2.IsChecked = true;
}
if (Convert.ToString(TempRow["Bio"]) == "Bio3")
and so on... I want to replace all this with something short and smart.
I tried using the number of the bio to relate to the button like so:
string bioselected = Convert.ToString(TempRow["Bio"]);
int i = Convert.ToInt16(bioselected.Substring(bioselected.Length - 1, 1));
BioRB[i].IsChecked = true;
but doing a BioRB[i] doesn't work, it ignores the [i] and says BioRB does not exist. Any other suggestions?
BioRB[i] is not doing anything like what you think it's doing. All variable references (controls included) have to be well-defined at compile time - you can't refer to a control's name by building a string that matches the name.**
Try creating a list of your radio buttons. Then you can index into the list:
List<RadioButton> radioButtons = new List<RadioButton>()
{
BioRB1,
BioRB2
};
string bioselected = Convert.ToString(TempRow["Bio"]);
int i = Convert.ToInt16(bioselected.Substring(bioselected.Length - 1, 1));
radioButtons[i].IsChecked = true;
** Technically you can do this via reflection, but it's far more complex than what you've tried.
Maybe this will look better:
string caseSwitch = Convert.ToString(TempRow["Bio"]);
switch (caseSwitch)
{
case "Bio1":
BioRB1.IsChecked = true;
break;
case "Bio2":
BioRB2.IsChecked = true;
break;
case "Bio3":
BioRB3.IsChecked = true;
break;
default:
Console.WriteLine("Default case...is optional");
break;
}
Also, try doing what Alybaba726 said and use CellContentClick or something like this:
private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
DataGridView dgv = (DataGridView)sender;
if(e.ColumnIndex == dgv.Columns["Bio"].Index)
{
string bioSelected = dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();
switch (bioSelected)
{
case "Bio1":
BioRB1.IsChecked = true;
break;
case "Bio2":
BioRB2.IsChecked = true;
break;
case "Bio3":
BioRB3.IsChecked = true;
break;
default:
Console.WriteLine("Default case...this is optional");
break;
}
}
}

What code would I place in my enter case?

I've made a switch-case where I use the up arrow key, down arrow key and enter key. However I can't think of how to put code into my enter case in which I can choose an option.
public static void entries()
{
keyPressed = Console.ReadKey(true);
switch (keyPressed.Key)
{
case ConsoleKey.DownArrow:
if (keyPressed.Key.ToString() == "DownArrow")// selects the curitem when the down arrow key is pressed
{
curItem++;
if (curItem > menuItems.Length - 1) curItem = 0;
}
break;
case ConsoleKey.UpArrow:
if (keyPressed.Key.ToString() == "UpArrow")// selects the curitem when the up arrow key is pressed
{
curItem--;
if (curItem < 0) curItem = Convert.ToInt16(menuItems.Length - 1);
}
break;
case ConsoleKey.Enter:
if (keyPressed.Key.ToString() == "Enter")// when enter is pressed it will go to one of the choices
{
}
break;
default:
break;
}
}
Your idea about nesting if statements is correct, or having another switch. I would probably re-factor this into another function though:
case ConsoleKey.Enter:
chooseOption(curItem);
break;
...
void chooseOption(int item)
{
switch(item)
{
case 1:
//Do item 1
break;
case 2:
//Do item 2
break;
case 3:
//Do item 3
break;
}
}
Based on your comment. here is how you can display the current item inside the menuItems array. I'm assuming menuItems holds some strings or ints or something that can be easily written to the console.
case ConsoleKey.Enter:
// when enter is pressed it will go to one of the choices
Console.Clear();
Console.WriteLine(menuItems[curItem]);
break;

Using threestate checkbox to determine 3 states of a field

Currently we call a web service to check if user has permission to use the chat room and we receive a 0 or a 1. I want to add to also be able to receive a 2, which will indicate it is enabled and authorized now
so I want to use three state check boxes, fully filled will mean he is authorized and also currently enabled (logged in), check will mean he is authorized but not enabled now, unchecked will mean he doesn't have permission for this chat room.
Here is some code I currently use. FYI I took over this project from our developer and I'm kind of new to this, if you can give me an example even just for authtac1 then I'll figure out how to do all others, and also do I need to add a new field ActiveTac1 like I have AuthTac1? Thanks for your help.
public partial class IMeditor : Form
{
private IMuser imu;
private IMui IMui;
private IMdata IMdata;
public IMeditor(IMui IMui, IMuser U, string Member)
{
InitializeComponent();
this.IMui= IMui;
imu = U;
if (imu.UID == 0)
{
Add.Text = "Add";
imu.MemberNick= Member;
}
else
Add.Text = "Update";
IMdata = new IMdata();
MemberHandle.Text = imu.MemberHandle;
IM.Text = imu.IM;
AuthChat.Checked = imu.AuthChat == 1;
AuthTac1.Checked = imu.AuthTac1 == 1;
AuthTac2.Checked = imu.AuthTac2 == 1;
AuthTac3.Checked = imu.AuthTac3 == 1;
AuthTac4.Checked = imu.AuthTac4 == 1;
switch (imu.Transport.ToLower()) {
case "aim": Transport.SelectedIndex = 0; break;
case "gtalk": Transport.SelectedIndex = 1; break;
case "msn": Transport.SelectedIndex = 2; break;
case "yahoo": Transport.SelectedIndex = 3; break;
}
private void Add_Click(object sender, EventArgs e)
{
IMdata.AddIM(IMui.Username, IMui.Password, imu.UID, MemberHandle.Text, Transport.Text, IM.Text,
AuthChat.Checked ? 1 : 0, 0,
AuthTac1.Checked ? 1 : 0, AuthTac2.Checked ? 1 : 0, AuthTac3.Checked ? 1 : 0,
AuthTac4.Checked ? 1 : 0);
Close();
}
last question, to make sure I didnt screw up anything else
#competemt_tech this is what I ended up using (thanks so so much for your help, and our developer who was able to assist me)
To know how to set the checkboxes when i get the data from the webservice I used
AuthChat.CheckState = GetCheckStateFromAuthCode(imu.AuthChat, imu.Enabled);
AuthTac1.CheckState = GetCheckStateFromAuthCode(imu.AuthTac1, imu.Tac1Enabled);
AuthTac2.CheckState = GetCheckStateFromAuthCode(imu.AuthTac2, imu.Tac2Enabled);
AuthTac3.CheckState = GetCheckStateFromAuthCode(imu.AuthTac3, imu.Tac3Enabled);
and then I used:
private CheckState GetCheckStateFromAuthCode(int AuthCode, int Enabled)
{
switch (AuthCode + Enabled)
{
case 0:
return CheckState.Unchecked; // Unauthorized
case 1:
return CheckState.Checked; // Authorized, not enabled
case 2:
return CheckState.Indeterminate; // Authorized and enabled
default:
return CheckState.Unchecked;
}
}
and after I make changes, to send back to the webservice I used:
imu.AuthChat, imu.Enabled = GetAuthCodeFromCheckState(AuthChat.CheckState),
imu.AuthTac1, imu.Tac1Enabled = GetAuthCodeFromCheckState(AuthTac1.CheckState),
imu.AuthTac2, imu.Tac2Enabled = GetAuthCodeFromCheckState(AuthTac2.CheckState),
imu.AuthTac3, imu.Tac3Enabled = GetAuthCodeFromCheckState(AuthTac3.CheckState),
and then I used:
private int GetAuthCodeFromCheckState(CheckState checkState)
{
switch (checkState)
{
case CheckState.Unchecked: // Unauthorized
return 0;
case CheckState.Indeterminate: // Authorized, not enabled
return 1;
case CheckState.Checked: // Authorized and enabled
return 2;
default:
return 0;
}
}
Based on your code, it appears that you are using WinForms, not WPF, correct?
If this is the case, then you will want to use the CheckState property of the checkbox and not Checked, since it supports three values:
Unchecked = 0
Checked = 1
Indeterminate = 2
Update
In order to address your requirements, you would have code similar to the following:
First, create a function that will generate the correct checkstate base on the authcode:
private CheckState GetCheckStateFromAuthCode(int AuthCode)
{
switch (AuthCode)
{
case 0:
// Unauthorized
return CheckState.Unchecked;
case 1:
// Authorized, not enabled
return CheckState.Indeterminate;
case 2:
// Authorized and enabled
return CheckState.Checked;
default:
throw new ArgumentException("Unrecognized AuthCode value " + AuthCode.ToString());
}
}
Then, use this code to set the state of your checkboxes:
AuthChat.CheckState = GetCheckStateFromAuthCode(imu.AuthChat);
AuthTac1.CheckState = GetCheckStateFromAuthCode(imu.AuthTac1);
etc.
Update 2
In order to retrieve the authcode from the checkstate:
First, create a function that will generate the correct checkstate base on the authcode:
private int GetAuthCodeFromCheckState(CheckState checkState)
{
switch (checkState)
{
case CheckState.Unchecked:
// Unauthorized
return 0;
case CheckState.Indeterminate:
// Authorized, not enabled
return 1;
case CheckState.Checked:
// Authorized and enabled
return 2;
default:
throw new ArgumentException("Unrecognized checkState value " + checkState.ToString());
}
}
Then, use this code to set the auth code from your checkboxes:
imu.AuthChat = GetAuthCodeFromCheckState(AuthChat.CheckState);
imu.AuthTac1 = GetAuthCodeFromCheckState(AuthTac1.CheckState);
etc.

Categories

Resources