I would like to use a numericupdown control on my application. I'm well aware that I could use a plain textbox instead, but I rather like the way this particular control's UI fits with what I'm doing in my application.
It also needs to have 0's at the left, per desired text output. If I'm not mistaken, this is not supported by standard numericupdown controls. It should never exceed 4 digits in length. However, if I input more, it must show favor to new keystrokes and drop left-most digits instead. The up and down arrows should increment/decrement the value per usual behavior. Even after keying in values.
It should never be allowed to run negative. It should only accept whole integers. This is easily handled by the stock functionality though.
Partly posting an answer for others who may follow. Partly looking for assurance that I'm not being an idiot.
Note that this hack depends on firing Sync() before extracting the final value. The timer will fire pretty quickly, but does not guarantee that things will happen in the correct order. It may not hurt to manually trigger Sync() immediately before extracting values.
public class UpDownWith0 : System.Windows.Forms.NumericUpDown
{
private System.Windows.Forms.Timer addzeros = new System.Windows.Forms.Timer();
public UpDownWith0()
{
this.addzeros.Interval = 500; //Set delay to allow multiple keystrokes before we start doing things
this.addzeros.Stop();
this.addzeros.Tick += new System.EventHandler(this.Sync);
}
protected override void OnTextBoxTextChanged(object source, System.EventArgs e)
{
this.addzeros.Stop(); //reset the elapsed time every time the event fires, handles multiple quick proximity changes as if they were one
this.addzeros.Start();
}
public void Sync(object sender, System.EventArgs e)
{
int val;
this.addzeros.Stop();
if (this.Text.Length > 4)
{
//I never want to allow input over 4 digits in length. Chop off leftmost values accordingly
this.Text = this.Text.Remove(0, this.Text.Length - 4);
}
int.TryParse(this.Text, out val); //could use Value = int.Parse() here if you preferred to catch the exceptions. I don't.
if (val > this.Maximum) { val = (int)this.Maximum; }
else if (val < this.Minimum) { val = (int)this.Minimum; }
this.Value = val; //Now we can update the value so that up/down buttons work right if we go back to using those instead of keying in input
this.Text = val.ToString().PadLeft(4, '0'); //IE: display will show 0014 instead of 14
this.Select(4, 0); //put cursor at end of string, otherwise it moves to the front. Typing more values after the timer fires causes them to insert at the wrong place
}
}
Related
I'm making autoclicker program and I already have an issue. I want my program to be able to change the clicks per second field just how the user wants it. So I made this.
private void textBoxCps_TextChanged(object sender, EventArgs e)
{
try
{
time = Convert.ToDouble(textBoxCps.Text);
time = 1000 / time;
Math.Round(time);
}
catch (Exception)
{
}
}
The math is right(I think...)
while (IsRunning)
{
if ((Control.ModifierKeys & Keys.Alt) != 0)
{
DoMouseClicks();
Thread.Sleep(Convert.ToInt32(time));
}
else
{
}
}
When I try to put 1 CPS into the textbox it does 1 click per second, the same for 2 and 3 but when it's 4 and higher I'm getting 3.80 and lower CPS.
So the operator types some text that should represent a period of time (TimeSpan), and after running is started, you want to call method DoMouseClicks every TimeSpan, until running is stopped.
One of the problems is, that while you are doing this procedure you want your user input to be responsive.
Instead of Sleep, you should use one of the windows timers. There are several of them, and each have their advantages and disadvantages. In your case, the timer that you use depends on the accuracy that you need. See this article for a comparison between the various timers
Is it a problem if the clicks are a bit delayed if the user thread is busy? If not, the easiest is a System.Timers.Timer
System.Timers.Timer timer = new System.Timers.Timer()
timer.Elapsed += TimerElapsed;
private void TimerElapsed(object sender, ...)
{
DoMouseClicks();
}
To change the interval:
TimeSpan TimerInterval
{
get => TimeSpan.FromMilliseconds(this.timer.Interval);
set => this.Timer.Interval = value.TotalMilliseconds;
}
I decided to use a TimeSpan as interval time. This way you code changes are minimal if in future versions you decide to let the operator type his interval times in seconds, or in time format ("01:00")
To start and stop the timer:
private bool IsTimerStarted
{
get => this.timer.Enable;
set => this.timer.Enabled = value;
}
Now we are ready to react on operator input. You decided to act on TextBoxChanged. Are you sure you want this? What happens if an operator wants to type "1000", to indicate one second time interval. He starts by typing "1", you immediately start the mouse clicks with a frequency of 1 msec. Is this what you want?
Another problem: if the operator makes a typing error: "10)0", instead of "1000"?
A proper user interface would let the operator indicate that he finished typing the interval by pressing a button. When the button is pressed you read the text. If there is an error, you notify the operator, if not, you start the timer.
An alternative is to disable the button as long as the text box contains invalid text. Although this seems nice, the disadvantage is that the operator does not know why his button is not enabled.
private void OnButtonStart_Clicked(object sender, ...)
{
TimeSpan intervalTime = this.ReadTextBoxInterval();
this.TimerInterval = intervalTime;
this.IsTimerStarted = true;
// if desired: show the operator that the action is running
}
private void OnButtonStop_Clicked(object sender, ...)
{
this.IsTimerStarted = false;
// if the timer was handling event Elapsed, it is finished neatly.
// if desired show the operator that the action is stopped.
}
I decided to separate the action from the interpretation of the operator input. This way, code changes are minimal if you decide to change the operator input from msec to seconds, or even time format ("00:01"). Or if you decide to use a ComboBox instead of an edit box.
TimeSpan ReadTextBoxInterval()
{
string textBoxText = this.TextBoxInterval.Text;
return IntervalFromMsecText(textBoxText);
}
TimeSpan IntervalFromMsecText(string intervalText)
{
if (Double.TryParse(intervalText, NumberStyles.Any, CultureInfo.CurrentCulture,
out double msecInterval))
{
// input is a proper double
return TimeSpan.FromMilliseconds(msecInterval);
}
else
{
// invalid input. Notify the operator?
}
}
I've created a simple DateTime array that contains 3 items. These items are set to use the values of three different DateTimePickers on my form. Before I go further into using the array, I need to make sure it is actually using the correct values, and it does not appear to be doing so. Here's my code:
namespace Test
{
public partial class Form1 : Form
{
DateTime[] monSchedule = new DateTime[3];
public Form1()
{
InitializeComponent();
monSchedule[0] = monStart.Value;
monSchedule[1] = monEnd.Value;
monSchedule[2] = monLunch.Value;
}
private void Form1_Load(object sender, EventArgs e)
{
setDefaults();
}
private void setDefaults()
{
monStart.Value = DateTime.Parse("00:00");
monEnd.Value = DateTime.Parse("00:00");
monLunch.Value = DateTime.Parse("00:00");
}
private void validate()
{
MessageBox.Show("You entered time " + monSchedule[0]);
}
When I load my form, setDefaults(); should change the values to the current date with a time of 00:00. When I press the button to show the value in the array, it is pulling current date and current time. I need it to pull whatever the current time in that DateTimePicker is. So if a user types 10:00 into the DateTimePicker (they are formatted HH:mm), then I need the MessageBox to say the time is 10:00 AM. If I change the value to 22:00, then I need the messagebox to say the time is 10:00 PM. etc. (Date is irrelevant in my scenario, I'm not concerned with what the date is at all. Only the time.)
I suspect it may be because of the order it's written in. Is the array storing the value of the DateTimePicker BEFORE setDefaults(); is run? If so, how do I make the values of the array items dynamic since the values of the DateTimePickers are going to change a lot and I need the array elements to be updating with the latest values?
EXTRA INFO:
-Using Visual Studio
-Added the DateTimePickers in design view, changed the format to HH:mm there, did not change the default values in design view
-Ignoring date completely, only concerned with time right now
PS: I was also struggling with where to declare the array so it was accessible in multiple other methods and found I had to declare the array initializer within public partial class Form1, but then add the items in the array within public Form1(), because it wouldn't let me add them under public partial class Form1. I don't know if this is correct though, but it seemed to work when I tested with an array of strings so I went with it.
I have to say that this is a bit of a regression. In your previous question, JoshPart gave you good advice in the form of user controls, although he may have left some gaps too large for you to fill on your own.
Using arrays in this manner might work for a single day, but it won't scale well to a full week.
In case anyone reading this is wondering why I'm talking about a full week, I refer you to the previous question. Also, I recognize that I'm going off-topic for this specific question but I believe this to be an XY problem and the previous question was actually based on the real problem and work that was more on-the-mark.
Let's start with what we know. I've gleaned this from the two questions and the various comments in both.
You have DateTimePicker controls for start, end, and lunch. You're only interested in the time portion so you have Format set to "Custom" and CustomFormat set to "HH:mm". Assumption: lunch is a fixed length so the end time isn't needed.
You have the aforementioned controls times seven, one set for each day of the week.
You've written validation code (range tests) to determine if values are entered correctly, and you're able to show a label with red exclamation marks when that test fails.
You've identified that it's getting too complicated just having a bunch of controls on a form.
So far, so good. Now for your goal.
You're looking for a way to organize the controls, and the data they collect, to make it easier to work with them.
A user control is still the way to go here. You'll benefit from encapsulating all that repeated functionality into a single place and being able to reuse it.
Start by creating a user control -- we'll call it DayPanel -- and put all the controls for a single day on that canvas. Name the controls without any regard for the day of week (e.g. start, lunch, and end). Your user control will neither know nor care which day it represents.
Add an event handler for the ValueChanged event to the DateTimePicker controls. Instead of double-clicking the control, go to the events list in the Properties tool window and type a name, such as the one below, for the ValueChanged event. Do the same for the other two controls and it will reuse the event handler that it created the first time. Whenever the user changes a time, this event handler will be called and it will effect changes to the UI.
private void picker_ValueChanged(object sender, EventArgs e)
{
// In case you need to know which DateTimePicker was changed, take a look at 'sender':
//DateTimePicker picker = (DateTimePicker)sender;
UpdateWarningState();
}
As Jimi mentioned, the sender object will be a reference to the DateTimePicker control that sent the event. You probably won't need it but it's there if you do.
UpdateWarningState just hides/shows the warning label based on the validity of the inputs.
private void UpdateWarningState()
{
warningLabel.Visible = !IsInputValid(start.Value.TimeOfDay, lunch.Value.TimeOfDay, end.Value.TimeOfDay);
}
I had suggested in comments on the previous question that it seemed to make sense to get true if the inputs are valid and then use the logical negative for the visibility of the warning label.
As Paul Hebert pointed out, you really only need to compare a TimeSpan, so IsInputValid receives the TimeOfDay property to deal with only that much.
private bool IsInputValid(TimeSpan startTime, TimeSpan lunchTime, TimeSpan endTime)
{
return startTime < lunchTime && lunchTime.Add(TimeSpan.FromMinutes(30)) < endTime;
}
In fact, even though you're only inputting a time, the control still returns a date part in its Value property. If you want to be certain that you're not comparing times in different dates, you'll definitely need to use the TimeOfDay property. That said, by not presenting the date part, you have a measure of control over that so it's not a pressing concern. If you had to worry about crossing over midnight, that would complicate things.
Note that I've dealt with that earlier assumption that lunch is a fixed length by adding 30 minutes in the comparison to the end time.
Why not just do all that in the ValueChanged event handler?
The Single Responsibility Principle. IsInputValid does one thing: business logic; it tells you if the inputs are valid based on range testing. UpdateWarningState does a different thing: UI logic; it updates the visibility of warning label based on the validity of the inputs.
UpdateWarningState is reusable. You can call it from other event handlers in the future. Event handlers really shouldn't ever do much. They're more like telephone operators: "how may I direct your call?"
IsInputValid is reusable. The business logic can be extracted from your UI code at some point in the future and be reused by something else. I'll admit that the name leaves something to be desired; it fits here but probably should be different outside this context.
But what good is this user control if you have no way of working with its data? The consumer needs to be able to interact with it. A user control is just another class so you can define public properties, methods, and events as you see fit. We'll add properties for the three values of interest:
public TimeSpan Start
{
get => start.Value.TimeOfDay;
set => start.Value = start.Value.Date + value;
}
public TimeSpan Lunch
{
get => lunch.Value.TimeOfDay;
set => lunch.Value = lunch.Value.Date + value;
}
public TimeSpan End
{
get => end.Value.TimeOfDay;
set => end.Value = end.Value.Date + value;
}
What's interesting to note about these properties is that they don't have their own backing storage. Instead, they defer to the controls and translate between their own TimeSpan data type and the controls' DateTime data type. On get, they return just the TimeOfDay property. On set, they remove the time portion (with .Date) and add the time of day.
If you were building this for someone else to consume, you'd want to ensure that the Days property is 0 and that the whole value is non-negative, and either throw ArgumentOutOfRangeException or (gasp!) clamp the value to the acceptable range.
Now that you have a functioning control for a single day, you can slap a bunch of them on the main form. Back in Form1, add seven instances of the DayPanel control and name them monday through sunday. Before we get to initialization, let's create a lookup for these user controls.
private readonly Dictionary<DayOfWeek, DayPanel> _dayPanelLookup;
public Form1()
{
InitializeComponent();
_dayPanelLookup = new Dictionary<DayOfWeek, DayPanel>()
{
[DayOfWeek.Monday] = monday,
[DayOfWeek.Tuesday] = tuesday,
[DayOfWeek.Wednesday] = wednesday,
[DayOfWeek.Thursday] = thursday,
[DayOfWeek.Friday] = friday,
[DayOfWeek.Saturday] = saturday,
[DayOfWeek.Sunday] = sunday
};
}
Now the Load handler can then initialize all the properties. This DefaultTime duplicates the TimeSpan.Zero constant for the purpose of giving it a distinct meaning and can help with refactoring later on.
private static readonly TimeSpan DefaultTime = TimeSpan.Zero;
private void Form1_Load(object sender, EventArgs e)
{
SetDefaults();
}
private void SetDefaults()
{
foreach (DayPanel dayPanel in _dayPanelLookup.Values)
{
dayPanel.Start = DefaultTime;
dayPanel.Lunch = DefaultTime;
dayPanel.End = DefaultTime;
}
}
And just for fun, we can use _dayPanelLookup to grab one of them based on a variable containing the day of the week.
public void someButton_Click(object sender,
{
DayOfWeek whichDay = SelectADay();
DayPanel dayPanel = _dayPanelLookup[whichDay];
// ...
}
That should address the main concern of organizing the controls and making it easy to work with them and their values. What you do with it once the user presses some as-yet-unidentified button on the form is a whole new adventure.
There are yet better ways of doing all of this, I'm sure. I'm not a UI developer, I just play one on TV. For your purposes, I hope this not only gives you the guidance you needed at this point in this project but also illuminates new avenues of thought about how to structure your programs in the future.
It is unclear what you want the date component of the date part of DateTime to be DateTime.Parse("00:00") should return midnight today or 12/27/18 12:00:00 AM;
This is also the same value as DateTime.Today
In addition, you can create a new DateTime with a constructor
monStart.Value = new DateTime(2018, 12, 27, 0, 0, 0);
This is midnight the today
Note:
Reading the description in your updated question, it appears that the DateTimePicker controls values are accessed on a Button Click. If this is the actual scenario, you probably don't need a DateTime array field at all: you could just read the values directly from the DTP controls and use the values in-place.
The example assumes (to comply with the question) that you need that array anyway.
A possible way to proceed:
Set the default values in the Form.Load event. Initialize the monSchedule array values right after, so the values are synchronized. Note that the Form.Load event handler code is (of course) executed after the class constructor (public Form1() { }): the Form object must be already initialized.
Assign an event handler to all the DateTimePicker controls (same event for all). The event handler is used to assign the new values to the monSchedule array. The event could be the ValueChanged event or, possibly, the more generic Validating event. The former is raised each time you change any part of the Time value (the hour value or minutes value). The latter only when the control loses the focus. Your choice.
Use the sender object in the event handler to determine which control raised the event and update the corresponding array value.
An example, using a switch statement and a case statement with a when clause:
Notes:
1. You need C# 7.0+ to use this switch syntax. Otherwise, you could switch using a Type pattern (see the Docs) or the DateTimePicker name (see the example).
2. The DTP_ValueChanged(object sender, EventArgs e) event (the ValueChanged handler) is assigned to all the DateTimePicker controls.
public partial class Form1 : Form
{
DateTime[] monSchedule = new DateTime[3];
private void Form1_Load(object sender, EventArgs e)
{
SetDefaultDTPValues();
}
private void SetDefaultDTPValues()
{
monStart.Value = DateTime.Parse("00:00");
monEnd.Value = DateTime.Parse("00:00");
monLunch.Value = DateTime.Parse("00:00");
monSchedule[0] = monStart.Value;
monSchedule[1] = monEnd.Value;
monSchedule[2] = monLunch.Value;
}
private void DTP_ValueChanged(object sender, EventArgs e)
{
switch (sender)
{
case DateTimePicker dtp when dtp.Equals(monStart):
monSchedule[0] = dtp.Value;
break;
case DateTimePicker dtp when dtp.Equals(monEnd):
monSchedule[1] = dtp.Value;
break;
case DateTimePicker dtp when dtp.Equals(monLunch):
monSchedule[2] = dtp.Value;
break;
}
}
}
On a Button.Click event:
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show($"Start: {monSchedule[0].ToString("hh:mm tt")} " +
$"End: {monSchedule[1].ToString("hh:mm tt")} " +
$"Lunch: {monSchedule[2].ToString("hh:mm tt")}");
}
If the C# version in use doesn't allow this switch statement syntax, you can use the DateTimePicker name instead (there are other options, see the examples in the Docs):
private void DTP_ValueChanged(object sender, EventArgs e)
{
DateTimePicker dtp = sender as DateTimePicker;
switch (dtp.Name)
{
case "monStart":
monSchedule[0] = dtp.Value;
break;
case "monEnd":
monSchedule[1] = dtp.Value;
break;
case "monLunch":
monSchedule[2] = dtp.Value;
break;
}
}
On a Form I have two controls, A and B. Each one sends an event when its value changes. The Form handles A's ValueChanged event by setting B to some value, and B's ValueChanged by setting A's value.
Do I need to do anything special to prevent an infinite loop, where the user changes A, sending a ValueChanged event which causes B to update, which now sends it ValueChanged event, causing A to update...
I know some GUI toolkits, such as Qt, have logic built into the infrastructure to prevent loops like that. (See the "Enter Your Age" example in Ch. 1 of the Blanchette & Summerfield book on Qt4.) Some older toolkits required the programmer to define and manage a flag to detect recursion. For WinForms, I haven't read a definitive statement anywhere on this.
For a concrete example, suppose A and B are NumericUpDown controls to show temperature in Fahrenheit and Celsius. When the user changes one, the other updates to show the corresponding temperature in the other system.
In my experience, the loop usually ends because the values stop actually changing. Your example falls into this case. Consider the following scenario:
User changes the Fahrenheit to 32
Events update the Celsius to 0
Events set the Fahrenheit to 32
Nothing further happens because Fahrenheit did not change
This is usually implemented in the properties by putting a check at the top of the setter to not raise the changed event when the new value is the same as the current value.
The way in which I've managed to prevent this problem when implementing custom controls, is by raising events only when the value actually does get changed, like this.
public string Text
{
get { return _text; }
set
{
if (_text != value)
{
_text = value;
OnTextChanged();
}
}
}
One could argue that not checking whether the value is actually different before firing the event is bad code.
I don't recall ever experiencing these issues with Windows Forms controls, so the ones I've been using must be doing this check correctly.
The events will not loop in the NumericUpDown example that you have provided. The ValueChanged event of the NumericUpDown will only get fired when the value changes i.e. If the value of a NumericUpDown is 5 and in code you again set it to 5, no event will be fired. This behavior of the event stops it from looping when you have two NumericUpDown controls.
Now suppose you have two NumericUpDown controls A & B.
A was changed by the user, it fires an event
On the event fired by A, you calculate and set the value of B. B detects a value change and fires an event
On the event fired by B, you calculate and set the value of A. However this value would be the same as the original value and Windows will not fire an event of ValueChanged.
So in the case of Windows Form Controls, the Framework manages it for you, if you want to achieve this for your own classes you follow a similar principle. On the setter of a value, check if the new value is different from the old value. Only if it differs fire an event.
I think you should detach B event before set B in A event. So B event never fire when you set B in A event. you should same thing for B event. You can break loop.
Sorry for my English. I hope it help you.
There isn't really any standard. As a user, you should handle controls that raise useless events, and controls that do not. Even for .NET's own controls, documentation is lacking on this aspect so you're left just trying it.
Using a flag to detect recursion is possible, but nicer IMO is to change control.Value = newvalue; to if (control.Value != newvalue) control.Value = newvalue;.
That said, if you write your own controls, please do not raise useless events, and please clearly document that fact.
To answer your question I have built a simple test with the code below.
public partial class Form1 : Form
{
Random r = new Random();
public Form1()
{
InitializeComponent();
}
private void Form1_Shown(object sender, EventArgs e)
{
// This triggers the infinite loop between the two controls
numericUpDown1.Value = 10;
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
int cur = (int)numericUpDown2.Value;
int r1 = r.Next(1, 100);
while (r1 == cur)
r1 = r.Next(1, 100);
Console.WriteLine("Random in NUM1=" + r1);
numericUpDown2.Value = r1;
}
private void numericUpDown2_ValueChanged(object sender, EventArgs e)
{
int cur = (int)numericUpDown1.Value;
int r1 = r.Next(1, 100);
while (r1 == cur)
r1 = r.Next(1, 100);
Console.WriteLine("Random in NUM2=" + r1);
numericUpDown1.Value = r1;
}
}
The form has only two NumericUpDown initialized with values 1 and 2.
As you could test in Visual Studio 2013 there is nothing to prevent an infinite recursion in the case in which you really manage to generate different numbers at every ValueChanged event.
To prevent the infinite loop you could use the usual technique. Declare a global form variable
bool changing = false;
Inside both events add this code
try
{
if (changing) return;
changing = true;
.... event code ....
}
finally
{
changing = false;
}
I need to add a textbox in my application that starts showing with the value 0.00 just like a ATM, as you type the numbers then it keeps the two decimal point until satisfied with the value for example the sequence to end up of a value of 1023.00 would be (as I type)
0.01
0.10
1.02
10.23
102.30
1023.00
Is this possible to do in a windows forms application?. I am just not sure how to go about it.
Thank you.
In this kind of scenario I would not use a textbox, but a label or a read-only textbox. To get the user input just use the key-press event on your form (you have to enable KeyPreview on the form too) - then just remember the keys/numbers pressed and output the format you are targeting - should be a rather easy algorithm (Char.IsNumber, and String.Format might come in handy):
private int _inputNumber = 0;
private void Form_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
if (!Char.IsNumber(e.KeyChar)) return;
_inputNumber = 10*_inputNumber + Int32.Parse(e.KeyChar.ToString());
ReformatOutput();
}
private void ReformatOutput()
{
myOutput.Text = String.Format("{0:0.00}", (double)_inputNumber / 100.0);
}
Note: Why not use a textbox - because it's internal logic with select/replace/remove/type is so overcomplicated that the cases you would have to check are just to much to handle gracefully - so instead of trying to change a cannon into a sling you should start with the sling.
I'm making a simple Guess-The-Number game with a GUI. I need to wait on a loop waiting for the user to input a number in a text box and press "OK". How do I wait for an event inside a loop?
Note: I don't want message boxes. This is done in the main window, hence the need to wait for input.
EDIT: I should have explained myself better. I know that there's a loop inside the GUI. What I want is another loop inside a method. Maybe there's a better way to do this. I could code stuff inside the button's event handler, now that I think about it. Although I'd need global variables. Whataver, I'll think about it, but I hope my question is clearer now.
EDIT 2: Sorry that my question wasn't clear and the edit didn't do much help. First of all, the code is too big to be posted here. I'd probably have to post a screenshot of the GUI, so it wouldn't be of much use. Basically, I have two fields, "Max number" and "Number of allowed guesses". The user enters these two and clicks "Play". A new panel becomes available, with a text box and a "Guess" button. The user enters a guess, and the program checks to see if it's correct.
The purpose of the second infinite loop is to avoid global variables. See, each time the user clicks "Play", the game has to generate a new random number as the correct guess. If everything is done inside a method, no problem. But if the "Guess" button's event handler is called multiple times, the number has to be stored as an instance variable of the Form. Sure, it's not big deal, but I think the number should be a property of the method directing the current game, not of the Form.
I'd also have to keep track of the remaining number of guesses outside of the method. Again, it's no big deal. I just want to avoid globals if I can.
Again, I'm sorry that my question wasn't too clear. I'm kind of tired, and I didn't feel like writing too much. If this still isn't clear, then don't bother. I'll think of something.
C# automatically loops infinitely waiting for events until your form is closed. You just need to respond to the button click event.
Jason Down's suggestion is wise, create a new GuessingGame class and add it to your project. I know you're worried about "global variables" (which everyone is taught in school never to use unless you absolutely have to), but think about your design specifications for a minute.
But if the "Guess" button's event handler is called multiple times, the number has to be stored as an instance variable of the Form. Sure, it's not big deal, but I think the number should be a property of the method directing the current game, not of the Form.
As an alternative, store an instance of your GuessingGame class in the form. This is not a global variable! You said so yourself, the point of the game is keep track of the guesses and generate new numbers to guess every time "Play" is clicked. If you store an instance of the game in the form then open another form (e.g. a Help or About box), then the game's instance would not be available (thus, not global).
The GuessingGame object is going to look something like:
public class GuessingGame
{
private static Random _RNG = new Random();
private bool _GameRunning;
private bool _GameWon;
private int _Number;
private int _GuessesRemaining;
public int GuessesRemaining
{
get { return _GuessesRemaining; }
}
public bool GameEnded
{
get { return !_GameRunning; }
}
public bool GameWon
{
get { return _GameWon; }
}
public GuessingGame()
{
_GameRunning = false;
_GameWon = false;
}
public void StartNewGame(int numberOfGuesses, int max)
{
if (max <= 0)
throw new ArgumentOutOfRangeException("max", "Must be > 0");
if (max == int.MaxValue)
_Number = _RNG.Next();
else
_Number = _RNG.Next(0, max + 1);
_GuessesRemaining = numberOfGuesses;
_GameRunning = true;
}
public bool MakeGuess(int guess)
{
if (_GameRunning)
{
_GuessesRemaining--;
if (_GuessesRemaining <= 0)
{
_GameRunning = false;
_GameWon = false;
return false;
}
if (guess == _Number)
{
_GameWon = true;
return true;
}
else
{
return false;
}
}
else
{
throw new Exception("The game is not running. Call StartNewGame() before making a guess.");
}
}
}
This way, all the data related to the game is encapsulated within the class. Hooking up the events is easy in the codebehind of the form:
GuessingGame game = new GuessingGame();
private void btnPlay_Click(object sender, EventArgs e)
{
int numberOfGuesses = Convert.ToInt32(txtNumberOfGuesses.Text);
int max = Convert.ToInt32(txtMax.Text);
game.StartNewGame(numberOfGuesses, max);
}
private void btnGuess_Click(object sender, EventArgs e)
{
int guess = Convert.ToInt32(txtGuess.Text);
bool correct = game.MakeGuess(guess);
if (correct)
lblWin.Visible = true;
if (game.GameEnded)
{
// disable guess button, show loss label
}
}
You should probably look for a book to actually learn windows programming.
The very basics:
1) There is already an infinite loop deep down in the windows code somewhere. Any windows program is constantly looping and scanning for input.
2) Once input is found, this loop fires off an Event.
3) Your mission, should you choose to accept it, is to write event handlers to handle those events.
you are most likely doing it wrong as it has already been pointed out, but you can use this
Application.DoEvents();
to process events when you are on an actual loop
to do it the right way
- don't use a loop
- use an edit box for the input, then a button
- implement the button onclick event
Yes, and What if I am waiting for Speech events, it could happen anytime event when a function is running, I need to handle that without recursively call a function