I'm new to C#, trying to make a program that is essentially a survey with 30 questions that are answered by selecting one of five radio buttons (Strongly Disagree, Disagree...Strongly Agree, etc).
I have set up a small "block" of code that will check which radio button is checked for the question and assign a value to an array (see below).
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void buttonScore_Click(object sender, EventArgs e)
{
this.textBoxScoreOutput.Text = " ";
int[] score = new int[2]; // Declares the integer of score and sets it to a value of zero
// Question 1
if (radioButtonSD1.Checked == true) // If Strongly Disagree checked give score a value of 1
score[0] = 1;
else if (radioButtonD1.Checked == true) // If Disagree checked give score a value of 2
score[0] = 2;
else if (radioButtonNS1.Checked == true) // If Not Sure checked give score a value of 3
score[0] = 3;
else if (radioButtonA1.Checked == true) // If Agree checked give score a value of 4
score[0] = 4;
else if (radioButtonSA1.Checked == true) // If Strongly Agree is checked give score a value of 5
score[0] = 5;
// Question 2
if (radioButtonSD2.Checked == true) // If Strongly Disagree checked give score a value of 1
score[1] = 1;
else if (radioButtonD2.Checked == true) // If Disagree checked give score a value of 2
score[1] = 2;
else if (radioButtonNS2.Checked == true) // If Not Sure checked give score a value of 3
score[1] = 3;
else if (radioButtonA2.Checked == true) // If Agree checked give score a value of 4
score[1] = 4;
else if (radioButtonSA2.Checked == true) // If Strongly Agree is checked give score a value of 5
score[1] = 5;
// Output values in array to text box
this.textBoxScoreOutput.Text = "Array: ";
foreach (int i in score)
{
this.textBoxScoreOutput.Text += "[" + i.ToString() + "] ";
}
int sum = score.Sum();
this.textBoxScoreOutput.Text += "The Sum of the array is: " + sum.ToString();
}
}
}
So this is checking the first two of the thirty questions and is working exactly how I need it to and thought it would.
I was wondering if I could loop just one of these "blocks" and have it check all thirty questions. I have searched and searched but can't find exactly what I am looking for (I also understand I may not be searching for the right thing either).
I am just trying to avoid having thirty of these "blocks" in my program. I feel like it would just be a mess with thirty of these. Is this possible?
Start with creating a UserControl which encapsulates the logic for a single question:
Question Text
Selected option
Once you have a single question working, you can drop any number of User Controls on to a form, configure the question text and then only have to loop through the set of user controls to get your answers. The answer would be best returned as an enumeration.
There are a number of ways to achieve this, such as code to generate the controls, or binding the selections back to a ViewModel class, but a user control is a great start.
Here's how I do it roughly:
var resultList = new List<KeyValuePair<string, int>>();
foreach (var control in this.Controls)
{
if (control is GroupBox)
{
GroupBox gb = (GroupBox)control;
foreach (Control controll in gb.Controls)
{
if (controll is RadioButton)
{
RadioButton rb = new RadioButton();
rb = (RadioButton)controll;
//rb will allow you to access all of the RadioButton's properties and act accordingly.
if (rb.Checked)
{
int score;
if (rb.Name.Contains("ButtonSD"))
score = 1;
if (rb.Name.Contains("ButtonD"))
score = 2;
//So on...
resultList.Add(new KeyValuePair<string, int>(gb.Name, score));
}
}
}
}
}
Had a rough day so maybe someone can come up with something better, but if you don't feel like reorganizing the whole thing, this might work.
Related
I have a list of player numbers(int list), I also have a number of laps (int laps). I have to enter the number of the player into a text box each time the player completes a lap. If valid, the player number entered into the text box will be sent to a list. However, each player number cannot be entered more than the number of laps.
I'm new to windows forms so I've only used lists to solve the problem, I've seen people use dictionary to solve similar issues but I'm not quite sure how to incorporate that to solve this issue.
private void textBox2_TextChanged(object sender, EventArgs e)
{
bool result;
int RunnerNumberInOrder;
repeat2 = (getLaps2 * rows2);
textBox2.Focus();
//ReadPlayerNumber is the list that contains the list of the runners in the race.
//PlayerNumberInOrder int list is a global list and it is the list that the runners number order gets sent to if valid
result = int.TryParse(textBox2.Text, out RunnerNumberInOrder); //checks if the value entered is an integer
if (result == false || !ReadRunnerNumbers2.Contains(RunnerNumberInOrder)) //if the value entered is not an integer an error message will pop up and ask to enter a valid number
{
label3.Text = "Please enter a valid player number from the list";
}
else
{
if (RunnerOrder.Contains(RunnerNumberInOrder))
{
count++;
}
if (count == getLaps2)
{
count = 0;
label3.Text = "max laps for this runner has been entered";
invalid.Add(RunnerNumberInOrder); //invalid is an int list that I used to send the numbers of the runners that have completed all laps.
textBox2.Clear();
return;
}
}
if (invalid.Contains(RunnerNumberInOrder))
{
MessageBox.Show("Max number of laps for this runner has been entered");
textBox2.Clear();
}
if (result == true && ReadRunnerNumbers2.Contains(RunnerNumberInOrder) && !invalid.Contains(RunnerNumberInOrder))
{
RunnerOrder.Add(RunnerNumberInOrder); //if number entered is valid, the it is added to the global variable
PlayerNumbersEntered++;
label8.Text = RunnerNumberInOrder.ToString();//counts how many valid car numbers have been enterted
textBox2.Clear();
}
}
The code I have kinda works but it sometimes only allows one number 2 times when the number of laps is 3 and sometimes will allow more than the laps entered. I want an error message to pop up when the player's number equals to the number of laps and the number shouldn't be allowed any further. I also have to use a text_change. Cheers for the help in advance.
The main issue I see in your code I believe is the count variable which appears to be a global variable but you seems to use it like is count for specific RunnerNumbers.
The way I see it you could solve this by either creating a class of Runners that would have a number ID and the Count or, even simpler, to just get the count of that runner on the go as follows:
specificRunnerCount = RunnerOrder.Where(x => x == RunnerNumberInOrder).Count()
With this you could modify your code to something like this:
int specificRunnerCount = RunnerOrder.Where(x => x == RunnerNumberInOrder).Count();
if ((specificRunnerCount + 1) == getLaps2) // +1 because we haven't added this number to the count yet.
{
label3.Text = "max laps for this runner has been entered";
invalid.Add(RunnerNumberInOrder);
textBox2.Clear();
return;
}
// Continue code as is
Also, from your code, the part of code where you add a value to the invalid list: invalid.Add(RunnerNumberInOrder);, you use a return statement which ends the function. Due to this I don't see the need for you to check if the invalid list contains the runner number in if (invalid.Contains(RunnerNumberInOrder))
Hope this helps you.
This is my first post. So..critique is always welcomed.
My question is straight forward just by looking at the title.
How can I use a loop to insert values to different labels by using their reference (get,set methods are in a different form)
What I've tried is to create an array with the references of the labels. The thing is.. it assigns the new values to the array rather than changing the reference which will change the label.
I find it a bit difficult to explain it further than that.
If you have any questions, I will try to answer them best to my knowledge
private void button1_Click(object sender, EventArgs e)
{
int numOfPeriods = Convert.ToInt32(cmbPeriods.Text)-1;
string initialString = cmbStartTime.Text; //Gets the input from combo box "cmbStartTime".
string newTime;
decimal dec = Convert.ToDecimal(TimeSpan.Parse(initialString).TotalHours); //Converts the set by user Lesson start time to a decimal value.
decimal dec2;
decimal lessonLength = 1; // Default lesson length is set to 1 hour.
TimeSpan time;
Test FormOpen = new Test();
string[] periodStartTimes = new string[9] //Loop referencing the labels on Form TEST
{
FormOpen.startTime,FormOpen.startTime2, FormOpen.startTime3, FormOpen.startTime4,
FormOpen.startTime5, FormOpen.startTime6, FormOpen.startTime7, FormOpen.startTime8,
FormOpen.startTime9
};
if (cmbLessonLength.Text != "") //If the combo box "cmbLessonLength" is not empty, use that value instead of the default lesson lenght.
{
lessonLength = Convert.ToDecimal(cmbLessonLength.Text)/60; //Converts minutes to hours.
}
dec2 = dec + lessonLength;
time = TimeSpan.FromHours(Double.Parse(dec2.ToString()));
newTime = time.ToString();
if (newTime[0] == '0')
{
FormOpen.startTime = initialString + " - " + newTime.Remove(5).Remove(0, 1);
}
else
{
FormOpen.startTime = initialString + " - " + newTime.Remove(5);
}
for (int x = 1; x <= numOfPeriods; x++) //LOOP THAT ASSIGNS VALUES TO THE ARRAY AND NOT THE REFERENCE INSIDE IT
{
decimal workingNumber = lessonLength*x;
decimal Convert0 = dec + workingNumber;
TimeSpan Convert1 = TimeSpan.FromHours(Double.Parse(Convert0.ToString()));
string Convert2 = Convert1.ToString().Remove(5);
periodStartTimes[x] = Convert2;
}
FormOpen.subjectName = cmbSubjects.Text;
FormOpen.startTime2 = periodStartTimes[1]; //IT DOES WORK LIKE THIS
FormOpen.startTime3 = periodStartTimes[2];
FormOpen.ShowDialog();
}
I have provided the whole code, so it's clearer and if there's a more efficient way of coding this I would be really thankful if someone could give me a few tips.
Your code cannot work using that array periodStartTimes This is an array of strings and when you initialize it you get the value of the property (IE the current text of the labels) not a reference to a property that you can use to change the labels.
In any case it is a bad practice to allow a different class instance change the internal values of another class. It is better to provide a public method used by the external classes to change the internal properties.
So for example your Test form class could have a public method named as SetTextLabel
public class Test : Form
{
....
public void SetLabelText(string name, string value)
{
switch(name)
{
case "startTime":
this.labelForStartTime.Text = value;
break;
case "label1":
this.firstLabelToUpdate.Text = value;
break;
...
// and so on for all other labels
}
}
}
In this way the code that changes the label is inside the Test class and you can add all the checks and validations required with full access to all the internal variables of the Test class.
Now your loop can be rewritten as
for (int x = 1; x <= numOfPeriods; x++)
{
decimal workingNumber = lessonLength*x;
decimal Convert0 = dec + workingNumber;
TimeSpan Convert1 = TimeSpan.FromHours(Double.Parse(Convert0.ToString()));
string Convert2 = Convert1.ToString().Remove(5);
FormOpen.SetLabelText("label" + x.ToString, Convert2;
}
Of course now you don't need anymore the array, and this will probably remove another error present in your actual code. The array is fixed at 9 elements but you loop for numOfPeriods starting from 1 (thus skipping the first element) and if numOfPeriods is bigger than 8 your code will crash with IndexOutOfRange exception
1 Choose Normal Or special
2 Choose special Price+5
3 Choose Normal Price-5
4 But Choose same is Price double
private void comboType_SelectedIndexChanged(object sender, EventArgs e)
{
if (lblProID.Text == "")
{
//MessageBox.Show("Please Find Food");
txtAmount.Text = "";
buttonFind.Focus();
}
else if (comboType.SelectedIndex == 1)
{
lblProCost.Text = (Convert.ToDouble(lblProCost.Text) + (5)).ToString("#,##0.00");
}
else
{
lblProCost.Text = (Convert.ToDouble(lblProCost.Text) - (5)).ToString("#,##0.00");
}
}
Logic IF Select Normal (Price -5)
else Select special (Price +5)
I want to check If Select same index not Sum price
Or You have idea New For Help Me ! Thank
The easiest way would be to create a variable / property on the form level that stores the previous combobox selected index value and you could compare comboType.selectedIndex with that variable - if the same do nothing, else do something and overwrite this variable.
The other way is to derive your own class from the combobox and implement the variable on the combobox devided class level + add a method that checks if selected index is the same - return true if not return false but also overwrite the old selected index value.
I'm making a sudoku in Windows Form Application.
I have 81 textboxes and I have named them all textBox1a, textBox1b... textBox2a, textBox2b...
I want to make it so that if any of the textboxes, in any of the rows, is equal to any other textbox in the same row, then both will get the background color red while the textboxes are equal.
I tried using this code just for test:
private void textBox1a_TextChanged_1(object sender, EventArgs e)
{
while (textBox1a.Text == textBox1b.Text)
{
textBox1a.BackColor = System.Drawing.Color.Red;
textBox1b.BackColor = System.Drawing.Color.Red;
}
It didn't work, and I don't know where I should put all this code, I know I shouldn't have it in the textboxes.
Should I use a code similar to this or is it totally wrong?
You want to iterate over the collection of text boxes just once, comparing it to those that haven't yet been compared against. If you have your textboxes in an array (let's call it textBoxes), and know which one was just changed (e.g. from the textChanged handler), you could do:
void highlightDuplicates(int i) // i is the index of the box that was changed
{
int iVal = textBoxes[i].Text;
for (int j = 0; j < 82; j++)
{
// don't compare to self
if (i == j) return;
if (textBoxes[j].Text == iVal)
{
textBoxes[i].BackgroundColor = System.Drawing.Color.Red;
textBoxes[j].BackgroundColor = System.Drawing.Color.Red;
}
}
}
If you wanted to get fancier, you could put your data in something like: Dictionary<int, TextBox>, where the key is the value and the TextBox is a reference to the text box with that value. Then you can quickly test for duplicate values with Dictionary.Contains() and color the matching text box by getting its value.
I think your current code would result in an infinite loop. The textboxes' values can't change while you are still in the event handler, so that loop would never exit.
If all of your boxes are named according to one convention, you could do something like this. More than one input can use the same handler, so you can just assign this handler to all the boxes.
The following code is not tested and may contain errors
private void textBox_TextChanged(object sender, EventArgs e){
var thisBox = sender as TextBox;
//given name like "textBox1a"
var boxNumber = thisBox.Name.SubString(7,1);
var boxLetter = thisBox.Name.SubString(8,1);
//numbers (horizontal?)
for(int i = 1; i<=9; i++){
if(i.ToString() == boxNumber)
continue; //don't compare to self
var otherBox = Page.FindControl("textBox" + i + boxLetter) as TextBox;
if (otherBox.Text == thisBox.Text)
{
thisBox.BackColor = System.Drawing.Color.Red;
otherBox.BackColor = System.Drawing.Color.Red;
}
}
//letters (vertical?)
for(int i = 1; i<=9; i++){
var j = ConvertNumberToLetter(i); //up to you how to do this
if(j == boxLetter)
continue; //don't compare to self
var otherBox = Page.FindControl("textBox" + boxNumber + j) as TextBox;
if (otherBox.Text == thisBox.Text)
{
thisBox.BackColor = System.Drawing.Color.Red;
otherBox.BackColor = System.Drawing.Color.Red;
}
}
}
I believe you will be more effective if create an Array (or a List) of Integers and compare them in memory, against compare them in UI (User Interface).
For instance, you could:
1) Create an Array of 81 integers.
2) Everytime the user input a new number, you search for it in that Array. If found, set the textbox as RED, otherwise, add the new value to that array.
3) The ENTER event may be allocated fot the entire Textboxes (utilize the Handles keyword with all Textboxes; like handles Text1.enter, Text2.enter, Text3.enter ... and so forth)
Something like:
int[] NumbersByUser = new int[81];
Private Sub Textbox1.Enter(sender as object, e as EventArgs) handles Textbox1.Enter, Textbox2.Enter, Textbox3.enter ...
int UserEntry = Convert.ToInt32(Sender.text);
int ValorSelecionado = Array.Find(NumbersByUser, Z => (Z == UserEntry));
if (ValorSelecionado > 0) {
Sender.forecolor = Red;
}
else
{
NumbersByUser(Index) = UserEntry;
}
You should have a 2 dimensional array of numbers (could be one dimensional, but 2 makes more sense) let's assume its called Values. I suggest that you have each textbox have a incrementing number (starting top left, going right, then next row). Now you can do the following:
All TextBox Changed events can point to the same function. The function then takes the tag to figure out the position in the 2dim array. (X coordinate is TAG % 9 and Y coordinate is TAG / 9)
In the callback you can loop over the textboxes and colorize all boxes as you like. First do the "check row" loop (pseudo code)
var currentTextBox = ((TextBox)sender)
var x = ((int)currentTextBox.Tag) % 9
var y = ((int)currentTextBox.Tag) / 9
// First assign the current value to the backing store
Values[currentTextBox] = int.parse(currentTextBox.Text)
// assuming variable x holding the column and y holding the row of current box
// Array to hold the status of a number (is it already used?)
bool isUsed[9] = {false, false, ...}
for(int col = 0; col <= 9; i++)
{
// do not compare with self
if(col == x) continue;
isUsed[textBox] = true;
}
// now we have the status of all other boxes
if( isUsed[Values[x,y]] ) currentTextBox.Background = Red else currentTextBox.Background = Green
// now repeat the procedure for the column iterating the rows and for the blocks
I would suggest a dynamic approach to this. Consider each board item as a cell (this would be it's own class). The class would contain a numeric value and other properties that could be useful (i.e. a list of possible values).
You would then create 3 collections of the cells, these would be:
A collection of rows of 9 cells (for tracking each row)
A collection of columns of 9 cells (for tracking each column)
A collection of 3x3 cells
These collections would share references - each cell object would appear once in each collection. Each cell could also contain a reference to each of the 3 collections.
Now, when a cell value is changed, you can get references to each of the 3 collections and then apply a standard set of Sudoku logic against any of those collections.
You then have some display logic that can walk the boards of cells and output to the display (your View) your values.
Enjoy - this is a fun project.
I am trying to figure out a way to get the Computer player to respond to my moves basically by seeing "This spot is taken, I should see if another is free and take it".
So far, I'm not making any improvements (been like 5 hours). I want the computer to realize if a certain button (which it chose at random) is taken, it should consider another choice. Not sure where the if/else should actually go or where/what I should put in for it to try another location.
Here's the snippet of code with comments on my idea (likely wrong placement where I want to do things):
if (c.Enabled == true) //if the button is free
{
if ((c.Name == "btn" + Convert.ToString(RandomGenerator.GenRand(1, 9)) )) //if a specific button is free
{
if ((c.Text != "X")) //if its empty
{
//do this
c.Text = "O"; //O will be inside the button
c.Enabled = false; //button can no long be used
CheckComputerWinner(); //check if it finishes task
return;
}
else //if it is an X
{
//try a different button choice instead
//see if that button is empty
//do stuff
//else
//... repeat until all buttons are checked if free
}
}
}
My question is simply: How can I fix this and understand what is going on? Or do it more efficiently?
You could create an array with those buttons so you don't have to check names:
Button[9] gameField;
Creating a [3,3] array might be more intuitive, but for this case a plain array is easier to work with.
Then, you could count how many of them are free:
int freeCount = gameField.Count(b => b.Text != "X");
If you want to randomly select one of the free ones, generate a random number in the range 0 - (freeCount - 1) and select the appropriate button:
int offset = RandomGenerator.GenRand(0, freeCount - 1);
Button target = gameField.Where(b => b.Text != "X").Skip(offset).FirstOrDefault();
if (target != null) {
// check it
}
The extension method Where filters your buttons to only return the free ones. Skip will skip the specified number of elements (for random selection) and FirstOrDefault returns the first element of the resulting sequence (or null if there is none).
Note: You might want to check for some cases before randomly selecting a field to make your AI a bit more ambitious:
Has the computer player two O in a row with the third field being free? If so, select that one.
Has the human player two X in a row with the third field being free? If so, select that one.
There are tricks to exploit that strategy and there are also better heuristics, but i'll leave that to you.
Your looking for a while loop here. You can modify your code as follows:
Button c;
// here you look for a Button within the Controls of your Form. It stops when an Enabled Button with Text != "X" is found
do
{
c = this.Controls.Find("btn" + Convert.ToString(RandomGenerator.GenRand(1, 9)), true).FirstOrDefault() as Button;
} while (c == null || !c.Enabled || c.Text == "X");
c.Text = "O"; //O will be inside the button
c.Enabled = false; //button can no long be used
CheckComputerWinner(); //check if it finishes task
return;