CHAPTER 11

image

Creating and Using Custom Controls

The built-in Silverlight controls that you’ve seen so far include labels, text boxes, autocomplete boxes, and data grids. But if you can’t find a control that suits your needs, there’s no need to worry. When you’re building Rich Client applications, you can customize the way that users enter and view data by using custom Silverlight controls. This opens up many extra ways for you to display your data. You can build your own Silverlight custom controls or use controls that other people have developed. In this chapter, you’ll learn how to

  • Display data using custom controls
  • Bind screen data to custom controls
  • Develop your own Silverlight custom controls

At the start of this chapter, you’ll learn how to use the controls that you’ll find in the Silverlight Software Development Kit (SDK). You’ll find out how to customize the HelpDesk application by allowing administrators to enter password details through a masked input control. You’ll learn how to apply a ComboBox box control to limit the priority values that a user can choose when entering an issue, how to allow users to set numeric values by using a Slider control, and how to display a web page on a screen. You’ll also find out how to build a duration control that allows minutes to be split into hours and minutes. This control will allow engineers in our example to add timesheet records more easily.

Using Custom Controls

The easiest way to get started is to use the UI controls in the Silverlight SDK. You’ll find these controls in the System.Windows.Controls namespace. Of course, you could use other third-party Silverlight controls. But the advantage of directly using the controls in the Silverlight SDK is that it quickly opens up a whole host of additional UI elements that you can use, such as the following:

  • PasswordBox
  • WebBrowser
  • TreeView
  • RichTextBox
  • MediaElement
  • MultiScaleImage
  • HyperLinkButton
  • ComboBox

An advantage of this technique is that you don’t need to create a separate project, and you don’t need to write very much code either. In the section that follows, you’ll learn how to create a screen in the HelpDesk system that allows managers to specify email server settings (as shown in Figure 11-1).

9781430250715_Fig11-01.jpg

Figure 11-1. PasswordBox control as it appears onscreen

This screen uses the password box control that you’ll find in the Silverlight SDK. The control provides a masked password input box, which replaces the characters that the user enters with a series of dots.

There are two parts to using a custom control. First, you need to specify a custom control for the data item that you want to use. Second, you’ll need to write the code that binds the control to your underlying data. You’ll now find out how to carry out both of these tasks.

Specifying a Custom Control

To use the password box control, use the New Data Screen template to create a screen based on the AppOptions table (or reuse the AppOptionsEdit screen that you created in Chapter 7, Listing 7-19). Now carry out the following steps in the screen designer:

  1. Select the SMTPPassword data item, and change the control type from Text Box to Custom Control (as shown in Figure 11-2).

    9781430250715_Fig11-02.jpg

    Figure 11-2. Specifying a custom control of type PasswordBox

  2. Open the properties sheet, and click on the Change link.
  3. When the Add Custom Control dialog appears, select System.Windows.Controls image PasswordBox.

If the PasswordBox control doesn’t appear in the Add Custom Control dialog, make sure that you’ve chosen the System.Windows DLL, rather than one of the other DLLs (as shown in Figure 11-3).

9781430250715_Fig11-03.jpg

Figure 11-3. Make sure to choose the first node

Binding Data to Custom Controls

Once you set a data item to use a custom control, the next step is to bind the control to your underlying data item. In this example, you’ll need to bind the password control’s PasswordProperty to the AppOption entity’s SMTPPassword property. To do this, click on the Write Code button, select the InitializeDataWorkspace method, and enter the code that’s shown in Listing 11-1.

Listing 11-1.  Calling the SetBinding Method

VB:
File:HelpDeskVBClientUserCodeAppOptionsEdit.vb
  
Private Sub AppOptionsEdit_InitializeDataWorkspace(
   saveChangesTo As List(Of Microsoft.LightSwitch.IDataService))
  
    Dim password = Me.FindControl("SMTPPassword")                                             images
    password.SetBinding(
       System.Windows.Controls.PasswordBox.PasswordProperty,
       "Value",
       Windows.Data.BindingMode.TwoWay)
  
End Sub
  
C#:
File:HelpDeskCSClientUserCodeAppOptionsEdit.cs
  
partial void CreateNewAppOption_InitializeDataWorkspace(
   List<IDataService> saveChangesTo)
 {
    var password = this.FindControl("SMTPPassword");                                      images
    password.SetBinding(
        System.Windows.Controls.PasswordBox.PasswordProperty,
        "Value",
        System.Windows.Data.BindingMode.TwoWay);
}

This code uses the FindControl method images to return an IContentItemProxy object. (See Chapter 7 for more details.) IContentItemProxy’s SetBinding method carries out the actual task of data binding. This method accepts three arguments.

The first argument specifies the dependency property to bind to. If you’re not sure what a dependency property is, just think of it as a property that you can bind data to. To help you choose the correct dependency property, use the code editor’s IntelliSense to view all the possible choices. You’ll need to specify the control type that you’ve used, and in this example, the dependency property value is prefixed with the System.Windows.Controls.PasswordBox control type.

The second argument is the binding path. This is the most difficult part to get right because this method expects a string value, and Visual Studio provides no guidance as to what you should supply. If you get this wrong, the data binding fails to work without giving you any compile or runtime errors.  This makes it difficult to trace the exact cause of a binding-path error.

You’ll commonly see the string property Value used as a binding path. This binds your dependency property to the Data Context item that you’ll find in the Properties sheet. (See Figure 11-4.) Table 11-1 shows the other binding paths that you can use.

9781430250715_Fig11-04.jpg

Figure 11-4. The Value data-binding path references the Data Context text box

Table 11-1. Data-Binding Values

Binding Path Data binds to  .  .  .
Value The value specified in the Data Context text box in the Properties sheet
StringValue The formatted version of the value specified in the Data Context text box in the Properties sheet
Details.Entity.Surname Another property in the same entity (Surname in this example)
Screen.localPropertyName A local property on your screen

