Ok so I had a little free time on my hands this weekend and was looking for a simple something to use WPF on. A few weeks back I'd been playing with an Justin Brown's Amazon book plug in for Live Writer, which is part of the Windows Live Writer Plugins on CodePlex. Back when I first looked at his plug-in which is great, I added a few small enhancements like configuration of the Amazon Service and Associate ID and a few minor changes in the way the pop up window and resulting HTML formatting worked.
The original version looks something like this:
It works but it's pretty basic and not real easy to tell much about the book information. So, why not experiment with creating a simple WPF form and create a richer display for the list, which should be easy. I don't know about easy, but what I ended up with is this:
Definitely a much more visual and more informative view of the data.
But I'd rather not admit how much time it actually took to build this simple freaking dialog <s>. Well, maybe I will...
There are a few other operational issues that stuck out at me with this 'new and improved' form.
First note that this form runs in combination with Live Writer, which is a Windows Forms application. In theory this shouldn't work - WPF forms can't easily co-exist with Windows Forms applications, although you can host WPF content using a special Windows Forms control that can be embedded into existing forms.
So why does this work here? It works merely because the form above is a dialog and sitting in a fixed message loop that apparently works properly with WPF and Windows Forms not stepping on each other. So this is really a specialty case and not adequate for common operation. Bummer - it would be nice to add some WPF functioality selectively to existing applications where needed. In order to do this requires a little work as you have to add the WPF page and referenced assemblies manually.
It works, but keep in mind that this requires .NET 3.0 installed to run and is a special case scenario that I kind of stumbled upon by accident.
So, yes it works, but there are a few other issues here that I thought I would point out. First off loading that WPF form is slow, slow, slow. It takes somewhere between 10 - 15 seconds to load that form. The WinForm pops up instantly. This is somewhat understandable I suppose given that the WPF services need to startup in the process. But if you thought WinForms take a long time to load just wait 'til you run a WPF form <s>.
Another minor issue/nit is that the actual display of the WPF form. Notice that on my system I run with ClearType off because it gives me a headache (and I'm not kidding - it hurts my eyes). So when you look at the text in Windows Writer, and compare with the text of the WPF form you can really see the difference in clarity. The WPF form ALWAYS uses anti-aliasing regardless of what the system settings are and you can see some font rendering issues especially in the bold text in the list, but even the plain text looks crappy. Experimenting with Fonts makes some difference but not much. Frankly the way the text looks is less than stellar using standard Tahoma. FWIW, turning on ClearType makes no difference on this crappy font rendering.
Not trivial
As I've mentioned on previous occasions i've been spending quite a bit of time reading and experimenting with rudimentarty concepts of WPF over the last 2 months or so, but I've really not spent any time building anything useful beyond drawing a few circles with funky fills <s> - not quite but it's not too far off the mark. WPF is a big topic and getting a handle on the object model and how the containership and eventing works is quite a head trip. To be fair I think it's is honestly quite well thought out and makes sense, but it's just not the kind of thing that (I at least) can absorb in one setting. I've been reading through several books several times now and I think I'm slowly getting the concepts into my skull. I think it's fair to say that WPF is exremely powerful in what it provides but getting started with it is anything but uhm powerful.
The biggest problem is that currently the tools are horrible. Well, if you are a developer and want to build something that resembles a typical forms based interface. The tool to use for that at the moment would be the .NET 3.0 SDK with its plug-in support for Visual Studio 2005. It works, sort of but it's incredibly slow, and the XAML editor always runs a split view that can't be turned off with the editor often preview editor and crashing quite frequently often injecting invalid XAML code into the document - that's a lot of fun.
For the above project I actually used the VS.NET Orcas install which was a bit better - the editor is more consistent and a little more responsive, but over all the experience still sucks as royally unresponsive. As you can see the UI above is pretty simple. It's basically a few simple controls and a listbox inside of a Grid container. But even for this simple form that includes a data template for the list display - the list can't be edited or even previewed visually. For most operations in Visual Studio it's actually much easier to work with the XAML directly. Other than dropping controls on the canvas in the first place is easier in most cases. Add to that WPF forms use a custom property editor that doesn't work like the standard property sheet in Visual Studio (similar but subtle differences). For example, you can't choose the grouping of the properties - so you only get the grouped display not an alphabetical list which makes finding the right property a real easter egg hunt <s>. One things that's pretty annoying is that there's no event hookup mechanism through the UI - so you get to do this in XAML and then in code assuming you know what event signature is required.
It's even worse if you want to some layered controls like a menu or toolbar where the layout semantics are quite counter intuitive and the default settings produce something that looks nothing like a standard Windows menu or toolbar.
My feeling here is that if you're thinking you can use WPF to build a form based application chances are that this won't happen easily. It's doable but man does it suck! It's certainly a lot less usable than even the WinForms designer interface which finally in Version 2.0 got to be reasonably usable. The designer story for WPF seems like 5 steps backwards. Hopefully Microsoft will address this but from remarks dropped by Microsoft folks this doesn't sound likely...
There's an alternative for UI design by using Expression Blend and the designer experience with Blend is somewhat better, but it's not really a good fit for a forms based application since Blend clearly has a focus on designer specific layout that is very graphics heavy with gradients and fills front and center above control based layout. Blend is actually quite nice - it's just that it's not a good match for forms based development. As it is I was going back and forth between the VS designer to drop controls, to set properties in Blend and then frequently re-arrange settings in the XAML in VS. A lot of Alt-Tabbing to do even the simplest things. Blend does allow for event hookups and it can work side by side with VS.NET reasonably well - changes made in either environment shows a dialog of a modified file. This isn't optimal but it's workable. Designers will probably work exclusively in Blend and then the process is a lot smoother.
Ok the above UI is very basic and laying out a few lables textboxes and labels isn't too terrible. It's in composition where things are painful. For example, the tab order is determined by the order of layout with no tools to help you reorganize the tab order. You end up having to actually move the controls around in XAML...
Building the List
The whole point of this form is the list display which gives the opportunity to use WPFs functionality to create lists with rich child content similar to the way ASP.NET lets you created nested controls. This is maybe one of the most appealing features of WPF - the ability to nest just about anything inside of other controls which makes it much easier to create rich control content.
My first pass as this was based on the original code I took from Justin's original code. The original form uses a ListView control and manually embeds the ListViewItems into the list view. So in naivitee I did the same with the WPF code, which actually was a good excercise to go through <s>. Live and learn, right?
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
this.lstItems.Items.Clear();
this.Cursor = Cursors.Wait;
try
{
if (this.txtSearchFor.Text == null)
throw new Exception("You must specify the text you wish to query for.");
// *** Set up the search object and get the results
AmazonLookup search = new AmazonLookup();
AmazonLookup.SearchCriteria searchBy = AmazonLookup.SearchCriteria.Author;
if ( this.radAuthor.IsChecked.Value )
searchBy = AmazonLookup.SearchCriteria.Author;
else if (this.radTitle.IsChecked.Value)
searchBy = AmazonLookup.SearchCriteria.Title;
results = search.SearchForBook(searchBy, this.txtSearchFor.Text);
if (results != null)
{
foreach (SearchResult result in results)
{
ListViewItem item = new ListViewItem();
item.Tag = result;
item.Margin = new Thickness(10F);
StackPanel outerPanel = new StackPanel();
outerPanel.Orientation = Orientation.Horizontal;
// *** Wrap in a sizing container
StackPanel imgCanvas = new StackPanel();
imgCanvas.Width = 100F;
Image img = new Image();
BitmapImage imgSource = new BitmapImage(new Uri(result.SmallImageUrl));
img.Source = imgSource;
imgCanvas.Children.Add(img);
outerPanel.Children.Add(imgCanvas);
// *** Now deal with the Text blocks
StackPanel innerPanel = new StackPanel();
innerPanel.Orientation = Orientation.Vertical;
TextBlock text1 = new TextBlock();
text1.Text = result.Title;
text1.TextWrapping = TextWrapping.Wrap;
innerPanel.Children.Add(text1);
text1 = new TextBlock();
text1.Text = result.Author;
innerPanel.Children.Add(text1);
outerPanel.Children.Add(innerPanel);
item.Content = outerPanel;
this.lstItems.Items.Add(item);
}
}
}
finally
{
// pointer
this.Cursor = Cursors.Arrow;
}
}
This code basically adds new ListViewItem at a time to a ListView control. One thing that was somewhat surprising is that I had to wrap just about everything into its own set of containers. So you above you have a stackpanel to horizontally stack the image and the test section, and then the image wrapped into a fixed width canvas. Then the text block is wrapped into a veritcal stack panel so that the text blocks flow together.
There's a lot of control there for sure but it gets complicated to visualize in code. This reminds me eerily of writing HTML code for tables in code <s>...
The above code works fine, but after I had this working I though WTF: I should be using templates to make this work so this layout can be described as XAML. And that's what I ended up with. However, ending up with the 15 lines or so of XAML took me quite some time <s>...
The final XAML I ended up with looks like this:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="LookupListForm.SearchForm"
Title="Amazon Book Lookup" Height="524" Width="633"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
WindowStyle="SingleBorderWindow"
FontFamily="Tahoma" FontSize="12" FontStyle="Normal" FontWeight="Normal">
<Grid Name="MasterContainer">
<Label Height="26" HorizontalAlignment="Left" Margin="15,7,0,0" Name="lblSearchFor" VerticalAlignment="Top" Width="78.9933333333333">Search for:</Label>
<TextBox Height="23" Margin="118,10,198,0" Name="txtSearchFor" VerticalAlignment="Top" />
<RadioButton Height="15.96" GroupName="SearchGroup" HorizontalAlignment="Right" Margin="0,10,73.09,0" VerticalAlignment="Top" Width="81.91" Name="radAuthor">Author</RadioButton>
<RadioButton Height="15.96" GroupName="SearchGroup" HorizontalAlignment="Right" Margin="0,10,10.09,0" Name="radTitle" VerticalAlignment="Top" Width="56.91" IsChecked="True">Title</RadioButton>
<ListBox Name="lstItems" Margin="0,40,0,40"
MouseDoubleClick="lstItems_MouseDoubleClick"
KeyDown="lstItems_KeyDown" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,5,0,5">
<Canvas Width="75" Height="75">
<Image Source="{Binding Path=SmallImageUrl}" />
</Canvas>
<StackPanel Orientation="Vertical" Margin="5,0,0,0">
<TextBlock FontWeight="Bold" Text="{Binding Path=Title}" />
<TextBlock Text="{Binding Path=Author}" />
<TextBlock FontSize="10">
<TextBlock Text="{Binding Path=Publisher}" />
(<TextBlock Text="{Binding Path=PublicationDate}" />)
</TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<CheckBox Height="23" HorizontalAlignment="Left" Margin="15,0,0,11.04" Name="chkSmallImage" VerticalAlignment="Bottom" Width="141.42">Use small image</CheckBox>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,108,11.04" Name="btnSearch" VerticalAlignment="Bottom" Width="75" Click="btnSearch_Click" IsDefault="True">_Search</Button>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,22,11.04" VerticalAlignment="Bottom" Width="75" Name="btnCancel" Content="Cancel" Click="btnCancel_Click" IsCancel="True" />
</Grid>
</Window>
I ended up exchanging the ListView for a plain ListBox and then using an ItemTemplate to handle the display, which roughly accomplishes the same thing as the code above did. 15 lines of XAML but it took quite a bit of time to get this right.
One of the things that I really dig about WPF is the databinding support which allows binding to arbitrary objects and collections and map to things like properties (on objects) or data fields (in database fields) or XPATH paths (in XML sources), which is very easy to deal with. However the syntax for bindings takes some getting used to to be sure. In the code above I'm basically binding to the list of SearchResult objects returned by the Amazon service. SearchResult is a plain old CLR object and the collection is merely a generic List<> yet the code above will happily bind the list and fields to individual properties.
With the data template in place the code gets a bit simpler:
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
this.lstItems.ItemsSource = null;
this.Cursor = Cursors.Wait;
try
{
if (this.txtSearchFor.Text == null)
throw new Exception("You must specify the text you wish to query for.");
// *** Set up the search object and get the results
AmazonLookup search = new AmazonLookup();
AmazonLookup.SearchCriteria searchBy = AmazonLookup.SearchCriteria.Author;
if ( this.radAuthor.IsChecked.Value )
searchBy = AmazonLookup.SearchCriteria.Author;
else if (this.radTitle.IsChecked.Value)
searchBy = AmazonLookup.SearchCriteria.Title;
results = search.SearchForBook(searchBy, this.txtSearchFor.Text);
// *** Simply assign to bind
this.lstItems.ItemsSource = results;
}
finally
{
this.Cursor = Cursors.Arrow;
}
}
This is obviously a bit simpler and clearly is the preferrable approach for being able to modify the UI externally and add functionality without writing any code. But this points at one of the hard parts in WPF - deciding which controls you should actually use for a given task. For example the ListView and ListBox don't have a heck of a lot of difference in functionality other than the databinding mechanism. The same thing goes for trying to figure out which type of panel to use as a container/wrapper around other controls - and so maybe (or maybe not) you can see how it's easy to waste away an hour or so just experimenting with different types of controls.
Yeah, but at the same time this sort of template based binding and representation is definitely nice and will likely be more familiar to ASP.NET developers than WinForms developers. If you look at the amount of code (both code and XAML) involved it's very small, although arriving at that small block of XAML takes some, uhm, training or at least time behind the wheel. As with ASP.NET when you start dealing with Markup it often becomes much more difficult to discover functionalty or options available. To implement this form a lot of different approaches could have been used - there's an endless variety possible here.
Overall, this was an interesting experience, but it really made me feel like Noob. And it made me think a bit about WPF as well.
Where exactly does WPF fit?
So I'm sitting on two small internal applications that I need to build. For a while I was considering using WPF for these little applications, but after going through this experience I'm pretty sure that I won't use it at this time. I had hoped to use these small apps as an opportunity for getting more familiar with WPF and trying to find the advantage of WPF. There are a lot of things to like about WPF no doubt, but for typical business applications I'm hard pressed to really find the need. This form's a good example - while sure WPF makes this list type interface a bit easier, in the amount of time I (ineffectively) spent trying to fumble my way through some of the basics I could have built an owner drawn ListView that does the same thing for a WinForms app. There are a number of subtle nice things in WPF like the ability to work with Web content directly (for example the image in the list is directly bound to a Web based URL - something that takes a bit more work in WinForms for example). So while there are some clear benefits, I don't think that WPF makes a lot of sense for more traditional LOB scenarios until Microsoft (or a third party vendor) steps up and provides better tool support as well as better control support. WPF has a decent set of base controls, but it still minimal. Examples: no date time input controls, no masked edit - all things that finally got decent enough in WinForms 2.0. Part of the reason for this lack of an abundance of controls may also be that creating controls for WPF is probably a lot more difficult than it was for WinForms. The control model is very complex and not only do you have to produce a control but it has to be themable and allow fitting into a style based environment.
There are a whole bunch of concepts that never applied to WinForms which is both good and bad. The platform is geared towards truly creative people with design based principles on their mind. Ok I can buy that - we need that too. But where are these design minded people going to spring up all of a sudden? They exist alright, but they speak a different language and they are just as expensive if not more so than developers. Who's going to pay for all this design work?
And if you're a small shop or lone developer like me where does that leave you really for stuff you build on your own? I can bushwhack my way throgh this stuff as I have been but I doubt I'll ever produce a shiny rich 'experience' application. Commercial software sold to the mass market certainly will want to take advantage of all this, but the average custom application? Can you really see that?
Anyway - I'm curious to see where all this goes, and especially in the even more downturned versions of WPF like Silverlight...
So how long did it take? I'll be vague: An entire evening, but either way you look at it - way to damn long Noobishness or no! <s>
Grab the code for this article
Other Posts you might also like