ShowDialog() Sucks: Use ApplicationContext and Run Instead

Update Jan 7: Added KeyboardInterop so that TextBoxes will work.

One of the lessons learned when making Popups and NotifyIcons in PowerShell and WPF is that the WPF window must be run the the proper context.

I was tipped off to this by Denniver Reining’s PowerShell / NotifyIcon article and Johnny J’s “Doing a (C#) NotifyIcon program the right way“.

While neither of the articles were using WPF, extensive testing showed that using ApplicationContexts and Application.Run instead of ShowDialog() made WPF (and WinForms) work far better. The NotifyIcon issue took a number of days to resolve as I battled with unresponsiveness when clicking on the ContextMenu to Exit. Then it would take about 5 seconds to disappear.

For about a week, this was the story of my life:

aero_busy

And it seemed especially true after hiding the Powershell taskbar application. So I scoured the Internet and found that a lot of other people had the same issue. Just search Google for “NotifyIcon doesn’t disappear.

In addition, the mouse sometimes showed as busy when hoovering over the popup window itself and sometimes the right clicking worked only once.

notify-wait

System.Windows.Forms to the rescue (!?)

So none of the solutions I found worked, but I remembered that Denniver’s NotifyIcon script was responsive, so I went back and noticed his app ended with this important line

[void][System.Windows.Forms.Application]::Run($form1)

This information, along with Johnny J’s article about NotifyIcons helped me figure out that the following would probably work, even for WPF. Now these two lines, along with Hide-PowerShell are always included in my finalized PowerShell-based WPF scripts.

$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)

According to Microsoft, “Application.Run begins running a standard application message loop on the current thread, with an ApplicationContext, which specifies the contextual information about an application thread.”

I’ll be honest, I’m probably not the best person to explain ApplicationContexts in depth. If you’d like to learn more, check out this article titled Use the ApplicationContext Class to Fully Encapsulate Splash Screen Functionality where the author goes into detail about the ApplicationContext class.

The code below (which is also the code from my previous post) shows a fully functioning WPF GUI App that runs in an application with an ApplicationContext. It also has a few other cool techniques you may enjoy.

Icon-Overlay

Note that this code should’t be copy/pasted into the console because the PowerShell window will disappear before it can complete the paste. Be sure to download the script or paste the code below into a .ps1 file and execute. It does appear to work well in the ISE, though!

Even though I had changed my own usage of WPF forms, it didn’t occur to me to blog about it until Doug Finke mentioned on Twitter that his WPF apps are sometimes unstable and crash. When I saw the ShowDialog() in his code, I knew exactly what the problem was, and knew I had to get the word out.

As of today, one of my servers has been running its WPF/PowerShell based-monitor for almost a month!

timespan

In conclusion, you should probably never use ShowDialog() to run your primary window in PowerShell unless you’re testing it (and I should probably update my old blog posts to clarify that).

Also, I don’t totally know what I’m doing, so take this with a grain of salt ;) It’s just worked better for me than ShowDialog(). I will probably revisit this one day with a PowerShell implementation of InitializeComponent().

Chrissy is a PowerShell MVP who has worked in IT for nearly 20 years, and currently serves as a Sr. Database Engineer in Belgium. Always an avid scripter, she attended the Monad session at Microsoft’s Professional Developers Conference in Los Angeles back in 2005 and has worked and played with PowerShell ever since. Chrissy is currently pursuing an MS in Systems Engineering at Regis University and helps maintain RealCajunRecipes.com in her spare time. She holds a number of certifications, including those relating to SQL Server, SuSE Linux, SharePoint and network security. She recently became co-lead of the SQL PASS PowerShell Virtual Chapter. You can follow her on Twitter at @cl.

Posted in PowerShell, WPF
9 comments on “ShowDialog() Sucks: Use ApplicationContext and Run Instead
  1. One issue with your code is the PowerShell session doesn’t close when the window is closed. I ran your script as instructed, closed the dialog, and the PowerShell session is still running. After taking your hide window code out, I re-ran. After closing the window the script was still running.

    I’m not sure if this is just me or what, but you may want to do something with the window on closing event… I added this in which makes the application exit:
    $window.Add_Closing({[System.Windows.Forms.Application]::Exit()})

    • Chrissy LeMaire says:

      Ah, yes! This was actually stripped from another script that has a Stop-Process. Thank you for pointing that out, I’ll add it to the code!

  2. Hey Chrissy – first – very nice post!

    You could also show up the powershell console again after the application has been closed:

    # Make PowerShell Disappear
    $windowcode = ‘[DllImport(“user32.dll”)] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);’
    $asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
    $process = Get-Process -PID $pid
    $null = $asyncwindow::ShowWindowAsync($process.MainWindowHandle, 0)

    #Closing the application
    $window.Add_Closed({
    $null = $asyncwindow::ShowWindowAsync(($process).MainWindowHandle, 3)
    [System.Windows.Forms.Application]::Exit();
    })

    Greetings

  3. Doyler says:

    Ah, thanks for that David!

    I ended up commenting out the section that made PowerShell disappear, commenting out the window exit, and switching back to ShowDialog().

    That said, I definitely prefer your method more, so thanks again.

    A note to anyone else looking to copy and paste from the comments section: the comments section currently uses ‘pretty’ quote characters, so you may need to update that code from David on line 2 (it will run regardless, but you’ll get some weird errors)

  4. I think the WPF way of doing this is to use Application.Run:

    $app = [Windows.Application]::new()
    $app.Run($window)

    I’ll try doing it that way in ShowUI and see if it helps…

    • Chrissy LeMaire says:

      Ohhh, that seems to work, too, and makes more sense. Let me know! I’ll add it to my SQL monitoring tool, too.

      • Chrissy LeMaire says:

        Interesting. My SQL Monitoring tool has a winform element (notifyicon) and it’s complaining about runspaces and crashing out. I’ll have to look into this more. Looks like pure WPF may be [Windows.Application]::new() and mixtures of WPF/Winforms may be the Appcontext solution.

    • Chrissy LeMaire says:

      So far liking that a lot more. Show() and EnableModelessKeyboardInterop are no longer required :D I never did like Show().

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Migrating SQL Server?

dbatools is an awesome PowerShell module that helps you migrate entire instances with a single command.


Available from dbatools.io and github

SqlServer Needs You

SqlServer now has a dedicated engineer and Microsoft is asking for our input!


 
Upvote priorities and cmdlets now

Authors


Chrissy LeMaire


Brandon Abshire
View Brandon Abshire, MCDBA's profile on LinkedIn

Awards

Chrissy has been awarded the Microsoft MVP for her work in the PowerShell community.

Join us!

Belgian PowerShell
User Group

  SQL PASS PowerShell
Virtual User Group

 

Upvotes Needed

Help persuade Microsoft to open source SQL Server's PowerShell module, SQLPS.


 
Upvote now on Microsoft Connect