Tip/Trick: Creating and Using Silverlight and WPF User Controls
One of the fundamental design goals of Silverlight and WPF is to enable developers to be able to easily encapsulate UI functionality into re-usable controls.
You can implement new custom controls by deriving a class from one of the existing Control classes (either a Control base class or from a control like TextBox, Button, etc). Alternatively you can create re-usable User Controls - which make it easy to use a XAML markup file to compose a control's UI (and which makes them super easy to build).
In Part 6 of my Digg.com tutorial blog series I showed how to create a new user control using VS 2008's "Add New Item" project item dialog and by then defining UI within it. This approach works great when you know up front that you want to encapsulate UI in a user control. You can also use the same technique with Expression Blend.
Taking Existing UI and Encapsulating it as a User Control
Sometimes you don't always know you want to encapsulate some UI functionality as a re-usable user control until after you've already started defining it on a parent page or control.
For example, we might be working on a form where we want to enable a user to enter shipping and billing information. We might begin by creating some UI to encapsulate the address information. To-do this we could add a <border> control to the page, nest a grid layout panel inside it (with 2 columns and 4 rows), and then place labels and textbox controls within it:
After carefully laying it all out, we might realize "hey - we are going to use the exact same UI for the billing address as well, maybe we should create a re-usable address user control so that we can avoid repeating ourselves".
We could use the "add new item" project template approach to create a blank new user control and then copy/paste the above UI contents into it.
An even faster trick that we can use within Blend, though, is to just select the controls we want to encapsulate as a user control in the designer, and then "right click" and choose the "Make Control" menu option:
When we select the "Make Control" menu item, Blend will prompt us for the name of a new user control to create:
We'll name it "AddressUserControl" and hit ok. This will cause Blend to create a new user control that contains the content we selected:
When we do a re-build of the project and go back to the original page, we'll see the same UI as before - except that the address UI is now encapsulated inside the AddressUserControl:
We could name this first AddressUserControl "ShippingAddress" and then add a second instance of the user control to the page to record the billing address (we'll name this second control instance "BillingAddress"):
And now if we want to change the look of our addresses, we can do it in a single place and have it apply for both the shipping and billing information.
Data Binding Address Objects to our AddressUserControl
Now that we have some user controls that encapsulate our Address UI, let's create an Address data model class that we can use to bind them against. We'll define the class like below (taking advantage of the new automatic properties language feature):
Within the code-behind file of our Page.xaml file we can then instantiate two instances of our Address object - one for the shipping address and one for the billing address (for the purposes of this sample we'll populate them with dummy data). We'll then programmatically bind the Address objects to our AddressUserControls on the page. We'll do that by setting the "DataContext" property on each user control to the appropriate shipping or billing address data model instance:
Our last step will be to declaratively add {Binding} statements within our AddressUserControl.xaml file that will setup two-way databinding relationships between the "Text" properties of the TextBox controls within the user control and the properties on the Address data model object that we attached to the user control:
When we press F5 to run the application we'll now get automatic data-binding of the Address data model objects with our AddressUserControls:
Because we setup the {Binding} declarations to be "Mode=TwoWay", changes users make in the textboxes will automatically get pushed back to the Address data model objects (no code required for this to happen).
For example, we could change our original shipping address in the browser to instead go to Disneyland:
If we wire-up a debugger breakpoint on the "Click" event handler of the "Save" button (and then click the button), we can see how the above TextBox changes are automatically reflected in our "_shippingAddress" data model object:
We could then implement the SaveBtn_Click event handler to persist the Shipping and Billing Address data model objects however we want - without ever having to manually retrieve or manipulate anything in the UI controls on the page.
This clean view/model separation that WPF and Silverlight supports makes it easy to later change the UI of the address user controls without having to update any of our code in the page. It also makes it possible to more easily unit test the functionality (read my last post to learn more about Silverlight Unit Testing).
Summary
WPF and Silverlight make it easy to encapsulate UI functionality within controls, and the user control mechanism they support provides a really easy way to take advantage of this. Combining user controls with binding enables some nice view/model separation scenarios that allow you to write very clean code when working with data.
You can download a completed version of the above sample here if you want to run it on your own machine.
To learn even more about Silverlight and WPF, check out my Silverlight Tutorials and Links Page. I also highly recommend Karen Corby's excellent MIX08 talk (which covers User Controls, Custom Controls, Styling, Control Templates and more), which you can watch online for free here.
Hope this helps,
Scott