Check if form is opened and update if opened - c#

I want to check if an instance of a form is opened and open the existing instance to update a textbox else create a new instance.
After searching I found : How to check if a windows form is already open, and close it if it is?
From the accepted answer I tried
try
{
foreach (Form fm in Application.OpenForms)
{
if (fm is Form2)
{
Form2 n1 = (Form2)Application.OpenForms["Form2"];
n1.textBox1.Text = textBox1.Text;
break;
}
else
{
Form2 n1 = new Form2();
n1.textBox1.Text = textBox1.Text;
n1.Show();
}
}
}
catch (InvalidOperationException)
{
}
Apart from that this code throws an InvalidOperationException (which i am already catching), The code doesn't work because if an instance already exists it still creates a new instance.
What am i doing wrong?

A better approach would be to filter the OpenForms based on the form type:
var form2collection = Application.OpenForms.OfType<Form2>();
You can then loop over those, or if the collection is empty, open a new form. The advantage is that you aren't relying on the form name, but the actual class definition of the form, which is more reliable.
Additionally, I tend to avoid direct manipulation of controls from other code. I find it more reliable if others call a method, such as
public void setSomeControl(string value)
{
this.controlName.Text = value;
}
then call
form2collection[0].setSomeControl("new value");
which allows your form to do all the housekeeping and the calling code can ignore those details.

Related

listview not updating when method is called from another instance

