Single instance form but not singleton - c#

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

Related

Updating Toolstripstatuslabel in Thread of closing form

I have a Order form. Once a order is complete, I use a thread to email the order to the supplier. The Thread is use to prevent the system hanging while the order is exported to pdf and sent.
The Problem: I would like to place an message on the MDIParent Toolstripstatuslabel once the threat completes without error to confirm the order was sent. But I get an error: "System.NullReferenceException: Object reference not set to an instance of an object". Which I could be wrong, refers to the Child windows disposed the toolstripstatuslabel reference on the parent form when it closed, so the threat cant access it anymore. I know the easy solution would be to use a MessageBox to confirm all went good and well...but why make it easy if you can do it elegant?
So my question: How can I reference a control in the parent form from the threat? I tried looking at invoke, but not sure how to implement it or if it is actually the correct direction.
EDIT:
My code from the childform
public partial class frm_n_order : Form
{
.
.
private void bProcess_Click(object sender, EventArgs e)
{
.
.
.
new Thread(new ThreadStart(delegate
{
fExportOrder(strOrderNo);
fSendMailv2(strPlant, strSupCode, strOrderNo);
})).Start();
this.close();
}
private void fExportOrder(string strOrderNo)
{
//export order to pdf
}
private void fSendMailv2(string strPlant, string strSupCode, string strOrderNo);
{
// get pdf
// get email address
try
{
// send email
((MDIParent1)MdiParent).tsslMain.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails; //here I need to find a different way of accessing the Toolstripstatuslabel in the parent form
}
catch
{
MessageBox.Show("Email did not send");
}
}
}
EDIT:
Ok, so after spending more than a day trying to figure out how to use Invoke, I realize while it seems like good practice when working with threads, its not my answer. My problem is directly related to the childform closing disposing all the controls so it looses its reference to the MdiParent. To solve the problem I did the following:
In my child class I added:
public static Form IsFormAlreadyOpen(Type FormType)
{
foreach (Form OpenForm in Application.OpenForms)
{
if (OpenForm.GetType() == FormType)
return OpenForm;
}
return null;
}
I dont think it is the most elegant solution but the theory is that my Parent form will always be open when I need to access the Toolstripstatuslabel. So I basically loop through all the open forms to find the reference to the active MdiParent instance which then gets passed back to the caller. In the thread I then use the following code.
MDIParent1 fm = null;
if ((fm = (MDIParent1)IsFormAlreadyOpen(typeof(MDIParent1))) != null)
{
fm.Toolstripstatuslabel1.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails;
}
I'm still looking for a better approach, but for now this works.
Ok, so after spending more than a day trying to figure out how to use Invoke, I realize while it seems like good practice when working with threads, its not my answer. My problem is directly related to the childform closing disposing all the controls so it looses its reference to the MdiParent. To solve the problem I did the following:
In my child class I added:
public static Form IsFormAlreadyOpen(Type FormType)
{
foreach (Form OpenForm in Application.OpenForms)
{
if (OpenForm.GetType() == FormType)
return OpenForm;
}
return null;
}
I dont think it is the most elegant solution but the theory is that my Parent form will always be open when I need to access the Toolstripstatuslabel. So I basically loop through all the open forms to find the reference to the active MdiParent instance which then gets passed back to the caller. In the thread I then use the following code.
MDIParent1 fm = null;
if ((fm = (MDIParent1)IsFormAlreadyOpen(typeof(MDIParent1))) != null)
{
fm.Toolstripstatuslabel1.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails;
}
I'm still looking for a better approach, but for now this works.
It's hard for me to overlook someone saying "but why make it easy if you can do it elegant?"
Awesome!
I figure if we can do something elegantly, then in the future it should be easy.... right?
Anyways, hoping you find the below helpful.
A note: It looked to me like you were declaring your thread as a local variable and not storing it outside the local scope. If we want something to live past the end of the scope, it's a good idea to store a reference to it (which is done using a private Task field in the below example).
Sure, the thread would get added to the threadpool and stored somewhere in the framework even if it's just a local variable, so I think it wouldn't abort due to garbage collection as you have it, but I don't like the idea of instances floating around that I don't have references to.
public class MyChildForm : Form
{
private Task longRunningTask;
private Task closeTask;
public string ResultOfTimeConsumingOperation { get; private set; }
protected override Dispose(bool disposing)
{
if (disposing)
{
longRunningTask?.Dispose();
closeTask?.Dispose();
}
base.Dispose(disposing);
}
private void TimeConsumingOperation1()
{
Thread.Sleep(TimeSpan.FromSeconds(8));
ResultOfTimeConsumingOperation = "Hooray we finished the work lol";
this.closeTask =
Task.Factory.FromAsync(
BeginInvoke(new Action(Close)),
EndInvoke);
}
protected override void OnLoad()
{
base.OnLoad();
this.longRunningTask =
Task.Run(TimeConsumingOperation1);
}
}
public class MyParentForm : Form
{
private List<Form> childForms;
public MyParentForm() : base()
{
childForms = new List<Form>();
}
protected override void OnLoad()
{
base.OnLoad();
RunChildForm();
}
private void RunChildForm()
{
var childForm = new MyChildForm();
childForms = childForms.Append(childForm).ToList();
childForm.FormClosing += ChidlForm_FormClosing;
childForm.Show();
}
private void ChildForm_FormClosing(object sender, FormClosingEventArgs e)
{
var childForm = sender as MyChildForm;
childForm.FormClosing -= ChildForm_FormClosing;
if (childForms.Contains(childForm))
childForms =
childForms.
Except(new Form[] { childForm }).
ToList();
// tada
myStatusLabel.Text = childForm.ResultOfLongRunningProcess;
}
// main window is closing
protected override void OnFormClosing(FormClosingEventArgs e)
{
// let's close any windows we left open
var localForms = childForms.ToList();
childForms = new List<Form>();
foreach (var form in localForms)
form.Close();
}
}

