Basically, I've got multiple button in my Form, and I want for it show a Stopwatch in the button.Text when the button is pressed. (Button is modified to be a toggle button.) and to stop and reset the timmer when the button is toggled off. Simple enough it seemed but because I have multiple buttons that could be pressed in any order, and I don't know anything about threading, this seems to be much more difficult that I presumed.
My origional intent was to have a function that constantly runs every second and interates a interager only if the button is pressed using this code:
public void Jogger()//purpose is to step up time[0] every second only when a button is on.
{
while (true)
{
for (int i = 0; i < 16; i++)
{
if (btnstat[i])
time[i]++;
}
Thread.Sleep(1000);
}
}
Problem is, I don't know threading so when I call the function, its stuck doing this and only this.
Either way, once this is called, all i do us call my update function that updates all the buttons including the button.Text which displays the time[0]; (array built around buttons)
Is their a better way of doing this that doesn't cause so much CPU use and/or simply works?
Thanks for all the help!
-John Ivey
Assuming you using checkbox with property Button = Appearence, in event handler for CheckedChanged:
private void CheckBoxCheckedChanged(object sender, EventArgs e)
{
CheckBox checkBox = (CheckBox) sender;
if (checkBox.Checked)
{
Timer timer = new Timer {Interval = 1000};
timer.Tick += Jogger;
timer.Start();
timer.Tag = new CheckboxCounter {CheckBox = checkBox, Time = 0};
checkBox.Tag = timer;
}
else
{
Timer timer = checkBox.Tag as Timer;
if (timer != null)
{
timer.Tag = null;
timer.Stop();
timer.Dispose();
checkBox.Tag = null;
}
}
}
Change your Jogger function:
private void Jogger(object a_sender, EventArgs a_eventArgs)
{
Timer timer = (Timer) a_sender;
CheckboxCounter data = (CheckboxCounter)timer.Tag;
data.Time++;
data.CheckBox.Text = data.Time.ToString();
}
You also need some simple class to store checkbox and current time:
class CheckboxCounter
{
public CheckBox CheckBox;
public int Time;
}
Then you can add any number of checkboxes and just set event CheckedChanged to CheckBoxCheckedChanged.
Try this out. After re-building or running, you should have the new "ButtonTimer" at the top of your ToolBox. Drop a couple on your Form, run it, and see what happens when you click them. Right click them to "Reset" them:
public class ButtonTimer : CheckBox
{
private System.Windows.Forms.Timer Tmr = new System.Windows.Forms.Timer();
private System.Diagnostics.Stopwatch SW = new System.Diagnostics.Stopwatch();
public ButtonTimer()
{
this.Tmr.Interval = 500;
this.Tmr.Tick += new EventHandler(tmr_Tick);
this.Appearance = System.Windows.Forms.Appearance.Button;
this.CheckedChanged += new EventHandler(ButtonTimer_CheckedChanged);
ContextMenuStrip cms = new ContextMenuStrip();
ToolStripItem tsi = cms.Items.Add("Reset");
tsi.Click += new EventHandler(tsi_Click);
this.ContextMenuStrip = cms;
}
protected override void OnLayout(LayoutEventArgs levent)
{
base.OnLayout(levent);
this.Text = TimeSpan.Zero.ToString(#"hh\:mm\:ss");
}
private void ButtonTimer_CheckedChanged(object sender, EventArgs e)
{
if (this.Checked)
{
this.SW.Start();
this.Tmr.Start();
}
else
{
this.SW.Stop();
this.Tmr.Stop();
}
}
private void tmr_Tick(object sender, EventArgs e)
{
this.UpdateTime();
}
private void UpdateTime()
{
this.Text = this.SW.Elapsed.ToString(#"hh\:mm\:ss");
}
private void tsi_Click(object sender, EventArgs e)
{
if (this.SW.IsRunning)
{
SW.Restart();
}
else
{
SW.Reset();
}
this.UpdateTime();
}
}
Application.DoEvents() for simplicity put inside loop . . but it is advisable to start to lean threading . you will just learn how to start thread and how make cross thread safe call
Next simple will be to use backgroundworker . look this http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
ok here is thread solution also as you wanted . Tested too . as a stop variable i used Tag. But u can inherit button to make state button.it be more clear way . And below code will use one thread per button . So u should make it in one thread to make it better solution . You can modify this code to do all checkings inside one thread . For this you start thread once can make delegate for attaching dinamically count function for each button or you can pass buttons before . With one word there are more than one way to do it. Good luck
this.button1.Click += new System.EventHandler(this.button_Click);
this.button2.Click += new System.EventHandler(this.button_Click);
...and so on
private void button_Click(object sender, EventArgs e)
{
Thread x= new Thread(new ParameterizedThreadStart(Jogger2));
x.Start(sender);
}
private void button_Click(object sender, EventArgs e)
{
Button mybtn=sender as Button;
if((string)mybtn.Tag=="start"){
mybtn.Tag ="";
return;
}
mybtn.Tag = "start";
Thread x= new Thread(new ParameterizedThreadStart(Jogger2));
x.Start(sender);
}
private bool setResult(object obj,string text)
{
if (this.textBox1.InvokeRequired)
{
Func<Button,string, bool > d = new Func<Button,string,bool >(setResult);
return (bool)this.Invoke(d,obj,text);
}
else
{
Button btn=obj as Button;
if (btn != null)
{
btn.Text = text;
if ((string)btn.Tag !="start") return false;
}
return true;
}
}
private void Jogger2(object mybtn)
{
int ii = 0;
while (true)
{
Thread.Sleep(1000);
//replace with your code
ii += 1;
if (!setResult(mybtn, ii.ToString())) break;
}
}
Related
I have a button, which I press and it starts a countdown.
But, if I press the same button again, the timer must reset and do another countdown (with another time defined by my program, but now this is irrelevant).
Is there any way I can do this reset inside the same button_click?
Maybe checking if the button was clicked again so I can reset the timer values?
I have this timer tick
private int milliSecondsLeft = 0;
private int t = 0;
private bool timeSet = false;
private void timer2_Tick(object sender, EventArgs e)
{
string timeOp = dataGridView1.Rows[t].Cells[5].Value + "";
t++;
DateTime timeConvert;
DateTime dateTime = DateTime.Now;
if (!timeSet)
{
DateTime.TryParse(timeOp, out timeConvert);
milliSecondsLeft = (int)timeConvert.TimeOfDay.TotalMilliseconds;
timeSet = true;
timeSetNxt = false;
}
milliSecondsLeft = milliSecondsLeft - 1000;
if (milliSecondsLeft > 0)
{
var span = new TimeSpan(0, 0, 0, 0, milliSecondsLeft);
lblLeft.Text = span.ToString(#"hh\:mm\:ss");
}
else
{
timer2.Stop();
}
}
and this button_click
each time I press my button it goes t++;, then it reads another time value on my datagrid. thats why it must reset
int t = 1;
private void btn2_Click(object sender, EventArgs e)
{
timer2.Start();
lblLeft.Text = dataGridView1.Rows[t].Cells[5].Value.ToString();
string value = dataGridView1.Rows[t].Cells[5].Value.ToString();
lblLeft.Text = value.ToString();
t++;
}
You could use the Tag property of the Button to set a flag for that logic you want to create.
on the button click event
if (btnExample.Tag==0)
{
btnExample.Tag=1;
//call startCountDown function
}
else
{
btnExample.Tag=0;
// call reset
}
Show your Timer Code. To get the Number of resets. Use code below.
int button_clicked = new int();
private void button1_Click(object sender, EventArgs e)
{
// How many times you have Reset
button_clicked++;
// Your Timer Code
}
Just start a new Timer with Every click. Also, dispose the last one.
You can use button_clicked to know if a timer has been started and hence dispose if the button_clicked > 0
I would check if the timer is enabled
if (!timer2.Enabled) StartTimer2();
else ResetTimer2();
I have a Windows Forms App written in C#. The idea is, that it draws a chart for 10 numbers after clicking a button. This works fine. I click the button, and I get a nice chart. However I also want to include a sort of "auto refresh" mode, where the chart is refreshed every few seconds. This would be enabled via Checkbox. Here's my code:
private void chartButton_Click(object sender, EventArgs e) //draw a chart after the button is clicked
{
Random rdn1 = new Random();
int value;
foreach (var series in ekran.Series) //clear previous values
{
series.Points.Clear();
}
for (int i = 0; i < 10; i++) //draw a chart from ten new values
{
value = rdn1.Next(0, 10); //for testing purpouses the value will be a random number a random number
ekran.Series["seria1"].Points.AddXY(i, value);
}
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
while(checkBox1.Checked) //click the chartButton every one second, when the checkbox is checked
{
//rysuj.PerformClick();
chartButton.PerformClick();
Thread.Sleep(1000);
}
}
And now for my problem. When I check the Checkbox, I will not get a chart until it finishes every iteration of the while loop. Since it's an infinite loop, I will never get my chart. If I rewrite the code to make only five iterations when the Checkbox is checked, I only get the chart for the fifth one (and after five seconds, as to be expected).
So my question is: how can I force this to draw a chart every time the button is clicked via chartButton.PerformClick()? When I click the button manually, everything works fine, it's just when I try to do it automatically, I get my problem.
EDIT
First of all,thank you for the replies. However, I'm still experiencing the same problem when using a timer. This is how my code looks now:
namespace ChartTest
{
public partial class Form1 : Form
{
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
public Form1()
{
InitializeComponent();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = 1000;
}
void timer_Tick(object sender, EventArgs e)
{
timer.Enabled = false;
chartButton.PerformClick();
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
while (checkBox1.Checked)
{
timer.Enabled = true; // Enable the timer
timer.Start(); // Start the timer
}
}
private void chartButton_Click(object sender, EventArgs e) //draw a chart after the button is clicked
{
Random rdn1 = new Random();
int value;
ekran.Series.Clear();
var series2 = new System.Windows.Forms.DataVisualization.Charting.Series
{
Name = "Series2",
Color = System.Drawing.Color.Green,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType = SeriesChartType.Line
};
this.ekran.Series.Add(series2);
for (int i = 0; i < 100; i++)
{
value = rdn1.Next(0, 10);
series2.Points.AddXY(i, value);
}
}
}
}
Sorry for being a total noob, but I have no idea, what am I doing wrong this time.
This is exactly what a Timer is for. Have the checkbox start/stop or enable/disable the timer, and handle the Timer.Tick event to redraw your chart. In your case, the event handler could simply call chartButton.PerformClick(), or insert whatever code the PerformClick() does.
ETA: If the chart refresh is not instant, you will probably want to push it off to a separate thread. If it's instant, there's not really any need to deal with the threading though.
I would go the route of using a thread with combination of checkbox's checkChange() event. Essentially this will allow your application to keep running while the update code will execute periodically. The refresh is determined by the sleep time, not your manual click or any other value.. Example below on how I to do this:
Thread refreshThread = null;
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (refreshThread == null) //No thread running, assume it starts this way
{
refreshThread = new Thread(chartRefresh);
refreshThread.Start();
}
else //Thread is running, must terminate
{
refreshThread.Abort();
refreshThread = null;
}
}
private void chartRefresh()
{
while (true)
{
//code to refresh chart
Thread.Sleep(10000);
}
}
I'm fairly new to C# (and programming in general), so please try to be detailed if you can. I tried to search for this but unable to find an answer. I have a form with 10 buttons. On each button click, I'm going to do the same thing initially, such as start a progress bar and disable the button until the button methods have been completed. Then I'll enable the button again and disable the progress bar. So my question is, how can I create a general method that allows me to do the the same function for any variable method? Example:
private void btnOne() {disableButton(); some statements here; enableButton()}
private void btnTwo() {disableButton(); some statements here; enableButton()}
private void btnThree() {disableButton(); some statements here; enableButton()}
private void btnFour() {disableButton(); some statements here; enableButton()}
private void disableButton()
{
this.button.Enabled = false;
progressBar.Visible = true;
}
private void enableButton()
{
this.button.Enabled = true;
progressBar.Visible = false;
}
So in the example above, I want to write one universal method that disables btnOne, btnTwo, btnThree, or btnFour, based on which one was clicked. Same for re-enabling the button.
I know I can get it to work doing this but it doesn't look efficient since I'm rewriting the same code over and over:
private void btnOne()
{btnOne.Enabled = false; some statements here; btnOne.Enabled = true;}
private void btnTwo()
{btnTwo.Enabled = false; some statements here; btnTwo.Enabled = true;}
private void btnThree()
{btnThree.Enabled = false; some statements here; btnThree.Enabled = true;}
private void btnFour()
{bthFour.Enabled = false; some statements here; bthFour.Enabled = true;}
You would just write a method that accepts a button:
private void DoTheButtonStuff(Button button)
{
button.Enabled = false;
progressBar.Visible = true;
// Do stuff here
button.Enabled = true;
progressBar.Visible = false;
}
Keep in mind though, that unless you're performing the tasks on a separated thread, the button disable/enable and the progress bar updates won't actually be seen. The interface will, in essence, be frozen until the task is finished. You could run Application.DoEvents() to get the interface to update, but that introduces another set of problems.
You have a few options, depending on what Some statements here means.
If all the statements will be the same then you can do this:
public void HandleButtonClick(object sender, EventArgs e)
{
(sender as Button).Enabled = false;
progressBar.Visible = true;
//Some statements
progressBar.Visible = false;
(sender as Button).Enabled = true;
}
And, in your form, designate the click event of each button to go to that method.
If Some statements here is different for each button, you can do this:
private void btnOne(object sender, EventArgs e) //Do this for each button
{
ChangeVisualState(sender as Button, false);
//Statements
ChangeVisualState(sender as Button, true);
}
public void ChangeVisualState(Button btn, bool buttonState)
{
btn.Enabled = buttonState;
progressBar.Visible = !buttonState;
}
Hopefully that helps
You need to handle the click events of each button. The example below creates a single event handler for all buttons.
For more info on button click events, see this link.
For simplicity, I would create a new event handler for each button. That way, you can handle that button's specific progress bar.
btnOne.Click += new EventHandler(this.HandleButtonClick);
btnTwo.Click += new EventHandler(this.HandleButtonClick);
btnThree.Click += new EventHandler(this.HandleButtonClick);
protected void HandleButtonClick(object sender, EventArgs e)
{
Button clickedButton = (Button)sender;
clickedButton.Enabled = false;
...
clickedButton.Enabled = true;
}
For a WinForms app:
As has been mentioned, you'd want to do your work in a background thread. Doing it on the UI thread would 'freeze' the screen which will make the disabling/re-enabling invisible to the user.
To generalize the Enabling/disabling
private void isEnabled(Button btn, bool _bool)
{
btn.Enabled = _bool;
}
You'll also need to create a separate method for making the progress bar visible/invisible. This will be used to make thread safe calls to the UI thread as explained below.
private void isVisible(bool _bool)
{
progressBar1.Visible = _bool;
}
You would then want to make sure you run your tasks on a background thread. You can create a single click event handler as follows:
private void btn_Click(object sender, EventArgs e)
{
//Sender is the button that was clicked
ThreadPool.QueueUserWorkItem(PerformTasks, sender);
}
Because you will be working with a background thread, you will need to create delegates to make thread safe calls back to the UI thread.
private delegate void ButtonDelegate(Button btn, bool isEnabled);
private delegate void ProgressBarDelegate(bool isVisible);
Finally, the work horse of your code. This method runs in the background and makes thread safe calls back to the UI thread when the job starts and completes.
private void PerformTasks(object obj)
{
//The passed in obj is the sender, in our case the clicked button
Button btn = obj as Button;
ButtonDelegate delButton = new ButtonDelegate(isEnabled);
ProgressBarDelegate delProgressBar = new ProgressBarDelegate(isVisible);
//Make a thread safe call to disable button and make progressbar visible
if (this.InvokeRequired)
{
btn.Invoke(delButton, btn, false);
progressBar1.Invoke(delProgressBar, true);
}
else
{
btn.Enabled = false;
progressBar1.Visible = true;
}
switch (btn.Name)
{
case "button1":
//Code to run in background for button 1
//alternatively, for organization/debugging purposes
//create a sub method
break;
case "button2":
//case 2
break;
//... more cases
}
//Make a thread safe call to re-enable button and make progress bar invisible
if (this.InvokeRequired)
{
btn.Invoke(delButton, btn, true);
progressBar1.Invoke(delProgressBar, false);
}
else
{
btn.Enabled = true;
progressBar1.Visible = false;
}
}
For more information on making thread safe calls in WinForms see the following msdn article.
EDIT: Here is the complete solution (for 2 buttons). You have to set the Click event for each button to be btn_Click
private void btn_Click(object sender, EventArgs e) {
Button btn = (Button)sender;
btn.Enabled = false;
MyBackgroundWorker bgw = new MyBackgroundWorker(btn);
if (btn == btnOne) {
bgw.DoWork += bgw_DoWork_One;
} else if (btn == btnTwo) {
bgw.DoWork += bgw_DoWork_Two;
}
//...
bgw.RunWorkerAsync(); // button enabled when completed
}
private void bgw_DoWork_One(object sender, DoWorkEventArgs e) {
// button 1 statements
}
private void bgw_DoWork_Two(object sender, DoWorkEventArgs e) {
// button 2 statements
}
public class MyBackgroundWorker : BackgroundWorker {
protected Button btn;
public MyBackgroundWorker(Button btn) : base() {
this.btn = btn;
this.btn.Enabled = false;
this.RunWorkerCompleted += bgw_RunWorkerCompleted;
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
btn.Enabled = true;
}
}
I get List of websites I need to loop through and to spend on each certain amount of time. Looping needs to be asynchronous, because on each website music will be played, and that's the main point - to hear the music in that amount of time, and then to load another page and to listen to its music and so on. Also, form need to be available for user actions.
Code I've got so far is this:
public void playSound(List<String> websites)
{
webBrowser.Navigate(Uri.EscapeDataString(websites[0]));
foreach (String website in websites.Skip(1))
{
StartAsyncTimedWork(website);
// problem when calling more times
}
}
private System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
private void StartAsyncTimedWork(String website)
{
myTimer.Interval = 7000;
myTimer.Tick += new EventHandler(myTimer_Tick);
myTimer.Start();
}
private void myTimer_Tick(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new EventHandler(myTimer_Tick), sender, e);
}
else
{
lock (myTimer)
{
if (this.myTimer.Enabled)
{
this.myTimer.Stop();
// here I should get my website which I need to search
// don't know how to pass that argument from StartAsyncTimedWork
}
}
}
}
One way to do this is as below.
Make websites a class field (if it isn't already), so the timer event handler can access this collection.
Add a field to keep track of the current index.
Add a field to prevent re-entrant calls to PlaySounds.
You're using a WinForms timer, which executes on the same thread as the form, so there's no need for InvokeRequired etc.
Some pseudo-code (warning, this is untested):
private bool isPlayingSounds;
private int index;
private List<String> websites;
private Timer myTimer;
private void Form1_Load()
{
myTimer = new System.Windows.Forms.Timer();
myTimer.Interval = 7000;
myTimer.Tick += new EventHandler(myTimer_Tick);
}
public void PlaySounds(List<String> websites)
{
if (isPlayingSounds)
{
// Already playing.
// Throw exception here, or stop and play new website collection.
}
else
{
isPlayingSounds = true;
this.websites = websites;
PlayNextSound();
}
}
private void PlayNextSound()
{
if (index < websites.Count)
{
webBrowser.Navigate(Uri.EscapeDataString(websites[index]));
myTimer.Start();
// Prepare for next website, if any.
index++;
}
else
{
// Remove reference to object supplied by caller
websites = null;
/ Reset index for next call to PlaySounds.
index = 0;
// Reset flag to indicate not playing.
isPlayingSounds = false;
}
}
private void myTimer_Tick(object sender, EventArgs e)
{
myTimer.Stop();
PlayNextSound();
}
I am using timer in form to send a command to a controller after every 3 seconds when user presses button. The timer should stop after user again presses same button. But in my case timer doesn't stop. I am using timer in the following way.
private void autoModeTempBtn_Click(object sender, EventArgs e)
{
System.Timers.Timer tempTimer = new System.Timers.Timer(3000);
tempTimer.SynchronizingObject = this;
tempTimer.AutoReset = true;
if (autoModeTempBtn.Text == "Get Temperature Auto Mode")
{
autoModeTempBtn.Text = "hello";
tempTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTemperatureEvent);
tempTimer.Enabled = true;
}
else /*user presses button second time */
{
tempTimer.Stop();
tempTimer.AutoReset = false;
tempTimer.Enabled = false;
autoModeTempBtn.Text = "Get Temperature Auto Mode";
}
}
private void OnTemperatureEvent(object source, System.Timers.ElapsedEventArgs e)
{
//do something
}
Where I am making mistake?
You are creating your timer new every time you click the button. Create the timer once and just Start/Stop it everytime you click. Also you should use the System.Windows.Forms.Timer instead of the System.Timers.Timer.
var _timer = new Timer() { Interval = 3000 };
private void autoModeTempBtn_Click(object sender, EventArgs e)
{
if (!validateSerialNumber())
return;
if (!_timer.Enabled)
{
_timer.Start();
autoModeTempBtn.Text = "hello";
}
else
{
_timer.Stop();
autoModeTempBtn.Text = "Get Temperature Auto Mode";
}
}
And add this code to your constructor:
_timer.Tick += OnTemperatureEvent;