views:

51

answers:

2

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...

+1  A: 

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

}
Muad'Dib
Like this better than the whole singleton with mutex thing. Does leave some margin for opening more than one window but the odds of that happening in UI based applications are slim to none - I'm a server dude, can't stop thinking of all the worst case scenarios in concurrency :)
brunodecarvalho
+2  A: 

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.
Wonko the Sane
Interesting! In this case I'm not missing the forest for the trees since I'm totally new to .NET and moreover, I mostly program server-side stuff! This is a whole new turf for me :) I'd appreciate if you could update your answer with an example!
brunodecarvalho
Thanks for the example! :D
brunodecarvalho