How to pause for loop when pause button is clicked? - c#

When I run the program and try to click the pause button, nothing happens. I am not sure how I can get this to work exactly. I have a bool variable called pause and pause is set to false. Once the pause button is clicked it should set that variable to true. Then the loop checks for that and should display a message to the user. Any help is greatly appreciated!
namespace Practice2
{
public partial class Form1 : Form
{
photocopier printer = new photocopier(500, 2500);
bool pause = false;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void btnStart_Click(object sender, EventArgs e)
{
if (checkText(txtNumberCopies.Text) == true)
{
int numberCopies = Convert.ToInt32(txtNumberCopies.Text);
int toner = Convert.ToInt32(lblTonerAmount.Text);
int paperCapacity = Convert.ToInt32(lblPaperAmount.Text);
if (toner <= 625 && paperCapacity <= 125)
{
txtMessage.Text = "Printer is low on Toner and Paper!";
}
else if (toner <= 625){
txtMessage.Text = "Printer Toner is low!";
}
else if (paperCapacity <= 125)
{
txtMessage.Text = "Printer Paper is low!";
}
else
{
txtMessage.Text = "Printing...";
txtMessage.Refresh();
for (int i = numberCopies; i != 0; i--)
{
int paper = Convert.ToInt32(lblPaperAmount.Text);
paper--;
if (paper == 480 || paper == 380 || paper == 400 || paper == 200)
{
MessageBox.Show("There is a paper Jam! Please remove the Jam and then hit the ok button to continue!", "Important Message", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
if (pause == true)
{
MessageBox.Show("Press the ok button when ready to continue", "Important Message", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
lblPaperAmount.Text = Convert.ToString(Convert.ToInt32(lblPaperAmount.Text) - 1);
lblTonerAmount.Text = Convert.ToString(Convert.ToInt32(lblTonerAmount.Text) - 1);
Thread.Sleep(1000);
}
txtMessage.Text = "Job is completed!";
}
}
}
private void btnAddPaper_Click(object sender, EventArgs e)
{
int paperAmount = Convert.ToInt32(lblPaperAmount.Text);
if (checkText(txtAddPaper.Text) == true && paperAmount <= 500)
{
lblPaperAmount.Text = Convert.ToString(paperAmount + Convert.ToInt32(txtAddPaper.Text));
}
else
{
txtMessage.Text = "Printer paper is at capacity!";
}
}
private bool checkText(string textBox)
{
if (textBox.Equals("") || textBox == null)
{
txtMessage.Text = "Please enter a value in the text box!";
return false;
}
return true;
}
private void btnReplaceToner_Click(object sender, EventArgs e)
{
lblTonerAmount.Text = Convert.ToString(printer.Toner);
}
private void btnPauseCancel_Click(object sender, EventArgs e)
{
pause = true;
}
}
}

The problem is that you're doing the work on the UI thread, so the UI thread is busy and can't process messages (e.g. button click). You need to do the work on a worker thread instead, using BackgroundWorker or Task.Run for instance.

A for loop is on the UI Thread so while the for loop is running you can't do anything with the UI. I suggest that you use a System.Windows.Forms.Timer to do the job. You set the interval to 1 and that will run pretty quickly, but not as quickly as a for loop, though. But interval = 1 is enough for you.
Let me show you:
Timer timer = new Timer () {Interval=1};
to create a new timer object.
enter
timer.Tick +=
in the constructer and press TAB twice and that should generate an event handler. Write the stuff you want to do in the event handler.
Call timer.Stop to pause the timer and timer.Start to start the timer.

Related

Why is the text not showing up on screen?

I'm trying to create a countdown where the text displays, "GAME STARTS IN: " and using a for loop and Thread.Sleep a variable counts down from three. I started by using the designer to create the "game starts in:" part, but after the variable wouldn't show up I moved it to code. Now nothing shows up. This is what I have now in my timer method:
if (!countedDown)
DoCountdown();
Countdown.Hide();
And then in a DoCountdown method:
this.Countdown.BackColor = System.Drawing.Color.Transparent;
this.Countdown.ForeColor = System.Drawing.Color.White;
this.Countdown.Location = new System.Drawing.Point(360, 17);
this.Countdown.Name = "Countdown";
this.Countdown.Font = new System.Drawing.Font("Segoe UI", 12F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point);
this.Countdown.Size = new System.Drawing.Size(185, 24);
this.Countdown.TabIndex = 6;
countedDown = true;
for (int i = 3; i > 0; i--)
{
Countdown.Text = "GAME STARTS IN: " + i;
System.Threading.Thread.Sleep(1000);
}
I put a breakpoint at System.Threading.Thread.Sleep(100) and everything seemed normal. Countdown.Text was equal to "GAME STARTS IN: 3". After trying to integrate the solutions the text doesn't show up. Here is some more context in my code:
This is from my start screen form
private void QuitGame(object sender, EventArgs e)
{
Application.Exit();
}
private void StartMultiplayerGame(object sender, EventArgs e)
{
GameScreen startGame = new GameScreen();
startGame.Show();
Hide();
}
Try something like below. A button is used to start the timer and set the initial values.
int count = 3;
private void button2_Click(object sender, EventArgs e) {
timer1.Interval = 1000;
count = 3;
label1.Text = "GAME STARTS IN: " + count;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e) {
count--;
if (count != 0) {
label1.Text = "GAME STARTS IN: " + count;
}
else {
timer1.Stop();
label1.Text = "GAME STARTED";
MessageBox.Show(" -> GO");
}
}
Edit per OP comments.
Try the code like this in the start screen form...
private void StartMultiplayerGame(object sender, EventArgs e) {
count = 3;
label1.Text = "GAME STARTS IN: " + count;
timer1.Start();
}
Then change the timer code to...
private void timer1_Tick(object sender, EventArgs e) {
count--;
if (count != 0) {
label1.Text = "GAME STARTS IN: " + count;;
}
else {
timer1.Stop();
label1.Text = "Game Started";
GameScreen startGame = new GameScreen();
startGame.Show();
this.Hide();
}
}
loop blocking the main thread to refresh UI so the required scenario can be archived by moving the loop to a separate method
void doCountDown()
{
for (int i = 10; i > 0; i--)
{
setCountDownText( "GAME STARTS IN: " + i);
System.Threading.Thread.Sleep(1000);
}
}
creating anew thread that start this method
new System.Threading.Thread(new System.Threading.ThreadStart(doCountDown)).Start();
and because of the need to update UI in another thread and to make it safe separate the setText in a separate method that update based on checking required to invoke property this will make it work in all cases
void setCountDownText(string txtValue)
{
if (Countdown.InvokeRequired)
{
Action safeWrite = delegate { setCountDownText(txtValue); };
Countdown.Invoke(safeWrite);
}
else
Countdown.Text = txtValue;
}
The "modern" way to do this is using async/await.
For example, launching the DoCountdown() from a button handler could look like this:
async void testBtn_Click(object sender, EventArgs e)
{
await DoCountdown();
}
async Task DoCountdown()
{
// <Initialisation of Countdown elided for brevity>
for (int i = 3; i > 0; i--)
{
Countdown.Text = "GAME STARTS IN: " + i;
await Task.Delay(1000);
}
}
However, whatever calls DoCountdown() will need to be declared as async, and so on up the call tree.
Note that the only acceptable place to have async void rather than async Task as a return type for an async method is where the method is an event handler such as the button handler in the example above.

Textbox delay events

I have an option form where the user has to enter parameters for a mini-game, going from 8 to 32. My problem is that as soon as I start typing, if I insert a number under 8 (I want to put 20, for example), the event activates as soon as I type 2 and turn it into 8.
private void TXBheight_TextChanged(object sender, EventArgs e)
{
if(int.Parse(TXBheight.Text) < 8)
{
TXBheight.Text = "8";
}
else if (int.Parse(TXBheight.Text) > 32)
{
TXBheight.Text = "32";
}
}
Is there any easy way to make a delay, or wait until I finish typing?
For those who identify this question as a possible duplicate, i took a look, and the possible answers are from 6 years ago. During that time, languages and compilers evolve, so maybe there is something new we can all learn from
Instead of using a TextChanged event, use the TextBox _Validating event and the _Validated event. The _Validating event is fired only when the text box loses focus, i.e., when the user clicks on another control, e.g., a Button or another TextBox. When this happens, the _Validating event is fired and you test the value in the text box. If it's invalid, you cancel the _Validating event. If its valid, you DON'T cancel the _Validating event, and as a a result the _Validated event is fired. In the _Validated event, you do what you neeed to do when the input data is valid. Use an errorprovider to inform the user when the input data is invalid.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
errorProvider1.SetError(TXBheight, "");
//NEW NEW NEW
buttonCancel.CausesValidation = false;
}
private void Button1_Click(object sender, EventArgs e)
{
// do what is needed when the button is clicked
}
private void TXBheight_Validating(object sender, CancelEventArgs e)
{
errorProvider1.SetError(TXBheight, "");
if (String.IsNullOrEmpty(TXBheight.Text))
{
errorProvider1.SetError(TXBheight, "Height is a required field");
e.Cancel = true;
return;
}
if (int.Parse(TXBheight.Text) < 8)
{
errorProvider1.SetError(TXBheight, "Height must be GE 8");
e.Cancel = true;
return;
}
if (int.Parse(TXBheight.Text) > 32)
{
errorProvider1.SetError(TXBheight, "Height must be LE 32");
e.Cancel = true;
return;
}
}
private void TXBheight_Validated(object sender, EventArgs e)
{
//this event is fired when the data is valid, i.e.,
// if e.Cancel in the _Validating method is NOT set to cancel
}
//NEW NEW NEW
private void ButtonCancel_Click(object sender, EventArgs e)
{
AutoValidate = AutoValidate.Disable;
Close();
}
// NEW #2
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
DialogResult result = MessageBox.Show("Do you really want to exit?", "Dialog Title", MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
Environment.Exit(0);
}
else
{
e.Cancel = true;
}
}
else
{
e.Cancel = true;
}
}
}
The simple answer is:
(C# 7 style)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.textBox1.TextChanged += TextBox1_TextChanged;
this.textBox1.Leave += TextBox1_Leave;
}
private void TextBox1_TextChanged(object sender, EventArgs e)
{
string text = this.textBox1.Text;
if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.CurrentCulture, out int number))
{
this.textBox1.Text = "";
return;
}
if (number > 32)
{
this.textBox1.Text = "32";
}
}
private void TextBox1_Leave(object sender, EventArgs e)
{
string text = this.textBox1.Text;
if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.CurrentCulture, out int number))
{
this.textBox1.Text = "8";
return;
}
if (number > 32)
{
this.textBox1.Text = "32";
}
if (number < 8)
{
this.textBox1.Text = "8";
}
}
I standardly do this with controlling the pressed keys and text changes (inclusive paste) to check correct content of the window. Unfortunately I have the code only for Borland C++ Builder and VS6 at work. Recreating this code is not that simple (too much code), therefore only the simple answer.
Use Microsoft's Reactive Framework and this becomes easy. Just do this:
private void Form1_Load(object sender, EventArgs e)
{
IObservable<long> query =
Observable
.FromEventPattern<EventHandler, EventArgs>(
h => TXBheight.TextChanged += h,
h => TXBheight.TextChanged -= h)
.Select(x => Observable.Timer(TimeSpan.FromMilliseconds(250.0)))
.Switch()
.ObserveOn(this);
IDisposable subscription = query.Subscribe(ep =>
{
if (int.Parse(TXBheight.Text) < 8)
{
TXBheight.Text = "8";
}
else if (int.Parse(TXBheight.Text) > 32)
{
TXBheight.Text = "32";
}
});
}
Now there is a 250.0 millisecond delay after the last character is typed before your code runs. If a new character is typed before the 250.0 milliseconds is up then a new timer starts and the old one doesn't fire.
The .ObserveOn(this) code marshalls the timer back to the UI thread.
Just NuGet "System.Reactive" and "System.Reactive.Windows.Forms". Also add using System.Reactive.Linq; at the top of your class.
You can use the return as your breakpoint, when the user hit enter then you run your code.
You can use that with the KeypressEvent.
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
char ch = e.KeyChar; // Getting the Key that was pressed
if (ch == 13) // Checking if it equal to 13 ASCII code for return
{
if (int.Parse(textBox1.Text) < 8)
{
textBox1.Text = ""; // emptying the textbox
textBox1.AppendText("8"); // using AppendText() to keep the cursor at the end
}
else if (int.Parse(textBox1.Text) > 32)
{
textBox1.Text = "";
textBox1.AppendText("32");
}
e.Handled = true; // To say that the event was handled.
}
}
Why not create task and check if it is completed before executing?
private Task task; //declare it at the top
private void TXBheight_TextChanged(object sender, EventArgs e)
{
if(task?.Status == TaskStatus.Running) return;
task = Task.Run( () =>
{
if(int.Parse(TXBheight.Text) < 8)
{
TXBheight.Text = "8";
}
else if (int.Parse(TXBheight.Text) > 32)
{
TXBheight.Text = "32";
}
});
}

