다음을 통해 공유


Using WPF and Silverlight UserControls

UserControls promote code reuse, separation of concerns and simplify development by partitioning applications into smaller blocks of code.

In this article you'll learn why you would use a UserControl, how to create one from the Toolbox, understand when design-time code executes and how to prevent blocks of code from running at design-time.

Table of Contents

  1. Introduction
  2. Automatically Populate Toolbox Items
  3. Root Object and Instances of Controls at Design-time
  4. Running Code in a Constructor
  5. Comments

Introduction 

The below demo application will be used in this article to illustrate important UserControl concepts. When writing WPF or Silverlight applications you have the option to partition your code into manageable blocks. This separating of concerns and responsibilities makes authoring and maintaining of the application easier and provides opportunities for code reuse. Additionally, unit and integration testing is simpler when testing smaller units of code.

The below application is very simple. It could easily be written with all the code in a single XAML file with a corresponding code-behind file. As time passes, requirements change, more fields are added, more tabs get added and before long you have a huge XAML file that gets difficult to understand and edit.

However, if we treat the below Window object as a "shell" for an application; we can then easily extend the application by adding small self-contained units of code such as UserControls.

Notice how short and simple the below XAML is. This is the XAML for the above form.

 <Grid>
    <TabControl>
        <TabItem Header="Customers">
            <Grid>
                <my:CustomersUserControl />
            </Grid>
        </TabItem>
        <TabItem Header="Sales" >
            <Grid />
        </TabItem>
    </TabControl>
</Grid>

In the below image, you can see the outer Window, the CustomersUserControl that is hosted in a TabItem and the CustomerEditUserControl that is nested within the CustomersUserControl.

In terms of separation of concerns and responsibilities, the Window is responsible for application navigation. This is accomplished by hosting UserControls within TabItems. The Window does not need to know anything about customers or sales, how to search and edit them, how to connect to a database or validate data entry.

The CustomersUserControl is responsible for providing search capabilities and for setting the DataContext on the CustomerEditUserControl when a Customer is selected.

The CustomerEditUserControl is responsible for providing editing capabilities. This UsesrControl can also be reused in other maintenance forms or possibly in an application child window (dialog box) if the requirements call for this functionality.

The below XAML is from the CustomersUserControl. Again, note the short and simple XAML.

The below short and simple XAML is from the CustomerEditUserControl.

After looking over the XAML for the Window and two UserControls, you can see that we have simplified the application by breaking it into small manageable components. The separated application is easier to maintain over time and test. Another benefit of this separation of the XAML is that the accompanying code-behind will also be simpler, shorter and with fewer methods per class.

Automatically Populate Toolbox Items 

By default, in Visual Studio 2010, each time you build a WPF and Silverlight project, any types that derive from FrameworkElement or higher will be automatically added to the Toolbox. The types that are added are grouped into Toolbox tab by assembly.

The purpose of this feature is to make it easy for developers to consume the UserControls and CustomControls they create on a form.

In the below image you can see two UserControls in the Toolbox in the CreateConsumeUserControl Toolbox Tab. These controls can be dragged onto the design surface just like the default WPF or Silverlight controls.

Enable or Disable Automatically Populate Feature

At your option you can enable or disable this feature by using the Visual Studio Options dialog.

To open this dialog:

  • Click the Visual Studio Tools menu item
  • Select Options…

Using the TreeView select:

  • Text Editor
  • XAML
  • Miscellaneous

To enabled or disable the feature, check or uncheck the Automatically populate toolbox items CheckBox.

DesignTimeVisible Attribute

At your option, you can keep individual items from appearing by decorating the class with the System.ComponentModel.DesignTimeVisible attribute.

The below code demonstrates how to keep the CustomerEditUserControl from appearing in the automatically populated Toolbox items listing.

    C#

 [DesignTimeVisible(false)]
public partial class CustomerEditUserControl : UserControl {

VB.NET

