【问题标题】:C# WPF how to enforce single instances of windowsC# WPF 如何强制执行单个窗口实例
【发布时间】:2011-04-28 08:12:20
【问题描述】:

我想知道在 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 中的某些内容(标签)。因此不断创建窗口。
此外,由于窗口框架上的 X 按钮,您总是必须处理关闭...

【问题讨论】:

    标签: c# wpf singleton window


    【解决方案1】:

    创建窗口时,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();
    

    【讨论】:

      【解决方案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>();
      

      【讨论】:

        【解决方案3】:

        我发现这是因为我试图确保我的用户不会打开 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>();
        

        【讨论】:

          【解决方案4】:

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

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

          【讨论】:

          • 这是一个很好的答案,谢谢。如果窗口打开,您将如何聚焦?
          【解决方案5】:

          使用单例怎么样?

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

          【讨论】:

            【解决方案6】:

            以下扩展了上述解决方案以重新显示已打开的窗口。在这种情况下,它是一个帮助窗口。

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

            【讨论】:

              【解决方案7】:

              如果您确实需要强制执行单个窗口实例,那么带有工厂创建方法的静态实例(您所拥有的某种风格)当然是一个可行的选择,就像使用数据库时的单个 DataContext 实例一样.

              您也可以编写自己的 WindowManager 类,虽然这看起来有点过头了,而且本质上是一样的(除了 Factory 方法将在一个类中)。

              但是,重新阅读您的帖子,我想知道这是否是一个只见树木不见森林的案例。您提到您的 SettingsWindow,而后者又调用 AccountWindow,这让我认为您应该简单地使用 ShowDialog()。这会以模态方式打开一个窗口,这意味着不能与调用窗口(或应用程序中的任何其他窗口)进行交互。您只需在该对话框中设置一个属性,在按下 OK 按钮时将 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 相关的 CheckBox 和一个用于 MyStringOption 的 TextBox。它还有一个确定按钮和一个取消按钮。代码隐藏可能会使用绑定,但现在我们将对值进行硬编码。

              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 完全陌生,而且我主要编写服务器端的东西!这对我来说是一片全新的地盘:)如果你能用一个例子更新你的答案,我将不胜感激!
              【解决方案8】:

              可能有更好的方法来做到这一点,但这里有一个相对简单的方法.... 在您的窗口类上放置一个静态布尔值以标记它是否打开。然后,在 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();
              
              }
              

              【讨论】:

              • 这样比使用互斥锁的整个单例更好。确实为打开多个窗口留下了一些余地,但在基于 UI 的应用程序中发生这种情况的可能性微乎其微——我是一名服务器老兄,无法停止思考并发中所有最坏的情况:)
              猜你喜欢
              • 2010-12-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-12-05
              • 2019-06-20
              • 2020-09-19
              • 2013-03-24
              相关资源
              最近更新 更多