Wait for button press in foreach - c#

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.

Related

How can I run an expensive process on SelectionChanged but not when scrolling quickly. (Delay event handler)?

I am having to run what can be a fairly slow task every time the SelectionChanged event of a DataGrid is fired.
The problem I have is that I need to keep the application responsive and if the user is scrolling very quickly using the arrow keys then I don't want to execute the task for every item. Only the item they stop on. (I hope this is making sense!)
I have setup a very basic example to demonstrate, that displays a list of words in a DataGrid and then when you scroll through them it adds them to a ListView.
This is what I have tried so far:
CancellationTokenSource cts;
private bool loading;
private async void dgData_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (loading)
{
cts.Cancel();
return;
}
cts = new CancellationTokenSource();
loading = true;
var x = dgData.SelectedItem.ToString();
await Task.Run(async () =>
{
Thread.Sleep(1000); //Wait a second to see if scrolling quickly...
await ExpensiveProcess(x);
});
loading = false;
}
private async Task ExpensiveProcess(string text)
{
if (cts.IsCancellationRequested)
{
loading = false;
return;
}
await Task.Factory.StartNew(() =>
{
//Expensive process will be done here...
});
Application.Current.Dispatcher.Invoke(() =>
{
lvwItems.Items.Add(text);
});
loading = false;
}
This appears to work in the fact that if arrow down quickly it misses items, but when I stop on one and want it to run it doesn't work?
Where am I going wrong? Is this even the best approach? Any advice is greatly appreciated and happy to provide further information. Thank you in advance.
UPDATE:
I found a a video on YouTube that suggested doing this which is working as I'd expect so for now I am going for this, but leaving the question open for now for any feedback.
Create a timer which will run the expensive process and set the interval to something low but not too slow so the key presses.
var myTimer = new DispatcherTimer();
myTimer.Interval = TimeSpan.FromSeconds(1);
myTimer.Tick += MyTimer_Tick
On the tick event of the timer run the long running process.
private void MyTimer_Tick(object sender, EventArgs e)
{
var x = dgData.SelectedItem.ToString();
Task.Run(async () =>
{
Thread.Sleep(1000); //Needs to be removed
await ExpensiveProcess(x);
});
}
Then in regular SelectionChanged event simply Stop and Start timer. Also don't forget to Stop the timer at the end of the long process.
You could start a timer in the SelectionChanged event handler and then check whether the item is still selected when the timer elapses.
If it is, you call the long-running method with a CancellationToken that you cancel if another selection occurs.
The following sample code should give you the idea:
private CancellationTokenSource _cts = null;
...
dataGrid.SelectionChanged += async(ss, ee) =>
{
//cancel any previous long running operation
if (_cts != null)
{
_cts.Cancel();
_cts.Dispose();
}
_cts = new CancellationTokenSource();
//store a local copy the unique id or something of the currently selected item
var id = (dataGrid.SelectedItem as TestItem).Id;
//wait a second and a half before doing anything...
await Task.Delay(1500);
//if no other item has been selected since {id} was selected, call the long running operation
if (_cts != null && id == (dataGrid.SelectedItem as TestItem).Id)
{
try
{
await LongRunningOperation(id, _cts.Token);
}
finally
{
_cts.Cancel();
_cts.Dispose();
_cts = null;
}
}
};
Instead of using the event, if you use a data-binding on the SelectedItem property you can achieve this easily using the Delay property. Delay will wait n milliseconds before processing a change.
<DataGrid ...
SelectedItem="{Binding SelectedItem, Delay=1000}">
...
</DataGrid>

Wait for button click while reading or during a loop

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();
}
}

C# Pause the loop and continue after button is clicked

I have a method that is called after the initialization component, the method signature is like:
public async void method_name ()
{
// code ...
}
Inside that method, I have a loop running with 4 different if statements. I need the loop to pause at each if statements and wait for the user to press a button. Since pressing that button will add info and stuff. Once the button is pressed, I want the loop to continue and of course pause at the next if statements.
I thought about doing it like await Task.Delay(30000); but if the user is done entering the info before that timer is over, he/she will just be waiting. and That's not efficient.
You can do that with TaskCompletionSource. Create it and await its Task property and when a button is clicked use it to complete that task. That allows to asynchronously wait for the user input without blocking a thread or using Task.Delay.
TaskCompletionSource<bool> _tcs;
async Task Foo()
{
// stuff
_tcs = new TaskCompletionSource<bool>();
await _tcs.Task
// other stuff
}
void ButtonClicked(object sender, EventArgs e)
{
_tcs.SetResult(false);
}