ObjectDisposedException after closing application on Timer

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

C# Take combobox item from one form and add its name as text to another

Ok so I'm attempting to create a simple game. In a nutshell it's a resource management game where the player will attempt to manage a thieves guild. In regards to running missions I've created a Thief class, a new instance of which is created when a new thief is recruited. I have coded within the thief class the ability to gain experience and level up.
Here's my specific problem:
I want the player to be able to select which thief/thieves to send on a mission. I have thought about it and figured that opening a new form and populating it with checkboxes is the easiest way to allow this. These checkboxes will be related to a List<thief> of thieves, the player then checks the thieves s/he wants to send and these are then stored in another List<thief> and passed on to the run mission function.
I've built a separate project with the intention of testing and playing around with this before putting it into the main program. The test project consists of two forms: The first (frmMain) with a textbox to hold the selected options and a button to open the second form (frmSelect). Currently I can open and populate the second form (frmSelect) but when I try to add the checked options to the textbox I simply...well can't.
So far I have tried directly accessing the textbox by typing frmMain.txtOptionsDisplay in the cs file of frmSelect but it causes the following error:
An object reference is required for the non-static field, method or
property
I tried to create a new form in frmSelect and make it equal to the active instance of frmMain with: Form frmTemp = frmMain.ActiveForm; and then alter the textbox using frmTemp as a go-between but that produced the error:
'System.Windows.Forms.Form' does not contain a definition for
'txtOptionsDisplay'.
Having searched both google and stackoverflow forums I've encountered answers that I either have never heard of (Threading) or answers that I kind've recognise but can't interpret the code pasted to make it relevant to my problem (delegates).
Any advice or pointers would be fantastic.
EDIT:
frmMain code:
public frmMain()
{
InitializeComponent();
selections.Add("Option 1");
selections.Add("Option 2");
}
private void btnClick_Click(object sender, EventArgs e)
{
frmSelectOptions.Show();
int length = selections.Count();
for (int i = 0; i < length; i++)
{
CheckBox box = new CheckBox();
box.Text = selections[i];
box.AutoSize = true;
box.Location = new Point(50, 50*(i+1));
frmSelectOptions.grpControls.Controls.Add(box);
}
}
public void updateText(string option)
{
txtOptionsDisplay.Text += option;
}
}
frmSelect code:
public List<CheckBox> selectedOptions = new List<CheckBox>();
Form frmTemp = frmMain.ActiveForm;
public frmSelect()
{
InitializeComponent();
}
private void btnSelect_Click(object sender, EventArgs e)
{
foreach (CheckBox box in grpControls.Controls)
{
if (box.Checked == true)
selectedOptions.Add(box);
}
this.Hide();
}
}
I hope this formats correctly... I'm kinda new and don't know how to indent. Oh look there's a preview...
Does this help?
Your problem is that controls defined within a form by default receive the private access identifier. Hence you could just add a property along the lines of
public ControlType ProxyProperty {
get {
return txtOptionsDisplay;
}
}
Besides from that you should think about wether what you're trying is actually a good solution. Manipulating forms from one to another will become a huge clusterfuck in terms of maintenance later on.
I'd suggest using the Singleton pattern for your frmMain. This will help safeguard you from accidentally launching another instance of frmMain and at the same time, will give you access to frmMain's objects. From there, you can either write accessors to Get your txtOptionsDisplay or you can make it public. Below is an example:
public class frmMain
{
private static frmMain Instance = null;
private static object LockObj = new object();
public static frmMain GetMain()
{
// Thread-safe singleton
lock(LockObj)
{
if(Instance == null)
Instance = new frmMain();
return Instance;
}
}
public string GetOptionsDisplayText()
{
return txtOptionsDisplay.Text;
}
}
public class frmSelect
{
private void frmSelect_Load(object sender, EventArgs e)
{
// Set whatever text you want to frmMain's txtOptionsDisplay text
txtDisplay.Text = frmMain.GetMain().GetOptionsDisplayText();
}
}
If you do go this route, don't forget to update Program.cs to use frmMain's singleton.
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Application.Run(new frmMain()); - Old method
Application.Run(frmMain.GetMain());
}

c# multiple instances of methods?

I have a c# program.
the form1 of the program makes new instances of form3 on different thread from time to time.
so each of the form3 instances contains these methods.
I suspect that the problem occours when two threads calls the same method at the same time or while another thread is allready using one of them. especially because they have the same webbrowser name
private void vent()
{
while (wb.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
}
public bool containss(string SearchText)
{
string page = wb.Document.Body.InnerText;
if (page.ToLower().Contains(SearchText.ToLower()))
return true;
else return false;
}
The Problem with this containss() method is that it works with
string page = wb.Document.Body.InnerHtml;
and not
string page = wb.Document.Body.InnerText;
I get: Object reference not set to an instance of an object.
on this line:
if (page.ToLower().Contains(SearchText.ToLower()))
I make the threads like this:
private void ThreadProc()
{
Form frm = new Form3(currentAB);
frm.ShowDialog();
}
Thread ab1 = new Thread(new ThreadStart(ThreadProc));
ab1.SetApartmentState(ApartmentState.STA);
ab1.Start();

Threading, let one thread know the others progress

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.

Categories