Showing posts with label Value Converter. Show all posts
Showing posts with label Value Converter. Show all posts

Sunday, July 8, 2012

Metrocizing XAML - Part 1: Data Templates

Let's face it: gradients and glassy buttons just aren't "cool" anymore.  In a way, it sucks; these applications aren't that old (just a year or two).  But now they look dated.  And this is what makes XAML awesome.  Since XAML is "lookless" (meaning that the visual representation of the controls is separated from the underlying operation), we can rip out an old look and drop in a new one without needing to change our application code.  And if we have proper separation of the thematic parts of our application, those updates can be isolated to a single location.

As I mentioned a couple weeks ago, I went through several of my sample projects and "metrocized" them.  Since these are XAML solutions (several WPF and one Silverlight), the updates were not complicated.  Let's take a look at an "old" and a "new" screen together.  These are taken from the IEnumerable, ISaveable, IDontGetIt: Understanding .NET Interfaces samples (specifically the IEnumerable.sln).

Old Layout

New Layout

These projects are available for download in a combined solution here: Jeremy Bytes - Downloads.  The "Old.UI" project contains the old layout; "New.UI" contains the new layout.

XAML is Awesome
The best part of this whole process is that XAML completely separates the visual display from the controls themselves.  This gives us the chance to change the way our application looks without having to change the underlying code.  And as we go through this example, we'll see just that.  We didn't need to change any of the application code. (Note: There is one small change to the Value Converter code; but this was done to put some different colors into the converter.)  The rest of the updates are in the XAML itself.

One thing to note: I am not a UX designer.  I put together passable user interfaces that are pleasing and functional, but I'm not one of those UI wizards (you know who I'm talking about -- the guys that come up with incredible designs, and you smack yourself on the forehead: "That's so obviously awesome!").  I put together the bulk of the design updates in about half a day (colors and layout).  It took me a little longer to iron out some of the kinks in the Control Templates.  Once the hard part was done, implementing the changes in the different application was very easy (mostly just replacing XAML in the right places).

We'll be looking at these updates in 2 parts.  The first part (this one) will cover the updates to the ListBox -- the one with the Person objects listed.  This is primarily concerned with the Data Template used by the ListBox, the Value Converter for the color, and a few other minor updates.

The second part (next time) will cover the updates to the Buttons.  Our original application used the standard button look-and-feel.  The new application uses a custom control template.  With the control template, we control the layout, the design (such as the arrows), and also the display behavior -- although you can't see it in the screenshot, the buttons have different colors when you hover over them or click them.  This template is fairly simple (compared to how far you can go with control templates), but it has the effect that I was looking for here.  We'll walk through this sample in Part 2.

ListBox Updates
The ListBox itself stays pretty much intact.  The primary differences are the placement in the application Grid (on the right instead of the left), and the inclusion of a WrapPanel -- this gives us the ability to show multiple columns in our ListBox.  Let's compare our old and new markup to see the changes.

First the old markup (from MainWindow.xaml in Old.UI):

Now the new markup (from MainWindow.xaml in New.UI):

The primary difference is the addition of a WrapPanel.  We did this by adding tags for the ListBox.ItemsPanel and the ItemsPanelTemplate.  By using the WrapPanel, we are specifying that if we run out of space, to "wrap" the list to another column.  The WrapPanel has an Orientation property to determine whether to wrap vertically or horizontally.  "Horizonal" is the default, and so that is the direction we have here.

Normally, a ListBox would just scroll in order to accommodate any items that don't fit on the screen (either horizontally or vertically).  Because we have our wrap panel going horizontally, we need to disable to built-in horizontal scrolling of the ListBox (otherwise, it won't actually "wrap" to the next row).  This is why we added the "ScrollViewer.HorizontalScrollBarVisibility="Disabled"" attribute: to disable horizontal scrolling.

The items in the screenshots are in the same order: John Koenig, Dylan Hunt, John Crichton, Dave Lister, John Sheridan, Dante Montana, Isaac Gampu.  If we look at the new sample, we see that Dylan Hunt (the 2nd item) comes horizontally after John Koenig.  Then we "wrap" to the next line for the 3rd and 4th items).

