how can I solve this loop? - winforms

I am currently building a windows form application and I have got the next problem,
I can't declare a variable global because the syntax im using won't allow me to do that, also, i need to declare the variable in the method it self and at last, it must loop so its able to count. This is what I have got so far:
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
int i;
m_bIsTimerOn = true;
while (m_bIsTimerOn)
{
i++;
label1->Text = (i.ToString());
}
}
private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) {
m_bIsTimerOn = false;
timer1->Enabled = false;
}
m_bIsTimerOn is a globally boolean. As you might see here my problem is if i press button1 the program is just stuck in the while loop. I would like to know that the moment you press button2 the while loop stops I also would like to know if this is even possible. If you could response in c++ that would also be fine.
Thank you in advance.

I suppose you want to increment i while timer is on. If you need to update i for each timer tick then it's pretty easy, remove that loop (and that variable BTW) and simply do update inside timer Tick event:
private:
int _i;
void button1_Click(Object^ sender, EventArgs^ e) {
_i = 0;
timer1->Enabled = true;
}
void _timer1_Tick(Object^ sender, EventArgs^ e) {
label1->Text = (i++).ToString();
}
void button2_Click(Object^ sender, EventArgs^ e) {
timer1->Enabled = false;
}
If i must be updated independently from timer ticks then you have to move it to a BackgroundWorker or simply in event handler for Application::Idle:
void OnIdle(Object^ sender, EventArgs^ e) {
label1->Text = (_i++).ToString();
}
void button1_Click(Object^ sender, EventArgs^ e) {
_i = 0;
Application::Idle += gcnew EventHandler(this, Form1::OnIdle);
timer1->Enabled = true;
}
void button2_Click(Object^ sender, EventArgs^ e) {
Application::Idle -= gcnew EventHandler(this, Form1::OnIdle);
timer1->Enabled = false;
}
As final note: you may even keep your loop as is and put a call to Application::DoEvents() just after your label1->Text = (i.ToString()); but this will probably consumes a lot of CPU, slow down your application and open your code to reentrancy, I'd really avoid something like that...

Your program is in endless loop because the while loop prevents the messages from getting processed and hence your button2_Click does not get invoked. To make you application capable of processing the messages that occurs, add Application.DoEvents() in your loop as:
while (m_bIsTimerOn)
{
i++;
label1->Text = (i.ToString());
Application.DoEvents(); // causes the application to handle pending events
}
So now, if you press the second button, m_bIsTimerOn will become false and your loop will terminate.

Related

How to run a thread for specific time in c#? Something like executing the same code again and again like a script for 5 seconds

