I am using a CheckBox as a Toggle Button (checkBox.Appearance = Appearance.Button) in a child form. If the users presses the button a measurement cycle will start if some criteria are satisfied (e.g. temperature within range). The CheckBox.Checked property remains true until the measurement is completed.
If e.g. the temperature is out of range a warning will appear and the button will be reset. The same happens if the cycle ended properly. In the end this self-explaining function is called:
/// <summary>
/// Resets the button states to false
/// </summary>
public void ResetButton()
{
checkBox_Start.Checked = false;
}
Now, when debugging, I see that the CheckBox_Start.Checked property is false and remains false. BUT the UI does not show the actual value. It seemingly remains checked. I have tried Refresh() and Update() on all levels.
Does anyone have an idea? What could possibly keep the UI from showing the actual value?
I found a similar problem, except with DataGridView. The solution I found was to invalidate the control and then request it to be redrawn, as follows:
public void ResetButton()
{
checkBox_Start.Checked = false;
checkBox_Start.Invalidate();
checkBox_Start.Update();
}
Try that, see if it fixes it.
this.MyCheckBox.Checked=false;
I had same problem, after spending some times on CheckBox properties and methods, finally i realize that where I am initializing this control is not a suitable one. I was using a semi framework which in some cases doesnt goes in this code. I use this simple property:
checkBox_Start.Checked = false;
Make sure you do not have InitializeComponent running twice.
That would cause the initial poster's control behaviour.
This usually comes with trying to update UI within un-main thread. So I recommend to create a generic function doing UI update. And let the main or un-main thread call it if need to update UI.
This is my sample code:
public void OnCheckBox(bool set_on) {
if (set_on == true) {
CBox.Invoke(new Action(() => {
CBox.Checked = true;
CBox.Invalidate();
CBox.Update();
}));
}
else {
CBox.Invoke(new Action(() => {
CBox.Checked = false;
CBox.Invalidate();
CBox.Update();
}));
}
}
And you can call it like:
void run() {
OnCheckBox(true);
Thread.Sleep(500);
OnCheckBox(false);
Thread.Sleep(500);
OnCheckBox(true);
Thread.Sleep(500);
OnCheckBox(false);
}
Thread t = new Thread(new ThreadStart(this.run));
t.Start();
Related
I have to work on a C# GUI application that displays information regarding an alternator that sends back messages every 20ms. So whenever I receive a certain message for ex regarding the temperature, I have to update a Textbox's text property on the UI with the correct information. The problem is I have tried using multithreading with delegates, did not work, and using invoke's did not work. Using a thread pool didn't work. I just want a chunk of code that forces the UI to update a textbox's text property and never thought this could be that hard.
At the moment I have this:
delegate void UpdateTextBoxTextDelegate(double newValue);
private void UpdateTextBoxText(double newValue)
{
if(tbxTemp.InvokeRequired)
{
UpdateTextBoxTextDelegate del = new UpdateTextBoxTextDelegate(UpdateTextBoxText);
tbxTemp.Invoke(del, new object[] {newValue});
}
else
{
tbxTemp.Text = Convert.ToString(newValue + "°C");
}
}
private double alternatorTemperature;
public double _alternatorTemperature
{
get { return alternatorTemperature; }
set
{
alternatorTemperature = value;
UpdateTextBoxText(alternatorTemperature);
}
}
And then I have this function which is basically executing every time a CAN message is received with a certain PGN. This function call hierarchy basically leads back up to the main thread of the application, not the UI thread if that helps.
//Alternator Temperature
case 0xFEA7:
temp = (UInt16)(canMessage.Data4);
alternatorTemperature = Convert.ToDouble(temp);
_alternatorTemperature = alternatorTemperature;
break;
The following code changes the textbox text almost 20 seconds later after there have been thousands of messages sent which is not acceptable. I have also tried the invoke way but only has the same delayed result
tbxRPM.Invoke(new Action(() => tbxRPM.Text = alternatorRPM.ToString() + "/rpm"));
If anyone has an easy way of changing a textbox.text property and then have it immediately show up on the UI, it would be great.
I would recomend you to create a class representing your "Alternator" with a property for "Temperature" and one method for "GetTemperature" that would be the same has your "function which is basically executing every time a CAN message is received with a certain PGN". Once it is done, bind your textbox to the instatiated class and property using:
tbxTemp.DataBindings.Add("Text",
objAlternator,
"Temperature",
false,
DataSourceUpdateMode.OnPropertyChanged);
In the end, the method I used to force UI controls to change even though the main thread was busy all the time was the following. I used this method on text boxes and it turned out to be very responsive
private string _someVariable = "Initial text";
public string SomeVariable
{
get { return _someVariable; }
set { _someVariable= value;
if(PropertyChanged != null)
{
PropertyChanged(this,new System.ComponentModel.PropertyChangedEventArgs(SomeVariable));
}
}
}
Within the main form's constructor I added the following:
lblTextBox.DataBindings.Add("Text", this, "SomeVariable");
And then to trigger changes on the UI controls when I needed I just changed the text property of the textbox you bound the data to and it just worked without the need for multi-threading, delegates, or any complex stuff.
eg.
Where you need a control to change:
lblTextBox.Text = "Some text";
I've spent 4 hours on this and totally failed.
I know that i need to use BackgroundWorker but all the tutorials refer to running a progress script on the actual form you are running the worker on.
I have a large datagrid, which the user can use a check box to "select all" and then press "UPDATE ALL"
This updates every grid with a bunch of options they choose.
For some users this may be 5 records which is nothing, but some might update 200 records with 5 options which takes about... 10-15 secs to iterate through them.
I have tried so many variations of running BGworker which loads a FrmLoading.Showdialog
Or trying to have BGworker "do work" running the code and then the main thread having the FrmLoading.Show()
However nothing is working.
If i have the update code in the background worker, it fails because the datagrid and everything is in a different thread.
The other way round, and it just hangs on FrmLoading.Show()
Any advice would be great.
I just can't seem to get my head around how to get this working for what seems to be an easy idea!
Current Update Code:
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//MessageBox.Show(Convert.ToBoolean(rowx.Cells["clnselected"].Value).ToString());
if (Convert.ToBoolean(rowx.Cells["clnselected"].Value) == true)
{
//if cycle has a value.
if (cmbcycle.SelectedIndex != -1)
{
rowx.Cells["clncycletype"].Value = cycle;
rowx.Cells["clnpackscollect"].Value = packs;
}
//if location has a value
if (cmblocation.SelectedIndex != -1)
{
location = Convert.ToInt32(cmblocation.SelectedValue);
rowx.Cells["clnlocation1"].Value = location;
}
if (cmbsize.SelectedIndex != -1)
{
size = Convert.ToInt32(cmbsize.SelectedValue);
rowx.Cells["clnpacksize"].Value = size;
}
if (chkDelivery.Checked == true)
{
rowx.Cells["clnDelivery"].Value = true;
}
if (chkSignSheet.Checked == true)
{
rowx.Cells["clnSigningSheet"].Value = true;
}
}
countupdated++;
}
foreach (DataGridViewRow row in dataGridpatients.Rows)
{
row.Cells["clnselected"].Value = false;
row.DefaultCellStyle.BackColor = Color.White;
}
cmbsize.SelectedIndex = -1;
cmblocation.SelectedIndex = -1;
cmbcycle.SelectedIndex = -1;
chkDelivery.Checked = false;
chkSignSheet.Checked = false;
#countupdated++;
I also have #CountSelected.
What i want to do is run this code above but have a popup overlay (dialog) with my logo + "Updating X%"
Where X = countupdated/countselected * 100
I now know i need to use the background worker and invoke for the above, but literally have no idea regarding how to invoke the grid and go from there.
I understand i need to invoke the variables I'm using
(eg. cmbcycle.SelectedIndex)
I know iterating through 150 records and updating individual cells is probably wrong,
My other option is creating a datatable from "selected" cells on that datatable
then Running the update via SQL instead of iterating through a bound table.
Then after the SQL i can re-create the table which will now have the new cell values updated in it?
Would that be a more appropriate way to do it?
Max rows on this table would be 200. Average ~70 so we are never talking 500 or 1000
EDIT:
So the checked answer works to run the background worker and refer to the controls on the form.
The issue is that if i do this:
backgroundWorker1.RunWorkerAsync();
splashy.ShowDialog();
Then the splash screen pops up after the background worker ends
If i do this:
splashy.ShowDialog();
backgroundWorker1.RunWorkerAsync();
Then the popup semi-forms and hangs until the end of the background worker, at which time it closes
because of the RunWorkerCompleted event.
EDIT:
I have no updated the code in DoWork and used Invokes to refer to the controls.
This works and the code runs fine.
I now need a popup ot appear showing the progress through the updates.
splashy.InvokeBy(() =>
{
splashy.Show();
});
backgroundWorker1.RunWorkerAsync();
Does not work. It causes the popup but freeze
splashy.ShowDialog();
backgroundWorker1.RunWorkerAsync();
Allows the Dialog to show (not 'frozen' and distorted) However the Lab (lblprogress) does not update.
This is because the form never get to the RunWorker method, it is stuck at ShowDialog.
It would be a good idea to make modifications on your DataSource itself and then bind it with the DataGridView.
But as from your existing code if you want to access your controls/UI to update or change values from BackgroundWorker.RunWorkerAsync method or any other Thread call for that matter, you can create an extension method to .Invoke() the controls like:
public static class MyExtensions
{
public static void InvokeBy(this Control ctl, MethodInvoker method)
{
if (ctl.InvokeRequired)
ctl.Invoke(method);
else method();
}
}
Keep this static class under the same Namespace as your main class for convenience.
Thus this code:
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//your codes
}
Will become:
dataGridpatients.InvokeBy(() =>
{
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//your codes
}
});
Similarly,
if (cmbcycle.SelectedIndex != -1)
{
//your codes
}
Will become:
cmbcycle.InvokeBy(() =>
{
if (cmbcycle.SelectedIndex != -1)
{
//your codes
}
});
This way you van safely access your controls, while keeping your UI responsive at the same time. Update your Popup Status UI the same way!
This answer is based around o_O's answer.
The main issue is that i wanted the UI to actually update and the background worker to supply the splash.
Instead of running all the 'hard code' in the BGW, i left it in the original thread, but called a BGW to display a popup Dialog form.
so at the start of the "hard code" I used:
backgroundWorker1.RunWorkerAsync();
This called:
FrmSplash splashy;
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
splashy = new FrmSplash();
splashy.ShowDialog();
}
In order to remove the dialog box, at the end of the code in the GUI thread, i used:
splashy.InvokeBy(() =>
{
splashy.Close();
}
);
backgroundWorker1.CancelAsync();
Which uses the extension supplied by O_o
public static class MyExtensions
{
public static void InvokeBy(this Control ctl, MethodInvoker method)
{
if (ctl.InvokeRequired)
ctl.Invoke(method);
else method();
}
}
I have also built a label update into splashy
So i could call
splashy.InvokeBy(() =>
{
splashy.SetStatus(countupdated.ToString());
}
);
As i iterated through the datagridview rows. This updated the label on the splash screen :)
I have a function called ExecuteCommand that does things based on a user's input. These things can range from simply doing a Console.Writeline(), checking a check box on my form, or simulating keystrokes to another process, completely independent from my own. The function runs on a separate thread, so changing the UI will requiring some invoking. I have 2 ways of doing it... one of which I'm not sure is a good way but it's very easy.
Code below, the 3rd line is what I have a question with:
private void ExecuteCommand()
{
this.Invoke((MethodInvoker)delegate()
{
if (current_line_index < command_que.Count)
{
current_line = command_que[current_line_index];
if (current_line.StartsWith(">>Auto Enter"))
{
chkAutoEnter.Checked = false;
}
else if (current_line.StartsWith("+WinWait("))
{
string title_to_wait_for = current_line;
title_to_wait_for = title_to_wait_for.Remove(0, "+WinWait(\"".Length);
title_to_wait_for = title_to_wait_for.Remove(title_to_wait_for.Length - 2, 2);
t_WinWait = new Thread(() => WinWait(title_to_wait_for));
t_WinWait.Name = "WinWait";
t_WinWait.Start();
}
}
});
}
The code works perfectly... but I am not sure if it's good practice.
Alternativly, I know I can do something like this to change the UI:
private delegate void CheckCheckBoxHandler(bool checked);
private void CheckCheckBox(bool checked)
{
if (this.chkAutoEnter.InvokeRequired)
{
this.chkAutoEnter.Invoke(new CheckCheckBoxHandler(this.CheckCheckBox), checked);
}
else
{
chkAutoEnter.Checked = checked;
}
}
But as I have multiple controls on my form that will be changed from another thread, I'd have to add a bunch of functions to do that, versus the simple method in the first example.
Is the first way bad in anyway? Are there any risks involved I haven't come across yet? It seems to good to be true...
Thanks!
No it's not bad. It doesn't matter which control that you call Invoke on since they all have the same effect. Invoke calls the delegate on the thread that owns the control - as long as all your controls are owned by the same thread, then there is no difference.
I have a button on my windows form that calls the RunWorkerAsync() method, this in turn performs an action which then updates a ListBox on the same form.
After the DoWork event has finished I assign the Result for the event (Which is a list), I process the RunWorkerCompleted() event and then perform the following code to update my Listbox
which calls this:
(Apologies, code formatting won't work)
Now when I run the application and press the refresh button the following exception appears:
How would I get around this?
Edit:
The exception is thrown on the folowing statement, this occurs in the DoWork method where I clear the contents to keep the list up to date;
listBoxServers.Items.Clear();
You may not call Invoke on the list box, but on the form. For WinForms applications I use something like:
...
this.Invoke((MethodInvoker)delegate()
{
// Do stuff on ANY control on the form.
});
...
Depending on the .NET version, you may have to declare a delegate for MethodInvoker yourself as
public delegate void MethodInvoker();
However, you might also consider using the ReportProgress feature of the Background Worker. The respective event handler should be called in the context of the form's thread.
Here's a snippet which I find very handy:
public static void ThreadSafe(Action action)
{
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal,
new MethodInvoker(action));
}
You can pass it any delegate of Action type or simply a lambda like this:
ThreadSafe(() =>
{
[your code here]
});
or
ThreadSafe(listBoxServers.Items.Clear);
What I've done is something like this every time you need to run something across threads:
listBoxServers.BeginInvoke(
(Action)
(() => listBoxServers.Items.Clear()));
Background threads are not allowed to update the UI in Windows applications, so you have to revert the control back to the UI thread for the actual update.
Create a method that will call UpdateServerDetails on the main thread, like this:
private void DispatchServerDetails(List<ServerDetails> details)
{
Action<List<ServerDetails>> action = UpdateServerDetails;
Dispatcher.Invoke(action)
}
and then call DispatchServerDetails instead of UpdateServerDetails.
Some caveats:
-This works best in WPF applications, for WinForms, you'll need to jump through some hoops, or you can use InvokeRequired
-The UI update is still synchronous, so if UpdateServerDetails does a lot of work, it will block the UI thread (not your case, just to be on the safe side).
Using Invoke in a windows forms project can be a little tricky, there're some pitfalls that are documented but easy to miss. I recommend using something like you'll find in this question:
Is it appropriate to extend Control to provide consistently safe Invoke/BeginInvoke functionality?
It handles cases where invoke is not required, is called from different threads, handle is or isn't created, etcetcetc. It could be easily modified to be SafeInvoke() and SafeBeginInvoke() if you're not a fan of the bool parameter.
(Included here for your convenience:
/// Usage:
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);
// or
string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);
/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of
/// updater. False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread. If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (!uiElement.IsHandleCreated)
{
// Do nothing if the handle isn't created already. The user's responsible
// for ensuring that the handle they give us exists.
return;
}
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
I just figured out a simpler way without using Invoke:
int fakepercentage = -1;
//some loop here......if no loop exists, just change the value to something else
if (fakepercentage == -1)
{
fakepercentage = -2;
}
else
{
fakepercentage = -1;
}
backgroundworker1.ReportProgress(fakepercentage);
Then in backgroundworker1_ProgressChanged(object sender, ProgressChangedEventArgs e):
if (e.ProgressPercentage < 0)
{
//access your ui control safely here
}
My app has a DataGridView object and a List of type MousePos. MousePos is a custom class that holds mouse X,Y coordinates (of type "Point") and a running count of this position. I have a thread (System.Timers.Timer) that raises an event once every second, checks the mouse position, adds and/or updates the count of the mouse position on this List.
I would like to have a similar running thread (again, I think System.Timers.Timer is a good choice) which would again raise an event once a second to automatically Refresh() the DataGridView so that the user can see the data on the screen update. (like TaskManager does.)
Unfortunately, calling the DataGridView.Refresh() method results in VS2005 stopping execution and noting that I've run into a cross-threading situation.
If I'm understanding correctly, I have 3 threads now:
Primary UI thread
MousePos List thread (Timer)
DataGridView Refresh thread (Timer)
To see if I could Refresh() the DataGridView on the primary thread, I added a button to the form which called DataGridView.Refresh(), but this (strangely) didn't do anything. I found a topic which seemed to indicate that if I set DataGridView.DataSource = null and back to my List, that it would refresh the datagrid. And indeed this worked, but only thru the button (which gets handled on the primary thread.)
So this question has turned into a two-parter:
Is setting DataGridView.DataSource to null and back to my List an acceptable way to refresh the datagrid? (It seems inefficient to me...)
How do I safely do this in a multi-threaded environment?
Here's the code I've written so far (C#/.Net 2.0)
public partial class Form1 : Form
{
private static List<MousePos> mousePositionList = new List<MousePos>();
private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000);
private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000);
public Form1()
{
InitializeComponent();
mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource
dataGridView1.DataSource = mousePositionList;
mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed);
mouseCheck.Start();
refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed);
refreshWindow.Start();
}
public void mouseCheck_Elapsed(object source, EventArgs e)
{
Point mPnt = Control.MousePosition;
MousePos mPos = mousePositionList.Find(ByPoint(mPnt));
if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); }
else { mPos.Count++; }
}
public void refreshWindow_Elapsed(object source, EventArgs e)
{
//dataGridView1.DataSource = null; // Old way
//dataGridView1.DataSource = mousePositionList; // Old way
dataGridView1.Invalidate(); // <= ANSWER!!
}
private static Predicate<MousePos> ByPoint(Point pnt)
{
return delegate(MousePos mPos) { return (mPos.Pnt == pnt); };
}
}
public class MousePos
{
private Point position = new Point();
private int count = 1;
public Point Pnt { get { return position; } }
public int X { get { return position.X; } set { position.X = value; } }
public int Y { get { return position.Y; } set { position.Y = value; } }
public int Count { get { return count; } set { count = value; } }
public MousePos() { }
public MousePos(Point mouse) { position = mouse; }
}
You have to update the grid on the main UI thread, like all the other controls. See control.Invoke or Control.BeginInvoke.
UPDATE! -- I partially figured out the answer to part #1 in the book "Pro .NET 2.0 Windows Forms and Customer Controls in C#"
I had originally thought that Refresh() wasn't doing anything and that I needed to call the Invalidate() method, to tell Windows to repaint my control at it's leisure. (which is usually right away, but if you need a guarantee to repaint it now, then follow up with an immediate call to the Update() method.)
dataGridView1.Invalidate();
But, it turns out that the Refresh() method is merely an alias for:
dataGridView1.Invalidate(true);
dataGridView1.Update(); // <== forces immediate redraw
The only glitch I found with this was that if there was no data in the dataGridView, no amount of invalidating would refresh the control. I had to reassign the datasource. Then it worked fine after that. But only for the amount of rows (or items in my list) -- If new items were added, the dataGridView would be unaware that there were more rows to display.
So it seems that when binding a source of data (List or Table) to the Datasource, the dataGridView counts the items (rows) and then sets this internally and never checks to see if there are new rows/items or rows/items deleted. This is why re-binding the datasource repeatedly was working before.
Now to figure out how to update the number of rows to display in dataGridView without having to re-bind the datasource... fun, fun, fun! :-)
After doing some digging, I think I have my answer to part #2 of my question (aka. safe Multi-threading):
Rather than using System.Timers.Timer, I found that I should be using System.Windows.Forms.Timer instead.
The event occurs such that the method that is used in the Callback automatically happens on the primary thread. No cross-threading issues!
The declaration looks like this:
private static System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 = new Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();
And the method is like this:
private void refreshWindow2_Tick(object sender, EventArgs e)
{
dataGridView1.Invalidate();
}
Looks like you have your answer right there!
Just in cawse you're curious about how to do cross thread calls back to ui:
All controls have a Invoke() method (or BEginInvoke()- in case you want to do things asynchronously), this is used to call any method on the control within the context of the main UI thread.
So, if you were going to call your datagridview from another thread you would need to do the following:
public void refreshWindow_Elapsed(object source, EventArgs e)
{
// we use anonymous delgate here as it saves us declaring a named delegate in our class
// however, as c# type inference sometimes need a bit of 'help' we need to cast it
// to an instance of MethodInvoker
dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); });
}