I was just having some fun with some recent blog posts…
WPF & PowerShell – Part 5 has a script for “Get-Listbox” and for “Show-Control” and Halr9000 wrote a script he called Get-PSBlogroll
I had modified the example from the WPF post to create a listbox which will “start” whatever you double click …
And I tried something like this to let me launch links from hal9000’s blogroll:
Of course, what I’d like is to see the titles, instead of the URL, but because of how Get-ListBox was written, it only accepts strings, and therefore only returns strings. I tried adding the URL as a NoteProperty on the title string, but there’s a bug in the script cmdlet implementation that strips ETS properties. So if you specify your parameter type as a [string], you loose the NoteProperty.
Of course, if they fix the bug this tip will go away, but in the meantime, you’re better off not casting the input from the PowerShell native [PsObject] type, because you loose any extended type system attributes (and if you cast XML to a string, it comes out yucky).
Even after fixing the Get-Listbox script so it outputs what it gets in without casting it, I still have to craft PsObjects with the output from Halr9000’s script, because otherwise Get-ListBox just outputs the .ToString() representation of the xml node, which is invariably useless — I could data-bind to the xml attributes, but it requires a separate syntax in the XAML — which means you’d have to have separate code for xml vs objects. So basically, you’re better off with a | Select * at the end of your Get-PsBlogRoll.
What I eventually ended up doing is rewriting Get-Listbox to take a “Properties” parameter so that I could specify which of the attributes of an object I wanted to use … that way, I can pass in the almost unmodified output from Get-PSBlogRoll into the new Get-ListBox, and run with it. In the end, it looks something like this:
Of course, because of how I wrote the DataTemplate for the properties (using a StackPanel and a loop to output multiple TextBlock objects) you could choose to show the Url as well, and all you’d have to do is change the call to Get-Listbox like this: Get-Listbox title,htmlUrl … which is pretty cool. Does anyone else have any ideas for making these even easier to compose?
A really nice trick would be if the wpf cmdlet could use Write-Output in the click handler and actually yield to allow the rest of the pipeline to process that item, but so far, I haven’t been able to find a way to do that (short of closing the WPF window, of course).
If you’ve been reading my series of articles about using WPF from PowerShell and wishing I had started with the basics about what WPF is … you’re in luck. Following my series of posts, the PowerShell team has written their own series — with a much more methodical approach. Starting with an introduction to WPF and PowerShell followed by a great overview of how to use some of the WPF controls from PowerShell and how to handle WPF events , they have now moved beyond the basics of XAML and PowerShell to show you how you can make GUI a part of your PowerShell pipeline like I did with my Select-Grid post. With their most recent post, they explain how to run WPF in a background runspace and they promise tomorrow they’ll expand on that to “make controls in the background stream data and talk to the main runspace” — these two topics are what I was going get to next, so this frees me up to play with something more interesting
….
After looking over the scripts I’ve pasted in the last few days it struck me that all of them load the UI from XAML — and for the most part, you can do pretty much whatever you need to do in WPF in pure PowerShell if you want to.
To demonstrate this, I wrote a simple Out-Grid script. But then I got carried away, and wrote the script I’ve shown in this screenshot.
The resulting script not only illustrates how to code WPF in PowerShell without XAML, it also illustrates one of the few areas where you’re much better off loading from XAML than writing the plain code — and that’s a DataTemplate. It is possible to write them in code, but believe it or not, some of their features are accessible only from XAML.
I’ve updated both the script and the screenshot to correct a bug I noticed last night after I posted this … so if you tried this and got a lot more columns than you had bargained for … here is the Select-Grid script again.
I updated it again just now to switch my Set-PsDebug statement for a Set-StrictMode statement, after reading Jeffrey Snover’s post about strict mode best practices.
I’m not going to paste the whole script in-line, but I thought I’d paste some of it here so that I could give a little explanation of what’s happening, in case you need a little help figuring it out…
Some of you may know that normally in a script like this we would put the “utility” functions inside the BEGIN block so-as to encapsulate them from your main runspace, and may be wondering why I didn’t in this case. It’s because the new CTP2 of PowerShell has this concept called a “module” ... which is somewhat like a script snapin — basically it allows you to have functions in the script which don’t end up in the main runspace, but which are still available to the other functions (or cmdlets, in this case) in the script.
I will write a separate post about how PowerShell Modules work, and what you can do with them … it’s too complicated to get into here. Suffice it to say that all I had to do to the script is add a line at the bottom: Export-ModuleMember Select-Grid and rename it to
Select-Grid.psm1 and put it in a specific location: Documents\WindowsPowerShell\Packages\Select-Grid\Select-Grid.psm1 — but of course, I couldn’t resist cleaning it up a bit as I was writing about it (if you grabbed it before v3.4, you should grab it again — look in the comment at the top).
Having done that, all you have to do is run Add-Module Select-Grid instead of dot-sourcing it, and then run it as before (see the screenshot).
There’s a few things I wanted to point out in the module’s code, so lets get to that. (more…)
After posting my last post, I started thinking that perhaps I shouldn’t really have started with something so splashy
. So I started thinking about what I could use as a proper example — not of what WPF can do, but of what you might want to use WPF for in PowerShell.
I came up with two really obvious ideas which I think are both good demonstrations of useful things to do from PowerShell, and are good examples of something that really just doesn’t work with WinForms.
I’ll write more about this in future posts, but for now, take as an ultimate example the latest post from mow, The PowerShell Guy in which he rewrites his PowerShell WMI Explorer to be WPF based — it seems to not only run faster, but be based on less code (of course, that might just be because he’s gained experience since the first version).
There are dozens of ways you can do data-bound graphs in PowerShell — including 3d — but the simplest to demonstrate (and coincidentally the easiest for me to come up with a use-case for) is a plain bar graph. I’ve written a simple graph window in XAML, and created a script Out-BarGraph which lets you send data to it … but there are, as the Genie says, some caveats, provisos, addendums, and quid pro quos.
(more…)
In my last post I wrote about how you could make a WPF Splash screen window in PowerShell, but I stated that: “if the images are remote, the WPF window has to download them, and therefore won’t work” correctly. I had played with downloading images directly by setting the Source attribute of the image to the image URL, and hadn’t been able to find a way to get the threading to work well enough to actually download the image.
Well, I figured that out, so I thought I’d go ahead and share … basically, all you have to do is call Invoke() on the window’s dispatcher. The reason that works is that WPF handles events and “work” in general in the order it needs to be done, so since the most important thing is drawing the UI it will do that first, and since your UI is based on downloading the image, it will take care of that first.
All of this code is written to target the CTP2 release of PowerShell v2 — it will not work in PowerShell v1 or even in v2 CTP1 … and it may not work in later releases, although I expect it will. Also, as with any code which uses WPF from PowerShell, these code examples require you to launch PowerShell with the -STA option.
Having said that, lets just straight to a WPF example (more…)
I mentioned in my last post that the new PowerShell 2 CTP2 release includes a STA switch which lets PowerShell run in Single Threaded Apartment mode. I also mentioned that this means we can do WPF and build UIs in XAML ... but I left it at that.
I thought I should go ahead and give you a better idea what sort of things that makes possible, but I have to admit that I haven’t had a whole lot of time to play with it — and there’s still going to be threading issues unless someone can help me figure out a way to spin out a new thread for the UI. So far everything I’ve tried to get a thread has just managed to crash PowerShell.
Luckily, the first couple of WPF tricks I could think of don’t really need threads at all, so they’ll work fine — lets just dive right in.
Don’t forget to run PowerShell with the -STA switch in order to try these demos, and before you run any of these scripts, you need to have .Net 3.0 or later installed. There are lots of ways to build WPF UIs, but perhaps the simplest thing is to build it in XAML since PowerShell supports XML natively. The first thing we have to do is add a reference to the WPF library, and then we can have some fun.
As you can see, I’ve also loaded my PoshHttp snapin so I can use Get-Web to retrieve a web page as XML and then load the image — this is mostly about convenience (to save myself the trouble of parsing html as text) but also because if the images are remote, the WPF window has to download them, and therefore won’t work unless you ShowDialog() and give the WPF window the thread. You can just use the path to any image on your hard-drive.
Microsoft has provided some very good documentation on how to package fonts with WPF applications, and I’ve been following it in several different ways in different apps I’ve written. Recently I provided a feature in Posh Console which allows you to load the startup banner from an external file: StartupBanner.xaml.
Loading the banner from an external XAML file means that users can configure their own startup banner and make it look however they like, but it broke our default banner. The startup banner we’ve been using has a logo and some text which are all defined in pure XAML and uses several fonts which we had embedded in a FontLibrary.dll resource assembly. It worked great as long as the XAML was defined in the application, but as soon as we removed it to an external file and loaded it in using the XamlReader.Load method, the fonts all went to the fallback default fonts instead of our embedded ones.
Normally this might not be a big deal, but since our logo is based on the “Q” in a Quake-like font … it looks really lame without fonts.
Apparently, when you load external XAML, it can’t use the Font Resource Library resources. At this point, I can’t seem to find anything to indicate why this is the case, but I suspect it has to do with the fact that the external, loaded XAML only has partial trust, and since the font is in an external assembly instead of embedded in the partial trust content, it can’t get to it. However, it might be simpler than that: it might just be that the base URI (Uniform Resource Identifier) for the externally loaded XAML is an actual file location rather than one of the pack://,,,application/ type URIs, so the slightly odd font path doesn’t resolve.
In any case, I found a way around it, which is of course, why I’m writing all of this down.
(more…)