【问题标题】:WPF: Cannot reuse window after it has been closedWPF:窗口关闭后无法重用
【发布时间】:2011-04-03 20:26:36
【问题描述】:

我试图保留一个Window 的实例,并在需要时调用ShowDialog。这在 winforms 中有效,但在 WPF 中我收到了这个异常:

System.InvalidOperationException:在窗口关闭后无法设置可见性或调用 Show、ShowDialog 或 WindowInteropHelper.EnsureHandle。

有没有办法在 WPF 中做这样的事情?

MyWindow.Instance.ShowDialog();

public class MyWindow : Window
{
    private static MyWindow _instance;

    public static MyWindow Instance
    {
        if( _instance == null )
        {
            _instance = new Window();
        }
        return _instance();
    }
}

【问题讨论】:

  • 每次都不能实例化一个新的有什么特别的原因吗?在我看来,无论如何它更安全、更好。
  • @Alex 问题的根源在于我正在使用的第三方控件。然后在加入 Prism 和 Unity 时变得更加复杂。我真的相信像 winform 时代那样的单例表单会更容易实现。在非模态对话框上尝试显示/隐藏时,性能很棒。然而,要求规定对话框必须是模态的。
  • 对话框的 Show 方法是否接受参数?我发现这个social.msdn.microsoft.com/Forums/en-US/wpf/thread/… 可能会有所帮助。
  • +1 正是我想要的。从 WinForms 到 WPF 的惊人变化
  • 在很多情况下,保持窗口周围是有用/重要的。在我的情况下,窗口包含我希望保留的已编辑文本。来自 WinForms 的奇怪行为变化。

标签: c# .net wpf singleton window


【解决方案1】:

如果您更改窗口的可见性而不是关闭它,我想您可以这样做。您需要在 Closing() 事件中执行此操作,然后取消关闭。如果您允许关闭发生,您当然不能重新打开关闭的窗口 - 来自here

如果 Closing 事件没有被取消, 发生以下情况:

...

由 Window 创建的非托管资源被释放。

在此之后,窗口将永远不再有效。

但我认为这不值得付出努力 - 每次创建一个新窗口确实不会对性能造成太大影响,而且您不太可能引入难以调试的错误/内存泄漏。 (另外,您需要确保它确实关闭并在应用程序关闭时释放它的资源)


刚刚读到您正在使用 ShowDialog(),这将使窗口模态化并且简单地隐藏它不会将控制权返回给父窗口。我怀疑使用模态窗口是否可以做到这一点。

【讨论】:

  • 实际上,一旦考虑了所有布局、初始化等的成本,创建一个新窗口是一个相当昂贵的提议。对于有些复杂的窗口,这可以显着提高性能 - 我已经尝试过了;- )。
【解决方案2】:

如果我没记错的话,您可以取消该窗口的关闭事件,而是将可见性设置为隐藏

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;
        this.Visibility = Visibility.Hidden;
    } 

【讨论】:

  • 这与 Martin Harris 的答案相同,只是添加了代码。
  • 如果有人遇到类似问题,我必须在我的窗口标签中将 Closing="Window_Closing" 添加到 XAML
【解决方案3】:

试试这个:

protected override void OnClosing(CancelEventArgs e)
{
    this.Visibility = Visibility.Hidden;
    e.Cancel = true;
}

【讨论】:

  • 这是最好的答案
【解决方案4】:

当我们试图显示关闭的窗口时,我们会得到以下异常。

“在窗口关闭后,无法设置可见性或调用 Show、ShowDialog 或 WindowInteropHelper.EnsureHandle。”

所以要处理这种情况,最好使用窗口的Visibility选项。我们需要将窗口的可见性设置为HiddenCollapsed,而不是直接关闭它。

this.Visibility = System.Windows.Visibility.Collapsed 或隐藏;

如果我们想再次显示,只需将可见性设置为可见

this.Visibility = System.Windows.Visibility.Visible;