C# - Wait for 2s before doing next task

private void materialRaisedButton16_Click(object sender, EventArgs e)
{
foreach (var process in Process.GetProcessesByName("RobloxPlayerBeta"))
{
process.Kill();
}
materialRaisedButton16.Text = "Successfully killed process!";
// sleep for 2s WITHOUT freezing GUI
materialRaisedButton16.Text = "Click to kill process";
}
Hi, my code is above. I need the text of the button to change for 2s then change back to the original. How is this possible?
Thanks,
Tim
Implement like this
private async Task DelayTask()
{
await Task.Delay(2000); //2000 = 2sec
DoyourStuffHere();
materialRaisedButton16.Text = "Click to kill process";
}
And Call It Like This
private void materialRaisedButton16_Click(object sender, EventArgs e)
{
foreach (var process in Process.GetProcessesByName("RobloxPlayerBeta"))
{
process.Kill();
}
materialRaisedButton16.Text = "Successfully killed process!";
// sleep for 2s WITHOUT freezing GUI
Task taketime = this.DelayTask();
}
Not freezing the GUI requires some form of Mutlitasking. Possibly even Multithreading. Very strictly speaking calling a bunch of helper processes is a primitive form of Multithreading already. Possibly the oldest one, we invented just as we came off Cooperative Multitasking back in the days.
You have many Options to do Multitasking (inlcuding Multithreading) in .NET Async...await. Tasks. Threads. For beginners in Multithreading, I would advise BackgroundWorker generally. I wrote this little intro examples a few years back that I link often:
#region Primenumbers
private void btnPrimStart_Click(object sender, EventArgs e)
{
if (!bgwPrim.IsBusy)
{
//Prepare ProgressBar and Textbox
int temp = (int)nudPrim.Value;
pgbPrim.Maximum = temp;
tbPrim.Text = "";
//Start processing
bgwPrim.RunWorkerAsync(temp);
}
}
private void btnPrimCancel_Click(object sender, EventArgs e)
{
if (bgwPrim.IsBusy)
{
bgwPrim.CancelAsync();
}
}
private void bgwPrim_DoWork(object sender, DoWorkEventArgs e)
{
int highestToCheck = (int)e.Argument;
//Get a reference to the BackgroundWorker running this code
//for Progress Updates and Cancelation checking
BackgroundWorker thisWorker = (BackgroundWorker)sender;
//Create the list that stores the results and is returned by DoWork
List<int> Primes = new List<int>();
//Check all uneven numbers between 1 and whatever the user choose as upper limit
for(int PrimeCandidate=1; PrimeCandidate < highestToCheck; PrimeCandidate+=2)
{
//Report progress
thisWorker.ReportProgress(PrimeCandidate);
bool isNoPrime = false;
//Check if the Cancelation was requested during the last loop
if (thisWorker.CancellationPending)
{
//Tell the Backgroundworker you are canceling and exit the for-loop
e.Cancel = true;
break;
}
//Determin if this is a Prime Number
for (int j = 3; j < PrimeCandidate && !isNoPrime; j += 2)
{
if (PrimeCandidate % j == 0)
isNoPrime = true;
}
if (!isNoPrime)
Primes.Add(PrimeCandidate);
}
//Tell the progress bar you are finished
thisWorker.ReportProgress(highestToCheck);
//Save Return Value
e.Result = Primes.ToArray();
}
private void bgwPrim_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbPrim.Value = e.ProgressPercentage;
}
private void bgwPrim_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pgbPrim.Value = pgbPrim.Maximum;
this.Refresh();
if (!e.Cancelled && e.Error == null)
{
//Show the Result
int[] Primes = (int[])e.Result;
StringBuilder sbOutput = new StringBuilder();
foreach (int Prim in Primes)
{
sbOutput.Append(Prim.ToString() + Environment.NewLine);
}
tbPrim.Text = sbOutput.ToString();
}
else
{
tbPrim.Text = "Operation canceled by user or Exception";
}
}
#endregion
Of course in your case, something much simpler could work: A baseline Timer. All you really want is a 2 second delay? Make a TImer set to 2 seconds, no repeat, start it in "materialRaisedButton16_Click". And let it's tick to the rest. All true multithreading really does is allow you to write the same stuff in slightly more readable form (with some tradeoffs for performance).
the easiest way would be:
foreach (var process in Process.GetProcessesByName("RobloxPlayerBeta"))
{
process.Kill();
}
materialRaisedButton16.Text = "Successfully killed process!";
// sleep for 2s WITHOUT freezing GUI
Task.Delay(2000).ContinueWith(()=>{
materialRaisedButton16.Text = "Click to kill process";
}, TaskScheduler.FromCurrentSynchronizationContext()); // this is to make it run in the UI thread again
You can use Timer.
On its tick event you update the text of the button back to the value u need.

