С# WPF, как применять отдельные экземпляры окон

Я хотел бы знать, что лучший способ (читайте наиболее элегантный), чтобы иметь один экземпляр данного окна для каждого приложения в WPF.

Я новичок в .NET и WPF, и то, что я придумал, выглядит довольно хромым.

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

Вещь... это выглядит как полное дерьмо. Должен быть какой-то способ достичь той же цели гораздо более элегантным способом, верно?

PS: Я часто использую событие Closed для изменения значений в других открытых окнах. Например, у меня есть SettingsWindow с кнопкой "Учетная запись". Когда я нажимаю эту кнопку, появляется сообщение AccountWindow. Когда я закрываю AcountWindow, я хочу что-то изменить в SettingsWindow (метка). Следовательно, постоянное создание окон.
Кроме того, Close - это то, с чем вам всегда приходится иметь дело, потому что кнопка X на рамке окна...

Ответ 1

Если вам действительно необходимо обеспечить соблюдение одного экземпляра окна, то статический экземпляр (какой-то вкус того, что у вас есть) с помощью метода создания factory, безусловно, является жизнеспособным вариантом, очень похожим на один экземпляр DataContext при работе с базы данных.

Вы также можете написать свой собственный класс WindowManager, хотя это кажется излишним и, по сути, будет одним и тем же (кроме методов factory будет в одном классе).

Однако, перечитывая свой пост, я задаюсь вопросом, является ли это случаем отсутствия леса для деревьев. Ваше упоминание о вашем SettingsWindow, которое, в свою очередь, вызывает AccountWindow, заставляет меня думать, что вы просто должны использовать ShowDialog(). Это открывает окно модально, то есть не может быть никакого взаимодействия с вызывающим окном (или любым другим окном в вашем приложении). Вы просто установите свойство в этом диалоговом окне, установите для параметра DialogResult значение true, когда нажата кнопка OK, и прочитайте это свойство в родительском окне.

В принципе, вы просто используете ShowDialog, как это. Я оставляю много деталей реализации, насколько это необходимо для привязки к жесткому кодированию. Эти детали не так важны, как просто увидеть, как работает ShowDialog.

Для простоты предположим, что у вас есть класс MyAppOptions, который хорошо отражает варианты вашего приложения. Для большей простоты я оставлю большинство деталей реализации, но скорее всего это будет реализовывать INotifyPropertyChanged, методы, поля и свойства и т.д.

public class MyAppOptions
{
    public MyAppOptions()
    {
    }

    public Boolean MyBooleanOption
    {
        get;
        set;
    }

    public String MyStringOption
    {
        get;
        set;
    }
}

Затем сделаем это простым и предположим, что вы хотите показать диалог "Параметры", когда вы нажимаете кнопку в каком-то окне. Кроме того, я буду предполагать, что есть переменные, которые были установлены с вашими параметрами, которые были загружены при запуске.

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

Теперь предположим, что OptionsDialog - это окно, которое вы создали в своем проекте, и у него есть CheckBox, связанный с MyBooleanOption и TextBox для MyStringOption. Он также имеет кнопку "ОК" и кнопку "Отмена". Скорее всего, код будет использовать Binding, но теперь мы будем жестко кодировать значения.

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

Это довольно простой пример в отношении деталей реализации. Поиск в Интернете для ShowDialog для получения более подробной информации. Важные ключи для запоминания:

  • ShowDialog открывает окно модально, что означает, что это единственное окно в вашем приложение, которое может взаимодействовать с.
  • Настройка DialogResult на true закроет диалог, который может быть проверен для вызывающего родителя.
  • Настройка DialogResult на false будет также закройте диалог, и в этом случае вы пропустите обновление значений в вызывающее окно.
  • Нажатие кнопки X в окне автоматически устанавливается DialogResult to false
  • Вы можете иметь общедоступные свойства в диалоговом окне, которые можно установить перед выполнением ShowDialog, и получить значения после исчезновения диалога. Он будет доступен, пока диалог по-прежнему находится в области видимости.

Ответ 2

Есть, вероятно, лучшие способы сделать это, но вот относительно простой способ... поместите статический bool в ваш класс окна, чтобы флаг, если он открыт или нет. то в событии load() установите значение true, а в событии close установите значение false. Затем в коде, который открывает окно, отметьте флаг.

вот какой-то псевдокод, чтобы дать вам идею...

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

}

Ответ 3

Ниже приведено решение о том, чтобы просмотреть окно, если оно уже открыто. В этом случае это окно справки.

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

Этот код находится в области просмотра (MVVM), связанной с окном. Он вызывается ICommand, подключенным к кнопке на окне (естественно, он показывает знак вопроса!!) При этом задействовано следующее свойство (в данном случае это Telerik RadWindow, но это может быть любой объект окна, и вы, вероятно, также можете просто сохранить дескриптор окна, но с помощью этого свойства разрешает манипулирование объектом более плавно, например HelpWin.BringToFront() as в приведенном выше примере...

    ...
    ...
    private Telerik.Windows.Controls.RadWindow **HelpWin**
    {
        get;
        set;
    }
    ...
    ...

В самом окне (окно WPF)

    ///<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;
    }

и в представлении Xaml    ...    ...

  DataContext="{Binding Path=OnlineHelpViewModelStatic,Source={StaticResource Locator}}" 
  RestoreMinimizedLocation="True" 
  **Loaded="HelpWindowLoaded" Unloaded="HelpWindowUnloaded"** >

Ответ 4

Как насчет использования Singleton?

public class MyWindow : Window {

    private static MyWindow instance;

    public static MyWindow Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new MyWindow();
            }
            return instance;
        }
    }
}

Затем просто используйте

    MyWindow.Instance.Show() and MyWindow.Instance.Hide()