 <DesignTimeVisible(false)>
Public Class CustomerEditUserControl

Silverlight Page Control

While the Silverlight Page derives from UserControl and you would expect these objects to appear in the Toolbox items listing, these controls are not listed in the Toolbox items listing.

The reason these are not listed is because these are root controls that are not typically consumed in other controls, instead they are navigated to and loaded into a Frame control. Since that are not typically consumed by other controls, there is no reason to load them into the Toolbox.

Root Object and Instances of Controls at Design-time 

It's very important to understand when and what user-code the WPF and Silverlight Designer runs at design-time, so that you can avoid unnecessary design-time load failures.

Root Object

The "root object" is the top level (or root) Window or Page or UserControl that you are actually designing. None of the code in the code-behind file for the root object is executed at design time (for example code in the constructor or Loaded event code). The MainWindow is the root object in the below image. 

What Code Executes, What Code Doesn't Execute

What code executes at design-time? Everything that gets created in the XAML:

  • Instances of Controls and UserControls
  • ValueConverters
  • DataTemplateSelectors
  • Classes referenced as resources

What code does not execute at design-time? The top level object you are designing:

  • Window, UserControl, Page etc. when you are designing it
  • Your Application class

Important

Never reference the Application or App class in code that gets executed at design-time because this will always be null (Nothing in VB.NET).

Instances of Controls

The Designer will execute all constructors and Loaded event code for each instances of any child controls.

In the below image both the CustomersUserControl and CustomerEditUserControl constructors and Loaded event code will be executed at design-time.

XAML View of the Above Form

The reason this is so important to understand is, your code may make assumptions that are always true at run-time and the application works property, however those same assumptions may not be true at design-time and could cause Designer load failures.

If you have not coded defensively, your user-code can cause unhandled exceptions that will prevent the Designer from loading. A NullReferenceException at design-time is the most common cause of Designer load failure. This is also true of Converters and DataTemplateSelectors that do not make null reference checks before attempting to access a reference type.

Please read the MSDN topic, Troubleshooting WPF and Silverlight Designer Load Failures. This topic contains good information about authoring design-time friendly code and troubleshooting common load failures.

Running Code in a Constructor 

Some developers choose to initiate an asynchronous method calls in their constructors. Running these method calls at design-time can cause design-time problems and load failures. For example, trying to call a web service when the web service is not running, or trying to access the database when the connection string information is in the Application object that is null at design-time; these will both cause problem and possibly keep the form from loading or unhandled exceptions thrown.

Both WPF and Silverlight developers have the ability to run a simple check that will verify if the code is currently running at design-time or not.

In the below Silverlight code, the line below InitializeComponent, System.ComponentModel.DesignerProperites.GetIsInDesignMode performs the check. If false (at run-time), then the code will execute.

C#

 public partial class ProductsView : UserControl {
    public ProductsView() {
        InitializeComponent();

        if (!DesignerProperties.GetIsInDesignMode(this)) {
            this.tbStatusBar.Text = "Getting products list...";
            ProductsServiceReference.ProductsServiceClient client =
                new ProductsServiceReference.ProductsServiceClient();
            client.DoWorkCompleted += new
                EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_DoWorkCompleted);
            client.DoWorkAsync();
        }
    }

    void client_DoWorkCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) {
        if (e.Error == null) {
            this.tbStatusBar.Text = "Products loaded";
        } else {
            this.tbStatusBar.Text = e.Error.Message;
        }
    }
}

