In my C# application I have an option dialog that can be opened from a menu command.
I want to ensure that the option dialog have only one instance (user cannot open more than one option window at a given time) without making it modal.
Also if the user already have this window opened, and he clicks in the menu item to open it again, the app just makes the already visible form became the top most window.
Can anyone point me directions on how accomplish these tasks?
Thank you very much.
Well, the simplest way is to have a static field which stores a reference to the single instance or null, and then a method to retrieve it or create a new one.
Note that this isn't the same as making it a singleton - because I assume if the form is closed, you'd want to create a new instance next time. (The alternative - hiding it and reusing it - is shown in STO's answer.) You may want something like this:
public class OptionsDialog : Form
{
private static OptionsDialog openForm = null;
// No need for locking - you'll be doing all this on the UI thread...
public static OptionsDialog GetInstance()
{
if (openForm == null)
{
openForm = new OptionsDialog();
openForm.FormClosed += delegate { openForm = null; };
}
return openForm;
}
}
You may want to make the method perform the "bring it to the front" steps as well, of course.
You need to prevent the form from closing. If you don't, the form will be disposed and becomes unusable. You can do this by implementing the FormClosing event:
protected override void OnFormClosing(FormClosingEventArgs e) {
if (e.CloseReason == CloseReason.UserClosing) {
this.Hide();
e.Cancel = true;
}
}
To make it a singleton, just keep track of the life time of the form in your main form class:
frmOptions options;
private void btnShowOptions_Click(object sender, EventArgs e) {
if (options == null) {
options = new frmOptions();
// To make absolutely sure:
options.FormClosed += (o, ea) => options = null;
}
else {
options.WindowState = FormWindowState.Normal;
}
options.Show();
}
You will need this form as property
Form1 myForm = null;
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
myForm = null;
}
private void ShowForm()
{
if (myForm != null)
{
myForm.BringToFront();
}
else
{
myForm = new Form1;
myForm.Show();
}
}
you may use code like this:
private MyDialogForm _FormInstance;
public void ShowOptions()
{
if (_FormInstance == null)
{
_FormInstance = new MyDialogForm();
_FormInstance.FormClosing += (s,e) =>
{
e.Cancel = true;
_FormInstance.Hide();
}
}
_FormInstance.Show();
}
I assume you have at least two forms. One form, call it frmMain, allows you to open frmOptions. In frmMain, add a variable of type frmOptions like this:
public partial class frmMain : Form
{
private frmOptions optionsInstance = null;
...
In the routine that opens the Options dialog, add this:
if (optionsInstance == null || !optionsInstance.Visible)
{
optionsInstance = new frmOptions();
optionsInstance.Show();
}
When frmOptions closes, optionsInstance will not be null, so that's why you check if it's visible before instantiating a frmOptions instance.
If this doesn't work for you, you could try a mutex, but that's probably overkill.
Based on Jon Skeet's answer, I'm using the following code for showing a form as a modal dialog box.
if (this.aboutForm == null)
{
this.aboutForm = new AboutForm();
this.aboutForm.FormClosed += (sender2, e2) => { this.aboutForm = null; };
this.aboutForm.ShowDialog(this);
}
else
{
this.aboutForm.Focus();
}
I have to do this because I have a menu item to display the About form in the menu of the main form, and in the context menu of the notify icon. If I open the About form using the menu of the main form, I still can open another instance by using the context menu item of the notify icon.
Main_Frm _main_Frm = null;
private void Show_bt_Click(object sender, EventArgs e)
{
if (_main_Frm != null)
{
_main_Frm .BringToFront();
}
else
{
_main_Frm = new Comission_Frm();
_main_Frm .Show();
}
//This condition used when you closed the form the form will disposed and when you reopen.
if (_main_Frm .IsDisposed)
{
_main_Frm = new _Main_Frm ();
_main_Frm .Show();
}
}
This May Help!
Note: The following code was taken from the article Below:
https://www.dotnetcurry.com/ShowArticle.aspx?ID=150
static class Program
{
///<summary>
/// The main entry point for the application.
///</summary>
[STAThread]
static void Main()
{
bool instanceCountOne = false;
using (Mutex mtex = new Mutex(true, "MyRunningApp", out instanceCountOne))
{
if (instanceCountOne)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
mtex.ReleaseMutex();
}
else
{
MessageBox.Show("An application instance is already running");
}
}
}
}
Related
I've made a Windows Forms solution. In the main shell, there is added a MenuStrip, and it's possible to add more Views onto it.
The problem is, that when I add/open a new View, it is opened behind the MenuStrip.
Somehow, I want the MenuStrip to have a border, so it is not possible to drag things behind it, but I have no idea how.
The same case should be with other Views.
You should set the Dock property for the control that you want to add.
OK, I have a solution - I don't totally like it but it works! You will need the usual MDI suspects in terms of flags, etc.
The main form that is the MDI container needs to have something like:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
int BodyCount = 0;
private void fileToolStripMenuItem_Click(object sender, EventArgs e)
{
MDIChildForm child = new MDIChildForm();
child.TitleText = String.Format("Child window {0}", ++BodyCount);
child.MdiParent = this;
child.Show();
}
/*
** This could be fun - shouldn't recurse!
*/
public void ShifTheChild(MDIChildForm spoiltBrat)
{
var m = menuStrip1.Height;
if (spoiltBrat.Location.Y < m)
spoiltBrat.Location = new Point(spoiltBrat.Location.X, 0);
return;
}
}
The child forms need the location changed event hooking:
public partial class MDIChildForm : Form
{
public String TitleText
{
get { return this.Text; }
set { this.Text = value; }
}
MainForm parent = null;
public MDIChildForm()
{
InitializeComponent();
this.ShowIcon = false;
}
private void MDIChildForm_LocationChanged(object sender, EventArgs e)
{
if (parent != null)
parent.ShifTheChild(this);
}
private void MDIChildForm_Load(object sender, EventArgs e)
{
parent = this.MdiParent as MainForm;
}
}
When you move a child into the twilight zone under the menu it will be snapped back out - the method that moves it will cause the event to fire again but the second time nothing should happen (so no recursion).
I don't like this solution simply because I can't get my brain around whether there is a condition that would make it recurse, and I don't like uncertainty.
Good luck.
I have an application that has a form named as MainAppForm (Thread1). I have a panel in this form which will host UserControls.
When a user clicks the button, I want to create another thread (Thread2) which will create an instance of the UserControl and call a method that is on the Thread1 to add UserControl to the panel in mentioned in the first paragraph.
This is how I call main Thread1 from Thread2
public class SecondThread
{
public void start()
{
ModuleWindow userControl = new ModuleWindow(new Module.ModuleLayer());
Global.SetModuleWindowThreadSafe(userControl);
}
}
My method that will add the passed in user control to the panel.
public static class Global
{
private delegate void SetModuleWindowThreadSafeDelegate(UserControl userControl);
public static void SetModuleWindowThreadSafe(UserControl userControl)
{
if (Global.mainAppForm.pnlMain.InvokeRequired)
{
Global.mainAppForm.pnlMain.Invoke(
new SetModuleWindowThreadSafeDelegate(SetModuleWindowThreadSafe),
userControl);
}
else
{
Global.mainAppForm.pnlMain.Controls.Add(userControl);
}
}
}
After I do the call in the SetModuleWindowThreadSafe() method it raises
Cross-thread operation not valid: Control 'menuStrip1' accessed from a thread other than the thread it was created on.
Note: menuStrip1 is a control on UserControl.
How can I add the UserControl that is created in the second thread to the panel???
UPDATED:
Thanks for the answers. I am sure they're helpful in some ways but not in my condition. The reason is my MainAppForm(AKTAP project) and the generated UserControl's(KKM project) are being created in different projets even solutions. The project output of KKM is a .dll and I am loading those dll files on runtime using reflections. So MainAppForm does not know what type of usercontrols and controls are being generated in each dll.
What I want to do is in the following order:
1- AKTAP project has an interface which is implemented by a class in KKM project.
2- KKM project is being built and puts dll files to a specified directory.
3- AKTAP starts to run and loads dll files using reflections by filtering the interface mentioned in 1.
4- AKTAP calls a method in KKM hich will generate and return the usercontrol.
5- AKTAP adds the returned usercontrol to the MainAppForm. (And this is where I get the exception above.)
How can I add the UserControl that is created in the second thread to the panel?
You don't. You create the UserControl in the UI thread, rather than in some background thread.
If you have some expensive CPU bound computation to do in order to figure out what data the user control will need then use another thread to compute that data and then have the UI thread take that data and create the UI controls to display it.
Servy is correct - you don't.
However, you can! Meaning, it is possible.
Passing data from a thread is complicated, but the System.ComponentModel.BackgroundWorker (part of WinForms) greatly simplifies threading operations and makes tasks like this rather fun to do.
Here is a generic technique that uses two (2) Windows Forms, one as a variable inside the other. Both are in the same namespace (same project, etc).
Form1:
public partial class Form1 : Form
{
private Button btnGetInteger;
private Button btnGetMenuStrip;
private Button btnGetString;
private Form2 _form2;
private Form2.ReturnType _getType;
private Object _form2Argument;
public Form1()
{
InitializeComponent();
btnGetInteger = new Button();
btnGetInteger.Click += Form2_GetInteger;
btnGetMenuStrip = new Button();
btnGetMenuStrip.Click += Form2_GetInteger;
btnGetString = new Button();
btnGetString.Click += Form2_GetString;
Shown += (s, e) => { Form2_CreateMenuStrip(s, EventArgs.Empty); };
}
public void Form2_ThreadChanged(object sender, ProgressChangedEventArgs e)
{
var returned = (Form2.ReturnType)e.ProgressPercentage;
switch (returned)
{
case Form2.ReturnType.MenuStrip:
var menuStrip = (MenuStrip)e.UserState;
this.Controls.Add(menuStrip);
break;
case Form2.ReturnType.Integer:
var numberBack = (int)e.UserState;
Text = String.Format("Form1 : (int){0}", numberBack);
break;
case Form2.ReturnType.String:
var stringBack = e.UserState.ToString();
Text = String.Format("Form1 : (String){0}", stringBack);
break;
}
}
public void Form2_ThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_form2Argument = null;
if (e.Error != null)
{
String title;
if (_form2 != null)
{
title = String.Format("{0}: {1}", _form2.Text, e.Error.GetType());
} else
{
title = String.Format("Form2: {0}", e.Error.GetType());
}
MessageBox.Show(e.Error.Message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
if (_form2 != null)
{
_form2.Close();
_form2.Dispose();
_form2 = null;
}
btnGetInteger.Enabled = true;
btnGetMenuStrip.Enabled = true;
btnGetString.Enabled = true;
}
private void Form2_CreateMenuStrip(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.MenuStrip;
var item = new ToolStripMenuItem(Text);
item.Click += Form2_GetInteger;
_form2Argument = item;
Form2_StartWork();
}
}
private void Form2_GetInteger(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.Integer;
Form2_StartWork();
}
}
private void Form2_GetString(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.String;
Form2_StartWork();
}
}
private void Form2_StartWork()
{
btnGetInteger.Enabled = false;
btnGetMenuStrip.Enabled = false;
btnGetString.Enabled = false;
_form2 = new Form2();
_form2.Show(); // Show returns immediately
_form2.StartThread(this, _form2Argument, _getType);
}
}
Form2_ThreadChanged and Form2_ThreadCompleted are both set to PUBLIC so that they can be visible by the instance of Form2.
Form2:
public partial class Form2 : Form
{
private ReturnType _getType; // thread safe
private BackgroundWorker _bwThread;
public enum ReturnType { MenuStrip, String, Integer }
public Form2() // Do Not Call this method
{
InitializeComponent();
}
public void StartThread(Form1 parent, Object argument, ReturnType getType)
{
_getType = getType;
if (_bwThread == null)
{
_bwThread = new BackgroundWorker() {
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_bwThread.DoWork += ThreadWork;
_bwThread.ProgressChanged += parent.Form2_ThreadChanged;
_bwThread.RunWorkerCompleted += parent.Form2_ThreadCompleted;
}
if (!_bwThread.IsBusy)
{
_bwThread.RunWorkerAsync(argument);
}
}
private void ThreadWork(object sender, DoWorkEventArgs e)
{
switch (_getType)
{
case ReturnType.MenuStrip:
var menuStrip = new MenuStrip();
if (e.Argument != null)
{
var mi = (ToolStripMenuItem)e.Argument;
menuStrip.Items.Add(mi);
}
_bwThread.ReportProgress((int)_getType, menuStrip);
break;
case ReturnType.Integer:
var numberBack = 1;
_bwThread.ReportProgress((int)_getType, numberBack);
break;
case ReturnType.String:
var stringBack = "Worker String";
_bwThread.ReportProgress((int)_getType, stringBack);
break;
}
}
}
If you make a new, small project with 2 empty forms in it called Form1 and Form2, you can go into the code and simply paste everything from above into those two forms.
With that done, just put breakpoints on all of the methods (both public and private) to see how they work.
In our app we use WPF and Caliburn Micro. We use a custom WindowManager:
public class OurWindowManager : Caliburn.Micro.WindowManager
{
protected override Window EnsureWindow(object model, object view, bool isDialog)
{
var window = base.EnsureWindow(model, view, isDialog);
if (isDialog) window.ResizeMode = ResizeMode.NoResize;
window.Icon = new BitmapImage(new Uri("pack://application:,,,/NWB.ico"));
// TODO: Change to dynamic minWidth/minHeight based on window
window.MinWidth = 600;
new WindowSettingsBehavior().Attach(window);
return window;
}
}
In our code we mostly use this WindowManager like so:
public void SomeMethod()
{
var result = _windowManager.ShowDialog(new ConfirmDialogViewModel("some title",
"some text"));
if(result == true){ // if OK is pressed
// do something on OK
}
// do nothing
}
In one of my recent methods I want to do the following (in semi pseudo-code):
public void SomeOtherMethod()
{
_windowManager.ShowDialog(new ConfirmDialogViewModel("some title", "some text"));
//if window is closed without pressing any of the buttons
return; // do nothing
//if OK is pressed {
// do something on OK
}
// if Cancel is pressed: do something else
}
Unfortunately, ShowDialog also returns false if the Window is closed (even though the ShowDialog returns a Nullable bool (bool?)).
So, what I did so far is just completely remove the Close Button by making a new Window-Behavior, and I've added it to the OurWindowManager class inside the if(isDialog):
if (isDialog)
{
window.ResizeMode = ResizeMode.NoResize;
new WindowHideBarBehavior().Attach(window);
}
This works, and I now got a Window with just a title, and without a Close (X) button. Unfortunately, the window can still be closed with Alt+F4 and such. I thought about catching Alt+F4 and cancel the closing, but since Alt+F4 is standard Window behavior, I don't think users will appreciate it a lot and I find it a bit unintuitive for the users to disable it..
So, my question: How can I accomplish the pseudo-code in SomeOtherMethod mentioned above. Is there a way to get the difference between closing a Dialog or canceling a Dialog. (NOTE: As mentioned above, keep in mind we use Caliburn.Micro.WindowManager, not the default C# WPF one. Don't know if there are a lot of differences, but I guess there are at least some.)
EDIT:
I also know I can catch the closing event and cancel the closing:
window.Closing -= DisableDialogClosing;
if (isDialog)
{
window.ResizeMode = ResizeMode.NoResize;
new WindowHideBarBehavior().Attach(window);
window.Closing += DisableDialogClosing;
}
...
private static void DisableDialogClosing(object sender, CancelEventArgs e)
{
e.Cancel = true;
}
But then it also cancels the closing when I want it to close (for example when the Cancel/OK button is pressed). Maybe I can add some kind of Property-flag to this overridden Closing-EventHandler, but perhaps you guys/girls have other suggestions to accomplish the same results.
You can fulfil your requirements if you just implement your own dialog Window by extending the Window class. From inside your custom Window, you can handle the Closed event and set the Window.DialogResult property to null in that case. For the normal Ok and Cancel states, you can simply attach Click handlers to those Buttons and set the Window.DialogResult property to true and false accordingly.
private void CustomDialogWindow_Close(object sender, RoutedEventArgs e)
{
DialogResult = null;
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
You could then check for the Window closed state like this:
if (CustomDialogWindow.DialogResult == null) DoSomethingUponDialogWindowClose();
You can find further helpful information in the following pages on MSDN:
Dialog Boxes Overview
Window.DialogResult Property
After #Sinatr's suggestion I've added a ClosedBy property to my ConfirmDialogViewModel:
(before):
public sealed class ConfirmDialogViewModel : Screen
{
public ConfirmDialogViewModel(string title, string message)
{
DisplayName = title;
Message = message;
}
public string Message { get; set; }
public void Ok()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
}
(after):
public sealed class ConfirmDialogViewModel : Screen
{
public ClosedBy CloseReason { get; private set; }
public ConfirmDialogViewModel(string title, string message)
{
DisplayName = title;
Message = message;
CloseReason = ClosedBy.Other;
}
public string Message { get; set; }
public void Ok()
{
CloseReason = ClosedBy.Ok;
TryClose(true);
}
public void Cancel()
{
CloseReason = ClosedBy.Cancel;
TryClose(false);
}
}
public enum ClosedBy
{
Other,
Ok,
Cancel
}
And I now use it like so:
public void SomeOtherMethod()
{
var confirmDialog = new ConfirmDialogViewModel("some title", "some text");
var result = _windowManager.ShowDialog(confirmDialog);
if(result == null || confirmDialog.CloseReason == ClosedBy.Other) return;
if(result == true && confirmDialog.CloseReason == ClosedBy.Ok){
// Do something on OK
}
// Do something on cancel
}
I still kept the behavior to remove the close button, and also added window.ShowInTaskbar = false; to the OurWindowManager inside the if(isDialog).
i am working on a project, i am creating a instance and opening a specific form in my program, like i use following code to open a form:
frm2 cs = new frm2();
cs.Show();
but the problem here is each time i click on the button it just open a new windows and the previous one is also opened, all i want to do here is when i click on button and that window is already open then it simply go to already opened windows except opening the new one.
hope you guys understand my question and will help me doing it.
EDIT:
i tried this code : the code i wrote in frm2.cs file is:
public partial class frm2 : Form
{
private static frm2 _form2 = null;
public frm2()
{
InitializeComponent();
}
public static frm2 Instance
{
get
{
if (_form2 == null)
{
_form2 = new frm2();
}
return _form2;
}
}
and in frmMain.cs file where i code to access the instance and open the project is :
private void addProductToolStripMenuItem_Click(object sender, EventArgs e)
{
frm2.Instance.Show();
//frm2 cs = new frm2();
//cs.Show();
}
it work fine the first time, but when i open the frm2 , close it and then again try to open it the compiler give error at frm2.Instance.Show(); and the error comes is
: Cannot access a disposed object.
Object name: 'frm2'.
Save the instance of the form opened in a global class variable and then, if this variable is not null just call Show instead of opening againg. Some tips should be followed though
public class Form1: Form
{
private frm2 _currentInstance = null;
....
if(_currentInstance == null)
{
_currentInstance = new frm2();
_currentInstance.FormClosed += instanceHasBeenClosed;
_currentInstance.Show();
}
_currentInstance.BringToFront();
....
private void instanceHasBeenClosed(object sender, FormClosedEventArgs e)
{
_currentInstance = null;
}
}
It is important to subscribe to the FormClosed event for the _currentInstance. In this way your Form1 will be notified when your user closes the instance. And you could reset the internal variable to null, so at the subsequent click you could reopen again the instance
There is also the possibility to use the Application.OpenForms collection to check if your form is shown
frm2 f = Application.OpenForms["NameOfForm2"];
if(f != null)
f.BringToFront();
else
{
frm2 f = new frm2();
f.Show();
}
I know you don't want to write that much code but it seems this is the only way to go.
At least, you can separate that code from your forms by writing a generic code.
var f = FormManager.Show<Form2>(); //create a new form.
f = FormManager.Show<Form2>(); //show the existing form.
f.Close();
f = FormManager.Show<Form2>(); //create a new form
public static class FormManager
{
static Dictionary<Type, Form> _Forms = new Dictionary<Type, Form>();
public static T Show<T>() where T: Form, new()
{
var type = typeof(T);
Form f = null;
if(_Forms.TryGetValue(type,out f))
{
f.BringToFront();
}
else
{
f = new T();
f.FormClosing += (s, e) => _Forms.Remove(s.GetType());
_Forms.Add(type, f);
f.Show();
}
return (T)f;
}
}
you can use singleton design pattern to return always the same instance
public partial class Form2 : Form
{
private static Form2 _form2 = null;
private Form2()
{
InitializeComponent();
}
public static Form2 Instance
{
get
{
if (_form2 == null)
{
_form2 = new Form2();
}
return _form2;
}
}
}
// use this to call the instance
Form2.Instance.Show();
Singleton pattern may be a way to go - just apply it to windows forms (you can check this link for the description of the singleton pattern in C#: http://msdn.microsoft.com/en-us/library/ff650316.aspx).
Check on multiple buttons in that Form is open or not:
private bool isOpen(string name)
{
IsOpen = false;
foreach (Form f in Application.OpenForms)
{
if (f.Text == name)
{
IsOpen = true;
f.Focus();
break;
}
}
if (IsOpen == false)
{
return IsOpen;
}
return IsOpen;
}
Call this in the button click to Check the current Form:
if (isOpen("Item Import Master")) ;
else
{
Item_Import_Master obj1 = new Item_Import_Master();
obj1.Show();
}
You need to check if a form is Disposed before you show it again, otherwise you get Cannot access a disposed object exeption
something like this should work
private static AdvancedRDSWindow form2;
private AdvancedRDSWindow()
{
InitializeComponent();
}
public static AdvancedRDSWindow Instance
{
get
{
if (form2 == null || form2.IsDisposed)
{
form2 = new AdvancedRDSWindow();
}
return form2;
and then you can show it with a button for instance
private void AdvancedRDS_Click(object sender, EventArgs e)
{
AdvancedRDSWindow.Instance.Show();
}
Use this code in the button click event
If the form is already opened, it will bring the form to front
# Here is my code.. #
foreach (Form form in Application.OpenForms)
{
if (form.GetType() == typeof(frm2))
{
form.Activate();
return;
}
}
frm2 frm = new frm2();
frm.Show();
I'd like to know what's the best way (read most elegant) to have a single instance of a given Window per application in WPF.
I'm a newcomer to .NET and WPF and what I came up with looks pretty lame.
private static readonly Object MUTEX = new Object();
private static AboutWindow INSTANCE;
public static AboutWindow GetOrCreate() {
lock (MUTEX) {
if (INSTANCE == null) {
INSTANCE = new AboutWindow();
}
INSTANCE.Show();
return INSTANCE;
}
}
private AboutWindow() {
InitializeComponent();
}
private void AboutWindow_Closed(object sender, EventArgs e) {
// the Closed events are handy for me to update values across
// different windows.
lock (MUTEX) {
INSTANCE = null;
}
}
Thing is... this looks like utter crap. There must be some way to achieve the same goal in a much more elegant way, right?
PS: I'm often using the Closed event to change values in other open windows. For instance I have the SettingsWindow with the "Account" button. When I push that button, the AccountWindow pops up. When I close AcountWindow, I want something in the SettingsWindow to change (a label). Hence the constant creation of windows.
Besides, Close is something you always have to deal with because of the X button on the window frame...
there are probably better ways to do this, but here is a relatively simple way....
put a static bool on your window class to flag if its open or not. then, in the load() event set it to true, and on the close event set it to false. Then, in the code that opens the window, check the flag.
here is some pseudo-code to give you an idea...
public class AboutWindow
{
public static bool IsOpen {get;private set;}
onLoadEvent(....)
{
IsOpen = true;
}
onUnloadEvent(...)
{
IsOpen = false;
}
}
public void OpenAbout()
{
if ( AboutWindow.IsOpen ) return;
AboutWindow win = new AboutWindow();
win.Show();
}
If you truly need to enforce a single instance of a window, then a static instance (some flavor of what you have) with a factory creation method is certainly a viable option, much like a single DataContext instance when working with a database.
You could also write your own WindowManager class, although that seems like overkill, and will essentially be the same thing (except the Factory methods would be in a single class).
However, re-reading your post, I wonder if this is a case of missing the forest for the trees. Your mentioning of your SettingsWindow, which in turn calls AccountWindow, makes me think that you should simply be using ShowDialog(). This opens a window modally, meaning that there can be no interaction with the calling window (or any other window in your application). You simply set a property in that dialog, set the DialogResult to true when the OK button is pressed, and read that property in the parent window.
Basically, you just use the ShowDialog like this. I am leaving out a lot of the implementation details, as far as binding vs. hard-coding to controls. Those details aren't as important as just seeing how ShowDialog works.
For simplicity, assume that you have a class called MyAppOptions that, well, reflect the options of your application. I will leave off most of the implementation details of this for simplicity, but it would likely implement INotifyPropertyChanged, have methods and fields and properties, etc.
public class MyAppOptions
{
public MyAppOptions()
{
}
public Boolean MyBooleanOption
{
get;
set;
}
public String MyStringOption
{
get;
set;
}
}
Then, let's make this simple, and assume that you want to show an Options dialog when you press a button on some window. Furthermore, I will assume that there are variables that have been set with your options, which were loaded at startup.
void btnOptions_Click(object sender, RoutedEventArgs e)
{
MyAppOptions options = new MyAppOptions();
options.MyBooleanOption = mSomeBoolean;
options.MyStringOption = mSomeString;
OptionsDialog optionsDialog = new optionsDialog(options);
if (optionsDialog.ShowDialog() == true)
{
// Assume this function saves the options to storage
// and updates the application (binding) appropriately
SetAndSaveOptions(optionsDialog.AppOptions);
}
}
Now assume that the OptionsDialog is a window you've created in your project, and it has a CheckBox on it related to MyBooleanOption and a TextBox for MyStringOption. It also has an Ok button and a Cancel button. The code-behind will likely use Binding, but for now we'll hard code the values.
public class OptionsDialog : Window
{
public OptionsDialog(MyAppOptions options)
{
chkBooleanOption.IsChecked = options.SomeBooleanOption;
txtStringOption.Text = options.SomeStringOption;
btnOK.Click += new RoutedEventHandler(btnOK_Click);
btnCancel.Click += new RoutedEventHandler(btnCancel_Click);
}
public MyAppOptions AppOptions
{
get;
set;
}
void btnOK_Click(object sender, RoutedEventArgs e)
{
this.AppOptions.SomeBooleanOption = (Boolean) chkBooleanOption.IsChecked;
this.AppOptions.SomeStringOption = txtStringOption.Text;
// this is the key step - it will close the dialog and return
// true to ShowDialog
this.DialogResult = true;
}
void btnClose_Click(object sender, RoutedEventArgs e)
{
// this will close the dialog and return false to ShowDialog
// Note that pressing the X button will also return false to ShowDialog
this.DialogResult = false;
}
}
This is a pretty basic example as far as implementation details. Search online for ShowDialog for more details. The important keys to remember are:
ShowDialog opens a window modally,
meaning it is the only window in your
application that can be interacted
with.
Setting DialogResult to true
will close the dialog, which can be
checked for from the calling parent.
Setting DialogResult to false will
also close the dialog, in which case
you skip updating the values in the
calling window.
Pressing the X button
on the window automatically sets the
DialogResult to false
You can have public properties in the dialog window that can be set before doing the ShowDialog, and can get values from after the dialog disappears. It will be available while the dialog is still in scope.
The following extends on the above solution to reshow the window if it is already open. In this case it is a help window.
///<summary>
/// Show help from the resources for a particular control by contextGUID
///</summary>
///<param name="contextGUID"></param>
private void ShowApplicationHelp(string contextGUID = "1")
{
if (HelpWin != null)
{
if (HelpWin.IsOpen)
{
HelpWin.BringToFront();
return;
}
}
HelpWin = new MigratorHelpWindow();
HelpWin.Owner = Application.Current.MainWindow;
HelpWin.ResizeMode = ResizeMode.CanResizeWithGrip;
HelpWin.Icon = new Image()
{
Source =
new BitmapImage(
new Uri(
"pack://application:,,,/ResourceLibrary;component/Resources/Images/Menu/Help.png",
UriKind.RelativeOrAbsolute))
};
HelpWin.Show();
HelpWin.BringToFront();
}
This code is all in a viewmodel (MVVM) associated with the window. It is called by an ICommand hooked to a button on the window (naturally, it shows a question mark!!)
The following property is involved (in this case it is a Telerik RadWindow but it can be any window object, and you can probably also just store the window handle but using this property permits manipulation of the object more smoothly e.g. HelpWin.BringToFront() as in the above example...
...
...
private Telerik.Windows.Controls.RadWindow **HelpWin**
{
get;
set;
}
...
...
In the window itself (WPF window)
///<summary>
/// Flag to indicate the window is open - use to prevent opening this particular help window multiple times...
///</summary>
public static bool IsOpen { get; private set; }
...
...
...
private void HelpWindowLoaded(object sender, RoutedEventArgs e)
{
IsOpen = true;
}
private void HelpWindowUnloaded(object sender, RoutedEventArgs e)
{
IsOpen = false;
}
and in the view Xaml
...
...
DataContext="{Binding Path=OnlineHelpViewModelStatic,Source={StaticResource Locator}}"
RestoreMinimizedLocation="True"
**Loaded="HelpWindowLoaded" Unloaded="HelpWindowUnloaded"** >
Here's an alternative approach that doesn't require a static property to set and update in each of your window:
public static bool IsWindowInstantiated<T>() where T : Window
{
var windows = Application.Current.Windows.Cast<Window>();
var any = windows.Any(s => s is T);
return any;
}
Usage:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (IsWindowInstantiated<SettingsWindow>())
return;
var window = new SettingsWindow();
window.Show();
}
How about using a Singleton?
public class MyWindow : Window {
private static MyWindow instance;
public static MyWindow Instance
{
get
{
if (instance == null)
{
instance = new MyWindow();
}
return instance;
}
}
}
Then just use
MyWindow.Instance.Show() and MyWindow.Instance.Hide()
I found this because I am trying to ensure my users do not open multiple instances of an rtsp stream window. I like Aybe's answer, it works well and is easy to understand.
I have built on it a bit as I wanted to bring the window into focus if it is open.
Here is my code:
public static void OpenWindow<T>() where T: Window
{
var windows = System.Windows.Application.Current.Windows.Cast<Window>();
var any = windows.Any(s => s is T);
if (any)
{
var win = windows.Where(s => s is T).ToList()[0];
if (win.WindowState == WindowState.Minimized)
win.WindowState = WindowState.Normal;
win.Focus();
}
else
{
var win = (Window)Activator.CreateInstance(typeof(T));
win.Show();
}
}
I am also quite new to C# and WPF so I am sure this can be improved even more.
Call it using
OpenWindow<SettingsWindow>();
public static void ShowWindow<T>() where T : Window, new()
{
var existingWindow = Application.Current.Windows.OfType<T>()
.SingleOrDefault();
if (existingWindow == null)
{
new T().Show();
return;
}
existingWindow.WindowState = WindowState.Normal;
existingWindow.Activate();
}
Usage:
ShowWindow<AboutWindow>();
When windows is created then Window.IsLoaded == true. My implementation of singleton windows is:
public partial class MySingletonWindow : Window
{
private static MySingletonWindow _instance = null;
private MySingletonWindow()
{
InitializeComponent();
}
public static MySingletonWindow Show(System.Windows.Window owner = null)
{
// On First call _instance will be null, on subsequent calls _instance will not be null but IsLoaded is false if windows was closed.
if (_instance == null || !_instance.IsLoaded)
_instance = new MySingletonWindow();
_instance.Owner = owner; // Optional owner
_instance.Show(); // Display the window
_instance.Focus(); // Bring it to front
return _instance; // Return instance if user needs it
}
}
Simply show windows using this call:
MySingletonWindow.Show(ParentWindow);
OR
MySingletonWindow.Show();