Wait for button click while reading or during a loop - c#

I am creating a time clock program. Our office staff would like to be able to send messages to specific employees when they clock in. The issue I have is if there are multiple messages to display, it loops through to the last one.
while(reader.Read()
{
richtextbox.Text = reader["Message"].ToString();
//need to pause here and wait for them to press the acknowledge button
{
Is there a way to wait or pause until the press the acknowledge button.
Any help is appreciated.
ps. I have tried using
public static class Utils
{
public static Task WhenClicked(this Control target)
{
var tcs = new TaskCompletionSource<object>();
EventHandler onClick = null;
onClick = (sender, e) =>
{
target.Click -= onClick;
tcs.TrySetResult(null);
};
target.Click += onClick;
return tcs.Task;
}
}
from this thread Wait until a click event has been fired C# but It did the same thing and blew past the button and showed the last message again.

I would approach the problem like this. Make your reader load all of the messages into a collection like a Queue (which is a first-in-first-out collection, ideal for what you are trying to do.) After the loop, if there are any messages, display the first one. Wire up the click event to a similar block of code that will display the next message (if any more exist) each time the button is clicked. The code would look like this:
Queue<string> EmployeeMessages = new Queue<string>();
private void OnLogin()
{
var reader = GetReader();
while (reader.Read())
{
EmployeeMessages.Enqueue(reader["Message"].ToString());
}
if (EmployeeMessages.Count > 0)
{
label1.Text = EmployeeMessages.Dequeue();
}
}
private void button1_Click(object sender, EventArgs e)
{
if (EmployeeMessages.Count > 0)
{
label1.Text = EmployeeMessages.Dequeue();
}
}

Related

Check multiple checkbox.checked state inside a do while cycle

I asked in a previous question how to "Threading 2 forms to use simultaneously C#".
I realize now that I was not explicit enough and was asking the wrong question.
Here is my scenario:
I have some data, that I receive from a local server, that I need to write to a file.
This data is being sent at a constant time rate that I cant control.
What I would like to do is to have one winform for the initial setup of the tcp stream and then click on a button to start reading the tcp stream and write it to a file, and at the same time launch another winform with multiple check-boxes that I need to check the checked state and add that info simultaneously to the same file.
This processing is to be stopped when a different button is pressed, closing the stream, the file and the second winform. (this button location is not specifically mandatory to any of the winforms).
Because of this cancel button (and before I tried to implement the 2nd form) I used a background worker to be able to asynchronously cancel the do while loop used to read the stream and write the file.
private void bRecord_Click(object sender, EventArgs e)
{
System.IO.StreamWriter file = new System.IO.StreamWriter(AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".xml", true);
data_feed = client.GetStream();
data_write = new StreamWriter(data_feed);
data_write.Write("<SEND_DATA/>\r\n");
data_write.Flush();
exit_state = false;
string behavior = null;
//code to launch form2 with the checkboxes
//...
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
int var = data_feed.ReadByte();
if (var != -1)
{
data_in += (char)var;
if (data_in.IndexOf("\r\n") != -1)
{
//code to check the checkboxes state in form2
//if (form2.checkBox1.Checked) behavior = form2.checkBox1.Text;
//if (form2.checkBoxn.Checked) behavior = form2.checkBoxn.Text;
file.WriteLine(data_in + behavior);
data_in = "";
}
}
}
while (exit_state == false);
});
worker.RunWorkerAsync();
}
private void bStop_Click(object sender, EventArgs e)
{
exit_state = true;
worker.CancelAsync();
}
I hope I've been clearer now.
I not experienced in event programming and just started in C# so please try to provide some simple examples in the answers if possible.
At first would it be enough to use one Winform? Disable all checkboxes, click a button which enables the checkboxes and start reading the tcpstream? If you need two Forms for other reasons let me know, but i think this isn't needed from what i can see in your question.
Then i would suggest you to use the Task Library from .Net. This is the "modern" way to handle multithreading. BackgroundWorker is kind of old school. If you just able to run on .Net 2.0 you have to use BackgroundWorker, but don't seem to be the case (example follows).
Further if you want to cancel a BackgroundWorker operation this isn't only call CancelAsync();. You also need to handle the e.Cancelled flag.
backgroundWorker.WorkerSupportsCancellation = true;
private void CancelBW()
{
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork += ((sender, args)
{
//Handle the cancellation (in your case do this in your loop for sure)
if (e.Cancelled) //Flag is true if someone call backgroundWorker.CancelAsync();
return;
//Do your stuff.
});
There is no common way to directly cancel the backgroundWorker
operation. You always need to handle this.
Now let's change your code to the modern TAP-Pattern and make some stuff you want to have.
private void MyForm : Form
{
private CancellationTokenSource ct;
public MyForm()
{
InitializeComponent();
checkbox1.Enable = false;
//Disable all checkboxes here.
ct = new CancellationTokenSource();
}
//Event if someone click your start button
private void buttonStart_Click(object sender, EventArgs e)
{
//Enable all checkboxes here
//This will be called if we get some progress from tcp
var progress = new Progress<string>(value =>
{
//check the behaviour of the checkboxes and write to file
file.WriteLine(value + behavior);
});
Task.Factory.StartNew(() => ListenToTcp(ct, progress as IProgress<string)); //starts the tcp listening async
}
//Event if someone click your stop button
private void buttonStop_Click(object sender, EventArgs e)
{
ct.Cancel();
//Disable all checkboxes (better make a method for this :D)
}
private void ListenToTcp(CancellationToken ct, IProgess<string> progress)
{
do
{
if (ct.IsCancellationRequested)
return;
int temp = data_feed.ReadByte(); //replaced var => temp because var is keyword
if (temp != -1)
{
data_in += (char)temp;
if (data_in.IndexOf("\r\n") != -1)
{
if (progress != null)
progress.Report(data_in); //Report the tcp-data to form thread
data_in = string.empty;
}
}
while (exit_state == false);
}
}
This snippet should do the trick. I don't test it so some syntax error maybe occur :P, but the principle will work.
The most important part is that you are not allowed to access gui
components in another thread then gui thread. You tried to access the
checkboxes within your BackgroundWorker DoWork which is no possible
and throw an exception.
So I use a Progress-Object to reuse the data we get in the Tcp-Stream, back to the Main-Thread. There we can access the checkboxes, build our string and write it to the file. More about BackgroundWorker vs. Task and the Progress behaviour you can find here.
Let me know if you have any further questions.

Wait for button press in foreach

I have a WPF application that needs to show the user the name of an object in an XML file, wait for them to read it, then allow them to press a Continue button and see the next one.
I've simplified the code below, but need a way to wait for the button press.
private void Waitforpress()
{
XDocument puppies = XDocument.Load(#"C:\puppies.xml");
foreach (var item in puppies.Descendants("Row")
{
PuppyName = item.Element("puppyName").Value;
// Call Print PuppyName function
// WAIT HERE FOR BUTTON PRESS BEFORE GOING TO NEXT PUPPY NAME
}
}
You should not really load the file inside the button like that, I would suggest you to create a procedure that reads the file into a queue and, when the user press the button, you read the next queued item and show it to the user, such as:
Queue<XElement> puppiesQueue = new Queue<XElement>();
void LoadPuppies()
{
XDocument puppies = XDocument.Load(#"C:\puppies.xml");
foreach (XElement puppie in puppies.Descendants("Row"))
puppiesQueue.Enqueue(puppie);
}
void Button_Click()
{
//Each time you click the button, it will return you the next puppie in the queue.
PuppyName = puppiesQueue.Dequeue().Element("puppyName").Value;
}
You can use the following method to create a Task that will be completed when the button is clicked:
public static Task WhenClicked(this Button button)
{
var tcs = new TaskCompletionSource<bool>();
RoutedEventHandler handler = null;
handler = (s, e) =>
{
tcs.TrySetResult(true);
button.Click -= handler;
};
button.Click += handler;
return tcs.Task;
}
You can then await that task so that your method will continue executing after the button is clicked:
private async Task Waitforpress()
{
XDocument puppies = XDocument.Load(#"C:\puppies.xml");
foreach (var item in puppies.Descendants("Row")
{
PuppyName = item.Element("puppyName").Value;
// Call Print PuppyName function
await button.WhenClicked();
}
}
Note that you probably want to be doing the file IO asynchronously, not synchronously, so as to not block the UI thread.

Tracking when x number of events have fired

Usually this is the stuff i would spend a few hours browsing google and stackoverflow for, however i ran into the problem of how the heck do i word this for a search engine.
I hoping there is a simple way of achieving this, as my current method feels far to "hackish"
What I need to do, if track when several sources of data have completed their loading, and only when all have completed do i load a new view (this is WPF mvvm). Now the data is loaded via a static class termed Repository each one creates a thread and ensure they only a single load operation can happen at once (to avoid multiple threads trying to load into the same collection), each of these classes fires an event called LoadingCompleted when they have finished loading.
I have a single location that loads a large portion of the data (for the first time, there are other locations where the data is reloaded however) what i planned was to hook into each repositories OnLoaded event, and keep track of which have already returned, and when one is returned, mark it as loaded and check to see if any remain. If none remain load the new view, else do nothing.
Something like this:
ShipmentRepository.LoadingComplete += ShipmentRepository_LoadingComplete;
ContainerRepository.LoadingComplete += ContainerRepository_LoadingComplete;
void ContainerRepository_LoadingComplete(object sender, EventArgs e)
{
_containerLoaded = true;
CheckLoaded();
}
void ShipmentRepository_LoadingComplete(object sender, EventArgs e)
{
_shipmentsLoaded = true;
CheckLoaded();
}
private void CheckLoaded()
{
if(_shipmentsLoaded && _containersLoaded && _packagesLoaded)
{
LoadView();
}
}
However as i mentioned this feels clumbsy and hackish, I was hoping there was a cleaner method of doing this.
You can achieve this with Reactive Extensions and using Observable.FromEventPattern in conjunction with the Observable.Zip method. You should be able to do something like:
var shipmentRepositoryLoadingComplete = Observable.FromEventPattern<EventHandler,EventArgs>(h => ShipmentRepository.LoadingComplete += h, h => ShipmentRepository.LoadingComplete -= h);
var containerRepositoryLoadingComplete = Observable.FromEventPattern<EventHandler, EventArgs>(h => ContainerRepository.LoadingComplete += h, h => ContainerRepository.LoadingComplete -= h);
Then you subscibe to the observalbes like this:
var subscription = Observable.Zip(shipmentRepositoryLoadingComplete, containerRepositoryLoadingComplete)
.Subscribe(l => LoadView()));
The subscirption needs to stay alive, so you can save this as a private variable. When both complete events are invoked, the LoadView method should be called. Here is a working console example I used to test this method.
using System;
using System.Reactive.Linq;
namespace RxEventCombine
{
class Program
{
public event EventHandler event1;
public event EventHandler event2;
public event EventHandler event3;
public Program()
{
event1 += Event1Completed;
event2 += Event2Completed;
event3 += Event3Completed;
}
public void Event1Completed(object sender, EventArgs args)
{
Console.WriteLine("Event 1 completed");
}
public void Event2Completed(object sender, EventArgs args)
{
Console.WriteLine("Event 2 completed");
}
public void Event3Completed(object sender, EventArgs args)
{
Console.WriteLine("Event 3 completed");
}
static void Main(string[] args)
{
var program = new Program();
var event1Observable = Observable.FromEventPattern<EventHandler,EventArgs>(h => program.event1 += h, h => program.event1 -= h);
var event2Observable = Observable.FromEventPattern<EventHandler, EventArgs>(h => program.event2 += h, h => program.event2 -= h);
var event3Observable = Observable.FromEventPattern<EventHandler, EventArgs>(h => program.event3 += h, h => program.event3 -= h);
using (var subscription = Observable.Zip(event1Observable, event2Observable, event3Observable)
.Subscribe(l => Console.WriteLine("All events completed")))
{
Console.WriteLine("Invoke event 1");
program.event1.Invoke(null, null);
Console.WriteLine("Invoke event 2");
program.event2.Invoke(null, null);
Console.WriteLine("Invoke event 3");
program.event3.Invoke(null, null);
}
Console.ReadKey();
}
}
}
Output
Invoke event 1
Event 1 completed
Invoke event 2
Event 2 completed
Invoke event 3
Event 3 completed
All events completed
Another way to do this: Add a property LoadingCompleted. For every instance you start a thread return that object to a list. On every loadcompleted set the property to true and in the place you catch the load completed loop through the list (myList.Any(x=>LoadingCompleted == false)) to figure out if everything is completed.
Not the most correct way to do it. But reading your scenario this might do the job.
If you are loading the shipments, containers and packages as asynchronous task then you have several options. As others suggested you can use WhenAll or Join() to wait for all threads to finish before proceeding. However, if your threads have to stay alive and the threads don't stop when they have finished loading, you can use the System.Threading.CountdownEvent as following:
Edit
Added how I would set up the threads and handle the events. Also moved the example from the static Program to an instance, more closely resembeling your situation. Again, if you do not need to do anything in the threads after they have loaded the data, just skip the CountdownEvent altogether and wait for all threads to finish. Much simpler, does not need events and can be achieved using Join() or in this case Task.WaitAll().
class Program
{
static void Main(string[] args)
{
var myWpfObject = new MyWpfObject();
}
}
public class MyWpfObject
{
CountdownEvent countdownEvent;
public MyWpfObject()
{
ShipmentRepository ShipmentRepository = new ShipmentRepository();
ContainerRepository ContainerRepository = new ContainerRepository();
PackageRepository PackageRepository = new PackageRepository();
ShipmentRepository.LoadingComplete += Repository_LoadingComplete;
ContainerRepository.LoadingComplete += Repository_LoadingComplete;
PackageRepository.LoadingComplete += Repository_LoadingComplete;
Task[] loadingTasks = new Task[] {
new Task(ShipmentRepository.Load),
new Task(ContainerRepository.Load),
new Task(PackageRepository.Load)
};
countdownEvent = new CountdownEvent(loadingTasks.Length);
foreach (var task in loadingTasks)
task.Start();
// Wait till everything is loaded.
countdownEvent.Wait();
Console.WriteLine("Everything Loaded");
//Wait till aditional tasks are completed.
Task.WaitAll(loadingTasks);
Console.WriteLine("Everything Completed");
Console.ReadKey();
}
public void Repository_LoadingComplete(object sender, EventArgs e)
{
countdownEvent.Signal();
}
}
And a mock Repository class:
public class ShipmentRepository
{
public ShipmentRepository()
{
}
public void Load()
{
//Simulate work
Thread.Sleep(1000);
if (LoadingComplete != null)
LoadingComplete(this, new EventArgs());
Console.WriteLine("Finished loading shipments");
DoAditionalWork();
}
private void DoAditionalWork()
{
//Do aditional work after everything is loaded
Thread.Sleep(5000);
Console.WriteLine("Finished aditional shipment work");
}
public event EventHandler LoadingComplete;
}

How to break an infinite loop using previewMouseUp?

Currently, I start looping upon PreviewMouseDown and I was thinking to break the loop using PreviewMouseUp but apparently failed. Below is the way I did it.
public bool stopFlag = true;
public int abc = 0;
private void TestingDown(){
while(stopFlag)
{
abc++;
}
}
private void TestingUp()
{
stopFlag = false;
Message.show(abc);
}
Actually my idea is to create sort of button holding effect, something like smartphone, hold a button for 3 seconds and do something. But there isnt any onHold event for button?
Start the Timer in PreviewMouseDown and check elapsed time in PreviewMouseUp.
In this case your loop runs in the same thread as the whole UI. But actually what you need is to calculate the time between MouseDown and MouseUp events.
DateTime holdStartTime;
public MainWindow()
{
InitializeComponent();
PreviewMouseDown += new MouseButtonEventHandler(MainWindow_PreviewMouseDown);
PreviewMouseUp += new MouseButtonEventHandler(MainWindow_PreviewMouseUp);
}
void MainWindow_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
holdStartTime = DateTime.Now;
}
void MainWindow_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if ((DateTime.Now - holdStartTime).TotalMilliseconds < 1000)
{
Console.WriteLine("Do something");
}
else
{
Console.WriteLine("Do something else");
}
}
I think you need backgroud thread for this as UI thread is busy in this loop.
You can refer below link for more info.
[Background Worker]

How to avoid copy/paste many event handlers

My application cannot access a specific menu item unless some condition is true (DataRepository.IsAllDataLoaded). I came up with this code, which works great. It checks for the condition first. If it is not ready, it calls a timer, which waits some milliseconds and call the same method again. The Timer needs an ElapsedEventHandler.
public void FirstMenuItem_Click(object sender, RoutedEventArgs e)
{
if (!DataRepository.IsAllDataLoaded)
{
WaitForDataLoading(FirstTimedEvent);
}
else
{
Dispatcher.BeginInvoke(new Action(() =>
{
IndividualEntryWindow Window = new IndividualEntryWindow();
Window.Show();
}));
}
}
private void FirstTimedEvent(object source, ElapsedEventArgs e)
{
FirstMenuItem_Click(null, null);
}
private static void WaitForDataLoading(ElapsedEventHandler timerEvent)
{
Timer t = new Timer();
t.Interval = 0.2;
t.AutoReset = false;
t.Elapsed += new ElapsedEventHandler(timerEvent);
t.Start();
}
Initially, the FirstMenuItem_Click was the only method. I had to add FirstTimedEvent handler for my timer. Is there a way to avoid creating that ElapsedEventHandler? Can I create it inline in my FirstMenuItem_Click method?
I now have to use that same pattern for many other Item_Click methods. I wish I don't have to create a ElapsedEventHandler for each Item_Click method.
Use an anonymous lambda expression:
WaitForDataLoading((s,e) => FirstMenuItem_Click(null, null));
You appear to be using WPF, based on your use of the Dispatcher class. That being the case, there are nicer means for you to control the access to your UI.
Two of these are:
bind your menu's Enabled property to ViewModel class, which would have a property to indicate whether the menu should be available. When your long-running job is complete, set the property to true and the menu will be enabled.
use an ICommand to drive the behaviour of your menu. The command's CanExecute return false while your long-running job is active, which will cause the menu to automatically be disabled until the job is complete.
It's worth noting that this will subtly change the behaviour of your menu - but not, I think, in a bad way. Your current code will wait for the job to complete before showing the dialog - but there's nothing to stop the user clicking the menu again in the meantime. These multiple clicks will each wait for the job to complete, and each display their own dialog when the job completes. In a trivial case this might mean that I see multiple dialogs appear; in a severe case the multiple timers that you're creating might badly affect the performance of the application.
Either of the methods suggested above would prevent the menu from being clicked while the job is running, which is not quite your current behaviour but, I think, would make more sense from a usability perspective.
In the following code you can call the method CheckDataShowWindow() anytime you wish to show the windows when the data is ready. If you want to add it to another cick handler, you can just make another like so:
public void Another_Click(object sender, RoutedEventArgs e)
{
CheckDataShowWindow();
}
Main code:
public void FirstMenuItem_Click(object sender, RoutedEventArgs e)
{
CheckDataShowWindow();
}
private void CheckDataShowWindow()
{
if (!DataRepository.IsAllDataLoaded)
{
Timer t = new Timer();
t.Interval = 0.2;
t.AutoReset = false;
t.Elapsed += (s,e) => CheckDataShowWindow();
t.Start();
}
else
{
Dispatcher.BeginInvoke(new Action(() =>
{
IndividualEntryWindow Window = new IndividualEntryWindow();
Window.Show();
}));
}
}
Update
If you can edit the code of the datarepository you should add an event for when the data is done loading.
public delegate void DoneLoadingHandler(object sender, EventArgs e);
public class DataRepository
{
public event DoneLoadingHandler DoneLoading;
//Your loading function
private void LoadAllData()
{
//Load like you do now
//Now fire the event that loading is done.
if(DoneLoading != null)
DoneLoading(this, new EventArgs());
}
}
Now in your other class:
public void FirstMenuItem_Click(object sender, RoutedEventArgs e)
{
CheckDataShowWindow();
}
private bool AllReadyWaiting = false;
private void CheckDataShowWindow()
{
if (!DataRepository.IsAllDataLoaded)
{
if(!AllReadyWaiting)
{
DataRepository.DoneLoading += (s,e) => ShowWindow();
AllReadyWaiting = true;
}
}
else
{
ShowWindow();
}
}
private void ShowWindow()
{
Dispatcher.BeginInvoke(new Action(() =>
{
IndividualEntryWindow Window = new IndividualEntryWindow();
Window.Show();
}));
}

Categories