VB.NET

 Partial Public Class ProductsView
    Inherits UserControl

    Public Sub New()
        InitializeComponent()

        If Not DesignerProperties.GetIsInDesignMode(Me) Then
            Me.tbStatusBar.Text = "Getting products list..."
            Dim client As new ProductsServiceReference.ProductsServiceClient()
            AddHandler client.DoWorkCompleted, AddressOf client_DoWorkCompleted
            client.DoWorkAsync()
        End If
    End Sub

    Private Sub client_DoWorkCompleted(ByVal sender As Object,
                                       ByVal e As System.ComponentModel.AsyncCompletedEventArgs)
        If e.Error Is Nothing Then
            Me.tbStatusBar.Text = "Products loaded"
        Else
            Me.tbStatusBar.Text = e.Error.Message
        End If
    End Sub

End Class

Comments 

Microsoft values your opinion about our products and documentation. In addition to your general feedback it is very helpful to understand:

  • How the above feature enables your workflow
  • What is missing from the above feature that would be helpful to you

Thank you for your feedback and have a great day,

Karl Shifflett

Expression Team

Comments

  • Anonymous
    June 14, 2010
    Excellent article!

  • Anonymous
    June 14, 2010
    So how about showing us how you bind the user control property values to each other?  Ie, binding the text boxes on the CustomerEditUserControl to the selected item on the CustomersUserControl.  Thanks!

  • Anonymous
    June 14, 2010
    Great Article Karl. For many SL Projects so I have been creating 'Componentizing' my app creating appropriate UserControls similar to Web User Controls in ASP.NET but I just missed the idea to introduce appropriate attributes as mentioned in your write up. Thank you for this. Regards KRK

  • Anonymous
    June 14, 2010
    Very nice and useful.

  • Anonymous
    June 14, 2010
    Aaron, Thank you your suggestion.  Please check out this article:  blogs.msdn.com/.../create-wpf-master-detail-ui-using-data-sources-window-object-datasource.aspx You can also check out all of our articles by clicking on the Learn link in the Pages tab at the top right of this blog. Thank you again for leaving a suggestion and have a great day, Karl

  • Anonymous
    June 15, 2010
    Hi Karl, I'm confused by your reply.  The link that you supplied does not address the issue of user controls that rely on the values of other user control's controls.  Did you supply the wrong link by chance? Without some kind of observer and interfaces, I don't see how one user control will be notified of another user control's changes via pure binding.  Or maybe I'm looking into this article too deep and missed the point of the article? Thanks again!

  • Anonymous
    June 15, 2010
    Aaron, Thanks for writing back.  One point of the article was to show a developer who has not seen the benefits of breaking their code into smaller more manageable parts, one way to do it. In the above fictitious application you can easily do this with DataBinding.  The CustomerEditUserControl DataContext would be bound to the SelectedItem in CustomersUserControl ListBox.   This enables the scenario of the CustomerEditUserControl just needs a DataContext set to function. Does this help? Cheers, Karl

  • Anonymous
    June 15, 2010
    Thanks again, Karl.   And in order for that to work, CustomersUserControl would need to expose the SelectedItem as a dependency property that the CustomerEditUserControl could bind to in XAML, correct.  Short of going the extra mile by introducing a VM into the mix.  Is this right, or am I missing something?

  • Anonymous
    June 15, 2010
    Aaron, Not exactly.  Since CustomerEditUserControl is a child of CustomersUserControl, the binding takes place inside CustomersUserControl.  Here is some short pseudo code. <CustomersUserControl>    <ListBox x:Name="lbCustomers" />    <CustomerEditUserControl DataContext="{Binding ElementName=lbCustomers, Path=SelectedItem}" /> </CustomersUserControl> Cheers, Karl

  • Anonymous
    June 16, 2010
    Ahh, I see what you did.  I'm sorry, I didn't notice that one UC was embedded in the other.  I was thinking that they were standalone controls residing in another UC...

  • Anonymous
    June 16, 2010
    Aaron, Glad we've sort this out and thank you for your questions.   Have a great day, Karl

  • Anonymous
    July 13, 2010
    Great article! Karl, hopefully we can read such new articles from you soon.

  • Anonymous
    July 14, 2010
    leiknospe, Very much appreciate your feedback.  Have a super day, Karl