PrintDialog can only be invoked from Application.Current.Dispatcher.CurrentThread
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
PrintDialog is not compatible with multithreaded WPF apps that create windows on threads other than the one specified by Application.Current.Dispatcher.CurrentThread.
PrintDialog.ShowDialog() copies the properties to an internal Win32PrintDialog class then calls Win32PrintDialog.ShowDialog():
https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/PrintDialog.cs
In the beginning of Win32PrintDialog.ShowDialog() you have this:
https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Printing/Win32PrintDialog.cs
```csharp
//
// Get the process main window handle
//
IntPtr owner = IntPtr.Zero;
if ((System.Windows.Application.Current != null) &&
(System.Windows.Application.Current.MainWindow != null))
{
System.Windows.Interop.WindowInteropHelper helper =
new System.Windows.Interop.WindowInteropHelper(System.Windows.Application.Current.MainWindow);
owner = helper.CriticalHandle;
}
```
The problem is that if you are not running on the same thread as Application.Current.Dispatcher.CurrentThread, then you are not allowed to even access the MainWindow property because it does a hard thread check with VerifyAccess() which throws an InvalidOperationException. Furthermore, there doesn't even seem to be clear guarantees that MainWindow itself was created on the thread.
https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Application.cs
```csharp
public Window MainWindow
{
get
{
base.VerifyAccess();
return this._mainWindow;
}
set { }
}
```
If the PrintDialog needs to force an owner window, it should use the active window for the thread not the global window saved in Application.MainWindow. Unfortunately WPF seems to offer no way to do this. Individual Dispatchers don't seem to keep track of windows. **Win32PrintDialog could use Win32's GetActiveWindow() directly because it doesn't use the Window object only the HWND value.**
To prevent PrintDialog from being unusable in the interim, the if-statement could at least be stripped out completely or patched to the following:
```csharp
if ((System.Windows.Application.Current != null) &&
System.Windows.Application.Current.CheckAccess() &&
(System.Windows.Application.Current.MainWindow != null)
System.Windows.Application.Current.MainWindow.CheckAccess())
{
System.Windows.Interop.WindowInteropHelper helper =
new System.Windows.Interop.WindowInteropHelper(System.Windows.Application.Current.MainWindow);
owner = helper.CriticalHandle;
}
```
or more cleanly:
```csharp
var appInstance = System.Windows.Application.Current;
if ((appInstance?.CheckAccess() == true) &&
(appInstance.MainWindow?.CheckAccess() == true)
{
System.Windows.Interop.WindowInteropHelper helper =
new System.Windows.Interop.WindowInteropHelper(appInstance.MainWindow);
owner = helper.CriticalHandle;
}
```
Contributor guide
Assessment
This issue has not been assessed yet.