Im working on a Network application. First I wanted to make DataGridView to refresh its data on every second. My implementation:
public partial class Form1 : Form
{
BindingList<Wifi> Networks = new BindingList<Wifi>();
System.Timers.Timer NetworksRefreshThread;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
NetshScanner netshScanner = new NetshScanner();
NetworksRefreshThread = new System.Timers.Timer(1000);
dataGridView1.DataSource = Networks;
NetworksRefreshThread.Elapsed += delegate
{
BindingList<Wifi> tmp = new BindingList<Wifi>(netshScanner.StartAndParseOutput());
this.Invoke((MethodInvoker)delegate
{
Networks.Clear();
foreach (Wifi net in tmp)
{
Networks.Add(net);
}
});
};
}
After closing the form, I get ObjectDisposedException inside the this.Invoke. Any advice?
What's probably happening is that the timer has fired and is currently busy refreshing your DataGridView. This happens on a seperate thread.
Then when you are closing the form, the GUI thread starts destroying its objects.
After that, the timer is done retrieving its new data but has no object left to put it.
To solve this. Add a boolean to your class called 'Updating' or something similar. Then set its value when you are updating to true.
NetworksRefreshThread.Elapsed += delegate
{
Updating = true;
BindingList<Wifi> tmp = new BindingList<Wifi>(netshScanner.StartAndParseOutput());
this.Invoke((MethodInvoker)delegate
{
Networks.Clear();
foreach (Wifi net in tmp)
{
Networks.Add(net);
}
});
Updating = false;
};
Now create a new method which you bind to the Form closing down event. In this method, do a Thread.Sleep() while the timer is updating:
while (Updating) Thread.Sleep(100);
Related
In WPF, one could use something like:
Application.Current.Dispatcher.BeginInvoke(new Action(() => Form1.grid.Items.Refresh()));
to access UI functions outside of the main thread. In Winforms however, the same functionality isn't there. What would be the easiest way to access a BindingList that exists inside of my Form1 class from my "working" thread? Currently I getting the following error when trying to access "Form1.record_list":
System.InvalidOperationException: Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
edit: I appreciate the help so far, but I'm lost on "this.Invoke". My method in the separate thread has no "Invoke".
Here's an example of my code so far.
public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
Form1.record_list.Add(record); //This is where I assume the problem spawns
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
public partial class Form1 : Form
{
public static BindingList<Record> record_list = new BindingList<Record> { };
public static DataGridViewCellStyle style = new DataGridViewCellStyle();
public Form1()
{
InitializeComponent();
Thread thread = new Thread(SCMClient.connect);
thread.IsBackground = true;
thread.Start();
FillData();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
foreach (DataGridViewRow row in dataGridView.Rows)
{
for (var i = 0; i < row.Cells.Count; i++)
{
Console.WriteLine(row.Cells[i].Value);
if (row.Cells[i].Value as string == "OK")
{
row.Cells[i].Style.BackColor = Color.Red;
Console.WriteLine("IF WAS TRUE");
}
}
}
}
I think the specific problem here is when I add Records to the Forms1.record_list. I'm not sure how to add items to that list without causing the Cross-thread error...
You MUST access your UI from your UI Thread only. But you can use the Control.Invoke method - a Form IS a Control - to ensure your code is run from the UI Thread.
Also, you have a static BindingList, but I assume you want to display the contents of that list in an specific form. You should make the BindingList an instance member instead... or get a reference to a valid Form. The Control.Invoke method is not static.
There are several ways to do so. I would do it like so:
First, create a method in your Form class that adds the record to the list.
public void AddRecord(Record r) {
if(this.InvokeRequired) {
this.Invoke(new MethodInvoker(() => this.AddRecord(r)));
} else {
this.record_list.Add(r);
}
}
Second, you need to have a reference to the form (in the next step, that is the theForm variable).
Then, in your listener method, invoke AddRecord method instead of adding the record in your BindingList directly.
public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
theForm.AddRecord(record); // You need a Form instance.
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
The below will work out in winforms. Have tried once. The below code helps to update the label in UI thread from another thread.
string _string = "Call from another thread";
this.Invoke((MethodInvoker)delegate {
label1.Text = _string;
});
You're looking for SynchronizationContext.Current, which works with both WinForms and WPF.
Note that you'll need to grab its value from the UI thread, since it's per-thread.
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();
}));
}
I'm working in a thread to check the GPRS connection in CompactFramework.
The idea of the thread is simple: If the program isn't connected then I run the code to connect (this code is giving me errors), but if the connection is OK then I recheck again in 60 seconds and so on.
Now, focusing in connection code. The following code check if it's connected or not, if it isn't then I subscribe to DataReceive event.
void initFormText()
{
if (isThereConnect()) //true if it is connected
{
//enable timer to recheck if it's connected
}
else //it isn't connected
{
serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(serialPort1_DataReceived);
if (serialPort1.IsOpen)
{
serialPort1.Close();
}
serialPort1.Open();
timerStep.Enabled = true;
}
}
Now comes the issue, in the serialPort1_DataReceived I check the data and set a variable which is tested by the timerStep and it make some steps.
The problem occurs in the DataReceived event, the thing is that when I run the following code outside of a thread it works fine, it does all the job and make the connection, but in the thread it doesn't work. I test this adding some MessageBoxand I realize that the ones inside the DataReceive never appear.
void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
byte[] data = new byte[1024];
int n = serialPort1.Read(data, 0, data.Length);
string rec = Encoding.GetEncoding("windows-1252").GetString(data, 0, n);
if (string.IsNullOrEmpty(rec))
{
return;
}
if (rec.Contains("AT+CIMI") && rec.Contains("OK"))
{
MessageBox.Show("serialPort 1");
currState = 1;
}
else if (rec.Contains("READY"))
{
MessageBox.Show("serialPort 11");
currState = 1;
}
else if (rec.Contains("0,1") || rec.Contains("0,5"))
{
MessageBox.Show("serialPort 2");
currState = 2;
}
}
So by some reason the serialPort isn't receiving anything and I can't figure it out why. The fact that it works outside the thread but not in the thread is frustrating me.
I appreciate any help. Thanks in advanced!
Yes, but I think that your thread finishes before event is fired. You should create your Form in a following manner, please note that this is code for desktop but simulates what is available in CompactFramework since I don't have it installed here. First Form1 is main form and it starts thread in which is the Form2. The Form2 has a button and Click EventHandler that is working, but you need to show your Form2 with Application.Run(). Here is the sample code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(ThreadMethod));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
void ThreadMethod()
{
Form2 f = new Form2();
Application.Run(f);
}
}
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Something");
}
}
Hope it will work this way.
The event must run in the same thread (I suppose UI thread) where you have already declared serialPort1. You can execute the code from the serialPort1_DataReceived event in different thread. That thread should be started by serialPort1_DataReceived event handler. The problem is that CompactFramework doesn't have ParameterisedThreadStart so you can not effectively pass received data to the thread. You will need to set some global field using delegates.
Ok, well I have been at it for a while now and I decided to just use threads. I am making a syntax highlighter but I keep getting terrible performance with the file sizes that it will usually be used for. So I made two forms, the first shows the file in plain text and has a button that says "openincolor" when you click that I start a new thread as such
private void button1_Click(object sender, EventArgs e)
{
ColoringThread colorer = new ColoringThread(this.m_bruteView.Text);
Thread theThread = new Thread(new ThreadStart(colorer.OpenColorWindow));
theThread.Start();
}
public class ColoringThread
{
string text;
public ColoringThread(string initText)
{
text = initText;
}
public void OpenColorWindow()
{
Form2 form2 = new Form2(text);
form2.ShowDialog();
}
};
I want this form to send back a message each time it has complete say x lines of coloring. Then I will take that and figure out the progress and display it to the user.
How might I go about sending a message, or event(...? can I do that) to my first form to let it know of the others progress?
One very simple way to do this is with BackgroundWorker. It already provides an event to report progress.
How about something like this? This adds an event to the ColoringThread class which is subscribed to by the calling class.
private void button1_Click(object sender, EventArgs e) {
ColoringThread colorer = new ColoringThread(this.m_bruteView.Text);
colorer.HighlightProgressChanged += UpdateProgress;
Thread theThread = new Thread(new ThreadStart(colorer.OpenColorWindow));
theThread.Start();
}
private void UpdateProgress(int linesComplete) {
// update progress bar here
}
public class ColoringThread
{
string text;
public delegate void HighlightEventHandler(int linesComplete);
public event HighlightEventHandler HighlightProgressChanged;
public ColoringThread(string initText) {
text = initText;
}
public void OpenColorWindow() {
Form2 form2 = new Form2(text);
form2.ShowDialog();
int linesColored = 0;
foreach (String line in text.Split(Environment.NewLine)) {
// colorize line here
// raise event
if (HighlightProgressChanged != null)
HighlightProgressChanged(++linesColored);
}
}
};
You can pass an object as argument to the Thread.Start and share your data between the current thread and the initiating thread.
Here is a good example:
How to share data between different threads In C# using AOP?
Or you can use BackgroundWorker which has ReportProgress
What you need is System.Windows.Threading.Dispatcher's BeginInvoke method. You can't directly modify a WPF object from your background thread, however you can dispatch a delegate to do that.
In your derived Window class object you have the Property Dispatcher, so you use it as follows:
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(status) => { StatusTextBox.Text = status },
thestatus
);
I'm sorry that I can't test that currently and I don't have the project here, where I did that. But I'm sure it will work, good luck ;)
Update: Oops, you're using Form's... I've written about WPF, sorry.
I cannot understand how this is possible. Please help!!
I have an app with a trayicon. I want a form to be show when the user double clicks the trayicon. I have a problem where it is possible to get 2 or more forms showing by quickly triple or quadruple clicking the trayicon. The reason I don't want a singleton is that I want the form to be released each time it is closed to save memory, maybe this is not a good idea?
I have a field called m_form1.
I have a method called ShowForm1;
I call the method ShowForm1 on the double-click of the TrayIcon.
private Form1 m_form1;
private void ShowForm1()
{
if (m_form1 == null)
{
Trace.WriteLine("*CREATE*" + Thread.CurrentThread.ManagedThreadId.ToString());
m_form1 = new Form1();
m_form1.FormClosed += new FormClosedEventHandler(m_form1_FormClosed);
m_form1.Show();
}
m_form1.BringToFront();
m_form1.Activate();
}
So when Form1 takes a while to construct, then it is possible to create 2 because m_form1 is still null when the second call arrives. Locking does not seem to work as it is the same thread both calls (I'm guessing the UI thread) ie the trace writes out *CREATE*1 twice (below).
[3560] *CREATE*1
[3560] *CREATE*1
Changing the code to include a lock statement does not help me.
private Form1 m_form1;
private object m_Locker = new object();
private void ShowForm1()
{
lock (m_Locker)
{
if (m_form1 == null)
{
Trace.WriteLine("****CREATE****" + Thread.CurrentThread.ManagedThreadId.ToString());
m_form1 = new Form1();
m_form1.FormClosed += new FormClosedEventHandler(m_form1_FormClosed);
m_form1.Show();
}
}
m_form1.BringToFront();
m_form1.Activate();
}
How should I handle this situation?
Thanks guys
Tim.
Have an additional boolean variable, "m_formUnderConstruction" which you test for before constructing the form, and which you set as soon as you've decided to construct it.
The re-entrancy makes all of this a little icky, unfortunately. I've removed the lock, as if this ever gets called from a different thread then you've got the nasty situation of trying to show a form from a different thread to the one it was constructed on.
private Form1 m_form1;
private bool m_underConstruction = false;
private void ShowForm1()
{
if (m_underConstruction)
{
// We're about to show it anyway
return;
}
m_underConstruction = true;
try
{
if (m_form1 == null)
{
m_form1 = new Form1();
m_form1.FormClosed += new FormClosedEventHandler(m_form1_FormClosed);
m_form1.Show();
}
}
finally
{
m_underConstruction = false;
}
m_form1.BringToFront();
m_form1.Activate();
}
Use Interlocked.Increment to change the nr of the tries. If it is 1, open the form, otherwise, don't. And use Interlocked.Decrement after the test or on form's close.
private int openedForms = 0;
private Form1 m_form1;
private void ShowForm1()
{
if (Interlocked.Increment(ref openedForms) = 1)
{
m_form1 = new Form1();
m_form1.FormClosed += new FormClosedEventHandler(m_form1_FormClosed);
m_form1.Show();
}
else
{
Interlocked.Decrement(ref openedForms);
}
if (m_form1 != null)
{
m_form1.BringToFront();
m_form1.Activate();
}
}
private void m_form1_FormClosed(object Sender, EventArgs args)
{
Interlocked.Decrement(ref openedForms);
}
Please see this, it handles all mouse event combinations for NotifyIcon as well as Form1.
More here: http://code.msdn.microsoft.com/TheNotifyIconExample