I want to run a thread, a small part of code to be executed for 5 seconds.
Execution should be only once, it should keep executing continuously for a specific time.
Note: It should not keep executing, something like a timer. Execution should be only once.
Real Problem:
There is a script called AHK. (Auto Hot Key).
It does some task like hiding a taskbar for specific time.
I am not allowed to modify the script.
I have to modify the same in application part(C#).
Sample Tried out codes:
void StartConnection()
{
stopwatch.Start();
Thread threadObj = new Thread(ThreadFunc);
threadObj.Start();
}
void ThreadFunc()
{
for (; stopwatch.Elapsed.TotalSeconds < 6; )
{
WindowsNativeCalls.HideTaskbar();
}
}
Is there any other dot net concept available to achieve this?
See if this fits your bill :
private void Button_Click(object sender, RoutedEventArgs e)
{
System.Threading.Tasks.Task.Factory.StartNew(() => { mycallBack(); });
}
private void mycallBack()
{
System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();
while (true)
{
System.Diagnostics.Debug.WriteLine("Running !");
if (stopWatch.Elapsed.Seconds >= 5)
break;
}
System.Diagnostics.Debug.WriteLine("Done !");
}
You can use DispatcherTimer
private DispatcherTimer myTimer;
private void StartMyTimer()
{
if (myTimer != null)
myTimer.Stop();
myTimer = new DispatcherTimer();
myTimer.Interval = TimeSpan.FromSeconds(5);// You can change this for minutes, hours and etc
myTimer.Tick += myTimer_Tick;
myTimer.Start();
}
void myTimer_Tick(object sender, EventArgs e)
{
//Do something here every 5 seconds
}
I hope this helps.

Windows Forms: background worker synchronization and management

I have a problem with following, very simplified case being part of my project. Consider we have GUI like below:
I have two background workers:
plot_bgworker - in this example, it increments plot counter,
data_bgworker - in this example, it increments data counter.
I also have label_timer, which updates incremented values diplayed on my form.
To manage both background workers and timer, I wrote two functions:
private: void turnOnAcquisition() {
if (!counting_paused)
return;
if (!plot_bgworker->IsBusy)
plot_bgworker->RunWorkerAsync();
if (!data_bgworker->IsBusy)
data_bgworker->RunWorkerAsync();
label_timer->Enabled = true;
counting_paused = false;
}
private: void turnOffAcquisition() {
if (counting_paused)
return;
if (plot_bgworker->IsBusy)
plot_bgworker->CancelAsync();
if (data_bgworker->IsBusy)
data_bgworker->CancelAsync();
label_timer->Enabled = false;
counting_paused = true;
}
Then, here is what happens when I click each of my buttons:
// Pauses counting on click
private: System::Void stop_btn_Click(System::Object^ sender, System::EventArgs^ e) {
turnOffAcquisition();
}
// Starts counting on click
private: System::Void start_btn_Click(System::Object^ sender, System::EventArgs^ e) {
turnOnAcquisition();
}
// Should restart counting on click, beginning from 0 (no matter what state counting is in right now)
private: System::Void restart_btn_Click(System::Object^ sender, System::EventArgs^ e) {
plot_counter = 0;
data_counter = 0;
turnOffAcquisition();
turnOnAcquisition();
}
Finally, here are my background workers (turned off / on by CancelAsync() / RunWorkerAsync() ) and timer:
// Calculating data counter
private: System::Void data_bgworker_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
for (;;) {
data_counter++;
Sleep(50);
if (data_bgworker->CancellationPending) {
e->Cancel = true;
return;
}
}
}
// Calculating plot counter
private: System::Void plot_bgworker_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
for (;;) {
plot_counter++;
Sleep(120);
if (plot_bgworker->CancellationPending) {
e->Cancel = true;
return;
}
}
}
// Display counters
private: System::Void label_timer_Tick(System::Object^ sender, System::EventArgs^ e) {
plot_counter_label->Text = numToMStr(plot_counter);
data_counter_label->Text = numToMStr(data_counter);
}
Start button and stop button work both as expected, but now I have a problem with restart button. When I click it in the middle of counting, it seems to reset values and stop background workers, but never start them again (as I would expect after calling turnOnAcquisition). However, when I click it when counting is off, I am able to turn on counting as expected.
My first shot was that cancellation flag is not yet set to another value when I tried to check if my workers were busy, but using Sleep() between calls didn't work. Another guess is that it is due to race condition failure, so I tried using MemoryBarrier(), but I don't know the libraries and I'm not sure if it would work. Also, I tried to use Interlocked class, but couldn't use it properly for void functions.
1. Is this way of thinking correct?
2. If yes, why simple Sleep() doesn't do the trick?
3. How would I use any of mentioned methods in this case and which one would be the best match?
Ok, I found the solution by myself. The problem here was about the race condition - one event tried to stop counting (which meant raising another event) and then starting it again (which was problematic, as my function (I guess) was already cluttered with the first one and probably the second event wasn't even added to the event detected queue). If I am wrong with the explanation, I would appreciate some criticism down there ;)
Here are two modified functions, which solved thread management correctly. The key was to let the other events do their work until I get desired state.
When I want to turn off counting, I let the applications do the events from the queue until both threads will not be busy (the 'while' loop):
private: void turnOffAcquisition() {
if (counting_paused)
return;
if (plot_bgworker->IsBusy)
plot_bgworker->CancelAsync();
if (data_bgworker->IsBusy)
data_bgworker->CancelAsync();
while((plot_bgworker->IsBusy) || (data_bgworker->IsBusy)) // Continue to process events until both workers stop working
Application::DoEvents(); // Then, you can process another thread requests! :)
label_timer->Enabled = false;
counting_paused = true;
}
Similarily, when I want to restart counting, I let the application do the events until I check that both threads are busy (again, the 'while' loop):
private: void turnOnAcquisition() {
if (!counting_paused)
return;
if (!plot_bgworker->IsBusy)
plot_bgworker->RunWorkerAsync();
if (!data_bgworker->IsBusy)
data_bgworker->RunWorkerAsync();
while((!plot_bgworker->IsBusy) || (!data_bgworker->IsBusy)) // Continue to process events until both workers start working
Application::DoEvents(); // Then, you can process another thread requests! :)
label_timer->Enabled = true;
counting_paused = false;
}

WPF equivilent of unity 3D's Screen.lockCursor

I haven't used Unity 3D but I gather you can use Screen.lockCursor to take control of the mouse for FPS games. Is this possible in WPF/Win32?
Obviously you have to release it when exiting or in the event of a crash
Thanks
I found the answer spread across a whole bunch of links, so
(1) Set a captureMouse flag, press once to go into this mode, again to come out,
hide the cursor while you are in there
bool captureMouse = false;
private void viewport3D1_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!captureMouse)
{
captureMouse = true;
Mouse.OverrideCursor = Cursors.None;
}
else
{
Mouse.OverrideCursor = null;
captureMouse = false;
}
}
(2) While you're in this mode constantly put the mouse back to the middle of the window
private void theWindow_MouseMove(object sender, MouseEventArgs e)
{
if (!captureMouse)
return;
Point windowPoint = WpfToRealPixels(theWindow, new Point(500, 500));
NativeMethods.SetCursorPos((int)windowPoint.X, (int)windowPoint.Y);
oldP = new Point(500, 500);
}
(3) Translate the co-ords
private Point WpfToRealPixels(Window w, Point p)
{
return theWindow.PointToScreen(p);
}
(4) To put the mouse back you'll need a native Win32 call
public partial class NativeMethods
{
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetCursorPos")]
[return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
public static extern bool SetCursorPos(int X, int Y);
}
Hope that helps someone.

Update GUI using BackgroundWorker

I've been searching and found that a good way to perform background work and update the GUI is using background workers. However, doing this (stupid) little task (counting from 1 to 10000) it doesn't update the label content but prints to the debug! (This is just a spike solution for another project of course...)
Here's the code:
public partial class MainWindow : Window
{
BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("DONE");
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Content = "going here: "+e.ProgressPercentage;
Debug.WriteLine(e.ProgressPercentage);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i=0; i < 10000; i++)
{
bw.ReportProgress((i*100)/10000);
}
}
}
The ProgressChanged event is raised on the UI thread, not the worker thread. In your code, the worker thread is doing almost nothing (just loop from 0 to 10000 and call ReportProgress), most of the work is done on the UI thread. Basically, you're sending too many progress notifications. Because of this, the UI thread is almost always busy and has no time to render the new content of the label.
Rendering in WPF is not performed immediately when you change a property of a control, it is done on a separate dispatcher frame, which is processed when the dispatcher has nothing more urgent to do, based on the priority of the task. The priority used for rendering has a value of 7 (DispatcherPriority.Render); the ProgressChanged event is marshalled to the UI thread with a priority of 9 (DispatcherPriority.Normal), as specified on MSDN. So the ProgressChanged notifications always have a higher priority than rendering, and since they keep coming, the dispatcher never has time to process the rendering tasks.
If you just decrease the frequency of the notifications, your app should work fine (currently you're sending 100 notifications for each percentage value, which is useless):
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10000; i++)
{
if (i % 100 == 0)
bw.ReportProgress(i / 100);
}
}
this.Dispatcher.BeginInvoke( (Action) delegate(){
label1.Content = "going here: "+e.ProgressPercentage;
});
Try to change the label using womething like this:
string Text = "going here: " + e.ProgressPercentage;
this.Invoke((MethodInvoker)delegate {
label1.Content = newText;
});
Note that i'm not sure it will work. I can not test it now. If it does not work, let me know and I will delete the answer.
If you need the a canonical way to do exactly what you want, look at the Hath answer in this post: How do I update the GUI from another thread?

Intercepting the value change of SetChildIndex

In a .NET CF-form i have multiple panels. I want to have a property that should always be informed about if a panel is in the front.
Can this be done using the GetChildIndex() method?
If yes, how do i intercept the change to SetChildIndex()?
Thanks in advance
For everybody who is interested for future use:
simply add a new event handler for the Paint event of each panel, for example:
panel1.Paint += new PaintEventHandler(panel1_Paint);
panel2.Paint += new PaintEventHandler(panel2_Paint);
and in each of the event handlers just call a Method which retrieves the state of all the panels like so:
void panel2_Paint(object sender, PaintEventArgs e)
{
GetPanelStates();
}
void panel1_Paint(object sender, PaintEventArgs e)
{
GetPanelStates();
}
void GetPanelStates()
{
Panel2IsInFront = panel2.Parent.Controls.GetChildIndex(panel2) == 0;
Panel1IsInFront = panel1.Parent.Controls.GetChildIndex(panel1) == 0;
}

Resources