You could also use the binding path StringValue rather than Value. StringValue reflects any specific formatting that’s applied by the property, whereas Value simply returns the raw value. These property names (Value and StringValue) are public properties of Microsoft.LightSwitch.Presentation.Implementation.ContentItem.

The final argument is the binding mode. You can supply one of three values: OneTime, OneWay, or TwoWay. OneTime updates the control from the source data when your application creates the data binding. OneWay updates the control from the source data when the data binding is created, and also whenever the source data changes afterward. TwoWay updates the control and source in both directions whenever either of them changes.

This completes the example, and you’re now ready to run your application. When you run your AppOptionsEdit screen, you’ll be able to enter an SMTP password using the password control.

image Tip  Rather than using hard-coded binding paths, you could create a helper library that exposes a set of constants. The C# class would look something like this:

public static class ContentItemProperty
{
    public const string Value = "Value";
    public const string StringValue = "StringValue";
    public const string DisplayName = "DisplayName";
    public const string Details = "Details";
    // etc.
}

EXERCISE 11.1 – INVESTIGATING CUSTOM CONTROLS

In the Add Custom Control dialog shown in Figure 11-2, notice the wide variety of controls that you can use. Investigate the custom controls that you can use by opening an existing screen and specifying Custom Control for an existing data item. Explore the Silverlight controls that you can use in the System.Windows.Controls namespace. Notice the Slider and WebBrowser controls—you’ll find out how to use these controls later in this chapter.

Binding Data Collections to Custom Controls

The password box example showed you how to bind a scalar value to a dependency property. In some circumstances, you might need to supply a custom control with a list of data or the results of a query. You do this by binding a collection of data to a dependency property on the custom control, and this example shows you how.

The new Issue screen allows engineers to enter a priority by using an autocomplete box. A disadvantage of this control is that engineers can type whatever text they choose into this control and if they enter invalid data, they won’t be notified of their mistake until they attempt to save their record. To overcome this problem, you’ll now find out how to use the ComboBox control. This control allows youto limit item selections to only those that are shown in the control. (See Figure 11-5.)

9781430250715_Fig11-05.jpg

Figure 11-5. Limiting item choices by using a ComboBox control

Designing the screen

The steps that you need to carry out to use a ComboBox control are very much the same as those that you used to set up the PasswordBox control. However, there’s one additional task that’s required. LightSwitch doesn’t know how to populate your ComboBox control with the item choices, so you’ll need to add some code that sets your ComboBox’s data source.  

To create this example, use the New Data Screen template to create a screen based on the Issue table. Name your screen CreateNewIssue. Select the Priority data item, and change it from an autocomplete box to a custom control. From the Properties sheet, click on the Change link and use the Add Custom Control dialog to select System.Windows.Controls image ComboBox.

Now click the Add Data Item button and create a new query called Priorities that returns all priorities. Click the Write Code button, select the screen’s Activated method, and enter the code shown in Listing 11-2.

Listing 11-2.  Data-Binding a ComboBox Control

VB:
File:HelpdeskVBClientUserCodeCreateNewIssue.vb
  
Private Sub CreateNewIssue_Activated()
    Dim comboControl As IContentItemProxy = Me.FindControl("Priority")
    comboControl.SetBinding(                                                              images
          System.Windows.Controls.ComboBox.ItemsSourceProperty,
          "Screen.Priorities",                                                            images
          Windows.Data.BindingMode.OneWay)
  
    comboControl.SetBinding(                                                              images
    System.Windows.Controls.ComboBox.SelectedItemProperty,
    "Screen.IssueProperty.Priority",
    Windows.Data.BindingMode.TwoWay)
  
End Sub
  
C#:
File:HelpDeskCSClientUserCodeCreateNewIssue.cs
  
partial void CreateNewIssue_Activated()
{
    var comboControl = this.FindControl("Priority");
    comboControl.SetBinding(                                                              images
        System.Windows.Controls.ComboBox.ItemsSourceProperty,
        "Screen.Priorities",                                                                images
        System.Windows.Data.BindingMode.OneWay);
  
    comboControl.SetBinding(                                                                images
        System.Windows.Controls.ComboBox.SelectedItemProperty,
        "Screen.IssueProperty.Priority",
        System.Windows.Data.BindingMode.TwoWay);
}

This code carries out two data-binding tasks by using the SetBinding method. First, it populates the items shown in the ComboBox by binding the ComboBox’s ItemsSourceProperty to your Priorities query images. The binding path that allows you to reference the Priorities query is Screen.Priorities images. The next line of code sets the item that’s selected in the combo box by binding the ComboBox’s SelectedItemsProperty to the Issue property’s Priority property images. This completes the example, and you’re now ready to run your screen.

Converting Values When Data Binding

Sometimes, the data type of the dependency property that you want to use might not exactly match the data type of your data source. In this case, you’ll need to create a value converter to enable the data binding to work.

The HelpDesk system stores details about each office location and includes the ability to record the office capacity of each location (that is, the maximum number of people that the building can hold). In this example, you’ll find out how to allow users to enter the maximum capacity by using Silverlight’s Slider control.

The Office table stores the capacity as an integer, but the Silverlight Slider control expects to be bound to a number that’s of data type double. This mismatch means that in order to use the Slider control, you’ll need to create a value converter that converts doubles to integers, and vice versa. To show you how to do this, this following example creates a new data screen that allows users to create entries for new offices and to set the capacity by using a Slider control. To complete this example, carry out the following tasks:

  1. Create a new data screen that’s based on the Office table, and name it CreateNewOffice.
  2. Change the Building Capacity data item from Text Box to Custom Control.
  3. In the Properties sheet, click the Change link. When the Add Custom Control dialog appears, select System.Windows.Controls image Slider.
  4. Set the Min Width value of the Slider control to 300. It’s important to set this to a value other than Auto; otherwise, LightSwitch shows only the Slider button, and not the Slider section that allows the user to make a selection.
  5. Create a new Columns Layout control beneath the main Rows Layout control for your Office Property. Set the Label Position setting of this group to Collapsed.
  6. Drag your Slider control into the Columns Layout control. When you do this, make sure the Label Position setting is still set to Left-Aligned.
  7. Add another Building Capacity data item into the Columns Layout control, and set the control type to Label. Set the Label Position setting of the label to Collapsed. This label is designed to show the selected integer value when the user uses the Slider control. Figure 11-6 shows how your screen now looks.

    9781430250715_Fig11-06.jpg

    Figure 11-6. Screen layout in design view

  8. Switch your project to File view. Create a new class in the UserCode folder of your Common project, and name it IntToDouble.
  9. (Optional) If you’ve added an HTML client or upgraded your project, you won’t have a Common project in your solution. Instead, create the IntToDouble class in the Client project’s UserCode image Shared folder.
  10. Add a reference to the PresentationFramework.dll assembly in the project in which you’ve added your IntToDouble class. The default location of this file on a 64-bit machine is C:Program Files (x86)Reference AssembliesMicrosoftFrameworkSilverlightv5.0.

