C# WPF如何强制窗口的单实例化

12

我想知道在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"事件来改变其他打开窗口中的值。例如,我有一个带有"账户"按钮的"设置窗口"。当我按下该按钮时,"账户窗口"弹出。当我关闭"账户窗口"时,我希望"设置窗口"中的某些内容(标签)发生变化。因此,需要不断地创建窗口。
此外,关闭是必须要处理的事情,因为窗口框架上有X按钮...
8个回答

16

可能有更好的方法,但这里有一个相对简单的方法......在您的窗口类上放置一个静态布尔标志,以标记它是否打开。然后,在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
比起使用互斥锁的单例模式,我更喜欢这种方法。虽然可能会有打开多个窗口的余地,但在基于 UI 的应用程序中,发生这种情况的几率微乎其微 - 我是一个服务器方面的人,无法停止考虑并发情况的最坏情况 :) - biasedbit

8
如果您真的需要强制窗口的单个实例,那么具有工厂创建方法的静态实例(类似于使用数据库时的单个DataContext实例)肯定是可行的选项之一,就像单个DataContext实例一样。
您也可以编写自己的WindowManager类,尽管这似乎有点过度,而且本质上与此相同(除了Factory方法将在一个类中)。
然而,重新阅读您的帖子,我想知道是否缺少了树林。 您提到的SettingsWindow,其又调用AccountWindow,使我想到您应该只是使用ShowDialog()。 这会以模式打开窗口,这意味着不能与调用窗口(或应用程序中的任何其他窗口)进行交互。 您只需在该对话框中设置属性,在按下“确定”按钮时将DialogResult设置为true,并在父窗口中读取该属性即可。
基本上,您只需像这样使用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是您项目中创建的一个窗口,它有一个与MyBooleanOption相关的复选框和一个用于MyStringOption的文本框。它还有一个确定按钮和一个取消按钮。代码后端可能会使用绑定,但现在我们将硬编码这些值。

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 设置为 false。
  • 您可以在对话框窗口中拥有公共属性,在执行 ShowDialog 前可以设置它们,并且可以在对话框消失后获取值。它在对话框仍然在作用域内时可用。

有趣!这种情况下,我不会因为完全是.NET的新手而无从下手,而且我大多数时间都在编写服务器端的程序!这对我来说是全新的领域 :) 如果你能提供一个例子来更新你的答案,我将不胜感激! - biasedbit

2
以下是一种替代方法,不需要在每个窗口中设置和更新静态属性:

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

使用方法:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    if (IsWindowInstantiated<SettingsWindow>())
        return;

    var window = new SettingsWindow();

    window.Show();
}

这是一个很好的答案,谢谢。如果窗口已经打开,你会如何将焦点放在它上面? - user6439507

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

使用方法:

ShowWindow<AboutWindow>();

1
以下是对上述解决方案的扩展,以重新显示窗口(如果它已经打开)。在这种情况下,它是一个帮助窗口。
    ///<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()...)

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

0

使用单例模式怎么样?

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

0

当窗口被创建时,Window.IsLoaded == true。我的单例窗口实现如下:

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

使用此调用简单地显示窗口:

MySingletonWindow.Show(ParentWindow);

或者

MySingletonWindow.Show();

0
我找到这个是因为我正在尝试确保我的用户不会打开多个rtsp流窗口实例。我喜欢 Aybe 的答案,它表现良好并且易于理解。
我稍微进行了一些改进,因为我想在窗口打开时把它带到焦点。
这是我的代码:
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();
            }
        }

我对C#和WPF也很新,所以我相信这可以进一步改进。

使用以下方式进行调用

OpenWindow<SettingsWindow>();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接