Run Event after button get pressed

I have a code which reads information from a file and displays them to the user.... now i want to STOP my code after displaying the information to the the user and WAIT for the buttonpress which starts my event, because the button clears the textbox and returns some information the the user / admin ...
But i don't have any idea how to break my code from running and wait for the button to get pressed...
Thx a lot
StringBuilder strbuildsection = new StringBuilder();
StringBuilder strbuildbetreff = new StringBuilder();
StringBuilder strbuildinhalt = new StringBuilder();
StringBuilder strbuilduser = new StringBuilder(System.Environment.UserName);
StringBuilder strbuildusercheck = new StringBuilder();
foreach (string Ccat in this.ini.IniGetCategories())
{
string readval = ini.IniReadValue(Ccat, "Read");
string usercheckvar = (this.ini.IniReadValue(Ccat, "SpecifyUser"));
string user = System.Environment.UserName;
if (readval == "0")
{
if (usercheckvar == user || usercheckvar.Equals("All"))
{
strbuildsection.Append(Ccat + Environment.NewLine);
foreach (string cat in this.ini.IniGetKeys(Ccat))
{
strbuildinhalt.Clear();
strbuildusercheck.Clear();
strbuildbetreff.Clear();
strbuildbetreff.Append(this.ini.IniReadValue(Ccat, "Betreff") + Environment.NewLine);
strbuildinhalt.Append(this.ini.IniReadValue(Ccat, "Inhalt") + Environment.NewLine);
}
textBox1.AppendText(strbuildsection.ToString() + strbuildbetreff.ToString() + strbuildinhalt.ToString() + strbuildusercheck.ToString() + Environment.NewLine);
strbuildsection.Clear();
// HERE I want to stop my process and wait until the User
// press the button and start my event
// but i don't know how to do this
// After this the loop continue and so on
}
private void BT_Bestaetigung_Click(object sender, EventArgs e)
{
CODE
}
So i want to start my ´Event´ if the button get pressed and if not, the code should wait for this
As it seems you placed the code in the form which you showed to the user and this blocks you from stopping to wait for the user to respond because you are still inside your loop.
The solution is to use a separate modal dialog:
Create a separate Form which you construct inside your loop and show it to the user when necessary - wait for the form to be closed - work on the results and repeat until done.
Inside this new form you place your controls and buttons that the user needs to interact with and fill them before you show it to him.
frmConfirmationDialog myConfirmationDialog = new frmConfirmationDialog()
//Fill in information to show to the user
myConfirmationDialog.textBox1.AppendText(strbuildsection.ToString() + strbuildbetreff.ToString() + strbuildinhalt.ToString() + strbuildusercheck.ToString() + Environment.NewLine);
//Open Form modally (this will stop your loop until the dialog is closed)
DialogResult myResult = myConfirmationDialog.ShowDialog();
if (myResult == System.Windows.Forms.DialogResult.OK)
{
//Do Stuff here
}
else //catch further type of results here (you could also work with a switch statement
{
//Do Stuff here
}
BTW to get a DialogResult when closing the form set the DialogResult Property of the Confirm or Cancel Buttons to the values you like. This will cause the modal form to be automatically closed with the DialogResult of the button. If you need to catch stuff before closing the form you can either implement an EventHandler for FormClosing or handle the Click event of the buttons.
You have to think it this way: you have a continous operation and you are waiting for the user interaction.
In winforms application, where you can not perform continous operation in UI thread (because your window required constantly receive messages from os, i.e. user input or repaint requrests and have to react to them, or you will have laggy or even frozen UI), typical solution is:
split operation into sub-operations (which are short and doesn't cause UI lags);
put operation into a thread.
So, basicaly, go this way (there is a pattern to avoid using switch with delegating steps, but for simplicity we show classical way):
_timer = new Timer();
_timer.Tick += timer_Tick;
...
// in timer tick
switch(_operation)
{
case operation1:
_operation = nextoperation; // _operation++
...
break;
case operation2:
// do nothing, wait for button press
if(_buttonPressed)
_operation = nextoperation;
break;
case operation3:
// continue after button press
..
...
}
or this way
_thread = new Thread(...)
thread.Start();
...
// in thread
// start operation
...
// wait for button press
while(_buttonPressed)
Thread.Sleep(0);
// continue operation
...
in both cases
// in button click event
_buttonPressed = true;
Random rnd = new Random();
bool stop = false;
Thread thread = null;
btnShow.Click += (source, e) =>
{
stop = true;
MessageBox.Show("Show something");
};
btnClose.Click += (source, e) =>
{
stop = false;
};
if (thread == null)
{
thread = new Thread(() =>
{
while (true) // your working loop
{
while (stop)
{}
// action of your loop
Console.WriteLine(rnd.Next());
}
});
thread.Start();
}

While loop program flow

I'm having trouble with my program flow in a while loop I created.
while (reader.Read())
{
// Store scenario information
int Id = (int)reader["ScenarioID"];
string Data = reader["ScenarioData"].ToString();
string Url = "http://google.com";
// Initialize result information
int HasSucceeded = 0;
var screenshot = new Byte[] { };
// Navigate to webBrowser
webBrowser2.Navigate(Url);
webBrowser2.DocumentCompleted += WebBrowserDocumentCompleted;
// Do test
TestScenarios(Url, HasSucceeded);
// Take screenshot
TakeScreenshot(screenshot);
// Insert results
InsertResults(Id, HasSucceeded, screenshot);
// Mark scenario for deletion
MarkScenario(Id);
}
private void WebBrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs Url)
{
MessageBox.Show("Operation has completed!");
}
The expected flow of the program should be
Read an item in the table
Initialize some variables/store some values
Navigate the webBrowser control toe the URL
When the webBrowser control is finished, do a test
Take a screenshot
Insert results into new table
Mark the item in the original table for deletion
Loop back to #1 until all items have been covered.
However, what is happening is everything in the while loop is running properly in order except for the webBrowser2.Navigate line, which does not show the Url until the while loop has exited. Immediately after the Url shows, 5 sequential messages "Operation has completed" (for the 5 items in the table) appear. How can I fix my flow?
Try this solution. Wrap your loop in another thread than UI thread. then make use of AutoResetEvent
new Thread(() =>
{
AutoResetEvent signal = new AutoResetEvent(false);
while (reader.Read())
{
// Store scenario information
int Id = (int)reader["ScenarioID"];
string Data = reader["ScenarioData"].ToString();
string Url = "http://google.com";
// Initialize result information
int HasSucceeded = 0;
var screenshot = new Byte[] { };
Action action = () =>
{
webBrowser2.Tag = signal;
// Navigate to webBrowser
webBrowser2.Navigate(Url);
webBrowser2.DocumentCompleted -= WebBrowserDocumentCompleted;
webBrowser2.DocumentCompleted += WebBrowserDocumentCompleted;
};
webBrowser2.Invoke(action);
signal.WaitOne();//Wait till it finishes
// Do test
TestScenarios(Url, HasSucceeded);
// Take screenshot
TakeScreenshot(screenshot);
// Insert results
InsertResults(Id, HasSucceeded, screenshot);
// Mark scenario for deletion
MarkScenario(Id);
}
}).Start();
private void WebBrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs Url)
{
MessageBox.Show("Operation has completed!");
((AutoResetEvent)((WebBrowser)sender).Tag).Set();
}
I asked worker thread to wait till the document loads then continue execution. simple.
Hope this helps
The Navigate method is probably queuing an event which will be later handled on the same thread your code is running in (the UI thread). You may have to put your code into a separate background worker thread to allow the UI events to be processed before your loop is finished.
I recomend you to ckeck the async and await operation if you are devleloping in .NET 4.5 Frammework. This propably will solve your problem.
Async and Await in MSDN

Categories