Invoke a method when another method ends in Windows Forms

I'm making a tictactoe game where you can play against another person or an AI. Two AI can also play against each other. When two AI play against each other I got a stackover flow, guard page error.
What has happening, was when the AI would click on a button my "ClickHandler" method would be called. At the end of this method I would calla method (playATurn) for the other player to pick a button and "ClickHandler" would be called again and I would have an endless recursion.
I have fixed this issue using a timer at the end of the method which calls "playATurn" after 1ms but this is slow.
My question is there an event or something else I can program that would call "playATurn" after my program has finished with "ClickHandler."
Thanks!
private void ClickHandler(object sender, System.EventArgs e)
{
Button tempButton = (Button)sender;
if (tempButton.Text != "") // if is it empty
{
MessageBox.Show("Button already has value!", "ERROR", MessageBoxButtons.OK);
return;
}
if (_isX) // put the character in the Text property
{
tempButton.Text = "X";
turn.Text = "O";
}
else
{
tempButton.Text = "O";
turn.Text = "X";
}
_isX = !_isX; // prepare for next character
this._isGameOver = CheckAndProcessWinner();
if (_isGameOver) gamesRemaining.Text = (--PlayerMenu.counterForNumberOfGames).ToString();
if (_isGameOver && PlayerMenu.counterForNumberOfGames == 0)
{
MessageBox.Show(playerOne.name + " Wins: " + playerOne.numberOfWins + " Loses: " + playerOne.numberOfLoses + " Ties: " + playerOne.numberOfTies);
}
else if (_isGameOver && PlayerMenu.counterForNumberOfGames > 0)
{
InitTicTacToe();
}
else if (!_isGameOver && PlayerMenu.counterForNumberOfGames > 0)
{
if (_isX)
playerOne.pickMove(_buttonArray, playerTwo);
else
playerTwo.pickMove(_buttonArray, playerOne);
}
myTimer.Start();
}
private void playATurn(object sender, System.EventArgs e)
{
if (!_isGameOver && PlayerMenu.counterForNumberOfGames > 0)
{
if (_isX)
playerOne.pickMove(_buttonArray, playerTwo);
else
playerTwo.pickMove(_buttonArray, playerOne);
}
}
You don't want to run it too tightly, or the windows message loop want run and it won't update. Perhaps consider:
private void PerformMove() {
// ... Your existing code
if(runAgain) {
this.BeginInvoke((MethodInvoker)delegate{
PerformMove();
});
}
}
private void ClickHandler(object sender, System.EventArgs e) {
PerformMove();
}
This then goes via the message-loop per iteration, so the UI should be responsive... Just about.