If we wanted to wrap vertically (so the items go down the first column, then down the second column), we would simply set the WrapPanel Orientation to Vertical, and then disable the Vertical scrollbar on the ListBox.

A Note About the WrapPanel
The WrapPanel is a standard control in WPF 4 (Visual Studio 2010).  If you are using Silverlight (4 or 5), the WrapPanel is available as a separate download as part of the Silverlight Toolkit.  I used this same ListBox layout in a Silverlight 5 application, and it worked just the same as the WPF version.

The ListBox Data Template
So, the updates to the ListBox itself are not very extensive, but the items are displayed completely differently.  This is because we are using a separate Data Template to control the layout.  This is denoted in our markup by the ItemSource = {StaticResource PersonListTemplate}" attribute.

If you are not familiar with Data Templates, I would highly recommend that you take a look at Introduction to Data Templates and Value Converters in Silverlight (this works the same in WPF).  This covers the creation of the Data Template that is used in the "old" application.  We'll just be looking at the differences here.

The Old Data Template
First, let's review the old Data Template.  This is located in the Resources section of MainWindow.xaml (in Old.UI):


Just a few notes here: first we have a Border that surrounds our entire template.  The BorderBrush is databound to the StartDate property of the Person object.  This goes through a Value Converter (myDecadeBrushConverter) to turn the date into a brush.  The result is the border color of each item is determined by the decade of the StartDate property (different colors for 1970s, 1980s, 1990s, and 2000s).

The rest of the Data Template is described in the article mentioned above.  Basically, we have a collection of StackPanels and TextBlocks to layout the data in the format that we want.

Here are the results for Dylan Hunt:

The New Data Template
The new Data Template is a bit more complex.  The first thing to note is that it is no longer in the MainPage.xaml file.  The Data Template (along with all of our other resources) have been moved to App.xaml.  The App.xaml Resources section gives us a place to put resources that are available to our entire application.  In this sample, we only have one screen, so we don't get much from sharing.  But we do get a big benefit from centralizing all of our "theming".  If we want to change the look in the future, we only have this one place to look (rather than in the separate XAML files).  Managing Resources is a bigger topic with lots of options (such as creating completely separate resource dictionaries and assemblies).  If you're building larger applications or a suite of applications that all share the same "look", then you'll want to check into this further.

The first part of the new Data Template contains the Border element (from App.xaml in New.UI):

The big change here is that we are no longer binding to the BorderBrush property, we are binding to the Background property.  This gives us our different color backgrounds for each item.  The Value Converter has also been updated a bit (for the new colors and a bit of optimization).  We'll take a look at this in just a bit.

Next, since our layout is a bit more complex, we have a Grid to help us layout our controls:

This gives us 3 rows and 2 columns to work with.  Then we have the main layout of our controls:


Again, nothing too unusual here: we're just using StackPanels and TextBlocks like we did before, just with a different layout.

The Styles for the items have been broken out (also in App.xaml of New.UI):

This lets us control the Font properties and alignment separately.  If we change our minds about these settings, we can just update the Styles, and our controls will pick them up automatically.  Note here that we are using "White" text since we have a contrasting background color.

Here's the result for Dylan Hunt:

Updates to the Value Converter
We also made some updates to the DecadeBrushConverter.  This converter returns a Brush object based on the decade of a DateTime property.  Our old converter controlled the Border color; the new converter controls the Background color.

Let's look at our original converter (in Converters.cs in Old.UI):

The original Value Converter used a series of "if" statements to determine whether the DateTime value was part of a particular decade.  If so, then it returned a SolidColorBrush with an appropriate color.

The new converter has been refactored just a little bit.  Here's the new code (in Converters.cs in New.UI):

The biggest functional difference is in the colors that are returned.  Rather than being the primary colors of the old border, we have selected more "Metroid" colors for the background.

