dotnet / dotnet/wpf

PrintDialog can only be invoked from Application.Current.Dispatcher.CurrentThread

Open
#3,194 4 comments 2 reactions 0 assignees View on GitHub
Bug
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.