Enabling/Disabling Moveup/MoveDown buttons on a ListView

with a form like this:
I wrote this piece of code to take care of enable/disable logic for moveup/down buttons when they click on at item ( we don't care about Avaiable list on the left, we just care about Selected list on the right)
private void SelectedLV_SelectedIndexChanged(object sender, EventArgs e)
{
// what to do wth move up button
if (SelectedLV.SelectedIndices.Count == 1 && SelectedLV.SelectedItems[0].Index > 0)
{
MoveUpBtn.Enabled = true;
}
else
{
MoveUpBtn.Enabled = false;
}
//what to do with move down button
if (SelectedLV.SelectedIndices.Count == 1 && SelectedLV.SelectedItems[0].Index < SelectedLV.Items.Count - 1)
{
MoveDownBtn.Enabled = true;
}
else
{
MoveDownBtn.Enabled = false;
}
}
I think it works fine for that scenario but my question is what about when we click off of Selected Listview, What is good logic to handle that and Disable Both Moveup/Down buttons?
I don't want them be enabled when we are not inside SelectedListView...
Also if you notice any issue with the code I pasted please let me know.
Thanks
You are about to shoot your foot with the focus requirement. These kind of UI updates are best done with the Application.Idle event, it only runs when nothing important is happening. And can help to eliminate a lot of event handlers. Like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
Application.Idle += Application_Idle;
this.FormClosed += delegate { Application.Idle -= Application_Idle; };
}
void Application_Idle(object sender, EventArgs e) {
bool focusOk = this.ActiveControl == SelectedLV;
bool selectOk = SelectedLV.SelectedIndices.Count == 1;
int index = selectOk ? SelectedLV.SelectedIndices[0] : -1;
MoveUpBtn.Enabled = focusOk && selectOk && index > 0;
MoveDownBtn.Enabled = focusOk && selectOk && index < SelectedLV.Items.Count-1;
}
}
Don't forget to set the focus back in the buttons' Click event handler. And don't forget about the ListView.HideSelection property. Set it to False so that focus doesn't matter anymore.
The problem is once you click on the Move buttons, then you are outside of the SelectedListView control, so the logic should really be based on if you have a correct index value or not:
private void SelectedLV_SelectedIndexChanged(object sender, EventArgs e)
if (SelectedLV.SelectedIndicies.Count == 0) {
MoveUpBtn.Enabled = false;
MoveDownBtn.Enabled = false;
} else {
// normal processing
}

Categories

Resources