Visual Studio LightSwitch provides a set of standard UI controls for displaying application data. These controls include common Windows UI elements like text boxes, as well as controls tailored for the data entry and editing tasks (like address viewer or data grid). Many applications can be built with only standard LightSwitch controls, but there are applications that require more advanced visualizations (e.g. charts or maps), or just have specific requirements that are not covered by standard control set. One possibility to address these requirements is to use a non-standard LightSwitch control. LightSwitch provides extensibility points for the 3rd party control vendors to extend the set of control available inside the IDE. From application development perspective they behave just like built-in controls—they show up in the same places in the IDE and their general working is no different from standard controls, so using them is very easy. The downside is that if a 3rd party control that satisfies the given scenario is not available, implementing one might be a task too difficult or labor-intensive for typical LightSwitch user. Fortunately there is an easier way: LightSwitch applications can use Silverlight “custom” controls directly, and re-using or authoring custom controls is much easier.
Custom controls defined
LightSwitch client application uses Silverlight framework as the foundation to build upon. LightSwitch controls are at the core just Silverlight controls, but they are enhanced with information and functionality that makes it possible for LightSwitch runtime to relieve the developer from many routine tasks associated with UI data binding, UI layout and command enablement. A custom control is a regular Silverlight control that is part of LightSwitch application UI (a screen). The main difference between LightSwitch controls and custom controls is that a custom control does not have LightSwitch-specific information associated with it. Therefore LightSwitch treats it as a “black box” and it is up to the developer to specify what data the control should display (data-bind the control to the screen) and to handle any events the control might raise. There are two possibilities here:
- The control might be built for a particular screen or entity (with intimate knowledge of the members of some screen or entity). For example, if we are building a custom control to display Customer data in a visually-rich way, we might explicitly bind parts of the control to Customer properties such as Name and Address. The advantage of this approach is that using this control will require little or no code, but obviously it cannot be used to display any other piece of data other than a Customer, so we lose some flexibility and reuse opportunities.
- The control might have no knowledge of the data it will display or the screen it will use—all data binding and interaction with the control can be specified in screen code. With this approach the control can be reused across different screens and applications. The drawbacks include the fact that there is more code to write and furthermore, the screen code targets a specific control which makes the screen harder to modify down the road. This goes against the notion of screen code being pure business logic.
In practice both of these two approaches can be used, even for a single control, and it is up to the developer to decide which one is more advantageous, given unique application requirements. An example will make things clearer, but before we jump into it, we need to learn how exactly custom controls show up on a screen.
Screen content tree and (custom) controls
A screen in a LightSwitch application is built of three elements:
- Screen members are what you see on the left in the screen designer inside LightSwitch IDE. They are the data the screen is operating on. Screen members can include collections of entities, single entities and scalar values. They can also include commands (both built-in and user-defined)
- Screen content tree defines the visual layout of the screen. It determines what is shown on the screen and how the information is visually arranged. Content tree consists of content items and it is shown on the right side of the screen designer. Some content items are used just for layout, but most are there to show a specific piece of screen data. In other words they are bound to a piece of data, or have a data binding. Content items can also have an associated control (visual) that will be used to visualize the item when the application is running.
- Screens can also have user code, which can be used to customize screen behavior programmatically and implement business logic. Screen code can be shown by clicking “Write Code” button in screen designer toolbar.
The screenshot below shows design view of a screen called ShipperListDetail.
Note how screen designer shows the associated control and the data binding for content items that have them. If you want to know more how content tree, controls and screen members work together please see The Anatomy of a LightSwitch Application Series Part 2 – The Presentation Tier.
So what does all this have to do with custom controls? Well, the way you add a custom control to a screen is by replacing the standard (default) control that LightSwitch assigns to a content item with a custom control. In the example above we have done it for the last control in the content tree and we will now show you how
Example: using Rating control for showing shipper rating
Let’s say we have a database of shippers that are available to ship goods from our manufacturing facility to various parts of the country. For now we will focus only on three pieces of information: shipper’s name, phone number and rating. Open Visual Studio, create a new LightSwitch project (you can call it “CustomControls”) and add a Shippers entity:
Also, our business rules state that shipper rating, if known, must be a number between 1 and 5, so click the Rating column in table designer, go to Properties window, find the “Custom Validation” link at the bottom of the property sheet for the Rating column and add the following code (only the body of the Rating_Validate method needs to be modified)
Next create a List and Details screen for Shippers entity, including details for the entity on the screen. Your screen should look like this in the designer:
Now we are ready to create the custom control to display the rating. We will use the Rating control from Silverlight toolkit, so if you do not have the toolkit installed yet, you can get it from http://silverlight.codeplex.com/. After you have the toolkit installed, right-click the solution node in Solution Explorer and choose Add | New Project. Choose Silverlight Class Library project and name it RatingControlWrapper. Choose Silverlight 4 as the target Silverlight version and delete the Class1 class automatically created as part of the project.
Note: this you won’t be able to complete this portion of the example (creating custom control wrapper) if you have only Visual Studio LightSwitch Beta 1 installed on your machine. You need both Visual Studio Professional (or higher SKU) and Visual Studio LightSwitch. This is because Silverlight class library projects are not supported by Visual Studio LightSwitch alone. Later on I will show you how to use Rating control directly and set up control binding from LightSwitch code; that does not require anything other than Visual Studio LightSwitch. Also, this portion of the example assumes familiarity with Silverlight user controls and XAML; for more information about these topics see Getting Started with Controls in Silverlight documentation.
After the project is created, right-click the project node and choose Add | New Item. Choose a Silverlight User Control item type and name the new control RatingControlWrapper. After the project is created, add a reference to System.Windows.Controls.Input.Toolkit assembly. You will find it under the directory where Silverlight toolkit is installed; on my machine it was “C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr10\Bin”. Next open the RatingControlWrapper.xaml file in XAML view, add a namespace declaration for the local and toolkit namespaces and finally add the Rating control itself to the content of our wrapper control. You should end up with XAML file that has the content shown below; we have highlighted the portions of the control XAML that we have changed. Note that we have replaced the default Grid layout for the content with a simpler StackPanel; this will come in handy later.
The most interesting portion of this code is the data binding specification: it binds the Rating control’s Value property (which controls how many rating “stars” the user sees, i.e. depicts the rating) to Screen.ShipperCollection.SelectedItem.Rating property. The TwoWay mode means that whenever one side of the binding changes, the other side will be updated. The default binding mode in Silverlight is OneWay, which means that UI reflects (screen) data; but changes in the UI do not affect the underlying data. We want the user to be able not only see the rating, but also to change the rating by clicking the control, giving a shipper desired number of “stars”, so we use TwoWay.
Now we can switch to our main project and replace the textbox that is used for the Shipper.Rating property with our custom control. Right-click the RatingControlWrapper project in the Solution Explorer and choose “Build”—it should build without errors. Open ShipperListDetail screen from our main application in the designer, select the Rating content item and open the control selection dropdown:
Choose “Custom Control” here. Switch to Properties window, scroll to Custom Control property and hit Change link—Add Custom Control window appears
Click “Add Reference” button, switch to Project tab and select RatingControlWrapper project. Hit OK to add project reference—you should now see the RatingControlWrapper assembly in the Add Custom Control dialog (see screenshot above). Expand the RatingControlWrapper namespace, select the RatingControlWrapper control and hit OK.
Handling data conversions and null values
At this point you could try to run the application and start adding some shippers. Our rating UI shows up, but does not quite work as expected—it seems like the only rating that sticks is five stars. Also there is no obvious way to clear the rating either. How can we fix this?
The first problem stems from the fact that the Value property of the Rating control is a floating-point number and Shippers.Rating column is an integer. We could store floating point numbers instead of integers for our rating otherwise we need to convert the Shipper’s rating of 1, 2, 3, 4 or 5 into 0.0 to 1.0 range that the Rating control can work with. Fortunately Silverlight has a concept of a value converter that is designed for just that. So let’s create a value converter for our control wrapper. Add a new class to the RatingControlWrapper project and name it Int2DoubleConverter. Then change the class code to this:
To make the converter work for various number ranges we are going to use a parameter (scaling factor). In our case the maximum rating is 5 and we will use this value as the scaling factor. Now open the RatingWrapperControl in the designer and add the converter infromation to the binding:
Changing a shipper’s rating to unknown requires setting the underlying property to null, but the Rating control does not have this capability built-in. We can add it by using a link label and a bit of code. Add the following line to RatingControlWrapper.xaml:
Double-click the OnClearAction in XAML editor—this should result in opening the code-behind file for the control (RapidControlWrapper.xaml.cs or RapidControlWrapper.xaml.vb, depending on the language you use). Change OnClearAction method body to
That is it. Now you can launch the application and it works as expected
Setting up data binding via code
Our application works, but the data binding for our control is hard-coded into control definition. If the screen members change and the data binding path becomes invalid, the application will no longer work. I will now show you how to specify data binding for custom controls in screen code. In this way each screen can data-bind to the control in its unique way, so you can reuse the control with multiple screens.
First, open control definition again and remove the whole Value property binding. You can also delete the Int2DoubleConverter declaration from control resources. On the other hand, we want to expose the inner Rating control from our wrapper so that we can set a binding on it, so we’ll add the FieldModifier attribute to the control declaration. The XAML should now look like this
Next open ShipperListDetails screen and override (screen)_Loaded method (in VB you will also have to add a reference to System.Windows.Controls.Input.Toolkit assembly to the client project). The Loaded method should look like this (including necessary namespace imports which you should put at the top of the file)
For now let’s ignore the portion of the code that deals with finding controls and control proxies and focus on the last 5 lines where the data binding is set up. The binding mode and value converter setting look very similar to the previous example but where is the binding path?
Well, this is a different way to accomplish the same goal. Remember that the data context for a control is its content item. The binding specification here binds the Rating control’s Value property to the content item’s Value property. The content item does not just expose screen data; it has several properties that aid the UI layer (controls) in providing the best possible data editing experience. For example
- DisplayName property is used for showing a caption
- Description property can be used for a helpful tooltip
- IsProcessing property indicates whether underlying data is available or still being loaded from the database
- DataError property that contains error information if the data load fails
- (there is more)
The full list of properties exposed by content items is beyond the scope of this post; but for our purposes Value property is the most important and sufficient: this is the property that returns the underlying piece of screen data that the content item represents. In our case the content item represents Shipper.Rating property, so the Rating.Value will be bound to Shipper.Rating, which is exactly what we want. The only thing remaining is to make sure that the control has the Name property set to “RatingControl” in LightSwitch screen designer, otherwise our code won’t work.
Run the application and verify that it still behaves properly.
In this post we have seen how you can use custom Silverlight controls to enhance UI of LightSwitch applications. We have learned how custom controls plug into screen content tree and how to bind them to screen data, both from XAML as well as from code. We also used a value converter to overcome the problem of type mismatch between control property types and entity member types and added a control gesture to set the underlying data to null. In the second part of this post we will talk about how LightSwitch run time uses threads and what implications this has on controls and screen code. We will also cover some ways of making the screen and the control work together (interact) and we will show you how to make custom controls work with hierarchical data.
Update for LightSwitch Beta 2 and LightSwitch 1.0
In LightSwitch Beta 2 Microsoft made some changes to make coding for custom controls more understandable and reliable. First, we changed the name of the screen’s “Loaded” method to “Created”. This is to avoid confusion with any data load operations—the method is called immediately after the screen UI is created and at that time the initial data load (if any) might still be in progress. This has no impact on custom control data binding because custom controls will be notified automatically when data becomes available.
More important changes are related to FindControl method and the IContentItemProxy interface it uses:
- IContentItemProxy now has a SetBinding method that lets you add data bindings to your custom control in one line of code, without having to switch to UI thread.
- If you need to set some custom control properties directly (without using data binding), you will need to hook up the new IControlItemProxy.ControlAvailable event. The old IContentItemProxy.Control property is gone. The reason for this change is that Silverlight handles control creation and unload internally and, for performance reasons, may delay its until it the very last moment, which is depends on what the end user is doing to the application. For example, if you use a Tabs control on your screen and the custom control is on a tab that is not shown initially (is not the first tab), Silverlight will not create the control until its parent tab is activated by the user. This made the usage of the old IControlItemProxy.Control property very tricky and unreliable. In LightSwitch Beta 2, the LightSwitch run time will call your code (event handler) when the control is created and your code can access the control (via passed-in parameter) in a safe and reliable way.
The following snippet illustrates the usage of these APIs in a simple example: