How to get update from BackgroundWorker.DoWork Event - c#

In my login window, when I click the login button, the configuration and login processes will be executed and those methods are in another class file. So far what I've achieved is when I clicked the login button, the loading animation will be displayed on top of the login window and those processes will be executed as well. There are some login error checking in the configuration class file, so when login error caught, a message box with relevant info will be prompted out and stop the login process, the problem is the message box will not be prompted out since I put those config and login process in the BackgroundWorker.DoWork event.
Here's the codes for Login.xaml.cs:
private void LoginBtn_Click(object sender, RoutedEventArgs e)
{
Loading.Visibility = Visibility.Visible; //The loading animation
Loading.Visibility = Visibility.Visible;
Cursor = Cursors.Wait;
bw.DoWork += new DoWorkEventHandler(LoginInfoVerification);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RunWorkerCompleted);
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
}
private void LoginInfoVerification(object sender, DoWorkEventArgs e) {
var loginInfoVerification = config.ServerConnection(loginInfo.userName,
loginInfo.galPassword, loginInfo.place,
loginInfo.host, loginInfo.port,
loginInfo.application);
}
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (GlobalVariables.loginSuccess == true)
{
//Pass these variables to main window
var mainWindow = new MainWindow(loginInfo.userName, loginInfo.place, loginInfo.host, loginInfo.port,
loginInfo.application);
mainWindow.Show();
this.Close();
}
else
Loading.Visibility = Visibility.Collapsed;
Cursor = Cursors.Arrow;
}
For Configuration.cs:
public Configuration ConfigServerConnection(string loginUserName, string loginPassword, string loginPlace, string loginHost, int loginPort, string loginApplication)
{
//Server connection
//Login info verification
//If login error caught, prompt message box, different errors, different message
}
Is there any better suggestion in handling UI update and process update at the same time for my case? Please help.

To display the message box you need to switch back to the UI thread with Dispatcher.Invoke, see this.
Application.Current.Dispatcher.Invoke(() => /* show appropriate message box */);
Alternatively if you are using .NET 4.5 or higher you can make your life a lot easier with async-await by marking LoginBtn_Click with the async keyword and then awaiting the log in process.
If there is an asynchronous version of ServerConnection that returns a task you can just await that otherwise you can use Task.Run() to execute ServerConnection on a thread pool thread.
Await will kick off the log in operation asynchronously and once complete will resume the rest of the method on the GUI thread so you can manipulate GUI components without using Dispatcher.Invoke.
private async void LoginBtn_Click(object sender, RoutedEventArgs e)
{
Loading.Visibility = Visibility.Visible; //The loading animation
Loading.Visibility = Visibility.Visible;
Cursor = Cursors.Wait;
LoginVerification loginInfoVerification = null;
await Task.Run(() =>
{
loginInfoVerification = config.ServerConnection(loginInfo.userName,
loginInfo.galPassword,
loginInfo.place,
loginInfo.host,
loginInfo.port,
loginInfo.application);
});
.. rest of code, check login success, show message box..
}

Related

How to disable a button after click for only 3 seconds in a WinUI 3 Application?

I have a button that I want to disable for 3 seconds so that it's not abused. I wanted to add a Timer(3000); inside the Click event however the example code I found is using outdated method and is not working. I tried another code (which can be found below) however this throws System.Runtime.InteropServices.COMException: 'The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))' error.
private void CodeButton_Click(object sender, RoutedEventArgs e)
{
CodeButton.IsEnabled = false;
var timer = new Timer(3000);
timer.Elapsed += (timer_s, timer_e) =>
{
CodeButton.IsEnabled = true;
timer.Dispose();
};
timer.Start();
Launcher.LaunchUriAsync(new Uri("https://www.hoppie.nl/acars/system/register.html"));
}
You need to use the main thread (the thread that instantiated UI components) to update UI. You get that error because the timer will work with another thread, not the main thread.
You can do it this way:
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
// You can update the UI because
// the Click event will use the main thread.
this.Button.IsEnabled = false;
List<Task> tasks = new();
// The main thread will be released here until
// LaunchUriAsync returns.
tasks.Add(Launcher.LaunchUriAsync(new Uri("https://www.hoppie.nl/acars/system/register.html")));
tasks.Add(Task.Delay(3000));
await Task.WhenAll(tasks);
// The main thread will be back here.
}
finally
{
// This will enable the button even if you face exceptions.
this.Button.IsEnabled = true;
}
}

How to make a program wait for user to type in a textbox?

When I run a WinForm program to a line, I would like to check if a textbox already has user input, if not, I will ask user to type in the textbox and wait till user types in some input, before running the next line of the code. I was wondering how to do the wait?
The program has to wait for the information required as input for the next line of code.
Thanks.
Waiting for something to happen in the GUI (using a timer, loops from other threads, etc...) is a massive waste of resources. Almost all functional programming languages have Events including C#
From Wikipedia:
event-driven programming is a programming paradigm in which the flow
of the program is determined by events such as user actions (mouse
clicks, key presses), sensor outputs, or message passing from other
programs or threads. Event-driven programming is the dominant paradigm
used in graphical user interfaces and other applications (e.g.,
JavaScript web applications) that are centered on performing certain
actions in response to user input. This is also true of programming
for device drivers (e.g., P in USB device driver stacks).
You can do it like this with the help of Control.TextChanged event inherited by the Textbox control:
private void Form1_Load(object sender, EventArgs e)
{
ValidateGUI();
}
private const int MIN_CHARS_TO_DO_SOMETHING = 8;
private const string NOT_VALID = "Oh No There is No User Input )-:";
private const string VALID = "Great We Can Do Something (-:";
private void textBox1_TextChanged(object sender, EventArgs e)
{
ValidateGUI();
}
private void ValidateGUI()
{
if (textBox1.Text.Length < MIN_CHARS_TO_DO_SOMETHING)
{
lblMessege.Text = NOT_VALID;
}
else
{
lblMessege.Text = VALID;
// Execute some code..
//...
//...
}
}
I'm going to assume you have a valid reason for waiting instead of monitoring input.
You just need to use background workers and then you need to set the DoWork event to wait for a specified amount of time, and the RunWorkerCompleted event to run your code checking if input is being made.
Here's an example assuming a label and a textbox are on the form already. Alternatively you can just add the background worker as a form element instead of creating it in code:
public Form1()
{
InitializeComponent();
waitForInput();
}
private void waitForInput()
{
BackgroundWorker waiter = new BackgroundWorker();
waiter.WorkerSupportsCancellation = true;
waiter.WorkerReportsProgress = true;
waiter.DoWork += wait10Seconds;
waiter.RunWorkerCompleted += doneWaiting;
waiter.RunWorkerAsync();
}
private void wait10Seconds(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(10000);
}
private void doneWaiting(object sender, RunWorkerCompletedEventArgs e)
{
if(textBox1.Text == "")
{
label1.Text = "Why haven't you typed anything?";
}
}
If I understand what you're getting at, a synchronization object like SemaphoreSlim might be a good fit for something like this. This declaration sets the initial count to 0 so the semaphore will block. The initSync method pauses halfway through and will await the textbox Enter key to release the semaphoreSlim before executing the next line. However, the UI thread is not blocked during the waiting period.
SemaphoreSlim _waitForText = new SemaphoreSlim(0, maxCount: 1);
private async Task initAsync()
{
richTextBox1.AppendText(
$"The async method that populates this RichTextBox waits indefinitely for input.");
richTextBox1.AppendText($"{Environment.NewLine}>");;
richTextBox1.SelectionColor = Color.Red;
// "check if a textbox already has user input"
if (string.IsNullOrWhiteSpace(textBox1.Text))
{
// "ask user to type"
textBox1.Text = "Enter ID";
await _waitForText.WaitAsync();
}
else onUserInputOK();
// The MOCK login has completed. Enable the app.
richTextBox1.Enabled = true;
richTextBox1.AppendText(
$"{Environment.NewLine}Now this method will complete, and you'll see a message box in 5 seconds");
}
Example
public MainForm() => InitializeComponent();
protected override async void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Disable UI response until "logged in"
richTextBox1.Enabled = false;
// Subscribe to TextChanged event
textBox1.KeyDown += detectUserInput;
await initAsync();
await Task.Delay(TimeSpan.FromSeconds(5));
MessageBox.Show("All done");
}
private void detectUserInput(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Return)
{
e.Handled = e.SuppressKeyPress = true;
if (!string.IsNullOrWhiteSpace(textBox1.Text))
{
onUserInputOK();
}
}
}
private void onUserInputOK()
{
richTextBox1.AppendText($"{textBox1.Text}");
Text = textBox1.Text; // Start echo to title bar
richTextBox1.SelectionColor = Color.DarkGreen;
// Remove this listener. Install normal runtime hook.
textBox1.KeyDown -= detectUserInput;
textBox1.TextChanged += normalTextboxListener;
_waitForText.Release();
}
private void normalTextboxListener(object sender, EventArgs e) => Text = textBox1.Text;

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.

update text on a WPF page with delays

I new to WPF, and have to put a basic application together
It consists of one main window with a frame, and one page
the page has a basic status text -
the requirement is that when the page loads up, the application has to do a bunch of REST call to fetch some data from remote source, and update the status text as it fetches
problem is, as I update the text, it doesn't seem to be reflected on the page, or maybe it's being blocked - even though I've used Task
so far, I have the following code for testing:
private void Page_Loaded(object sender, RoutedEventArgs e) {
var wnd = Window.GetWindow(this);
wnd.ContentRendered += Wnd_ContentRendered;
}
private void Wnd_ContentRendered(object sender, EventArgs e) {
DisplayMessages();
}
private void DisplayMessages() {
authenticationText.Text = "text one";
var t = Task.Delay(5000);
t.Wait();
authenticationText.Text = "text two";
t = Task.Delay(5000);
t.Wait();
authenticationText.Text = "text three";
t = Task.Delay(5000);
t.Wait();
}
even though I'm waiting after each task, the UI doesn't get updated - rather it just displays text three directly after method is finished - suggestions ?
P.S: there's also a WPF loader on that page, I've noticed that it doesn't get animated as well - it seems the delay is working but everything on the UI isn't updated
I would suggest for getting the data from REST implementation , you should use the background worker and on the basis of completion of thread or progress changed you need to update the UI thread accordingly.
for getting the better insights on background worker.. kindly use this link
How to use WPF Background Worker
In your case you can use progresschanged event of the backgroundworker..
Please Create some property lets say StatusText with InotifyPropertyChanged Interface implemented and bind (use TwoWay Binding) it with the Text property of the authenticationText control .... and in the progress changed event of the backgroundworker set the value of the StatusText property,., which will automatically updates the UI.
You could try to invoke these results on the UI Thread...
Run your task normally with Task.Run or whatever. Each time you are ready to set some property on UI Thread you should invoke it through the dispatcher..
Task.Run(() =>
{
var _Temp = getSomePropTask();
Thread.Sleep(1000);
App.Current.Dispatcher.Invoke(()=>{
authenticationText.Text = _Temp;
});
});
Thanks to suggestion by Ashok, I did some background reading and have come up with the following solution using Task, async and await - which is simpler to manage than background worker threads:
private void Page_Loaded(object sender, RoutedEventArgs e) {
var wnd = Window.GetWindow(this);
wnd.ContentRendered += Wnd_ContentRendered;
}
private void Wnd_ContentRendered(object sender, EventArgs e) {
GetDataAsync();
}
private async void GetDataAsync() {
authenticationText.Text = "Connecting...";
await Task.Delay(5000);
authenticationText.Text = "Getting Member Details...";
List<MemberServiceModel> memberList = await GetMembersAsync();
// more code for handling response
}
private List<MemberServiceModel> GetMembers() {
//get all members synchronous
var request = new RestRequest("Members/Admin", Method.GET);
var response = _client.Execute<List<MemberServiceModel>>(request);
if (response.ResponseStatus != ResponseStatus.Completed) {
//TODO
_restErrorStatus = response.ResponseStatus.ToString();
_restErrorMessage = response.StatusDescription;
_logger.Error("Error in GetMembers");
_logger.Error("Status:" + _restErrorStatus);
_logger.Error("Description:" + _restErrorMessage);
}
return response.Data; ;
}
private Task<List<MemberServiceModel>> GetMembersAsync() {
//get all members asynchronous
return Task.Run(new Func<List<MemberServiceModel>>(GetMembers));
}

Updating UI Thread immediately

I'm trying to enable a busy indicator on log in. The problem I'm having is it won't enable until everything is done executing. How can I immediately tell the thread to update the UI as soon as I log in to start the indicator asap?
private void LoginButton_Click(object sender, RoutedEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
radBusyIndicator.IsBusy = true;
//var backgroundWorker = new System.ComponentModel.BackgroundWorker();
//backgroundWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(backgroundWorker_DoWork);
//backgroundWorker.RunWorkerAsync();
}));
string error = string.Empty;
long userId = 0;
//Login code here....
//........... bunch of other code. etc..
}
The UI will update as soon as the UI thread is free. There is no need for Dispatcher.Invoke in this case, as you're already in the UI thread.
The key here is to move the "work" into a background thread, ie:
private void LoginButton_Click(object sender, RoutedEventArgs e)
{
radBusyIndicator.IsBusy = true;
LoginButton.IsEnabled = false; // Prevent clicking twice
string error = string.Empty;
long userId = 0;
// Start this in the background
var task = Task.Factory.StartNew(()=>
{
//Login code here....
//........... bunch of other code. etc..
});
// Run, on the UI thread, cleanup code afterwards
task.ContinueWith(t =>
{
// TODO: Handle exceptions by checking t.Exception or similar...
radBusyIndicator.IsBusy = false;
LoginButton.IsEnabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
If you're using C# 5, you can simplify this by making your login and other code asynchronous:
private async void LoginButton_Click(object sender, RoutedEventArgs e)
{
radBusyIndicator.IsBusy = true;
LoginButton.IsEnabled = false; // Prevent clicking twice
long userId = 0;
// Call async method with await, etc...
string error = await DoLoginAsync(userId);
var result = await BunchOfOtherCodeAsync();
radBusyIndicator.IsBusy = false;
LoginButton.IsEnabled = true;
}
You can use BAckground Worker Thread and subsribe its two eventHandlers to your events which you want to work on..
for eg-
BackgroundWorker Worker=new BackgroundWorker();
worker.DoWork+=Yorevent which will do the timeTaking Task();
Worker.RunWorkerCompleted+=YOurEvent which will Update your UI after the work is done();
worker.RunWorkerAsync();
this way it will not cause any thread Error too..
Just Enable your BusyIndicator as Your TimeTaking TAsk start and when the timeTaking Task is done just Disable your Busy Indicator in RUnWorkerCompleted Event.

Categories