I have a class with an event and a custom EventArgs. The meaningful code:
public void OnTickReceived(TickReceivedEventArgs e)
{
EventHandler<TickReceivedEventArgs> handler = TickReceived;
if (handler != null)
handler(this, e);
}
public event EventHandler<TickReceivedEventArgs> TickReceived = delegate { };
and consuming the class in a UI Windows Form subscribing the event like this
private void button4_Click(object sender, EventArgs e)
{
bool esito;
t = new T3OpenStockReader();
esito = t.Connect();
textBox1.Text += "Connection: " + esito.ToString() + "\r\n";
Application.DoEvents();
if (esito)
{
esito = t.Subscribe("MI.EQCON.2552");
textBox1.Text += "Subscription: " + esito.ToString() + "\r\n";
Application.DoEvents();
}
if (esito)
{
t.Start();
t.TickReceived += NewTick_Event;
System.Diagnostics.Debug.Print("Reading started...");
}
}
private void NewTick_Event(object sender, TickReceivedEventArgs e)
{
textBox1.Text += e.tick.DT + " " + e.tick.Price + " " + e.tick.Volume + "\r\n";
}
I receive an InvalidOperationException - cross.thread operation. What am I doing wrong?
I receive InvalidOperationException - cross.thread operation. Where my error?
Presumably T3OpenStockReader raises events on its own thread - but you're trying to modify the UI in your event handler... which means you're doing it in the wrong thread. You should probably change your event handler to something like:
private void NewTick_Event(object sender, TickReceivedEventArgs e)
{
Action action = () => textBox1.Text += e.tick.DT + " " + e.tick.Price
+ " " + e.tick.Volume + "\r\n";
textBox1.BeginInvoke(action);
}
I'd also suggest that you get rid of your Application.DoEvents() calls - they're a symptom of trying to do too much in the UI thread.
I assume you are trying to update a UI component on a non-UI thread i.e. NewTick_Event. You need to force the update back onto the UI thread e.g.
private void NewTick_Event(object sender, TickReceivedEventArgs e)
{
textBox1.Invoke(new Action(() => textBox1.Text += e.tick.DT + " " + e.tick.Price + " " + e.tick.Volume + "\r\n"));
}
NewTick_Event is invoked from another thread and the changes on controls must be invoked on the UI thread (for example by using BeginInvoke method)
private void NewTick_Event(object sender, TickReceivedEventArgs e)
{
this.BeginInvoke( (Action) () =>
{
textBox1.Text += e.tick.DT + " " + e.tick.Price + " " + e.tick.Volume + "\r\n";
});
}
You get the violation because textBox1 is owned by a different thread. You have to invoke the functionality on the other thread. Invoking is like asking to the thread (thats owns the textBox1): "Here thread, execute this when you have time...". so the thread that owns the textBox1 will execute the functionality, instead of the thread that the event is raised from (or called, as events are call backs...).
I would go for this solution. Its a more generic approach than invoking on the textbox itself. And it doesn't matter what the event does, so you do not have to put all the functionality done by the event in a (Action) or BeginInvoke. You just simply call the event again, invoked, from the thread that owns the textbox.
Other answers in this topic do not check if invoke is required. I would say, that InvokeRequired boolean is there for a reason.
The FormParentOfTextBox in the example below is the instance of the form the textbox is put on. You could also use textBox1 here, but again, it would become less generic approach.
private void NewTick_Event(object sender, TickReceivedEventArgs e)
{
//Thread safe approach, generally for every event.
if (FormParentOfTextBox.InvokeRequired)
{
this.Invoke(NewTick_Event(sender, e));
return;
}
textBox1.Text += e.tick.DT + " " + e.tick.Price + " " + e.tick.Volume + "\r\n";
}
Related
I started coding my first WPF app and I'm having trouble with a textbox that displays some system info (cpu, memory, disk usage, mac address, etc.).
My apps has navigation between two pages and the said textbox is on one of the pages. The textbox's content is retrieved via WMI queries.
The issue I have noticed is that while navigating to tha page with the textbox it freezes the UI for about two seconds before going to and display the page.
I'm a newbie and my best guess is that eighter the WMI queries(could be badly coded too) are doing that or I'm loading the content in the textbox wrongfully.
An example of how my queries are constructed
public string getCPU()
{
ManagementObjectSearcher searcher = new
ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Processor");
StringBuilder sb = new StringBuilder();
foreach (ManagementObject wmi in searcher.Get())
{
try
{
sb.Append("Processor: " + wmi.GetPropertyValue("Name").ToString() + Environment.NewLine);
}
catch
{
return sb.ToString();
}
}
return sb.ToString();
}
public string getRAMsize()
{
ManagementClass mc = new ManagementClass("Win32_ComputerSystem");
ManagementObjectCollection moc = mc.GetInstances();
foreach (ManagementObject item in moc)
{
return Convert.ToString(Math.Round(Convert.ToDouble(item.Properties["TotalPhysicalMemory"].Value) / (1024 * 1024 * 1024), 0)) + " GB";
}
return "RAMsize";
}
And this is what I use to retrieve the data in the textbox:
private void TextBox1_Loaded(object sender, RoutedEventArgs e)
{
TextBox1.Text = getCPU();
TextBox1.Text += "Memory: " + getRAMsize() + Environment.NewLine;
TextBox1.Text += "Free Space: " + GetTotalFreeSpace(sysdrive) + " GB" + Environment.NewLine;
if (Is64BitSystem)
{
TextBox1.Text += getOS() + " 64bit" + Environment.NewLine;
}
else
{
TextBox1.Text += getOS() + " 32 Bit" + Environment.NewLine;
}
TextBox1.Text += "MAC Address : " + System.Text.RegularExpressions.Regex.Replace(GetMacAddress().ToString(), ".{2}", "$0 ") + Environment.NewLine;
TextBox1.Text += av();
}
My question is what am I doing wrong and how can I get around with it. In my mind , if the queries are constructed correctly, it would be because they are done again and again everytime the textbox is loaded (on navigation or at startup) and maybe If I could get it to load only once and remember those values(since most of the data should stay the same).
But as I said I'm a noob and any help will be greatly appreciated.
Thanks in advance
You are aquiring your data in the UI Thread.
You should load the data in the background thread and then, since you use wpf, binding the textbox text to a variable.
Example (This code is Copy'n'Paste ready):
// INotifyPropertyChanged is an Interface which enables binding of Properties in your window
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _systemInformation;
// Stub (works actually)
private bool is64BitSystem = (IntPtr.Size == 8);
// Stub
private string sysdrive = "YOLO\\:";
public MainWindow()
{
InitializeComponent();
// Set Datacontext normally set to a viewmodel but ther is none in this code
this.DataContext = this;
// The Backgroundworker does things in the background (nonblocking)
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += DoItButDontInterruptMeDuh;
bw.RunWorkerAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
public string SystemInformation { get => _systemInformation; set => _systemInformation = value; }
//Stub
public string getCPU()
{
return "Fancy CPU";
}
//Stub
public string getRAMsize()
{
return "1 PB";
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
//Stub
private string av()
{
return "Whatever av means.";
}
private void DoItButDontInterruptMeDuh(object sender, DoWorkEventArgs e)
{
// Simulate loading time
Thread.Sleep(5000);
SystemInformation = getCPU();
SystemInformation += "Memory: " + getRAMsize() + Environment.NewLine;
SystemInformation += "Free Space: " + GetTotalFreeSpace(sysdrive) + " GB" + Environment.NewLine;
if (is64BitSystem)
{
SystemInformation += getOS() + " 64bit" + Environment.NewLine;
}
else
{
SystemInformation += getOS() + " 32 Bit" + Environment.NewLine;
}
SystemInformation += "MAC Address : " + System.Text.RegularExpressions.Regex.Replace(GetMacAddress().ToString(), ".{2}", "$0 ") + Environment.NewLine;
SystemInformation += av();
OnPropertyChanged("SystemInformation");
}
//Stub
private object GetMacAddress()
{
return "Macintoshstreet 1234";
}
//Stub
private string getOS()
{
return "Cool OS";
}
//Stub
private string GetTotalFreeSpace(object sysdrive)
{
return "0";
}
}
and in the .xaml:
<TextBox Text={Binding Path=SystemInformation}/>
After checking each and every query I found out that the getCPU one is causing a delay in the load.
I replaced it with a Registry.GetValue, wich is very fast.
Thanks to Sebastian L, because altough his code didn't work for me it put me on the right path and I was able to adapt own code with a backgroundworker to avoid UI freeze of any kind
my working code:
private void TextBox1_Loaded(object sender, RoutedEventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(delegate (object o, DoWorkEventArgs args)
{
});
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate (object o, RunWorkerCompletedEventArgs args)
{
TextBox1.Text = (string)Registry.GetValue(#"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0", "ProcessorNameString", null) + Environment.NewLine;
TextBox1.Text += "Memory: " + getRAMsize() + Environment.NewLine;
TextBox1.Text += "Free Space: " + GetTotalFreeSpace(sysdrive) + " GB" + Environment.NewLine;
if (Is64BitSystem)
{
TextBox1.Text += getOS() + " 64bit" + Environment.NewLine;
}
else
{
TextBox1.Text += getOS() + " 32 Bit" + Environment.NewLine;
}
TextBox1.Text += "MAC Address : " + System.Text.RegularExpressions.Regex.Replace(GetMacAddress().ToString(), ".{2}", "$0 ") + Environment.NewLine;
TextBox1.Text += av();
});
bw.RunWorkerAsync();
}
I am creating an windows based application which download the data from server.
I am using background thread which is created on different class to perform these download operation.And I want to continuously show the download status on rich textbox i.e on main thread.But i am unable to do this,get an Cross-thread operation not valid.
Please help me to resolve this problem.
method on Form1.cs
public void UpdateRichText(string Text)
{
SetRichText(Text);
}
public delegate void SetRichTextTextDelegate(string text);
public void SetRichText(object number)
{
if (InvokeRequired)
{
this.BeginInvoke(new SetRichTextTextDelegate(SetRichText),text);
return;
}
richTextBox1.Text += number.ToString() + "\n";
}
private void button3_Click_1(object sender, EventArgs e)
{
demo d = new demo();
d.display();
}
methods on demo.cs
Form1 f = new Form1();
public void display()
{
Thread t = new Thread(new ThreadStart(call));
t.Start();
}
public void call()
{
//when i call this method every time if(InvokeRequired) is false.
f.UpdateRichText("Called from Thread");
}
Try changing your check to:
if (richTextBox1.InvokeRequired)
{
richTextBox1.BeginInvoke(new SetRichTextTextDelegate(SetRichText),text);
Try using the following
if (richTextBox1.InvokeRequired)
{
richTextBox1.Invoke(new Action(delegate { richTextBox1.Text += number.ToString() + "\n"; richTextBox1.ScrollToCaret(); }));
}
else
{
richTextBox1.Text += number.ToString() + "\n";
richTextBox1.ScrollToCaret();
}
richTextBox1.Text += number.ToString() + "\n"; Can be changed as follows,
rtbEvents.AppendText(Environment.NewLine + number.ToString() );
I have a module whith an event for serial port sygnal
serialPort.DataReceived.AddHandler(SerialDataReceivedEventHandler(DataReceived));
where DataReceived is
let DataReceived a b =
rxstring <- serialPort.ReadExisting()
arrayRead <- System.Text.Encoding.UTF8.GetBytes(rxstring)
if arrayRead.[0] = 0x0Auy then
ProcessData(a, null)
and ProcessData is invoking WinForms method
let ProcessData(a, b) =
dataProcessor.Invoke(a, b) |> ignore
which is
private void ProcessData(object sender, EventArgs e) {
byte[] m = Core.ncon.ArrayRead;
switch (m[1]) {
case 0x01: {
if (m.Length > 5) {
int myval = BitConverter.ToInt32(m, 3);
textBox1.Text += " val: " + myval.ToString() + " ";
but when it's trying to access textBox1 I'm getting:
Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.
So the question is How to access WinForm elements from another module events?
You need to use the forms dispatcher.
FormContaingTheTextbox.Invoke(new MethodInvoker(delegate(){
textBox1.Text += " val: " + myval.ToString() + " ";
}));
This makes that code run in the forms thread instead of yours.
Try Using below Code:
this.Invoke(new MethodInvoker(delegate()
{
//Access your controls
}));
hope this helps
my class start new process (Tshark) and start capturing, from the main form i am checking the class properties in order to update my GUI, sometimes the received packets rate i so high that my GUI stuck so i want the option to check whose properties every 1-2 second.
this is my progress change function who checking my class all the time and in this point i am update my GUi, how can i checking those properties every 2 seconds ?
Tshark tshark = new Tshark();
private void bgWSniffer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
tshark = e.UserState as Tshark;
lblNumberOfReceivedPackets.Text = tshark._receivesPackets.ToString("#,##0");
lblTrafficRate.Text = (tshark._bitsPerSecond * 0.000001).ToString("0.##") + " Mbit/sec" + " (" + tshark._bitsPerSecond.ToString("#,##0") + " Bits/sec" + ")";
lblPacketsRate.Text = tshark._packetsPerSecond.ToString("#,##0") + " Packets/sec";
lblStatus.Text = tshark._status;
lblFileSize.Text = formatBytes(tshark._myFile.Length);
tshark._myFile.Refresh();
}
Check if 2 seconds has passed since the last check. Here, I'm using a class member to tract that time.
private DateTime _LastCheck = DateTime.MinValue;
private private void bgWSniffer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (_LastCheck.AddSeconds(2) <= DateTime.Now)
{
_LastCheck = DateTime.Now;
// do the UI update.
}
}
Instead of updating the UI within the BackgroundWorker you can just create a Timer to do the job
private void bgWSniffer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
tshark = e.UserState as Tshark;
}
In the ctor create the timer:
_timer = new Timer()
_timer.Intrerval = 2000;
_timer.Tick += UpdateUI;
_timer.Start();
You can add some checking in case the values have changed so you don't update the UI needlessly:
private void UpdateUI()
{
var local = _tshark;
if(local != null)
{
lblNumberOfReceivedPackets.Text = local._receivesPackets.ToString("#,##0");
lblTrafficRate.Text = (local._bitsPerSecond * 0.000001).ToString("0.##") + " Mbit/sec" + " (" + local._bitsPerSecond.ToString("#,##0") + " Bits/sec" + ")";
lblPacketsRate.Text = local._packetsPerSecond.ToString("#,##0") + " Packets/sec";
lblStatus.Text = local._status;
lblFileSize.Text = formatBytes(local._myFile.Length);
local._myFile.Refresh();
}
}
Ok, in my app there are times when loading the DataGridView can take a minute or two. What I want to do is show a GIF in a form with no border until it reaches the end of the loading function. However, if I do:
Views.Loading ldw = new Views.Loading();
ldw.Show();
...
ldw.Close();
...it never actually draws it to the screen and I can't see it. If I do ShowDialog(), it shows the window but never gets past that line of code. I have a feeling it's because it's not a background worker or because the focus gets set back to the parent because of processing...I don't know.
My form is a blank form, added a picture box, added a gif to the picture box, and made FormBorderStyle = none. Any and all help is appreciated.
Update: Current (non-working) Code
private void InitializeBackgroundWorker()
{
//Defines the DoWork Event Handler for _backgroundWorker.
_bgWorkerReports.DoWork += new DoWorkEventHandler(bgWorkerReports_DoWork);
//Defines the RunWorkCompleted Event Handler for _backgroundWorker.
_bgWorkerReports.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorkerReports_RunWorkerCompleted);
}
private void bgWorkerReports_DoWork(object sender, DoWorkEventArgs e)
{
ldw.Show();
try
{
string strFilter = "";
if (!_strSearchFilter.Equals(""))
{
strFilter += strFilter.Equals("") ? " " + _strSearchFilter : " and " + _strSearchFilter;
}
if (tvFigure.Nodes.Count > 0)
{
if (_strFigureFilter == "ALL")
{
strFilter += " " + Constants.GetColumnName("Figure") + " LIKE '%%' ";
}
else if (!_strFigureFilter.Equals("") && !_strFigureFilter.Equals(tvFigure.TopNode.Name))
{
if (_strSearchFilter.Equals("") || !cbCurrentFigure.Checked)
{
strFilter += strFilter.Equals("") ? " " + Constants.GetColumnName("Figure") + "='" + _strFigureFilter + "'" : " and " + Constants.GetColumnName("Figure") + "='" + _strFigureFilter + "'";
}
}
}
if (!_strIndentureFilter.Equals(""))
{
strFilter += strFilter.Equals("") ? " " + _strIndentureFilter : " and " + _strIndentureFilter;
}
if (!_strReportFilter.Equals(""))
{
strFilter += (!strFilter.Equals("") ? " and" : "") + " part_id in (" + _strReportFilter + ")";
}
if (strFilter.Length > 0)
{
BindingSource bSource = new BindingSource();
bSource.DataSource = _dataController.PopulateDataGrid(_nViewMode, strFilter).Tables[0];
//Set DataSource to bindingSource for DataGridView.
if (_lstValidationResults.Count > 0)
{
dgvParts.DataSource = _lstValidationResults;
foreach (DataGridViewColumn dc in dgvParts.Columns)
{
dc.DataPropertyName = "ErrorMessage";
dc.Visible = true;
dc.SortMode = DataGridViewColumnSortMode.Programmatic;
dc.AutoSizeMode = DataGridViewAutoSizeColumnMode.ColumnHeader;
}
dgvParts.AutoResizeColumns();
return;
}
else if (!string.IsNullOrEmpty(_strFigureFilter))
{
dgvParts.DataSource = bSource;
dgvParts.Columns[0].Visible = false;
dgvParts.Columns["Description"].Resizable = DataGridViewTriState.False;
dgvParts.Columns["Description"].Width = 750;
}
// Automatically resize the visible rows.
foreach (DataGridViewColumn col in dgvParts.Columns)
{
col.SortMode = DataGridViewColumnSortMode.Automatic;
if (col.Name != "Description")
{
dgvParts.AutoResizeColumn(col.Index);
}
}
dgvParts.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
// Hide the ToolTips for all the cells - redisplay if there is a report.
dgvParts.ShowCellToolTips = true;
// Set the dataGridView control's border.
dgvParts.BorderStyle = BorderStyle.Fixed3D;
// Get and set the ipb_number to the label.
string ipb_number = _dataController.IPBNumber;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void bgWorkerReports_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ldw.Close();
this.Cursor = Cursors.Default; //Throws error (Cross-thread)
FormatCells();
BuildColumnsComboBox();
int nTotalCount = 0;
foreach (ListViewItem lvi in listView1.Items)
{
int nCount = _lstReportRecords.Where(rr => lvi.Text.Contains(rr.Description)).Count();
nTotalCount += nCount;
lvi.Text = (lvi.Text.Contains("(") ? lvi.Text.Substring(0, lvi.Text.IndexOf("(") + 1) : lvi.Text.Trim() + " (") + nCount.ToString() + ")";
}
rbAllReports.Text = (rbAllReports.Text.Contains("(") ? rbAllReports.Text.Substring(0, rbAllReports.Text.IndexOf("(") + 1) : rbAllReports.Text + " (") + nTotalCount.ToString() + ")";
int nTaggedCount = _lstReportRecords.Where(rr => rr.Description.Contains("Tagged")).Count();
rbTaggedRecords.Text = (rbTaggedRecords.Text.Contains("(") ? rbTaggedRecords.Text.Substring(0, rbTaggedRecords.Text.IndexOf("(") + 1) : rbTaggedRecords.Text + " (") + nTaggedCount.ToString() + ")";
}
Ideally you would have two threads: the GUI thread and the working thread (which can be a BackgroundWorker). Create and show the window in the GUI thread. Handle the loading in the BackgroundWorker's DoWork event. When the loading is done you can call Close() on the load window from the RunWorkerCompleted event and dispose of it.
LoadWindow loadWindow = new LoadWindow();
loadWindow.TopMost = true; // make sure it doesn't get created behind other forms
loadWindow.Show();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
void worker_DoWork(object sender, DoWorkEventArgs e)
{
// do your loading here
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// set DataGridView datasource here
...
// close loading window
loadWindow.Close();
}
The problem you might have with displaying the window could be from the TopMost property, which must be set to true. You can also try calling BringToFront() on the loading window after you've created and shown it.
Yes, BackgroundWorker is for exactly this type of purpose. A couple things to add:
Do not interact with the UI in the worker_DoWork event as it is running on a background thread. Set e.Result when you're finished, which you can check from the RunWorkerCompleted event - or use a form-level variable.
Let any exceptions fall through in the worker_DoWork event and you will see them in the worker_RunWorkerCompleted event in e.Error.
If you need the ability to cancel your load, set worker.WorkerSupportsCancellation and check the e.Cancel while in your DoWork event, then you can check e.Cancelled in your RunWorkerCompleted event.
You should call .Dispose() on your BackgroundWorker when finished.
You'll have to run the code to fill the grid on another thread. Something like:
// Set the picturebox loading state, resize the form etc.
PictureBox.SetLoadingImage();
// Initialize a new thread
Thread t = new Thread(new ThreadStart(() =>
{
// Fill the gridview here
GridView1.DataSource = FillWithData();
GridView1.DataBind();
// When finished, reset the picturebox on it's own thread
PictureBox.Invoke((MethodInvoker)(()=> PictureBox.ClearLoadingImage() ));
}));
// Run the thread.
t.Start();