The change from the series of "if" statements to a "switch" statement was made due to cyclomatic complexity.  If you have Visual Studio 2010 Ultimate, you can calculate the Code Metrics for a project (under the "Analyze" menu).  One of the items is the Cyclomatic Complexity.  This value is like golf scores: smaller is better.  The cyclomatic complexity basically tells how many different possible code paths exist in the code.

With the series of "if" statement, the cyclomatic complexity was increased because we have 2 conditions in each "if" (the year is greater than or equal to one value and less than another value).  In our refactored code, we calculate the decade before running through the decision process (by doing integer math, when we divide by 10 we lose the last digit; when we multiply by 10 we add a "0" back on -- this gives us the decade).  Since we have the decade already, we can use a "switch" statement which only has 1 condition for each item (instead of 2).  This reduces our cyclomatic complexity even though our ultimate output is exactly the same.

As a bigger benefit, I think the second version is easier to read (but that's just a personal preference).

Changing the Look with XAML
So, we went from this:

to this:

without changing any of our application code.  All we had to do was update our XAML and our Value Converter.  That's pretty cool.

Next Time
Today, we looked at how we can update XAML Data Templates to give us a completely different look to our ListBox -- without changing the application code at all.

Next time, we'll look at how I created the control templates that are used for the buttons.  The old solution just used the default button templates, but we'll be able to see the changes from a combination of border, text, and button to a single custom-templated button that matches our Metro-ish style.

Happy Coding!

Monday, May 31, 2010

Updated Sessions

I have updated a number of my sessions for Visual Studio 2010 and/or Silverlight 4.  I will be presenting three of these sessions at the So Cal Code Camp coming up on June 26 & 27, 2010.  These are all available for download here: http://www.jeremybytes.com/demos.aspx.

Introduction to the BackgroundWorker Component with WPF
Long running processes are a user experience killer. How many times have you had an application "lock up" while trying to do some function? The BackgroundWorker component in .NET allows you to spawn those long running processes in the background and keep your WPF, Silverlight, or WinForms user interface responsive. We'll take a look at the features of the BackgroundWorker in a WPF application including running a background process, updating the progress in the UI, and cancelling the process before it has completed.

This session has been expanded to include Exception Handling and additional error checking.

Walkthrough: BackgroundWorker-WPF2010.pdf
Sample Code: BackgroundWorker-WPF2010.zip

Introduction to Data Templates and Value Converters in Silverlight
Business applications are all about data, and laying out that data is critical to creating a good user experience. Silverlight has several tools, including Data Templates and Value Converters, that make this easier for the business developer to manage. By the time we're done, you will have a good understanding of the basics of both of these valuable tools.

This session has been updated for Silverlight 4.

Walkthrough: DataTemplatesAndConverters2010.pdf
Sample Code: DataTemplatesAndConverters2010.zip

Introduction to XAML - WPF 2010 / Silverlight 4
Understanding XAML (eXtensible Application Markup Language) is a key to creating the latest .NET user experiences in WPF and Silverlight. We will introduce the basic concepts around XAML and take a look at various features such as namespaces, elements, properties, events, attached properties and some basic layout. We’ll create a simple WPF or Silverlight application that covers these fundamentals. Although you will probably end up doing most of your UI design with a drag-and-drop tool such as Expression Blend, knowing the internals gives you a leg up in making the final tweaks to ensure an excellent user experience.

This session has been updated to include both WPF and Silverlight versions.  The same core XAML features are covered in each environment.

Walkthrough (WPF): IntroToXAML-WPF2010.pdf
Sample Code (WPF): IntroToXAML-WPF2010.zip

Walkthrough (Silverlight): IntroToXAML-Silverlight4.pdf
Sample Code (Silverlight): IntroToXAML-Silverlight4.zip

As always, your feedback/questions on any of these sessions is appreciated: feedback@jeremybytes.com.

Happy Coding!

Monday, January 4, 2010

Introduction to Data Templates and Value Converters in Silverlight 3

(Editor's note: I will be presenting this topic at the SoCal Code Camp Jan 30 & 31, 2010.)

Overview
Business applications are all about data, and laying out that data is critical to creating a good user experience. Fortunately, Silverlight 3 has several tools, including Data Templates and Value Converters, that make this easier for the business application developer to manage. Today, we'll take a look at consuming a WCF service and creating the layout of the data. By the time we're done, you should have a good understanding of the basics of Data Templates and Value Converters.

Get It Here
As mentioned in a previous post, the demos are now available in PDF form here: http://www.jeremybytes.com/Demos.aspx.

Here's some direct links:
PDF Walkthough
Code Download

Happy coding!

Saturday, June 6, 2009

More Silverlight Part 1 - Data Templates & Value Converters

I've pretty much finished up my Silverlight 2 project at work, so I thought I'd share a few more useful things that I've come across. This time, we'll take a look at Data Templates (which make List Boxes extremely flexible) and Value Converters.

Setting Up the App

We're going to start by building a application similar to what we built in the last few posts: a Silverlight 2 application that gets data from a WCF service. Start by following Steps 1 - 3 from this post. I'll point out where we'll make a couple of changes.

Step 1: Create a New Silverlight Application
We'll call it "TemplatesConvertersEvents" this time. Let it create the ASP.NET hosting site as well.

Step 2: Build a WCF Service

Step 2a: The WCF Service Contract
We'll create the same PersonService, but we'll add a new field "BirthDate" to the Person class. See the Service Contract below:

(Click image to enlarge)


Step 2b: The Service Implementation
Again, this will look pretty similar. We'll just add a BirthDate value to each of the Person objects.

(Click image to enlarge)


Step 2c: WCF Service Configuration
Same as last time. Make sure to change the binding from "wsHttpBinding" to "basicHttpBinding".

Step 3: Add the WCF Service Reference to the Silverlight Application

Step 4: Update the XAML.
This is pretty similar to the previous examples, with just a few differences. Check out the code sample below for Page.xaml. Then we'll walk through it.

(Click image to enlarge)


Start by changing the Width and Height properties of the page (500 x 300). Then replace the default Grid with a Border. The Border contains a 2 column grid. We'll be concentrating on the first column here (the 2nd column will be used in Part 2). The Grid contains a ListBox.

Notice that the ListBox looks a little different. We are no longer using the "DisplayMemberPath". Instead, we have an ItemTemplate that contains a DataTemplate. The DataTemplate is simply a container that can hold whatever content you like. For now, we'll just include a TextBlock that is bound to the FirstName field. This will have the same effect as if we were to set the DisplayMemberPath. One thing to be aware of: the DisplayMemberPath and ItemTemplate are mutually exclusive; if you try to include both, you will get an error.

Finally, we have a Button that looks similar to our previous examples. As a reminder, to add the Click handler, just type "Click=" and Visual Studio will pop up an option for "New Event Handler". This will create a handler based on the control name and put the stub in the code-behind.

Next we implement the handler. You can right-click on "GetPeopleButton_Click" and choose "Navigate to Event Handler" and it will take you to appropriate spot in Page.cs. The handler implementation should look like this (again similar to what we've seen before):

(Click image to enlarge)


Another reminder: you will need to add the "using" statement for the PersonService. If you type in the code above, you can place the cursor on "PersonServiceClient", then press Ctrl+. to bring up the option to add the "using".

You should be able to build and run the application now. After you click the button, the results should look like this:

(Click image to enlarge)


Expanding the Data Template
Now we'll see the power that the ListBox has by expanding the DataTemplate a bit. The DataTemplate can only have a single child element, so if we add multiple items, we'll need to wrap them in some sort of container. We'll use a set of nested StackPanels -- some vertical and some horizontal.

(Click image to enlarge)


This is fairly straightforward XAML. Note that we have multiple TextBlocks that are databound and a little bit of formatting. Here's what we get when we run the app now.

(Click image to enlarge)


Creating a Value Converter
One thing I don't like about the output is the format of the BirthDate field. To take care of this, we'll use a value converter. A value converter is simply a class that implements the IValueConverter interface (profound, eh?). The interface consists of 2 methods: Convert and ConvertBack. An incoming value is passed in as a parameter; all you need to do is do the conversion and return the new value.

Our DateConverter isn't going to be that interesting, but it's a good place to start (then we'll look at something a little more challenging).

In the Silverlight project, add a new Class. I've called mine "Converters.cs" because I'll use the same file to hold all of my converters. Then we'll rename the class from "Converter" to "DateConverter" and add the IValueConverter interface. IValueConverter is in the System.Windows.Data namespace, so you'll need to add this to the "using" block as well.

As we've done before, we'll just right-click on "IValueConverter", choose "Implement Interface", then "Implement Interface" again. This will put the "Convert" and "ConvertBack" method stubs into our class. We'll only be doing one-way conversion (read-only data), so we will only implement "Convert". If you had a TextBox or other editable control, then you would also implement "ConvertBack" which reverses the process. Here's our code:

(Click image to enlarge)


Since the "value" parameter is of type object, we'll need to start off by casting it to the inbound type that we are expecting (DateTime in this case). Our outbound type is simply a string, so we'll call the ToString function with a formatting string that we like. Note that there are additional options (some that are especially useful for Date conversions) by using the "parameter" and "culture" parameters. Take a look at the Microsoft Help file to get more information on those.

Using a Value Converter
So, now that we have a DateConverter, how do we use it? First, we'll need to add the namespace to our xaml (see below). Again, VisualStudio is helpful here; if you type "xmlsns:local=", then you will get a list of namespaces that are in the project, including everything that appears in the project References. We'll select the project namespace "TemplatesConvertersEvents". You can use whatever alias you like for the namespace; "local" is a convention for items contained within your project.

The next step is to add our DateConverter as a Static Resource.

(Click image to enlarge)


Now all that's left is to add the Converter to the Binding declaration in our TextBlock.

(Click image to enlarge)


If you run the application now, you'll see that we have a nicely formatted mm/dd/yyyy date for the BirthDate field. Let's try something a little more complex.

An Age Converter
In addition to the BirthDate, I'd like to display the age of the Person as well. The problem is that we don't have an Age field. So, instead, we'll use the BirthDate field to calculate the age. We'll just add another class (AgeConverter) to the Converters.cs file.

(Click image to enlarge)


We won't worry about the specifics of the code too much (I'm sure that I'll get letters with better ways to calculate age). The main point is that we are taking a DateTime (BirthDate) and converting it to an Integer (Age). Well, actually, we're converting it to a string for display purposes, but you get the idea.

Using the AgeConverter is just the same as the DateConverter. Add a Static Resource (I called mine "myAgeConverter") and then use it in a Binding statement. Notice that we are still Binding to the BirthDate field; by using the AgeConverter, we control what we will actually display. Here is a portion of our expanded DataTemplate (we appended the Age portions to the section with the BirthDate).

(Click image to enlarge)


If you run the app now, you'll see both the BirthDate and the Age are included.

One More Converter
As one final step, we'll get a little more creative with our converter. So far, we've more or less just converted a value from one format to another. This time, we'll convert to a completely different type. I want to display ages that are over 30 in a green font and 30 or under in blue. We'll do this by changing the BirthDate to a Brush.

Once again, create a new class "AgeBrushConverter" in the Converter.cs file. The code looks like the "AgeConverter" code, but instead of returning the age, it uses it to create a SolidColorBrush:

(Click image to enlarge)


For the implementation, our final "Resources" section looks like this:

(Click image to enlarge)


And our final DataTemplate (notice that we are using the AgeBrushConverter for the Foreground property of the TextBlock):

(Click image to enlarge)


And our final result:

(Click image to enlarge)


Summary
We've taken a chance to look at DataTemplates and ValueConverters to add flexibility to how we display our data in ListBoxes. DataTemplates can contain complex layouts (including Grids and Images, if desired). ValueConverters also go beyond simple formatting (like Dates and Currency); they can also be used to show or hide items (think of Binding a value to the "Visible" property) or even to add text formatting to differentiate values (such as using red for negative numbers and black for positive, or possibly graying-out inactive items).

Hang on to this application. In Part 2, we'll take a look at creating a custom user control that abstracts out part of the functionality and exposes an event that we can use.