XTab's Blog

Ged Mead's Blog at vbCity

This blog hosted by:
http://blogs.vbcity.com      
  Home :: Syndication  :: Login

AugSeptember 2007Oct
SMTWTFS
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

Archives

Topics

Ramblings

VB.NET

Thursday, September 20, 2007 #

  Moving Beyond Simple Strings

Introduction

  In the previous item on Listboxes I looked at some ways of tweaking a WPF ListBox which contained only strings. As the range of what can be included in a ListBox is far greater than simple strings, I want now to move on to more complex displays.

 In WPF it is very easy to create listboxes that contain a variety of content types. For example you can list a collection of images one below the other:

 Although it's not immediately obvious from the screenshot, the second item - the Motor Caravan - is the currently selected item. This is signified by the blue borders to the left and right of the image. There are ways we can improve on this.

 A rather fragile version of the XAML code for this ListBox is as follows :

Code Copy
<ListBox Name="lstBox1" Margin="12" Width="99">
<Image Source="C:\Tent4A.jpg" Margin="2" />
<
Image Source="C:\Motorhome1.jpg" Margin="2" />
<
Image Source="C:\caravan.jpg" Margin="2" />
ListBox>


When is a ListBox Item not a ListBoxItem?
 

 You may remember that in my earlier ListBox blog example, I placed each of the strings that I wanted to list inside a separate ListBoxItem. The main reason for doing this was to ensure that each string appeared on a new line. If I had simply listed the strings one after the other (i.e. without enclosing them inside the ListBoxItem tags) then the result would have been a ListBox with one single line which would read: "First Item Second Item Third Item ..." etc. That is, the ListBox would in fact contain only one item.

 Another reason for wrapping the strings inside the ListBoxItem tags is to ensure that each item acts in the way we would expect an item in a ListBox to behave. If you don't do this, then the item will behave according to its actual type. So, to come back to our example above where we use a list of images, those images will behave exactly as you would expect images to behave and not as you may wish ListBoxItems to behave. Often it will not be an important distinction, but it's useful to understand that this is the situation.

  So the simple answer to the conundrum above "When is a ListBox Item not a ListBoxItem?" is:- "When you don't specifically make an item in a ListBox a ListBoxItem type.


Using Resources to Store Images

 As I mentioned above, the XAML code to create the ListBox isn't very robust as it uses hard coded paths for the three images. In reality you would probably do one of two things. Either place the images in your project as Resources (see my blog items here and here for more info on how to do this). Alternatively, very often you will bind some kind of collection of images (or images and text) to the ListBox. In this article I will only deal with the first alternative, but plan to cover the data binding aspects in a later one.

Combining Images and Text in a ListBox

  Let's take a look at a basic ListBox layout that combines an image with text. Here is one example:

 

 In this example the third item is currently selected and highlighted in blue.

 What you see in the screenshot is a ListBox which contains three ListBoxItems. Each ListBoxItem includes both an image and some text. Now, you may well be tempted to try and code it like so:

Code Copy
<ListBoxItem>
<Image Source="Images/Tent4A.jpg" Margin="2" />
<
TextBlock >Tent - Grassed areas onlyTextBlock>
ListBoxItem>

  But if you do that, then all you would get for your effort is a syntax error telling you that the Content property is declared twice. The ListBoxItem can only contain one child. This is a very common theme in XAML and you soon learn that in order to display multi layered elements all you have to do is to place sub-elements and sub-sub-elements and so on inside new container controls - the Russian Doll approach.

 To create this first example, all I have done is create a StackPanel - which is obviously a container control by its very nature - and placed the Image as well as the text inside the StackPanel. (If you want an introduction to the basics of the StackPanel, check out my earlier blog item here.)

 The StackPanel won't take the text as a plain string in the way that a ListBoxItem will. So the way round this is to place the text inside a TextBlock. (A TextBlock is a control whose sole purpose in life is to display text in a format of your choice - a label with attitude!)

 So here is the XAML code so far:

Code Copy
<ListBox Name="lstBox1" Margin="12"
   HorizontalContentAlignment="Center"  >
<ListBoxItem>
<StackPanel >
<Image Source="Images/Tent4A.jpg" Margin="2" />
<
TextBlock >Tent - Grassed areas onlyTextBlock>
StackPanel>
ListBoxItem>
<ListBoxItem>
<StackPanel >
<Image Source="Images/Motorhome1.jpg" Margin="2" />
<
TextBlock >Motor home - Hard standing onlyTextBlock>
StackPanel>
ListBoxItem>
<ListBoxItem>
<
StackPanel>
<Image Source="Images/Caravan.jpg" Margin="2" />
<
TextBlock >Caravan - Grassed areas or Hard standing
TextBlock>
StackPanel>
ListBoxItem>
ListBox>

 For all that it may be a bit verbose, I don't think anyone would say that this visual tree is particularly hard to follow - in the sense that each step of the creation procedure of each element is laid out and clear to see. I've previously touched on ways of reducing the bloat, such as using Styles. In a future blog I will cover Control Templates, which is another immensely powerful route to reducing repetition, among other benefits.

  If you click with the mouse on any part of a ListBoxItem or its contents then you are able to use that event in whatever way you choose by means of the code behind. The VB.NET code will work in just the same way as you are familiar with in Windows Forms. There are several differences in the syntax, but the general thrust is the same. So in the VB.NET partial class behind your window you might have something like:

Code Copy
Private Sub lstBox1_SelectionChanged(ByVal sender As Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) Handles lstBox1.SelectionChanged

       Console.WriteLine("You selected item # " &   lstBox1.SelectedIndex.ToString)

    End Sub

 Or maybe something slightly more realistic, such as displaying the locations of grass pitches so that one can be allocated:-

Code Copy
  Private Sub lstBox1_SelectionChanged(ByVal sender As Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) Handles lstBox1.SelectionChanged

  Select Case lstBox1.SelectedIndex
       Case 0
          Dim gpl As New GrassPitchesLayout
          gpl.ShowDialog()
  End Select

  End Sub

 One important feature to note here is that in WPF it is possible to identify and isolate exactly which sub-element was selected. So in the XAML example above, the clicking of any part of the ListBoxItem will fire the SelectionChanged event, exactly as you would expect. However, if for some reason you also wanted to know if the user had clicked on the tent image within that ListBoxItem (i.e not on the text or any of the white space areas), then this information is very easily obtainable, so long as you assign a Name to the Image element.

 In the following example, the Image in the first ListBoxItem is assigned the Name "Tent" :-

Code Copy
<ListBox Name="lstBox1" Margin="12"
   HorizontalContentAlignment="Center"  >
<ListBoxItem>
<StackPanel >
  <Image Source="Images/Tent4A.jpg" Margin="2" Name="Tent"/>
  <TextBlock >Tent - Grassed areas onlyTextBlock>
StackPanel>
ListBoxItem>

ListBox>

 That Image is now accessible in the code behind by its name. The following code snippet uses the MouseDown event of the Image to display some information:

Code Copy
Private Sub Tent_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Tent.MouseDown

    Console.WriteLine("You clicked on the tent image")

End
Sub

 At this point you might be wondering if the Image MouseDown event somehow cancels out the ListBox SelectionChanged event. The answer is that it doesn't. Both events fire; my experiments show that the Image MouseDown fires first.

 If you're really inquisitive, you might also wonder what happens if the user traverses the ListBox using the keyboard and not the mouse. Somewhat confusingly (to me anyway), the MouseDown event of the Image fires whenever that first item is selected via the keyboard arrow keys.

 Anyway, this ability to access elements with the mouse is something that you may be glad you knew about at some point in your future coding.

 

Other Layout Combinations

  It is probably more traditional in cases where you have an image and some text to see the image at the left and the text placed to the right. So let's fix that now:

Code Copy
<ListBox Name="lstBox1" Margin="12"
   HorizontalContentAlignment="Left"  >
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="Images/Tent4A.jpg" Margin="2" />
<TextBlock >Tent - Grassed areas onlyTextBlock>
StackPanel>
ListBoxItem>

ListBox>

 You will see from the code above that I changed the Orientation of the StackPanels to Horizontal. The default previously was Vertical.

 If you're particularly eagle-eyed you may have noticed that I also changed the HorizontalContentAlignment of the ListBox itself from Center to Left. This forces the three items to be neatly lined up at the left side of the ListBox.

 The result of the above code is:

 But, you know, we can do much better than that. What's the point of having this sophisticated layout tool if we don't use it to move from the bog standard plain text to something a little better?

 An improved layout for the ListBox

 Or:-

 

 OK, so they won't win any Designer of the Year prizes, but I think you can see that there is huge potential for creating individual user interfaces - and much more easily than you could hope to do with standard Windows Forms.

 The XAML code for the above ListBox is:-

Code Copy
<ListBox Name="lstBox1" Margin="12"
   HorizontalContentAlignment="Left"
   FontFamily="Verdana" >
<ListBoxItem Margin="0,3,0,3">
<
Border BorderBrush="DeepSkyBlue" BorderThickness="2" CornerRadius="2" Width="258">
<StackPanel Orientation="Horizontal" Margin="3">
<
Image Source="Images/Tent4A.jpg" Margin="2" />
<StackPanel>
<TextBlock Margin="5" Padding="2" FontSize="13"
FontWeight="Bold" Foreground="Navy"> Tent
TextBlock>
<
TextBlock Margin="8" Padding="8,2,2,2" FontSize="11"
FontWeight="Normal" Foreground="Green">Grassed areas only
TextBlock>
StackPanel>
StackPanel>
Border>
ListBoxItem>
ListBox>

 (And as I will show you in a later article, you can abstract all this UI markup away into a reusable DataTemplate and store that too as a Resource)

If you want to see the ListBox pushed to the limits, then check out a couple of demos from Josh Smith and Bea Costa which go way beyond the standard image+text layout that I have covered so far.

 First there is Josh's Sliding ListBox:

 

 More details on the code here.

 And taking the ListBox to extremes (literally) is Bea Costa's Planet ListBox:


Yep - that really is a ListBox and clicking on a planet makes that planet the selected item! More details can be found here.

 I plan to write a future blog item that takes the caravan example a step further and uses data binding to automatically populate the listbox items. In the meantime I hope you've found this description of the basics to be useful.

posted @ 4:09 PM | Feedback (1)