Now add the code that’s shown in Listing 11-3 to your IntToDouble class.

Listing 11-3.  Value Converter Code

VB:
File:HelpDeskVBCommonUserCodeIntToDouble.vb
  
Imports System.Windows.Data
  
Public Class IntToDoubleConverter
    Implements IValueConverter                                                            images
  
    Public Function Convert(                                                              images
        value As Object,
        targetType As System.Type,
        parameter As Object,
        culture As System.Globalization.CultureInfo
    ) As Object Implements System.Windows.Data.IValueConverter.Convert
  
        Return CDbl(value)
    End Function

    Public Function ConvertBack(                                                          images
        value As Object,
        targetType As System.Type,
        parameter As Object,
        culture As System.Globalization.CultureInfo
    ) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
  
        Return CInt(value)
    End Function
  
End Class
  
C#:
File:HelpDeskCSCommonUserCodeIntToDouble.cs
  
namespace LightSwitchApplication.UserCode
{
    public class IntToDoubleConverter : IValueConverter                                     images
    {
        public object Convert(object value, Type targetType,                                images
            object parameter, System.Globalization.CultureInfo culture)
        {
            return Double.Parse(value.ToString());
        }
  
        public object ConvertBack(object value, Type targetType,                            images
            object parameter, System.Globalization.CultureInfo culture)
        {
            return int.Parse(value.ToString());
        }
    }
}

The IntToDouble class implements the IValueConverter interface images, and contains the logic that carries out the data conversion. The Convert method images converts an integer value to a double, whereas the ConvertBack images method coverts a double to an integer. This example demonstrates a simple value converter that works well in this scenario. But in general, value converters should also use the culture parameter to provide better conversations. In particular, the culture information allows you to parse and format numbers and dates based on your user’s language.

After you create your value converter, return to the screen designer, click the Write Code button, and select the screen’s Activated method. Add the code shown in Listing 11-4.

Listing 11-4.  Data-Binding the Slider Control

VB:
File:HelpDeskVBClientUserCodeCreateNewOffice.vb
  
Imports System.Windows.Controls
Imports System.Windows.Data
  
Private Sub CreateNewOffice_Activated ()
    Dim buildingCapacity As IContentItemProxy =
       Me.FindControl("BuildingCapacity")
    Dim converter As New IntToDoubleConverter
    buildingCapacity.SetBinding(
        Slider.ValueProperty,
        "Value",
        converter,
        BindingMode.TwoWay)
End Sub
  
C#:
File:HelpdeskCSClientUserCodeCreateNewOffice.cs
  
using System.Windows.Controls;
using System.Windows.Data;
  
partial void CreateNewOffice_Activated()
{
    var buildingCapacity = this.FindControl("BuildingCapacity");
    IntToDoubleConverter converter = new IntToDoubleConverter();
    buildingCapacity.SetBinding(
        Slider.ValueProperty,
        "Value",
        converter,
        BindingMode.TwoWay);
}

You’re now ready to run your application. Figure 11-7 shows the appearance of the Slider control on the final screen.

9781430250715_Fig11-07.jpg

Figure 11-7. Illustration of the Slider control

image Tip  Value converters allow you to do much more than just basic type conversions, and you can use them in many other imaginative ways. For example, you could bind the background color of a text box or text block control to an integer value. If your data includes a priority field that stores numbers, say, from 1 to 5, you could set the background color of your control to green if the priority is 1, or set it to red if the priority is 5. Using a value converter, you can bind to the BackgroundProperty dependency property by converting the priority number into an object of type System.Drawing.Color.

Creating a Custom Silverlight Control

The examples that you’ve seen so far have used the controls from the System.Windows.Controls namespace. If there isn’t a control in this namespace that suits your needs and if you can’t use a third-party control that does what you need, another option is to create your own custom Silverlight control.

The time-tracking feature in the HelpDesk application allows engineers to record the time in minutes that they’ve spent on resolving issues. To improve the presentation of this screen, you’ll now learn how to create a custom control that allows engineers to enter and view time durations in hours and minutes (as shown in Figure 11-8).

9781430250715_Fig11-08.jpg

Figure 11-8. Duration control

Understanding Dependency Properties

The duration control that you’ll create requires a dependency property you use to bind the data item on your LightSwitch screen to your control. But before you move on further, let’s take a closer look at what dependency properties are and how they work.

Dependency properties are very often used in Silverlight and Windows Presentation Foundation (WPF). They’re very similar to normal .NET properties, but they’re more powerful. They use memory more efficiently, help support features like styles and animations, and also provide advanced features like coercion (I’ll explain what this is later), validation, and change notification. Change notification is particularly important because it alerts your LightSwitch screen whenever a user makes a change to a custom control value.

Traditional .NET properties include two methods, called get and set. In the vast majority of cases, these methods retrieve and set the value of a private field. Unlike traditional .NET properties, dependency properties don’t read their values from a private field. When you retrieve the value of a dependency property, .NET dynamically calculates the return value using a process called dynamic value resolution.

Let’s imagine that you write some code that retrieves the background color of a Silverlight text box control. The background color might depend on various factors. It could be inherited from a parent control or could be set in styles and themes. Furthermore, the control might be involved in an animation that constantly changes the background color. To retrieve the correct value, Silverlight searches for the background color in the following places, in the order that’s shown. It returns the first value that it finds.

  1. Animations
  2. Local Value
  3. Styles
  4. Property Value Inheritance
  5. Default Value

Dynamic value resolution begins by checking whether the text box is involved in an animation. If so, it returns the background color that’s applied by the animation. If not, it searches for a local value. This refers to the value that’s explicitly set in code (for example, MyCtrl.BackColor = Colors.Green) or in XAML. If your background color is bound to data, the process also classifies a data-bound value as a locally set value. If you did not set a local value, dynamic value resolution searches for a value that has been set in a style or a template. If that doesn’t return a match, it tries to find the value that was set at the parent control. It continues searching up the tree of parent controls until it reaches the root control. And if it doesn’t find a value here, it returns the default value.

The big benefit of this approach is that it’s highly efficient in terms of memory usage. Around 90% of the properties on a typical control stay at their initial value, so it’s inefficient to allocate memory by storing the value of every property on a control. Dependency properties store only the value of properties that have changed, which is much more efficient. The local value of a dependency property isn’t stored in a field of your object; instead, it’s stored in a dictionary of keys and values that’s provided by the base class DependencyObject. The dictionary uses the property name as the key value for the dictionary entry.

In summary, dependency properties don’t hold a concrete value. Their value can be derived from many places—hence the name dependency property. They’re important in LightSwitch because if you want to bind screen data to a property on a custom control, that property must be a dependency property.

Creating a New Control and Dependency Property

Now that you understand how dependency properties work, let’s create the custom control that allows users to enter time durations. This process involves creating a Silverlight class library that contains a Silverlight user control. Your Silverlight user control contains the text boxes and UI elements that define your custom control. To create a custom control, carry out the following steps:

  1. Start Visual Studio. Click File image New image Project. When the New Project dialog appears, select the Silverlight Class Library template and name your project ApressControls. If you’re having trouble finding this template, you can use the search box that appears at the top of the New Project dialog.
  2. The next dialog that appears prompts you to choose a Silverlight version. Choose Silverlight 5 (if it’s available).
  3. When your project opens, right-click your project in Solution Explorer and choose the Add image New Item option.
  4. When the Add New Item dialog appears, choose Silverlight User Control and name your control DurationEditorInternal.xaml.
  5. Drag two TextBox controls from the toolbox into your user control. Name these controls HourTextbox and MinuteTextbox. Add two TextBlock controls to the right of each TextBox control, and set the text attribute of the controls to Hrs and Mins. Listing 11-5 shows the XAML for the control.
  6. Right-click an area of your XAML file, click on the View Code option, and add the .NET code shown in Listing 11-6.

    Listing 11-5.  DurationEditorInternal.XAML Contents

    File:ApressControlsVBDurationEditorInternal.xaml
      
    <UserControl x:Class="ApressControlsVB.DurationEditorInternal"               images
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
      
        <Grid x:Name="LayoutRoot" Background="White" Margin="0,0,0,0">
            <TextBox Name="HourTextbox"
                TextChanged="HourTextbox_TextChanged"/>                          images
            <TextBlock Text="Hrs" />                                             images
            <TextBox Name="MinuteTextbox"
                TextChanged="MinuteTextbox_TextChanged"/>
            <TextBlock Text="Mins"/>
        </Grid>
    </UserControl>

Listing 11-5 shows the XAML for the Visual Basic version of the custom control. In this code, the project is named ApressControlsVB, so if you’re re-creating this example, make sure that the Class setting images matches the class name of your custom control.

The line of code in images defines the TextBox that allows users to enter the number of hours in the time duration. The TextBlock control images shows the text Hrs to the right of the TextBox. The following lines of code repeat the same thing with the minute component of the time duration.

If you dragged the controls onto your control (as described in step 5), Visual Studio adds formatting attributes to your controls, such as HorizontalAlignment, TextWrapping, VerticalAlignment, Width and Height. Listing 11-5 omits these attributes to make the code easier to read, but when you’re re-creating this example, make sure to use the designer to include sizing and positioning details; otherwise, you’ll find that all the controls overlap each other.

Listing 11-6.  Code-Behind for the DurationEditorInternal Control

VB:
File:ApressControlsVBDurationEditorInternal.xaml.vb
  
Partial Public Class DurationEditorInternal
    Inherits UserControl
  
    Public Sub New
        InitializeComponent()
    End Sub
  
    '1 Code that registers the Dependency Property                                                images
    Public Shared ReadOnly DurationProperty As DependencyProperty =
        DependencyProperty.Register(
            "Duration",
            GetType(Integer),
            GetType(DurationEditorInternal),
            New PropertyMetadata(0, AddressOf OnDurationPropertyChanged))
  
    Public Property Duration As Integer
        Get
            Return MyBase.GetValue(DurationEditorInternal.DurationProperty)
        End Get
        Set(value As Integer)
            MyBase.SetValue(DurationEditorInternal.DurationProperty, value)
        End Set
    End Property
  
    '2 Code that runs when the underlying data value changes                                      images
    Public Shared Sub OnDurationPropertyChanged(
        re As DependencyObject, e As DependencyPropertyChangedEventArgs)
  
        Dim de As DurationEditorInternal = DirectCast(re, DurationEditorInternal)
        Dim ts As TimeSpan = TimeSpan.FromMinutes(CInt(e.NewValue.ToString))
  
        de.HourTextbox.Text = Math.Floor(ts.TotalHours).ToString
        de.MinuteTextbox.Text = ts.Minutes.ToString

    End Sub
  
    '3 Code that runs when the user changes the value                                    images
    Private Sub HourTextbox_TextChanged(
        sender As Object, e As TextChangedEventArgs)
           Duration = CalculateDuration()
    End Sub
  
    Private Sub MinuteTextbox_TextChanged(
        sender As Object, e As TextChangedEventArgs)
           Duration = CalculateDuration()
    End Sub
  
    Private Function CalculateDuration() As Integer                                     images
        Dim dur As Integer
        Try
            dur = (CInt(HourTextbox.Text) * 60) + CInt(MinuteTextbox.Text)
        Catch ex As Exception
        End Try
        Return dur
    End Function
  