When I call the Method idload() from another instance it does not update the list view list its supposed to. I know the method is getting called correctly because I placed a MessageBox after each statement in idload() and it shown. if idload() is called from Form2.cs [the form that it's in] it works fine but If I call it from Form4.cs it does not update the listview.
I used MessageBox.Show(xmlReader.GetAttribute("id")); and when idload() gets called from Form2.cs it loops through every id in the xml once and updates the list view as expected. when it gets called from Form4.cs it loops through everything twice and does not update the listview.
Here are the Relevant parts of code:
Form4.cs
public void myMethod()
{
Form2 form2 = new Form2();
form2.idload();
}
public void idwrite()
{
XElement xml = XElement.Load("settings.xml");
xml.Add(new XElement("Chat",
new XAttribute("id", textBox1.Text),
new XAttribute("name", textBox2.Text)));
xml.Save("settings.xml");
myMethod();
this.Close();
}
Form2.cs
public void idload()
{
listView1.Items.Clear();
XmlReader xmlReader = XmlReader.Create("settings.xml");
while (xmlReader.Read())
{
if ((xmlReader.NodeType == XmlNodeType.Element) && (xmlReader.Name == "Chat"))
{
if (xmlReader.HasAttributes)
{
// listView1.Items.Add(xmlReader.GetAttribute("id"));
// listView1.Items.Add(xmlReader.GetAttribute("name"));
string[] arr = new string[4];
ListViewItem itm;
arr[0] = (xmlReader.GetAttribute("id"));
arr[1] = (xmlReader.GetAttribute("name"));
itm = new ListViewItem(arr);
MessageBox.Show(xmlReader.GetAttribute("id"));
listView1.Items.Add(itm);
}
}
}
xmlReader.Close();
}
Here is the Project if needed: https://ufile.io/8dc20
Really confused why this is happening as there are no errors when debugging so any help is appreciated.
Thanks.
This is a common problem that is rooted in the OOP concept of instance. You have already an instance of Form2 displayed, but your code creates a NEW instance of Form2 and never shows it. Now the code calls the method using that new instance and works with the its ListView.
Of course, being the instance never shown, you don't see any change.
If you call form2.Show after the call to idload you will see your changes on the different instance.
The simplest workaround is to ask the Winforms engine to give you back a reference to the already shown instance of Form2 through the Application.OpenForms collection. Here you can select the reference to the Form2 instance with the extension OfType and getting the first form of that type available. If there is none then create and show it.
public void myMethod()
{
Form2 form2 = Application.OpenForms.OfType<Form2>().FirstOrDefault();
if(form2 != null)
form2.idload();
else
{
form2 = new Form2();
form2.Show();
form2.idload();
}
}

ManagementObjectSearcher causes re-entrancy issues for onclick handler

I am having an odd problem with protecting a section of code. My application is a tray app. I create a NotifyIcon inside my class (ApplicationContext). I have assigned a balloon click handler and a double click handler to the NotifyIcon object. there is also a context menu but I am not showing all code. Only important pieces.
public class SysTrayApplicationContext: ApplicationContext
{
private NotifyIcon notifyIcon;
private MainForm afDashBoardForm;
public SysTrayApplicationContext()
{
this.notifyIcon = new NotifyIcon();
this.notifyIcon.BalloonTipClicked += notifyIcon_BalloonTipClicked;
this.notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick;
// ... more code
}
Both handlers launch or create/show my form:
private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
openDashboard();
}
}
private void notifyIcon_BalloonTipClicked(object sender, EventArgs e)
{
openDashboard();
}
private void openDashboard()
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
There is a problem with the above code. Maybe more than 1. Issue: it is possible to display 2 dashboard forms which is not what I want. If user double clicks on tray icon while balloon message is displaying causes a race condition in openDashboard. I can reproduce this easily. So I added a lock around the code in openDashboard code and, to my surprise, that did NOT prevent 2 dashboard forms from displaying. I should not be able to create 2 MainForms. Where am I going wrong here?
here is the updated code with lock statement:
private void openDashboard()
{
lock (dashBoardFormlocker)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
}
Note: lock object was added to the class and initialized in constructor.
private object dashBoardFormlocker;
UPDATE: Showing more code. this is how code gets started :
static void Main()
{
if (SingleInstance.Start())
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
XmlConfigurator.Configure();
// For a system tray application we don't want to create
// a form, we instead create a new ApplicationContext. The Run method takes
Application.Run(new SysTrayApplicationContext());
SingleInstance.Stop();
SingleInstance.Dispose();
}
}
}
UPDATE 2: Provide more code for clarity
public partial class MainForm : Form
{
public MainForm()
{
log.Trace("MainForm constructor...");
InitializeComponent();
// ... code not shown
this.label_OSVersion.Text = getOSFriendlyName();
// .. more code
}
private string getOSFriendlyName()
{
try
{
string result = string.Empty;
var mgmtObj = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType<ManagementObject>()
select x.GetPropertyValue("Caption")).FirstOrDefault();
result = mgmtObj != null ? mgmtObj.ToString() : string.Empty;
OperatingSystem os = Environment.OSVersion;
String sp = os.ServicePack ?? string.Empty;
return !string.IsNullOrWhiteSpace(result) ? result + sp : "Unknown";
}
catch (System.Exception ex)
{
log.Error("Error trying to get the OS version", ex);
return "Unknown";
}
}
}
The main UI thread must always pump a message loop to support communication from COM components.
So when you do a blocking operation from the UI thread like locking or joining a thread, (EDIT: edited based on Peter Duniho's fix) the UI thread will enter an 'alertable' state, allowing COM to dispatch certain type of messages, which in turn can cause re-entrancy issues like in your scenario.
Look at the answer to this question (Why did entering a lock on a UI thread trigger an OnPaint event?) for a much more accurate explanation.
Looking at the source code of ManagementObjectSearcher.Get there is a lock (inside Initialize), and since you call it from the constructor of your form, it may lead to the second event triggering while the form's constructor has not finished. The assignment to the dashBoardFormlocker variable only happens after the constructor finishes, so that would explain why it was null on the second entry.
The moral of the story is never do blocking operations on the UI thread.
Without a good, minimal, complete code example that reliably reproduces the problem, it's impossible to know for sure what the problem is. But the guess by answerer tzachs seems reasonable. If so, you can fix your problem by changing your method to look like this:
private bool _dashboardOpen;
private void openDashboard()
{
if (_dashboardOpen)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
}
else
{
log.Debug("Dashboard form does not exist, create it");
_dashboardOpen = true;
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
In that way, any re-entrant attempt to open the window will be detected. Note that you still need the check for null before actually activating; you can't activate a window that hasn't actually finished being created yet. The subsequent call to Show() will take care of activation anyway, so ignoring the activation in the re-entrant case shouldn't matter.

Have only one instance of a subform open at a time c#

I'm writing an application that uses a wizard-like series of 5 simple forms. The first form, NewProfile, is opened from a menu item on the main application, MainForm, so is a subform of MainForm. The second form, TwoProfile, is opened from a button on NewProfile. The third form, ThreeProfile is opened from a button on TwoProfile, and so on for all 5 forms. Here is the sequence:
MainForm --> NewProfile <--> TwoProfile <--> ThreeProfile <--> FourProfile <--> FiveProfile. My problem is that when any form (NewProfile, TwoProfile, ThreeProfile, FourProfile or FiveProfile) is open, I don't want a user to be able to create an instance of NewProfile.
I started out by implementing a Singleton pattern, which half-way works. It works if NewProfile is open and I go to MainForm and try to create another instance of NewProfile. It does not work if NewProfile has been destroyed, by advancing to the next form and one of TwoProfile, ThreeProfile, FourProfile or FiveProfile is open. It tells me that NewProfile.IsDisposed is true, giving me a bad reference to the Singleton instance.
What I can't figure out is how to do my logic so that NewProfile won't be created if one of TwoProfile, ThreeProfile, FourProfile or FiveProfile is open or if NewProfile itself is open.
I hope this made sense. I don't really have much code to post, except what I did for my Singleton.
private static NewProfile _instance = null;
public static NewProfile Instance
{
get
{
if (_instance == null)
{
_instance = new NewProfile();
}
return _instance
}
}
Thank you :)
As suggested in the comments, each "form" could actually be a usercontrol you swap. That way, you only have one form and multiple pages. Alternatively, you can hide the form.
If you want multiple forms instead, then you could loop through all the open forms and see if the the ones you want to check are open. If not, you can open NewProfile.
bool shouldOpenNewDialog = true;
foreach (Form f in Application.OpenForms)
{
//give each dialog a Tag value of "opened" (or whatever name)
if (f.Tag.ToString() == "opened")
shouldOpenNewDialog = false;
}
if(shouldOpenNewDialog)
np = new NewProfile();
It's untested, but it should loop through all open forms and look for any that has a Tag saying opened. If it comes across one, then it set the shouldOpenNewDialog flag to false and NewProfile won't be called.
The way that we handle this is to have a static window manager class that keeps track of the open form instances. When the user performs an action that would cause a new window to open, we first check the window manager to see if the form is already open. If it is, we set focus on it rather than creating a new instance.
Every form that is opened inherits from a base form implementation that automatically registers itself with the window manager when it is opened and removes its registration when it is closed.
Here is a rough outline of the WindowManager class:
public class WindowManager
{
private static Dictionary<string, Form> m_cOpenForms = new Dictionary<string, Form>();
public static Form GetOpenForm(string sKey)
{
if (m_cOpenForms.ContainsKey(sKey))
{
return m_cOpenForms[sKey];
}
else
{
return null;
}
}
public static void RegisterForm(Form oForm)
{
m_cOpenForms.Add(GetFormKey(oForm), oForm);
oForm.FormClosed += FormClosed;
}
private static void FormClosed(object sender, FormClosedEventArgs e)
{
Form oForm = (Form)sender;
oForm.FormClosed -= FormClosed;
m_cOpenForms.Remove(GetFormKey(oForm);
}
private static string GetFormKey(Form oForm)
{
return oForm.Name;
}
}
And you can use it as follows:
Form oForm = WindowManager.GetOpenForm("Form1");
if (oForm != null)
{
oForm.Focus();
oForm.BringToFront();
}
else
{
oForm = new Form1();
WindowManager.RegisterForm(oForm);
// Open the form, etc
}

C# Closing a specific form in a multiform application

I hope you can help me with this one. My application is monitoring a database for alerts. When a alert shows up in the database, my application will add it to the main form in a datagridview, and depending on its priority it will also create a small winform popup with the event.
In the datagridview is a button to flag the alert as "seen", it will then update the database and be it will be gone from the list. However the popup form for this event is still open.
Does anyone know how to close this form? I need a way to find a specific form between the possible multiple alert forms that are open.
The closest I have come this far is the following code:
private void closeForm(int id)
{
foreach (Form f in Application.OpenForms)
{
if (Convert.ToString(id) == f.Name)
{
this.Close();
}
}
}
Which works up until the point that it closes the correct form. then it gives an error saying "Collection was modified; enumeration operation may not execute." This kinda makes sense, but I simply can't figure out another way to do it.
I have a winform class called Alert, wich makes the new forms. As you can see they will get a standard text "Alarm" and a unique Name based on the alert ID.
Alert alertform = new Alert(id);
alertform.Name = formid;
alertform.Text = "Alarm";
alertform.Show();
I hope someone has some good ideas how I can go about this. I have searched but cannot realy find a simple and elegant way to do this.
You need to add break; to your loop after you close the form. The collection is modified when you close the form (that form is removed from the collection), thus rendering the foreach loop invalid. And should you not be calling f.Close, rather than this.Close?
private void closeForm(int id)
{
foreach (Form f in Application.OpenForms)
if (Convert.ToString(id) == f.Name)
{
f.Close();
break;
}
}
You should simply be able to store a reference to your form in the DataGridView or its DataSource then close the form using that reference. This approach is less likely to break in the future than iterating over all the application forms.
What would probably work best is to add a hidden column to the DataGridView that holds the form id, then also have a Dictionary<int, Form> which you use to get the reference to the Form you want to close.
Then you can simply get the form reference out of the dictionary and call close on it:
private void CloseAlertForm(int id)
{
Form f = dict[id];
f.Close();
dict.Remove(id);
}
Additionally you could store Action delegates instead of form references allowing you to slightly decouple the alert forms and the grid form.
just get ref. from foreach loop and close the form outside it.
private void closeForm(int id)
{
Form formtoclose=null;
foreach (Form f in Application.OpenForms)
{
if (Convert.ToString(id) == f.Name)
{
formtoclose = f;
}
}
if(formtoclose!=null)
formtoclose.Close();
}
A Close modifies your OpenForms collection, so instead of enumeration over the OpenForms collection directly, you could enumerate over a copy.
LINQ is very handy into making copies, like this:
foreach (Form f in Application.OpenForms.Where(i => Convert.ToString(id) == i.Name).ToList()) {
// Save to close the form here
}
The ToList executes the query, and stores the copy.
var names = Application.OpenForms.Select(rs=>rs.name).ToList()
foreach (string name in names)
if (Convert.ToString(id) == name)
{
Application.OpenForms[name].Close();
}
You could use the type of the forms to find them (And ToArray to create a new collection an avoid changing the collection you are enumerating).
private void CloseAlerts()
{
var openForms = Application.OpenForms.Cast<Form>();
foreach (var f in openForms.Where(f => f is Alert).ToArray())
{
f.Close();
}
}
In this case you don't need to set a name :
Alert alertform = new Alert(id);
alertform.Text = "Alarm";
alertform.Show();

How can I make sure only one WPF Window is open at a time?

I have a WPF window that I am launching from inside of a winform app. I only want to allow once instance of that WPF window to be open at a time, and not warn that user if they try to open it again.
I am having a problem however trying to search for that WPF window being open because the window is being launched from a winform. What I normaly do is when searching for a winform, I search for any instances of that winform existing in the Application.Current.OpenForms, and when in WPF I search for Application.Current.Windows
The problem I have is that System.Windows.Application.Current is null when launched from inside of a winform, so I can't search for the WPF window that way. Is there any better way of searching for an existing instance of an open window?
My Code:
if (System.Windows.Application.Current != null)
{
foreach (System.Windows.Window win in System.Windows.Application.Current.Windows)
{
if (win is frmCaseWpf)
{
MessageBox.Show("You may have only one active case open at a time.", "Open Case",
MessageBoxButtons.OK,
MessageBoxIcon.Stop);
win.WindowState = System.Windows.WindowState.Normal;
win.Focus();
win.Activate();
return;
}
}
}
Instead of searching the static application objects, you could instead just track this within your window, with a single static variable. Just keep a variable in the window:
private static frmCaseWpf openWindow = null; // Assuming your class name is frmCaseWpf
When you create a window, either in the initialize routines, or OnLoaded, depending on how you want it to work..:
partial class frmCaseWpf {
public frmCaseWpf {
this.OnLoaded += frmCaseWpf_OnLoaded;
}
private void frmCaseWpf_OnLoaded(object sender, RoutedEventArgs e)
{
if (this.openWindow != null)
{
// Show message box, active this.openWindow, close this
}
this.openWindow = this;
}
}
If you want this window to be reusable, make sure to set this.openWindow = null; when you close the window, as well.
Here's something that's working for me.
private About aboutWin;
private void AboutOpenClicked(object sender, RoutedEventArgs e)
{
if(aboutWin == null)
{
aboutWin = new About();
aboutWin.Closed += (a, b) => aboutWin = null;
aboutWin.Show();
}
else
{
aboutWin.Show();
}
}
It would be better make the frmCaseWpf class a singleton. That way you can't create another instance
Rather than try to search for a Window instance, many people use a session- (or system-) wide "Mutex" or a Mutual Exclusion lock. I was going to rewrite one for you, but I found a good codeproject article demonstrating the technique. It's not complex and very simple.
http://www.codeproject.com/KB/cs/SingleInstanceAppMutex.aspx?msg=2908697
Sneak peek:
[STAThread]
static void Main()
{
bool onlyInstance = false;
Mutex mutex = new Mutex(true, "UniqueApplicationName", out onlyInstance);
if (!onlyInstance) {
return;
}
Application.Run(new MainForm);
GC.KeepAlive(mutex);
}
Hope this helps.
(edit: of course you'll have to modify this slightly for your particular use-case, but it demos the general idea)
I am not really a 'proper' programmer, however I have achieved this in a WPF application (not from a winforms one) by using the following:
Dim wdwDetails As New detailsNew()
Private Sub openNewDetails(ByVal recordID As String)
wdwDetails.Owner = Me
wdwDetails.recordID = recordID
wdwDetails.WindowStartupLocation = Windows.WindowStartupLocation.CenterOwner
wdwDetails.Show()
End Sub
Essentially because I am creating the window object outside of the sub that opens it, there will only be a single window. Any new call to the window open sub will use the same object. But I guess that is what Thomas is referring to also.
Like I said, not sure if this will help you or not though.
You can use XXXwindown.isLoad to check if window is loaded before you create a new window:
if ( !ChildWindow.IsLoaded)
{
childWindow= new ChildWindow();
childWindow.Show();
}

Categories

Resources