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