in

vbCity Blogs

New (temp) place for vbCity Blogs

Ged Mead's Blog

August 2009 - Posts

  • More on DataBinding to a WPF ListView

    In a previous Blog, I showed how to databind an XML file to a WPF ListView. As some people are more comfortable with collections of objects than they are with XML, in this blog item I thought we could look at binding to a collection.

    One difference to note is that if you use XML data and XPath, you have the benefit of seeing all the data in the Design Pane at design time. With this collection of objects approach, the data is being created on the fly, so of course it can't be seen at design time because it doesn't exist. It isn't usually a problem, but if you have become used to seeing the data at design time, this can be disconcerting at first.

    Creating a Collection of Objects
    Constructing a class that you can use to create objects is exactly the same as with Windows Forms. Here is a simple class named DrinkProduct:  

    Public Class DrinkProduct

     

        Sub New(ByVal ProductID As String, ByVal ProductName As String, ByVal PackageType As String)

            Me.ProductID = ProductID

            Me.ProductName = ProductName

            Me.PackageType = PackageType

        End Sub

     

        Private _ProductID As String

        Public Property ProductID() As String

            Get

                Return _ProductID

            End Get

            Set(ByVal value As String)

                _ProductID = value

            End Set

        End Property

     

        Private _ProductName As String

        Public Property ProductName() As String

            Get

                Return _ProductName

            End Get

            Set(ByVal value As String)

                _ProductName = value

            End Set

        End Property

     

        Private _PackageType As String

        Public Property PackageType() As String

            Get

                Return _PackageType

            End Get

            Set(ByVal value As String)

                _PackageType = value

            End Set

        End Property

     

    End Class 

    The next step is to create a collection of DrinkProduct objects. Again, there is nothing new here: 

        Public Function StockCheck() As List(Of DrinkProduct)

            Dim CurrentProducts As New List(Of DrinkProduct)

            With CurrentProducts

                .Add(New DrinkProduct("CF1kg", "Instant Coffee", "1 Kg"))

                .Add(New DrinkProduct("CFG500g", "Ground Coffee", "500 g"))

                .Add(New DrinkProduct("Te500g", "Tea", "500 g"))

                .Add(New DrinkProduct("SMlk1lt", "Skimmed Milk", "1 Litre"))

                .Add(New DrinkProduct("HiJ300g", "HiJuice Drink Mix", "300 g"))

            End With

     

            Return CurrentProducts

        End Function 

    Where you place the above function is a matter of choice, depending on what kind of scope you want it to have within the application. I've chosen to place it in a helper module named modStockControl so that it is available application wide.

    The next step includes a small change. The link between that collection of objects and the ListView that will display them in will be a DataContext. This is very much like the DataSource object that you will be familiar with from Windows Forms. In the code-behind for the Window, insert the following code:  

        Dim CurrentProducts As New List(Of DrinkProduct)

     

        Private Sub Part2_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

            CurrentProducts = StockCheck()

            ProductsListView.DataContext = CurrentProducts

        End Sub 

    Clearly, all this does is create an empty List, then generates some data to populate the List. The final line sets the DataContext of the ListView specifically to be the that List. At the risk of confusing the issue at this early stage, it isn't necessary to bind directly to the ListView. I could have set the DataContext on the Window or the Grid, for example and it may still be accessible by the ListView. This is something I will cover in a future blog.

    Let's turn our attention to the ListView markup. A skeleton version is: 

        <ListView Name="ProductsListView"

             ItemsSource="{Binding}">

          <ListView.View>

            <GridView>

              <!-- Product ID -->

              <GridViewColumn

             Header="ProductID" >

                <GridViewColumn.CellTemplate>

                  <DataTemplate>

                  <TextBlock Text="{Binding Path=ProductID}" />

                  </DataTemplate>

                </GridViewColumn.CellTemplate>

              </GridViewColumn>

     

            <!-- Remaining two columns removed for brevity -->

     

            </GridView>

          </ListView.View>

        </ListView> 

    As you can see in the markup, the ListView is named and this name is the one we used in the code-behind to link to the DataContext. Even more crucially, the ItemsSource property is set to point to that same DataContext. Even though it only contains the word "Binding", WPF is smart enough to work out which particular binding is the appropriate one. So those two properties are the glue that enables the ListView to be populated with DrinkProduct objects.

    The remaining markup creates a View and in that View there are three GridViewColumns (although I have only shown the markup for the first one). Inside the GridViewColumn markup you see a CellTemplate and a DataTemplate, both of which are simply devices for packaging the look of the data. In this case, a basic TextBlock is used to house the text data.

    The line of markup: 

     <TextBlock Text="{Binding Path=ProductID}" /> 

    is the final piece of the Binding puzzle. The pointer to the "Binding" ensures that WPF looks back up the tree to find the source of the data that has been set. (In this case, it looks no further than the ItemsSource which in turn knows that the data will come from that collection of DrinkProduct items.)

    The Path identifies the exact field in the data source that is to be bound to this column. For this column it is the ProductID field.

    So, as you can see from the few steps we have taken above, data binding the ListView to a data source is a trivial task.

    At the moment, the GUI isn't too impressive, because I've stripped out everything but the basics:

       

    I originally intended to look at several other tweaks that you can make to the look and layout of WPF ListViews. But to keep this blog to a reasonable length (and so as not to confuse the data binding technique with other peripheral stuff) I'll cover that in my next blog.

    Posted Aug 28 2009, 03:17 PM by XTab with 2 comment(s)
    Filed under: ,
  • WPF ListView: DataBinding and Column Headers

     A recent question on VBCity came from a developer who was searching for a way of changing the background color, font and border of the Column Headers in a ListView. This has always been a particularly tough call in Windows Forms. (A DataGridView, on the other hand, does have this feature, so that might often be an easy workaround.)

    But, as usual when questions of difficult layout come up, my mind turns to WPF as a possible solution. Usually I would say 'an easy solution', but in this case honesty forces me to say that I've always found the WPF ListView to be one of its less user-friendly elements. Windows Forms certainly makes it easier to create data by hand, with the ability to add sub-items via the properties window. WPF effectively forces you to use data binding as the means of populating the ListView - although the data source can be a relatively simple collection of objects.   So I thought it might be useful to blog how to create a relatively uncomplicated ListView in WPF - one with a differently colored Column Headers,of course.

    There is quite a lot of repetition of the graphical characteristics of the various sub-elements of a WPF ListView - the Column Headers and the content of the columns themselves, for example. So, it is always worth spending the time to create Styles or Templates which can be reused. Not only does this save time in the long run, it reduces the amount of markup used to create the actual ListView itself in the Window which usually makes it a little easier to read.

    The first version isn't going to be too ambitious and will look like this:

      

    Of course I could have added lots of other graphics junk in there, but I want to try and keep things as straightforward as possible. By the time we've worked our way through the Styles, Templates and XML Data Provider, I think that will be quite enough for one session. In a later blog, I plan to expand on this version.

    The Basic ListView
    The first step is to drag an empty ListView from the Toolbox and drop it on the XAML pane. Note that I say XAML pane, not the Design pane. As in this case, it is often easier to start with an empty element rather than have to edit the default property settings that you will see in the XAML if you drop it on to the Design pane.

    In order to display the content, a WPF ListView requires the use of a View. This has the same role as the View property in the Windows Forms ListView, i.e. it controls the manner in which the data is laid out. The similarities end there, however. At the time of writing, the WPF ListView only has one choice of view - the GridView. In its basic form, this is similar to the WinForms ListView Details View, (except of course that WPF isn't limited in the way you can package and display the data).

    As you can see from the following screenshot, even if you wanted to choose a value for the View property, this is something that has to be created in XAML. Future versions of WPF may perhaps increase this choice, but WPF already gives you the freedom to use the equivalent of Large Icon, Small Icon, List, etc, if that is the look you want, so maybe not.

      

    The markup to create the View is simple: 

          <ListView >

             <ListView.View>

                 <GridView>

     

                 </GridView>

             </ListView.View>

           </ListView>

     There will be very little to see in the Design Pane yet. The next step is to add some columns. Add three columns to the GridView of the ListView:

                    <GridView>

                <GridViewColumn Width="85">

                </GridViewColumn>

                <GridViewColumn></GridViewColumn>

                <GridViewColumn></GridViewColumn>

            </GridView>

     If you check the Design pane now, you will see a fine outline of the Column Headers. There is a divider after the first column, because I have set a Width value on it. The other two columns are not visible because they currently have a zero width.

      

    Create a Reusable Style
    Each of the three Column Headers will have some properties that are set to the same values as each other. Therefore it makes sense to set those property values in one place and assign them to each Column Header. The alternative approach would be to repeat virtually the same XAML three times, which clearly isn't a sensible way forward. WPF Styles is the technique which makes this very easy to implement.

    In this simple example I am going to use a TextBlock to hold the text of the Column Header. The following markup, which is placed in the Application.xaml file (for VB) or App.xaml file (for C#) creates a Style for a TextBlock:  

        <Style x:Key="ColHeaderText" TargetType="TextBlock">

          <Setter Property="Width" Value="110" />

          <Setter Property="Height" Value="29" />

          <Setter Property="FontSize" Value="14" />

          <Setter Property="FontWeight" Value="Bold" />

          <Setter Property="Background" Value="Yellow" />

          <Setter Property="Foreground" Value="Navy" />

        </Style>

     If you're not familiar with the concept of Styles, all it does is set values on an assortment of TextBlock properties. The Style is assigned a key - in this case 'ColHeaderText'. This Style can now be applied to any TextBlock in the application.

    The markup for the ListView can now be extended to include a Header that contains a TextBlock:  

     <GridViewColumn Width="85">

                 <GridViewColumn.Header>

                  <TextBlock Text=" Product ID"

                  Style="{StaticResource ColHeaderText}"  />

                </GridViewColumn.Header>

              </GridViewColumn>

     The TextBlock created by this markup has Text content of 'Product ID' and its color, font, etc are all set by the values stored in the Style. You can see the result in the Design pane screenshot below.

     

      

    In the final version, I have added similar markup for the remaining two columns. I have left them out of the snippet above for brevity.

    Populating with Data
    As I mentioned earlier, you need to use some form of data binding to populate the ListView. In this example I am using a simple XML file. You can view this file here. The markup that creates the link between the application and the XML file is straightforward:

     

        <XmlDataProvider x:Key="ProductList" Source="Products.xml" />

     

    It uses an XMLDataProvider which I've keyed as 'ProductList' so that it can be referred to elsewhere in the project. The source data comes from the Products.xml file, which is located with the other project files in Solution Explorer.

    Now that we have a data source, the ListView's ItemsSource property can be set to point to it. The markup is a bit tortuous, but not as complex as it might first appear. Essentially, the ItemsSource property is assigned a Binding and that Binding links to the ProductsList data provider. From the ProductsList, XPath syntax is used to point directly to the Product elements in the XML file. XPath isn't always the easiest syntax to use, but in the case of our simple XML file this is relatively straightforward.  

    ItemsSource="{Binding Source={StaticResource ProductList}, XPath=Products/Product}" 

    Next, the various columns can be bound to the appropriate fields. The GridViewColumn has a DisplayMemberBinding property which , as its name implies, sets the Binding to be used for the content of the column. Once again, XPath is used for this.  

       DisplayMemberBinding="{Binding XPath=ProductID}" 

    In the above snippet, DisplayMemberPath is assigned a Binding which links back to the ProductID element of the Products.xml file. (Note: If the field in the data source is an XML Attribute, as opposed to an Element, it is necessary to add a leading @ symbol to the XPath name.)

    For this basic ListView, that's all that is needed. You can view the Application.xaml file here, and the markup for the Window here. In a future blog, I'll improve this basic version and look at some other functionality, but if you haven't previously dealt with WPF ListViews I hope this will get you started.

  • How To Create a Splash Screen with WPF

    One small but sometimes useful new feature that was bundled with Service Pack 1 (SP1) of Visual Studio 2008 is the Splash Screen feature of WPF. This enables you to create an image file which is then used as splash while the main application spins up and loads.

    Probably the main selling point of this feature is its ease of use. Here are the steps involved:

     Create or select a suitable image file

    1. Add it to the project in the Solution Explorer.
    2. Left-click on the file name in Solution Explorer to select it
    3. Set its Build Action to Splash Screen.

     I chose to use a simple image with some text, but you can be as artistic as you like

    At its simplest, that's really all there is to it. If you run the project now, you will see the splash screen appear, followed by the main startup form of the application. If you have a fast machine and a small project, the length of time the splash screen appears may be very short.

    This isn't necessarily a problem. After all, the idea is usually to reassure the user that something is happening in the background. If the boot up time is short, then maybe you don't need a splash screen at all.

    Of course, you may not know the speed of systems on which your application is going to be installed. Or you may have an opening message that you particularly want to display in any event. In that case, you need some way of controlling how long the splash screen stays on view. And as you probably guessed, this feature is also available.

    Remembering that the splash screen is shown at the very start of the application life cycle, it makes sense for the code to be placed in the Application.xaml.vb file. Specifically, in the Application_Startup event.

    There are three elements to the code required.

    • The first step is to create a variable for the required splash screen.
    • The second step is to turn off the AutoClose option of the splash screen (this being the option that you will have seen if you followed the steps first described above).
    • Finally, you use the Close method of the splash screen, but set a TimeSpan for the delay before the splash screen totally disappears.

     That expression 'totally disappears' bears a little more explanation. What actually happens is that the splash screen fades from an Opacity level of 1 (fully opaque) to a level of 0 (completely transparent) over the period set by the TimeSpan.

    Here is the code:

         Private Sub Application_Startup(ByVal sender As Object, ByVal e As System.Windows.StartupEventArgs) Handles Me.Startup

            Dim splash As New System.Windows.SplashScreen("LoadingScreen.png")

            splash.Show(False)

            splash.Close(New TimeSpan(0, 0, 6))

      

      End Sub

     The above snippet displays the splash screen for 6 seconds.

    In most cases, the splash screen works best if you just use it in those cases where you genuinely have, or expect, a delay in the startup. One problem otherwise is that you have to start fiddling with the timing and possibly the opacity of the display of the startup Window so that it synchs with the splash screen. If you don't, then the startup Window may appear (fully opaque) while the splash screen is still gently fading out. This generally doesn't look right. But for a straightforward "Don't worry, this application is working" message to users, the WPF Splash Screen is great.

    If you haven't yet installed SP1, you can access it here.

     

     

Copyright 1998-2009 vbCity.com LLC
Powered by Community Server (Non-Commercial Edition), by Telligent Systems