开发者

WPF: Cannot reuse window after it has been closed

I am trying to keep one instance of a Window around and when needed call ShowDialog. This worked find in winforms, but in WPF I recieve this exeception:

System.InvalidOperationException: Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after a Window has closed.

Is there any way to do something like this in WPF?

MyWindow.Instance.ShowDialog();

public class MyWindow : Window
{
    private static MyWindow _instance;

    public static MyWindow Insta开发者_Python百科nce
    {
        if( _instance == null )
        {
            _instance = new Window();
        }
        return _instance();
    }
}


If I'm not wrong, you can cancel the closing event of that window and instead set visibility to hidden

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


I suppose you could do it if you changed visibility of the window rather than closing it. You'd need to do that in the Closing() event and then cancel the close. If you allow the close to happen you certainly can't reopen a closed window - from here:

If the Closing event isn't canceled, the following occurs:

...

Unmanaged resources created by the Window are disposed.

After that happens the window will never be valid again.

I don't think it's worth the effort though - it really isn't that much of a performance hit to make a new window each time and you are far less likely to introduce hard to debug bugs / memory leaks. (Plus you'd need to make sure that it did close and release it's resources when the application is shut down)


Just read that you are using ShowDialog(), this will make the window modal and simply hiding it won't return control to the parent window. I doubt it is possible to do this at all with modal windows.


Try this:

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


When we try to show the Window which is closed, we will get the following exception.

"Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after a Window has closed."

So to handle this case it would be better if we use Visibility option of the window. We need to set the visibility of the window to Hidden or Collapsed instead of closing it directly.

this.Visibility = System.Windows.Visibility.Collapsed or Hidden;

If we want to show it again, just set the visibility to Visible

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


if you cancel the close event and set visibility =hidden then you can override this issue

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


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


Here is how I handle :

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


I had somehow similar problem. So modal dialog, but in that dialog you have button "Select" which needs to switch to main form (preferably without closing modal dialog), select some area from there and then return back to modal dialog with selection information. I have tried to play a little with with modeless dialogs / show / hide and after could not find any good (easy to code) solution, coded somehow hacky approach using win32 native function calls. What I have tested - it works good with winforms and also with xaml.

The problem itself is also not necessary easy one - so user presses "Select", and then he can might forget that he was selecting something, and return back to same of different selection dialog, which can result in two or more instances of same dialog.

I'm trying to tackle this problem by using static variables (instance / parent) - if you have pure winforms or pure wpf technology, you might get parent from instance.Parent or 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

    ...

Same paradigm might have problems modeless dialog, since each selection function call chain eats stack and eventually you might get stack overflow, or you might get problems also with managing parent window state (enable / disable it).

So I think this is quite lightweight solution to a problem, even thus it looks rather complex.


It's maybe a logic that i don't understand but closing a window it's not reversible

if you want to "close" your window and re opening hit with a button you can actualy Hide it like this:

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

If your window can be closed i suggest you to handle it with the Closed event. (here's the solution i used)

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

I hope I helped someone

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