【讨论】:

    【解决方案5】:

    如果您取消关闭事件并设置可见性=隐藏,那么您可以覆盖此问题

    Private Sub ChildWindow_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles Me.Closing
            e.Cancel = True
            Me.Visibility = Windows.Visibility.Hidden
    End Sub
    

    【讨论】:

      【解决方案6】:
      public class MyWindow : Window
      
      public MyWindow ()
          {
              InitializeComponent();            
              Closed += new System.EventHandler(MyWindow_Closed);
          }
      
      private static MyWindow _instance;
      
      public static MyWindow Instance
      {
          if( _instance == null )
          {
              _instance = new Window();
          }
          return _instance();
      }
      void MyWindow_Closed(object sender, System.EventArgs e)
          {
               _instance = null;
          }
      

      【讨论】:

        【解决方案7】:

        这是我的处理方式:

        public partial class MainWindow 
        {
            bool IsAboutWindowOpen = false;
        
            private void Help_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
            {
                if (!IsAboutWindowOpen)
                {
                    var aboutWindow = new About();
                    aboutWindow.Closed += new EventHandler(aboutWindow_Closed);
                    aboutWindow.Show();
                    IsAboutWindowOpen = true;
                }
            }
        
            void aboutWindow_Closed(object sender, EventArgs e)
            {
                IsAboutWindowOpen = false;
            }
        }
        

        【讨论】:

          【解决方案8】:

          我遇到了类似的问题。所以模态对话框,但在那个对话框中你有按钮“选择”,它需要切换到主窗体(最好不关闭模态对话框),从那里选择一些区域,然后返回到带有选择信息的模态对话框。我尝试过使用无模式对话框/显示/隐藏,但在找不到任何好的(易于编码)解决方案之后,使用 win32 本机函数调用以某种骇人听闻的方法进行编码。我已经测试过 - 它适用于 winforms 和 xaml。

          这个问题本身也不是一件容易的事 - 所以用户按下“选择”,然后他可能会忘记他正在选择某些东西,并返回到相同的不同选择对话框,这可能导致两个或多个实例同一个对话框。

          我正在尝试通过使用静态变量(实例/父级)来解决这个问题 - 如果您有纯 winform 或纯 wpf 技术,您可能会从 instance.Parent 或 instance.Owner 获取父级。

          public partial class MeasureModalDialog : Window
          {
              //  Dialog has "Select area" button, need special launch mechanism. (showDialog / SwitchParentChildWindows)
              public static MeasureModalDialog instance = null;
              public static object parent = null;
          
              static public void showDialog(object _parent)
              {
                  parent = _parent;
                  if (instance == null)
                  {
                      instance = new MeasureModalDialog();
          
                      // Parent is winforms, child is xaml, this is just glue to get correct window owner to child dialog.
                      if (parent != null && parent is System.Windows.Forms.IWin32Window)
                          new System.Windows.Interop.WindowInteropHelper(instance).Owner = (parent as System.Windows.Forms.IWin32Window).Handle;
          
                      // Enable parent window if it was disabled.
                      instance.Closed += (_sender, _e) => { instance.SwitchParentChildWindows(true); };
                      instance.ShowDialog();
          
                      instance = null;
                      parent = null;
                  }
                  else
                  {
                      // Try to switch to child dialog.
                      instance.SwitchParentChildWindows(false);
                  }
              } //showDialog
          
              public void SwitchParentChildWindows( bool bParentActive )
              {
                  View3d.SwitchParentChildWindows(bParentActive, parent, this);
              }
          
          
              public void AreaSelected( String selectedAreaInfo )
              {
                  if( selectedAreaInfo != null )     // Not cancelled
                      textAreaInfo.Text = selectedAreaInfo;
          
                  SwitchParentChildWindows(false);
              }
          
              private void buttonAreaSelect_Click(object sender, RoutedEventArgs e)
              {
                  SwitchParentChildWindows(true);
                  View3d.SelectArea(AreaSelected);
              }
          
              ...
          
          public static class View3d
          {
          
              [DllImport("user32.dll")]
              [return: MarshalAs(UnmanagedType.Bool)]
              static extern bool EnableWindow(IntPtr hWnd, bool bEnable);
          
              [DllImport("user32.dll")]
              static extern bool SetForegroundWindow(IntPtr hWnd);
          
              [DllImport("user32.dll", SetLastError = true)]
              static extern bool BringWindowToTop(IntPtr hWnd);
          
              [DllImport("user32.dll", SetLastError = true)]
              static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
          
              [DllImport("user32.dll")]
              [return: MarshalAs(UnmanagedType.Bool)]
              static extern bool IsWindowEnabled(IntPtr hWnd);
          
              /// <summary>
              /// Extracts window handle in technology independent wise.
              /// </summary>
              /// <param name="formOrWindow">form or window</param>
              /// <returns>window handle</returns>
              static public IntPtr getHandle( object formOrWindow )
              {
                  System.Windows.Window window = formOrWindow as System.Windows.Window;
                  if( window != null )
                      return new System.Windows.Interop.WindowInteropHelper(window).Handle;
          
                  System.Windows.Forms.IWin32Window form = formOrWindow as System.Windows.Forms.IWin32Window;
                  if (form != null)
                      return form.Handle;
          
                  return IntPtr.Zero;
              }
          
              /// <summary>
              /// Switches between modal sub dialog and parent form, when sub dialog does not needs to be destroyed (e.g. selecting 
              /// something from parent form)
              /// </summary>
              /// <param name="bParentActive">true to set parent form active, false - child dialog.</param>
              /// <param name="parent">parent form or window</param>
              /// <param name="dlg">sub dialog form or window</param>
              static public void SwitchParentChildWindows(bool bParentActive, object parent, object dlg)
              {
                  if( parent == null || dlg == null )
                      return;
          
                  IntPtr hParent = getHandle(parent);
                  IntPtr hDlg = getHandle(dlg);
          
                  if( !bParentActive )
                  {
                      //
                      // Prevent recursive loops which can be triggered from UI. (Main form => sub dialog => select (sub dialog hidden) => sub dialog in again.
                      // We try to end measuring here - if parent window becomes inactive - 
                      // means that we returned to dialog from where we launched measuring. Meaning nothing special needs to be done.
                      //
                      bool bEnabled = IsWindowEnabled(hParent);
                      View3d.EndMeasuring(true);   // Potentially can trigger SwitchParentChildWindows(false,...) call.
                      bool bEnabled2 = IsWindowEnabled(hParent);
          
                      if( bEnabled != bEnabled2 )
                          return;
                  }
          
                  if( bParentActive )
                  {
                      EnableWindow(hDlg, false);      // Disable so won't eat parent keyboard presses.
                      ShowWindow(hDlg, 0);  //SW_HIDE
                  }
          
                  EnableWindow(hParent, bParentActive);
          
                  if( bParentActive )
                  {
                      SetForegroundWindow(hParent);
                      BringWindowToTop(hParent);
                  } else {
                      ShowWindow(hDlg, 5 );  //SW_SHOW
                      EnableWindow(hDlg, true);
                      SetForegroundWindow(hDlg);
                  }
              } //SwitchParentChildWindows
          
              ...
          

          同样的范式可能会出现无模式对话框的问题,因为每个选择函数调用链都会占用堆栈,最终您可能会遇到堆栈溢出,或者您可能还会遇到管理父窗口状态的问题(启用/禁用它)。

          所以我认为这是一个非常轻量级的问题解决方案,即使它看起来相当复杂。

          【讨论】:

            【解决方案9】:

            这可能是一个我不明白的逻辑,但关闭一个窗口它是不可逆的

            如果你想“关闭”你的窗口并用一个按钮重新打开你实际上可以隐藏它 像这样:

             private MyWindow myWindow;
             private void OpenMyWindow_OnClick(object sender, RoutedEventArgs e)
             {
                 if (myWindow == null)
                 {
                     myWindow = new MyWindow();
                 }
            
                 if(!myWindow.IsVisible)
                 {
                     myWindow.Show();
                 }
                 else
                 {
                     myWindow.Hide();
                 }
             }
            

            如果您的窗口可以关闭,我建议您使用 Closed 事件来处理它。 (这是我使用的解决方案)

            private MyWindow myWindow;
            private void OpenMyWindow_OnClick(object sender, RoutedEventArgs e)
            {
                if (myWindow == null)
                {
                    myWindow = new MyWindow();
                    myWindow.Closed += OnMyWindowClosed;
                }
            
                if(!myWindow.IsVisible)
                {
                    myWindow.Show();
                }
                else
                {
                    myWindow.Hide();
                }
            }
            
            private void OnMyWindowClosed(object obj, EventArgs e)
            {
                myWindow = null;
            }
            
            

            我希望我帮助了某人

            【讨论】:

              猜你喜欢
              • 2012-06-28
              • 2016-01-22
              • 2017-08-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-03-14
              • 1970-01-01
              相关资源
              最近更新 更多