I haven't set out to make this a complete tutorial on XAML, but I thought it might be useful if I explained what the various elements do and also where you should make changes for your own purposes.
The WPF UserControl
As the UserControl (and it's DataTemplate) is probably what you are least familiar with, let me start with that.
Here's the screenshot of the code again:
1. The Grid
The first element is the Grid. This is the overall container for the UserControl. I have set its Background to Transparent simply for display purposes. If you change this to a visible color it will have the effect of adding a further outside border to the control when it is used.
The Grid is the default root element of UserControl and so is inserted for you. As it happens, the Grid isn't needed in this particular control, but I've left it in anyway. If you wanted to create a UserControl that contained more controls than just the DockPanel/ListBox combination then the Grid would become useful again.
2. The DockPanel
The DockPanel is the real container for the ListBox. By setting its Background to Blue and setting a Margin value the markup creates that light blue border around the whole thing.
Resources are a key feature of WPF. When you create a Resource, you are making a set of reusable markup. The "scope" of the Resource is controlled by where you place it. In this case the Resource will be accessible by any control that resides inside this DockPanel.
Clearly then it follows that if you chose to move the Resource further up the tree - to the Grid, the UserControl or even to the whole Application - then you widen the accessibility of the resource. And conversely if you moved it down the tree - to make it a Resource of an individual control that is contained inside the DockPanel, then you reduce its accessibility or scope.
This is a great feature of WPF! In non-technical terms a DataTemplate is a set of visual blocks put together in the layout that you want and then saved for later re-use by means of the Key name you assign to it.
In our example here, the visual blocks include the image at the left side and the textblocks at the right, plus some other stuff.
Once you have created the template, it can be applied to controls in a similar sort of way that you can use CSS with HTML.
In the graphic below I've shown a breakdown of how this DataTemplate has been put together.
The outermost element is a Border. I've given this a BorderBrush color of LightSteelBlue. Again you can change this. Rather strangely to my mind a Border is more than just a border - it's really a border and an interior, and the interior can be a different color from the border itself.
So with that in mind, you'll realise that it's possible to set the Background color also. Often this will be a solid color, although in this case I've gone one step further and created a gradient effect.
Without going too deeply into this kind of brush, you need to know that it consists of a Start point, an End point and a number of GradientStops.
The GradientStops represent points along the line (it is "linear", remember) where a new color is placed. WPF calculates how to change one color to another in an even linear fashion between these stops.
The sample here uses Hex values because I was able to pick the exact shades I wanted in Expression Blend's color picker. Alternatively you can manually type in values and keep tweaking them until you're happy - sounds painful I know, but that's what I used to do. Or the much easier approach is to type in the names of known colors.
The Start and End points decree where the gradient starts and ends - which may sound obvious but in fact is quite a sophisticated attribute. By changing these two points you can substantially alter the look of gradient and change it from the default of diagonal to horizontal or vertical or in fact any angle in between.
The default setting is (0,0) for the StartPoint - which represents the top left corner - and (1,1), the bottom right. My advice is play around with the settings and see what effects you get, until you find one you like. Or use Expression Blend, in which case the gradient world is almost unlimited and very, very easy.
c. Outer Stack Panel
Because we need to have the image and two lots of text all bundled together somehow and neatly lined up, we have to put them all in the same container. (There will be more sub-containers inside this one).
There is actually another reason why we need this StackPanel. Something you have to get used to with WPF is learning which controls will hold only one child and which will hold many. Because the Border will only hold one, and we need to stuff several separate things in there, we solve this dilemma by making a StackPanel the single child - and that StackPanel can then hold several children of its own.
The StackPanel has its Orientation property set to Horizontal, meaning that all its children will be placed side by side from left to right. The Margin property of 3 is there to put a bit of space between this StackPanel and the Border.
Image placement is straightforward enough. As it is the first child of the horizontally oriented StackPanel it will automatically be placed at the left hand edge of the panel.
The Source property however is a little more technical. As you will see when I deal with the DataContext later, this DataTemplate is (not surprisingly) bound to a data source. Because this is all being built for a specific purpose (to list Accommodation class objects in a ListBox) we obviously know in advance that the data source will have an image and we know that the Property that will hold that image is the ImagePath property.
So the markup of:
binds the ImagePath property to the Image itself.
In other words, each item in that list is an instance of the Accommodation Class and the path and file name assigned to each instance's ImagePath property will be used to ensure that the correct image appears.
e. Inner StackPanel
This is the StackPanel at the right hand side of the list item. The reason for adding in yet another StackPanel is that I have two lots of text, one above the other, but each text item has different font properties and text color.
So in order to get two separate TextBlocks to sit on one top of the other I've employed a StackPanel, this time with Vertical Orientation.
If you refer back to the graphical breakdown above, you'll see how this fits in to the overall layout.
f. Upper TextBlock
TextBlocks are WinForms Labels on Steroids! You have tremendous control over their appearance and use. As you can see, I have set several properties so that it has exactly the look I wanted.
As with the Image, this control is bound to the data source; this time it will be hooked into the Name property.
g. Lower TextBlock
Similar to its sibling above, this TextBlock is databound to the MainDescription property. It has various properties set so that more text can be shown and also to make it obviously different from the Name that is shown in the TextBlock above it.
Also note that because I know there will be more than one line of text to be displayed, the TextWrapping property is set to deal with this.
Another point to note is that I set a width property on this control. Try deleting it and then run the project and you will see why.
I hope there is enough detail above for you to see how you can create DataTemplates of your own. So the remaining markup for this UserControl is in fact the ListBox itself.
5. The ListBox
This ListBox has had a value assigned to its Name property and also it has been set to dock to the bottom of the containing DockPanel. To be honest, these are both redundant in the example used in this article and are there because the original sample was more complex: The ListBox was referred to elsewhere in the code behind (and therefore a name is needed for a reference), and there were other sub-controls contained within the DockPanel (and therefore the docked position of each was important for the correct layout).
For now just file away the thought that your UserControl can contain multiple sub-controls if that is what is needed.
ItemTemplate is crucial for this example to work. Remember back at the start I described that a DataTemplate has a Key that can be used to assign the template to an element? Well, this is where it takes place. The ItemTemplate property set here ensures that each ListItem in the ListBox is formatted using the DataTemplate named "AccnDataTemplate" that we just looked at in detail above.
Another very important element in this ListBox is the ItemsSource. Again this will fall more into place when I deal with the DataContext, but keep in mind that this property is what binds this ListBox to the Accommodation Class objects that it will display.
WPF UserControl - Code Behind
The code was quite simple:
Public Sub New()
' A List collection to hold the various accn info
Dim AllCampers As List(Of Accommodation)
Private Sub AccnListBox_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
Me.DataContext = Accommodation.GetAllCampers
A constructor to enable New instances to be created, a type safe List to hold the info we created in the Accommodation Class and a line in the Loaded event that assigns the specific Accommodation instances to the DataContext for this UserControl.
Just to mention in passing: it isn't necessary to assign the DataContext to the UserControl. There may be situations where you want to use more than one DataContext within a single UserControl, in which case you simply assign the DataContext further down the tree.
In this project, for example, you could assign it to the DockPanel and it will still run as expected. The reason it works is that each control looks for its own DataContext. So in this example, if the DockPanel did have its own DataContext, then that is what it would use. Because there isn't one, it next looks to see if the Grid has one; it doesn't either. So it moves up the tree again until it finds the DataContext assigned to the complete UserControl. Very powerful stuff when you stop and think about the possibilities.
Windows Forms Project - Form Code
I think that this too is fairly straightforward:
Public Class Form1
' Create an instance of the referenced WPF UserControl
Dim CamperList As New UserControl1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
' Place it in the ElementHost
Me.ElementHost1.Child = CamperList
The Imports statement is optional, but allows you to refer to UserControl1 in the abbreviated way.
The next main code line creates an instance of the WPF UserControl. The reason I have used this approach and not just dragged an instance from the Toolbox is that now I have a means of referring to this control in code if I should need to. If I had used the designer and the toolbox I only have code access to the ElementHost. In fact the ElementHost's properties will appear in the Properties Window, but the UserControl will not. This makes perfect sense when you think about it; the Windows Form has no real knowledge of the UserControl, it only knows about the ElementHost.
Finally in the Form's Load event this new instance of the UserControl is set as the Child of ElementHost1. ElementHost, you will remember, is the control that allows this interop between Win32 and WPF, and acts as a container for WPF controls. Thi syntax is different from the standard Windows Forms Add method which adds a new control to a container; in WPF containers have children or in some cases a single Child.
And that's pretty much all there is to it. This ListBox is very basic, as WPF controls go. It was tempting to create something much more complex and include various animations when an item is selected or realtive to mouse events, but I decided to keep it as simple as possible so that you can use the basics to build your own versions.
I hope there is enough information here to help you to do that. Feel free to post follow up questions on any aspect. If the question (and answer) involve code or markup it is far better to post the question in the vbCity Forums. The Comments boxes here don't have any kind of formatting.
If you don't get a swift answer from a forum post, please post a link to it as a comment - I get immediate notifications of all comments to the article so if I'm around I will try and check out your questions as soon as I can.