Making a UI element "blendable" (i.e. being able to edit that element in Microsoft Expression Blend) is very important if you want to enable a better workflow for the graphics designers. In complex user interfaces, editing UI elements "in place", for example items in a ListView or a ComboBox can speed up the design by factors,
In one past post and another, I talked about my quest to display test data in Expression Blend. The goal behind this is to enable the graphics designers to work on the controls' look and feel directly in Blend, and if possible directly in the element's context. Instead of working on "bits" (typically on DataTemplates stored in resources), the designer is able to see the result of his work without starting the application! I eventually found a way with the help of IdentityMine's Jonathan Russ, and I am now able to give test data to our graphics designers, allowing them to work in a very comfortable way.
In this article, the technique detailed can be applied to any control deriving from ItemsControl. That can be an ItemsControl directly, a ListView, a ListBox, a ComboBox, etc... For simplicity, I will only show the examples for a ListBox control.
You can actually already have Blend automatically generate test data for a ListBox. The way to do this is as follows (Preconditions: I created a WPF application. I created a class named "DataItem", with 3 properties "MyString", "MyInt" and "MyColor". Inside the Window1 class, I added a public property "MyCollection" of type ObservableCollection
If you check the main window containing the ListBox now, you will see that Blend generated test data, 4 random strings "Apple", "Grapefruit", ""Pear" and "Banana", as well as 4 random integers. This provides the designers with something to work on. Of course, this only applies to simple data items, and if a complex data structure must be displayed, it will be necessary to create more elaborate test data.
How does it work?
If you change to the XAML editor, you'll notice that Blend added 2 new namespaces to the Window declaration:
Also, the ListBox now has a new attached property:
If this property is set to false, or removed altogether, Blend will not create test data. Note that this data is only used in Blend. It will not be displayed in Cider (Visual Studio 2008 WPF designer).
Another way to display test data in Blend is to use an XmlDataProvider in the Window's resources. However, I don't prefer that way of doing, because binding to an XmlDataProvider requires a different syntax than binding to a CLR object or to a DependencyProperty. For instance, you need to use a XPath instead of a Path. In general, UI is bound to CLR objects rather than to XML objects, so I think it makes more sense to use CLR objects also for the design mode.
Here is however how you can create inline test data in the XAML file, and bind to it.
The disadvantages of using XmlDataProvider for test data are quite obvious in this simple example: The bindings must be modified to bind to the test data. However, since the data is created in the XAML file itself, Blend is able to display the data:
To be thorough, note that the XPath syntax also allows filtering the XML data. Also, the XML test data can be defined in an external XML file, in which case the XmlDataProvider's Source property must be used.
Since many applications use CLR objects as their datasource (and not XML data), it is great to be able to use CLR objects to display data in Blend too. However, Blend is a bit annoying with code-behind: When a Window is opened, the code in that Window is not executed by Blend. The reasons behind this are quite complex and linked to the way how Blend loads and displays the Window object. I talked about this in one of my previous blog entries.
However, Blend will instantiate the objects defined in the Window's resources when they're bound to an element in the XAML file. Playing with that feature, it is possible to define a data provider in the resources, and to check in the constructor if the Window is running in design mode or in runtime mode.
The first step is to create a data provider. This object must offer a collection to bind to. This collection will contain the data items which must be displayed. Many types of collections are supported in WPF (all IEnumerable), but typically we use ObservableCollection, because the UI will automatically be updated when items are added, removed or moved in the collection.
The data items we will display in this example offer 3 DependencyProperties:
The advantage of using the Item class is that we can use exactly the same data structure in runtime mode and in design mode. We just need to create test items if the code runs in design mode, which is the task of the DataProvider class:
In this class, notice that a property uses the DesignerProperties.GetIsInDesignMode to test if the code is run in design mode or in runtime mode. The IsRuntime property seeems unnecessary, but remember that we need to bind to properties, and bindings cannot evaluate expressions (except if you use IdentityMine's excellent EvalBinding markup extension). This is why we have two properties for this purpose.
The DataProvider's constructor will be called by the framework when the instance is needed. We'll see later how the object is instantiated and used in the XAML code. In the constructor, we check if the code is run in design mode, and if it is, we add test data to the collection! This data will be displayed by Expression Blend as we'll see in just a moment.
In XAML, we must declare a resource pointing to the DataProvider. Since this object only has a default constructor, we don't need to "pack" it in an ObjectDataProvider, it is sufficient to simply declare the object itself. If a more complex construct is needed, however, an ObjectDataProvider might be needed. For details about the ODP, check Bea Costa's excellent blog about binding.
The DataTemplate we used with the XmlDataProvider needs to be slightly changed, because we don't use the XPath syntax anymore, but the Path property instead. The XAML code becomes:
Opening that project in Blend shows the advantage of addind data in design mode. The Window displays data in the ListView. But there's more! You can actually edit the DataTemplate in place. Here is how to do this:
Select the ListView in the "Objects and Timeline" (or in the main window), right click and then select "Edit Other Templates / Edit Generated Items (ItemTemplate) / Edit Template". Notice that Blend doesn't change to another window like usual, but instead allows editing the DataTemplate directly in the window. Notice also how modifying one of the item will immediately affect the others. This is really a very nice way to edit a ListView, and makes the designers' work much easier!
To be complete, we must note that in-place editing is only possible if the DataTemplate is stored in the same XAML file as the ListView it applies to. If you move that resource to a ResourceDictionary somewhere else in the project (or in an external "Skins" DLL), you cannot edit in place any more, but instead the edited DataTemplate will be opened in a new window. It's not too bad, but if that's a problem, you can always copy the DataTemplate temporarily in the same XAML file as the ListView, edit, and then copy it back to the original place. In Blend, copy/paste of resources can be done by dragging-and-dropping resources in the Resources panel.
Another great side effect of the design mode as implemented here is the possibility to run the application in test mode. Testing the UI in special conditions can be difficult, especially if data is coming from the network. Having a network service generate test data in order to test the UI can be tricky, or even impossible. Sometimes you want to create a simple test layer and hook your UI directly to it. We will extend our example with a test mode in the DataProvider. This boolean property is set by the Test UI. In our simple example, the Test UI is placed in the same window as the ListView, and is a simple checkbox. When the checkbox is checked by the user, the DataProvider is set in test mode, and the test data is generated. To make things more interesting, we will create a lot of entries using a "for" loop. Exactly the same data is used in design mode and in test mode. However, it would be easy to create test data on the fly in test mode, to see the UI react in a dynamic way.
Running the application in test mode also allows visualizing the animations, which Blend often cannot display. Animations triggered by a change of state, for example, cannot be seen in Blend, but can be easily simulated in test mode.
Modifying the DataProvider, it now becomes:
The last thing we need to take care of is the Release version. Typically, a .NET application is developed in Debug version, and then a Release version is created, for example using Visual Studio's configuration manager. Typically in Release version, test code is not included, to avoid distributing unneeded code.
Note however that the Release version must be tested carefully, because "switching off" some code may create unwanted side effects! To remove code from the Release version, but leave it in the Debug version, we use the precompiler directive #if DEBUG, where DEBUG is a variable which is only defined in the Debug version (that's the default configuration in Visual Studio, check MSDN for details.
We will enclose our test-and-design mode code in "#if DEBUG" directives, so that it is only available in the application's debug version. Additionally, we will create a "IsDebug" property, which will return true if the application runs in Debug mode, and false otherwise. We will use this property's value to hide the test UI in release mode. To do this, we simply bind the test UI's "Visible" property to the "IsDebug" property, but we must also use an out-of-the-box BooleanToVisibilityConverter to convert a boolean value to Visibility.Visible or Visibility.Collapsed.
The UI's code becomes:
As for the DataProvider's full code, it is now:
A sample project is available for download. It's a Visual Studio 2008 project. If you want to get a Visual Studio 2005 project instead, send me an email. The project demonstrates the techniques used here.
I think that everyone will agree that having data showing up in Blend will make a designer's life much easier. Using the integrated "Create test data" feature is a help, but often it is not enough to visualize a complex UI. Using XML data is a good alternative, but only if the runtime data are also in XML form. Since the binding to an XmlDataProvider is using a different syntax than to CLR collections, you'll have to change the syntax in runtime mode, which is annoying.
Using the same objects in design mode and runtime mode is a great advantage. This allows in-place editing in Blend and in Cider, and provides a test mode almost for free, allowing to submit the UI to special conditions difficult to reproduce in real life, but which must be tested anyway. All this speaks in favor of a design as introduced in this article. I hope that it has made been clear enough, and will be happy to answer questions if you have some!
|14.09.2007||V1.0.0||First published version|