Skip to content

syntax-tm/WindowsHangBug

Repository files navigation

WindowsCrashDemo

Background

This project is an extremely stripped down version of a WPF applicaiton that still had some WinForms components not yet converted to WPF but still needed to still be supported. The application used the DevExpress Dock Layout to manage the panels which contained either a UserControl or Form. Following the MVVM architectural pattern, there was a View and ViewModel for each type of hosted content. That View and ViewModel functioned as a wrapper for the child control so that it could be docked, undocked, resized, maximized, etc. The Form would also use the Text for the panel's header as well as the Icon and dispose of unmanaged resources with IDisposable, and more.

The Windwos Forms interopability was done using the FormsHost Control. FormsHost is a custom UserControl that contains a WindowsFormsHost element to host WinForms Controls.

On startup, the application creates the MainWindow. MainWindow is a Window containing the DockLayoutManager and its ViewModel, MainWindowViewModel, contains the collection of document panels and groups bound using the DockLayoutManager's ItemsSource.

Architecture

classDiagram
    App --> MainWindow
    MainWindow --> PanelWorkspaceViewModel : DockLayoutManager ItemsSource
    PanelWorkspaceViewModel .. WindowViewModel : Inherits
    PanelWorkspaceViewModel .. FormViewModel : Inherits
    class App {
        WPF Application
    }
    class MainWindow {
        <<IWin32Window>>
        +ObservableCollection~WorkspaceViewModel~ Workspaces
        +DockLayoutManager MainContainer
    }
    class PanelWorkspaceViewModel {        
        +ViewType ViewType
        +string Title
        +bool IsPinned
        +bool IsOpen
        +bool IsActive
        +bool IsClosed
        +Close()
        +CanCloseResult CanClose()
    }
    class WindowViewModel {
        <<PanelWorkspaceViewModel>>
        +UserControl ControlContext
        +object DataContext
    }
    class FormViewModel {
        <<PanelWorkspaceViewModel>>
        +FormsHost FormContext
        +Form Form
    }
Loading

The Bug

MainWindow implements the IWin32Window interface to expose the HWND Handle. The Handle is needed for Windows API calls, the HwndSource class has an AddHook method used to receive window messages, and various other things. Only the IWin32Window implementation was necessary for this demo.

This issue is with the WindowInteropHelper and its Owner property. Instead of setting the Owner to the HWND of the Application's MainWindow Handle, this code sets it to the newly created Form's Handle. Basically, the Owner is set to the child.

WindowInteropHelper

An example scenario is if you need to host a WPF dialog box in a Win32 application. Initialize the WindowInteropHelper with a WPF window object for the dialog box. You can then get the WPF window's handle (HWND) from the Handle property and specify the owner for the WPF window with the Owner property. The following code example shows how to use WindowInteropHelper when hosting a WPF dialog box in a Win32 application.

WindowInteropHelper wih = new WindowInteropHelper(myDialog);
wih.Owner = ownerHwnd;
myDialog.ShowDialog();

Another scenario supported by this class is to obtain a HwndSource object from a WPF Window object. The HwndSource enables direct processing of Win32 messages through the AddHook method. By using HwndSource and AddHook in place of a Window you can still handle messages that have no equivalent or handling in WPF. Create a WindowInteropHelper with the Window source, then call Handle on the WindowInteropHelper to get from HWND to HwndSource. (Source)

Demo

public void OpenFormWorkspace(Type formType, ViewType viewType, bool setInvalidOwner = false)
{
    try
    {
        var closeCommand = new DelegateCommand(() => CloseWorkspace(viewType));
        var workspace = new FormViewModel(viewType, formType, closeCommand);

        if (setInvalidOwner)
        {
            // ...

            var winForm = workspace.WinForm;

            //  this should NOT be allowed to happen, the WinForms control is inside of a FormsIntegrationHost
            //  which is inside of the WPF Window. The Window is the parent, but the Window's Owner can be set to
            //  a child WinForms control's Handle which crashes Windows
            var windowInteropHelper = new WindowInteropHelper(MainWindow);
            windowInteropHelper.Owner = winForm.Handle;
        }

        Workspaces.Add(workspace);

        SelectedWorkspace = workspace;
    }
    catch (Exception ex)
    {
        // ...
    }
}

The relevant part here is the new WindowInteropHelper created using the application's MainWindow and then the Owner set to the winForm.Handle.

var windowInteropHelper = new WindowInteropHelper(MainWindow);
windowInteropHelper.Owner = winForm.Handle;

Adding and selecting the newly created workspace (FormViewModel) does not crash Windows. The crash comes from when we create a modal (dialog) inside that winForm.

The MainWindowViewModel has two ICommand properties for creating either a normal frmChildForm or an invalid frmChildForm which uses the WindowInteropHelper above.

public MainWindowViewModel()
{
    // ...
    OpenFormCommand = new DelegateCommand(() => OpenChildForm());
    OpenFormErrorCommand = new DelegateCommand(() => OpenChildForm(true));
    // ...
}

private void OpenChildForm(bool setParent = false)
{
    var type = typeof(frmChildForm);
    OpenFormWorkspace(type, ViewType.ChildForm, setParent);
}

Selecting the Error Form menu item will execute the ICommand set in the binding (OpenFormErrorCommand). After the new frmChildForm is created you can use either the ShowDialog Form or Show Form button. Both of these will create a new frmDialogTest but ShowDialog Form will call ShowDialog and Show Form will instead call Show. Both methods will pass the Parent property of the Form (inherited from Control).

Private Sub btnOpenForm_Click(sender As Object, e As EventArgs) Handles btnOpenForm.Click
    Dim frm As New frmDialogTest
    frm.Show(Parent)
End Sub

Private Sub btnOpenModal_Click(sender As Object, e As EventArgs) Handles btnOpenModal.Click
    Dim frm As New frmDialogTest
    frm.ShowDialog(Parent)
End Sub

References

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published