End Class
  
C#:
File:ApressControlsCSDurationEditorInternal.xaml.cs
  
using System;
using System.Windows;
using System.Windows.Controls;
  
namespace ApressControlsCS
{
    public partial class DurationEditorInternal : UserControl
    {
        public DurationEditorInternal()
        {
            InitializeComponent();
        }
  
        // 1 Code that registers the Dependency Property                                images
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register(
                "Duration",
                typeof(int),
                typeof(DurationEditorInternal),
                new PropertyMetadata(0, OnDurationPropertyChanged));
  
        public int Duration
        {
            get {
                return (int)base.GetValue(
                    DurationEditorInternal.DurationProperty);
            }
            set {
                base.SetValue(
                    DurationEditorInternal.DurationProperty, value);
            }
        }
  
       // 2 Code that runs when the underlying data value changes                           images
       public static void OnDurationPropertyChanged(
            DependencyObject re, DependencyPropertyChangedEventArgs e)
        {
            DurationEditorInternal de = (DurationEditorInternal)re;
            TimeSpan ts = TimeSpan.FromMinutes(int.Parse(e.NewValue.ToString()));
            de.HourTextbox.Text = Math.Floor(ts.TotalHours).ToString();
            de.MinuteTextbox.Text = ts.Minutes.ToString();
        }
  
        //  3 Code that runs when the user changes the value                                images
       private void HourTextbox_TextChanged(
           object sender, TextChangedEventArgs e)
       {
           Duration = CalculateDuration();
       }
  
       private void MinuteTextbox_TextChanged(
           object sender, TextChangedEventArgs e)
       {
           Duration = CalculateDuration();
       }
  
        private int CalculateDuration()                                                     images
        {
            int dur = 0;
            try
            {
                dur = (int.Parse(HourTextbox.Text) * 60) +
                          int.Parse(MinuteTextbox.Text);
  
            }
            catch (Exception ex)
            {
  
            }
            return dur;
        }
  
    }
}

The code in this listing contains three distinct parts:

  • The first part of the code images registers the dependency property. The next section explains exactly how this code works.
  • The code in images runs when the underlying data value changes, and it updates the values of the Hour and Minute text boxes.
  • The code in images runs when the user updates the value of the Hour or Minute text box. It uses the CalculateDuration method images to convert the duration that the user has entered back into minutes, and it updates the dependency property.

Creating a dependency property

There are two tasks that you need to carry out to create a dependency property:

  1. Create a shared/static field of type DependencyProperty, and call the Register method to create an instance of a DependencyProperty object.
  2. Create a normal .NET property that backs your dependency property. This property stores the dependency property value by using the GetValue and SetValue methods that are inherited from DependencyObject. The DependencyProperty acts as the key for the GetValue and SetValue methods.

Let’s take a closer look at the code that registers your dependency property. To illustrate the arguments that you need to supply, here’s the C# code from Listing 11-6:

public static readonly DependencyProperty DurationProperty =
    DependencyProperty.Register(
        "Duration",                                                                      images
        typeof(int),                                                                     images
        typeof(DurationEditorInternal),                                                  images
        new PropertyMetadata(0, OnDurationPropertyChanged));                             images

This code defines a dependency property called DurationProperty. This name identifies your dependency property when you call the SetBinding method from your LightSwitch code. To adhere to .NET naming conventions, you should always make sure that the name of your dependency properties end with the word Property.

The Register method accepts four arguments, and returns an instance of a new dependency property object. The arguments that you need to supply to this method are as follows:

  • images The first argument specifies the .NET property that backs your dependency property. In this example, the .NET property is called Duration.
  • images The second argument specifies the data type of the .NET property.
  • images The third argument specifies the type that the dependency property belongs to.
  • images The fourth argument allows you to specify PropertyMetadata. The next section explains exactly what this does.

Specifying the behavior of dependency properties

The beauty of dependency properties is that you can specify default values, run code when the value of your dependency property changes, coerce values, and attach validation logic. The PropertyMetadata object is the key that makes all of this possible. As the following line shows, the PropertyMetadata constructor accepts four arguments:

new PropertyMetadata(0, OnDurationPropertyChanged, null, null)
  • The first argument specifies the default value. The dependency property uses the default value only if it’s unable to determine a value elsewhere. Dynamic value resolution chooses the default value as the very last resort. Therefore, it chooses the default value if a value hasn’t been explicitly set or if it isn’t able to find an inherited value. If you don’t want to set a default value, you can pass in null (C#) or nothing (VB).
  • The second argument specifies a Value Changed Callback Method. This is a method that’s called each time your dependency property value changes. In this example, OnDurationPropertyChanged is the Value Changed Callback Method. This method contains the logic that updates the values of the hour and minute text boxes.
  • The third argument allows you to specify a Coerce Callback Method. This allows you to run some code if a user attempts to set the value of your dependency property to a value that’s beyond what you intend your dependency property to store. For example, you could create a method that sets the duration value to 0 if a user attempts to supply a negative value. Your method could also set the duration to a predefined maximum value if the user attempts to supply a value that is too large. The Coerce Callback Method executes before the Value Changed Callback Method.
  • The fourth argument allows you to specify a Validation Callback Method. The method that you specify must return a Boolean, and allows you to write code that validates the value the user wants to set. If your method returns false, your dependency property will throw an ArgumentException.

Binding Dependency Properties to the Data Context

You almost have a custom control that you can use. Although you could now compile and use the duration control in your LightSwitch application, you’d need to write code on your LightSwitch screen that calls the SetBinding method, just like the code sample that you saw in Listing 11-1. You’ll now improve the duration control so that it binds directly to the associated data item on your LightSwitch screen. This improvement saves you from having to write extra code on your LightSwitch screen every time that you want to use the duration control.

To bind your DurationProperty to the associated data item on your LightSwitch screen, you’ll need to bind DurationProperty to the binding path of Value. The problem is that you have to specify the data-binding path in the XAML, but custom Silverlight controls don’t allow you to set dependency property values in your XAML markup. The trick to get around this problem is to wrap a parent control around your custom control. This parent control acts as a conduit and exposes the XAML that allows the control to bind to the associated data item on your LightSwitch screen.

To complete the duration control, you’ll need to carry out the following steps:

  1. Right-click your project in Solution Explorer, and choose the Add image New Item option.
  2. When the Add New Item dialog appears, create a new Silverlight User Control and name it DurationEditor.xaml.
  3. Drag the DurationEditorInternal control from your toolbox onto your DurationEditor control. If the control doesn’t show on your toolbox, try rebuilding your project. Listing 11-7 shows what your XAML looks like.

Listing 11-7.  Duration Editor Control

File:ApressControlsVBDurationEditor.xaml

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:ApressControlsVB"                                         images
    x:Class="ApressControlsVB.DurationEditor"
    mc:Ignorable="d">
    <Grid x:Name="LayoutRoot" Background="White">
        <local:DurationEditorInternal
            Duration="{Binding Value, Mode=TwoWay}"/>                                    images
    </Grid>
</UserControl>

In the code that’s shown, Visual Studio adds the local namespace images when you drag the DurationEditorInternal control onto your screen. If you’re re-creating this example by directly retyping the XAML that’s shown in this book, you’ll need to make sure that you enter the correct class names.

The definition of the DurationEditorInternal control images within the XAML allows you to data-bind the “normal” Duration property by specifying the binding path Value.

You’re now ready to build your ApressControls project. Save the output file (ApressControls.DLL) into a location that you can refer to later.

image Tip  Creating a wrapper control provides a simple, declarative way to bind a custom control to the data context. If you prefer not to create two controls, another way to achieve this is to create a custom control that includes a call to SetBinding in its constructor.

Applying the Duration Control on a Screen

Your duration control is now ready for use. To demonstrate it, you’ll now add it to a New Data Entry screen that allows engineers to enter timesheet records. To carry out this example, complete the following steps:

  1. Create a New Data Entry screen for the TimeTracking table. By default, LightSwitch will name this screen CreateNewTimeTracking.
  2. Change the DurationMins data item from Text Box to Custom Control.
  3. Open the Properties sheet, and click on the Change link. When the Add Custom Control dialog appears, click on the Browse button and select the ApressControls.DLL file that you built earlier. This allows you to select the DurationEditor control.

You can now run your application. Figure 11-9 shows the result of this screen, both at design time and at runtime.

9781430250715_Fig11-09.jpg

Figure 11-9. Duration control

Calling Custom Control Methods via Dependency Properties

Another practical reason for creating dependency properties is to use them as a trigger to call methods in your custom controls. As an example, let’s take a look at the WebBrowser control that you’ll find in the System.Windows.Controls namespace. As the name suggests, this control allows you to display web pages or HTML content on a screen. It’s important to note that this control works only in LightSwitch desktop applications and won’t work in browser applications.

The WebBrowser control includes a method called Navigate that allows you to supply a web address. When you call the Navigate method, the WebBrowser control will display the web page that you’ve specified. If you were to add the WebBrowser control to a LightSwitch screen, how exactly would you call the Navigate method? There isn’t a simple way to call custom control methods from a LightSwitch screen, so a practical solution is to wrap the Navigate method in a dependency property. By doing this, you can bind a web address to the WebBrowser control and automatically refresh the page shown on the WebBrowser control whenever the underlying web address changes.

To create the custom web browser control, carry out the following steps:

  1. Right-click your ApressControls project in Solution Explorer, and choose the Add image New Item option.
  2. When the Add New Item dialog appears, choose Silverlight User Control and name your control WebBrowser.xaml.
  3. Amend the XAML for your WebBrowser control as shown in Listing 11-8.

    Listing 11-8.  WebBroswer Custom Control

    File:ApressControlsVBWebBrowser.xaml
      
    <UserControl x:Class="ApressControlsVB.WebBrowser"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="300" d:DesignWidth="400">
      
        <Grid x:Name="LayoutRoot" Background="White">
            <WebBrowser  Name="wb" HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch" />
        </Grid>
    </UserControl>
  4. Right-click your XAML file, click on the View Code option, and add the .NET code that’s shown in Listing 11-9.

    Listing 11-9.  WebBrowser Control .NET Code

    VB:
    File:ApressControlsVBWebBrowser.xaml.vb
      
    Partial Public Class WebBrowser
        Inherits UserControl
      
        Public Sub New
            InitializeComponent()
        End Sub
      
        '1 Code that registers the Dependency Property
        Public Shared ReadOnly URIProperty As DependencyProperty =                images
            DependencyProperty.Register(
                "uri",
                GetType(Uri),
                GetType(WebBrowser),
                New PropertyMetadata(Nothing, AddressOf OnUriPropertyChanged))
      
        Public Property uri() As Uri                                              images
            Get
                Return DirectCast(GetValue(URIProperty), Uri)
            End Get
            Set(value As Uri)
                SetValue(URIProperty, value)
            End Set
        End Property
      
        '2 Code that runs when the underlying URL changes
        Private Shared Sub OnUriPropertyChanged(
            re As DependencyObject, e As DependencyPropertyChangedEventArgs)
      
            If e.NewValue IsNot Nothing Then
                Dim web As WebBrowser =
                   DirectCast(re, WebBrowser)
                web.wb.Navigate(DirectCast(e.NewValue, Uri))                      images
            End If
        End Sub
      
    End Class
      
    C#:
    File:ApressControlsCSWebBrowser.xaml.cs
      
    using System;
    using System.Windows;
    using System.Windows.Controls;
      
    namespace ApressControlsCS
    {
        public partial class WebBrowser : UserControl
        {
            public WebBrowser()
            {
                InitializeComponent();
            }
      
           // 1 Code that registers the Dependency Property
            public static readonly DependencyProperty URIProperty =                        images
                DependencyProperty.Register(
                    "uri",
                    typeof(Uri),
                    typeof(WebBrowser),
                    new PropertyMetadata(null, OnUriPropertyChanged));
      
            public Uri uri                                                                 images
            {
                get { return (Uri)GetValue(URIProperty); }
                set { SetValue(URIProperty, value); }
            }
      
           // 2 Code that runs when the underlying URL changes
            private static void OnUriPropertyChanged(
                DependencyObject re, DependencyPropertyChangedEventArgs e)
            {
      
                if (e.NewValue != null)
                {
                    WebBrowser web = (WebBrowser)re;
                    web.wb.Navigate((Uri)e.NewValue);                                      images
                }
      
            }
        }
    }
      

Much of the code here is similar to the other samples that you’ve seen in this chapter. The .NET code includes the same dependency property logic that you saw earlier. The code creates a dependency property called URIProperty images, and bases it on a normal .NET property called uri images. The dependency property’s metadata specifies a Value Callback Method that’s called OnUriPropertyChanged. Whenever the underlying web address data changes, the OnUriPropertyChanged method executes and calls the WebBrowser control’s Navigate method images to update the web page that’s shown in the control.

You’re now ready to build and use your custom control. The custom web-browser control provides a useful tool when you’re developing desktop applications. Chapter 14 explains how you can use this control to show HTML reports on LightSwitch screens.

Calling Screen Code from a Custom Control

As you might recall from Chapter 1, controls are simply views of the data and contain minimal business logic. In keeping with Model-View-ViewModel (MVVM) principles, it’s good practice to place your business logic in screen methods and to call these from your custom controls, rather than placing it directly in the custom control.

In this example, you’ll see how to create a stylized Save button. When the user clicks on this button, it calls business logic on your LightSwitch screen rather than logic on the control itself. To create this control, carry out the following steps:

  1. Right-click your ApressControls project in Solution Explorer, and choose the Add image New Item option.
  2. When the Add New Item dialog appears, choose Silverlight User Control and create a control called SaveControl.xaml.
  3. Amend the XAML for your SaveControl control as shown in Listing 11-10. This XAML applies a green style to your control, but you can use the designer to apply a different style.

    Listing 11-10.  SaveControl XAML

    File:ApressControlsVBSaveControl.xaml
      
    <UserControl x:Class="ApressControlsVB.SaveControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="300" d:DesignWidth="400">
      
        <Grid x:Name="LayoutRoot" Background="White">
            <Button Content="Save Data" Height="125" HorizontalAlignment="Left"
                Margin="34,63,0,0"  Name="CustomButton1"
                VerticalAlignment="Top" Width="295" Background="#FF1FC453"
                Click= "CustomButton_Click" />
        </Grid>
    </UserControl>
      
  4. Right-click your project in Solution Explorer, choose the Add Reference option, and add references to the Microsoft.LightSwitch.Client.dll and Microsoft.LightSwitch.dll files. The default location of these files on a 64-bit machine is C:Program Files (x86)Microsoft Visual Studio 11.0Common7IDELightSwitchClient.
  5. Right-click the contents of your SaveControl.xaml file, click on the View Code option, and add the .NET code that’s shown in Listing 11-11.

    Listing 11-11.  Code to Call a Screen Method Called SaveData

    VB:
    File:ApressControlsVBSaveControl.xaml.vb
      
    Imports Microsoft.LightSwitch.Presentation
      
    Partial Public Class CustomButton
        Inherits UserControl
      
        Public Sub New()
            InitializeComponent()
        End Sub
      
        Private Sub CustomButton_Click(
            sender As System.Object, e As System.Windows.RoutedEventArgs)                         images
      
            ' Get a reference to the LightSwitch Screen
            Dim objDataContext = DirectCast(Me.DataContext, IContentItem)
            Dim clientScreen = DirectCast(
                objDataContext.Screen, Microsoft.LightSwitch.Client.IScreenObject)                images
      
            Me.CustomButton1.IsEnabled = False                                                    images
      
            clientScreen.Details.Dispatcher.BeginInvoke(                                          images
                Sub()
                    Try
                        ' Call the Method on the LightSwitch screen
                        clientScreen.Details.Commands.Item("SaveData").Execute()                  images
                    Finally
                        SetEnabled()
                    End Try
                End Sub)
      
        End Sub
      
        Private Sub SetEnabled()
            Me.Dispatcher.BeginInvoke(                                                            images
                Sub()
                    Me.CustomButton1.IsEnabled = True
                End Sub
            )
        End Sub
      
    End Class
      
    C#:
    File:ApressControlsCSSaveControl.xaml.cs
      
    using System.Windows;
    using System.Windows.Controls;
    using Microsoft.LightSwitch.Presentation;
      
    namespace ApressControlsCS
    {
        public partial class CustomButton : UserControl
        {
            public CustomButton()
            {
                InitializeComponent();
            }
      
            private void CustomButton_Click(System.Object sender,
               System.Windows.RoutedEventArgs e)                                              images
            {
                // Get a reference to the LightSwitch Screen
                var objDataContext = (IContentItem)this.DataContext;
      
                var clientScreen =
                (Microsoft.LightSwitch.Client.IScreenObject)objDataContext.Screen;            images
      
                this.CustomButton1.IsEnabled = false;                                         images
      
                // Call the Method on the LightSwitch screen
                clientScreen.Details.Dispatcher.BeginInvoke(                                  images
                    () =>
                    {
                        try
                        {
                            clientScreen.Details.Commands["SaveData"].Execute();              images
                        }
                        finally
                        {
                            this.SetEnabled();
                        }
                    });
            }
      
            private void SetEnabled()
            {
                this.Dispatcher.BeginInvoke(() =>                                             images
                {
                    this.CustomButton1.IsEnabled = true ;
                });
            }
        }
    }
      

When a user clicks the custom button, the application executes the CustomButton_Click method images. All custom controls data-bind to objects that implement the IContentItem interface. This object allows you to access the control’s parent screen via the Screen member images. The initial part of this code disables the custom button to prevent users from clicking on it further images.

The next block of code calls the method that you’ve defined on your LightSwitch screen. This could be a long-running data operation, so you’ll need to execute this code on the screen’s logic thread images. This prevents the process from locking up the UI and leaving your application unresponsive. The next line of code accesses a screen command called SaveData via the screen’s Details.Commands collection, and runs the command by calling its Execute method images. If the command succeeds, the code in the finally block re-enables the custom button by calling a method called SetEnabled.

The code in the SetEnabled method needs to be invoked on the UI thread because it performs logic that modifies the UI images. You can now build your project.

To show you how to use this control on a screen, let’s add it to the timesheet screen that you created earlier (as shown in Figure 11-9). To do this, you’ll need to carry out the following steps:

  1. Open the CreateNewTimeTracking screen that you created earlier in this chapter. (See Figure 11-9.)
  2. Click on the Add Data Item button, and create new method called SaveData.
  3. Click on the Add Data Item button, create a local property of data type String, and name it SaveDataButton. Uncheck the Is Required check box.
  4. Add SaveDataButton to your screen, and change the control type from Text Box to Custom Control.
  5. In the Properties sheet, click on the Change link. When the Add Custom Control dialog appears, click on the Browse button and select your ApressControls.DLL file. After you do this, choose the CustomButton control. Set the Label Style value for the data item to Collapsed. Figure 11-10 shows how your screen now looks.

    9781430250715_Fig11-10.jpg

    Figure 11-10. Custom button screen in design view

  6. Right-click the SaveData method in the Screen Member list, and click on the Edit Execute Method option. Now enter the code shown in Listing 11-12.

    Listing 11-12.  Screen Code Called by the Custom Button

    VB:
    File:HelpDeskVBClientUserCodeCreateNewTimeTracking.vb
      
    Private Sub SaveData_Execute()
        Me.Save()
        ShowMessageBox("Data Saved")
    End Sub
      
    C#:
    File:HelpDeskCSClientUserCodeCreateNewTimeTracking.cs
      
    partial void SaveData_Execute()
    {
        this.Save();
        //Add additional screen logic here
        this.ShowMessageBox("Data Saved");
    }

Note that the screen method that you want to call must be added using the Add Data Item dialog box in the screen designer, as described in step 2. If you directly create a method in your code file, your custom control won’t find the method in the screen’s Details.Commands collection and your code won’t work. The name of the method shown in Listing 11-12 is SaveData_Execute. But when you access commands via the screen’s Details.Commands collection, you would refer to the method without the _Execute suffix (as shown in Listing 11-11 images)

You can now run your application. Figure 11-11 shows how your screen looks at runtime. In case you’re reading the ‘black and white’ print version of this book, Figure 11-11 illustrates how the Save button includes a bright green background color.

9781430250715_Fig11-11.jpg

Figure 11-11. Custom button control

The custom control that we’ve created contains just a button, and the example is designed to show you how to call screen code from a custom control. More complex custom controls might feature multiple UI elements, and a button (or some other event) that calls screen code would make up part of the custom control. In this example, you’ll notice how you’ve bound this control to local string property. In Chapter 12, you’ll learn how to adapt this code sample to enable you to bind directly to your screen command.

image Tip  In this example, we’ve hard-coded the name of the command (SaveData) into our custom control. You can make this example more reusable by passing the command name into the custom control rather than hard-coding it. Using the techniques that you’ve learned in this chapter, you could do this by creating a dependency property. By doing this, you can use the data-binding mechanisms that you’ve seen to bind your custom control to the command object names in LightSwitch.

Summary

In Rich Client applications, you can customize the way that users enter and view data by using custom Silverlight controls. This means that you’re not just limited to using the standard set of controls that LightSwitch provides. For example, you could write your own controls to show data by using charts, Slider controls, or other more-sophisticated input-entry controls.

One of the simplest ways to enrich your application is to use the controls in the System.Windows.Controls namespace. The controls that you can use from this namespace include the combo box, password, and web browser controls. To apply a custom control to a data item on your screen, use the data item’s drop-down box and set the control type to Custom Control. If you apply a control directly from the System.Windows.Controls namespace, you’ll need to write code that binds your control to data by using the FindControl method and calling the SetBinding method on the IContentItem object that FindControl returns. This method requires you to supply a dependency property and a binding path. To bind a dependency property to the value that’s stored by the data item, you would use the binding path of Value. Other binding path expressions enable you to bind your control to other entities, properties, or local screen properties.

If you want to bind screen data to a property on a custom control, that property must be a dependency property. Unlike normal properties, dependency properties don’t hold a concrete value. .NET derives their values from multiple sources, and the benefit of this is that it enables your application to use memory more efficiently.

If you’re creating your own custom control, you’ll need to create a dependency property if you want to consume LightSwitch data from within your custom control.

To create a dependency property, you would define a dependency property object and instantiate it by calling the Register method. You’ll also need to define a normal .NET property that backs your dependency property. This property stores your dependency property value in a dictionary that the base class DependencyObject provides.

If you want to use a custom control that you’ve created and save yourself from having to write screen code that performs the data binding for each instance of your control by calling the SetBinding method, you can wrap your control in a parent control and specify the data binding in the XAML for your parent control.

Another reason for using dependency properties is to provide the ability to call methods in your custom control. As a demonstration of this, you saw how to wrap the web-browser control’s navigate method inside a dependency property. This allows the control to update its display whenever the underlying data changes.

If you want to bind a data item to a dependency property but the data types don’t match, you can solve this problem by writing a value converter. You’ve seen an example of how to write a value converter that converts integers to doubles, and vice versa.

Finally, you’ve learned how to call a screen method from a custom control. In keeping with good practice, this allows you to keep your custom control free of business logic. The custom-control code uses the screen’s Details.Commands collection to find the screen method. After it obtains a reference to the screen method, it can call its execute method to run the screen method.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.5.15