If I correctly understood the advice to make a separate thread for my window, then below is an example of code that does this:
using NLog;
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Interop;
using System.Windows.Threading;
namespace WpfTest
{
public class LoginUIApplicationInstance: IDisposable
{
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
static protected readonly Logger logger = LogManager.GetLogger("WpfTest");
TestWindow? mainWindow;
Thread uiThread;
Dispatcher? uiDispatcher;
TaskCompletionSource windowCreateCompletion = new TaskCompletionSource();
public LoginUIApplicationInstance(IntPtr ownerHwnd)
{
// credential provider thread ID
var credentialProviderThreadId = Environment.CurrentManagedThreadId;
// ID of the parent window thread obtained using OnCreatingWindow
var parentWindowThreadId = GetWindowThreadProcessId(ownerHwnd, IntPtr.Zero);
uiThread = new Thread(() =>
{
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
uiDispatcher = Dispatcher.CurrentDispatcher;
mainWindow = new TestWindow();
var interopHelper = new WindowInteropHelper(mainWindow);
interopHelper.Owner = ownerHwnd;
mainWindow.Closed += (sender, e) => mainWindow.Dispatcher.InvokeShutdown();
windowCreateCompletion.SetResult();
Dispatcher.Run();
});
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.Start();
logger.Info("Credential Provider threadId: {id1}, Parent window threadId: {id2}, Current window threadId: {id3}",
credentialProviderThreadId, parentWindowThreadId, uiThread.ManagedThreadId);
}
public void Dispose()
{
windowCreateCompletion.Task.ContinueWith((t) => {
if (mainWindow != null)
uiDispatcher?.BeginInvoke(mainWindow.Close);
});
uiThread.Join();
}
public void Hide()
{
windowCreateCompletion.Task.ContinueWith((t) => {
if (mainWindow != null)
uiDispatcher?.BeginInvoke(mainWindow.Hide);
});
}
public void Show()
{
windowCreateCompletion.Task.ContinueWith((t) => {
if (mainWindow != null)
uiDispatcher?.BeginInvoke(mainWindow.Show);
});
}
}
}
The credential provider receives the handle of the parent window (via OnCreatingWindow) and creates a window in a new UI thread where it sets the parent via interopHelper.Owner = ownerHwnd. Then, when the provider needs to show or hide the window, it calls the appropriate show/hide methods.
The interesting thing is that the parent window is created on a thread different from the one in which the provider methods are executed: credentialProviderThreadId != parentWindowThreadId.