CHAPTER 12

image

Creating Control Extensions

Custom controls provide a powerful way for you to customize the UI of your Silverlight application. However, a disadvantage is that they can be quite difficult to work with. Each time you want to use a custom control, you need to manually select the DLL that contains your control and write the .NET code that performs the data binding. By investing some time and wrapping your control in an extension, you can avoid all this effort and make it easier to work with your control. But that’s not the only advantage. Control extensions allow you to customize your UI in ways that wouldn’t be possible with stand-alone custom controls. For example, writing a layout control (like RowsLayout and ColumnsLayout) is something that you can accomplish only through a custom extension. And another advantage is that you can more easily share controls that are packaged as extensions, and even sell your work to other developers. The topics that you’ll cover in this chapter include

  • How to set up, build, and deploy extensions
  • How to build control extensions and add control attributes
  • How to extend the property sheets that appear in Visual Studio’s screen designer

This chapter teaches you how to create control extensions and reuses some of the examples that you covered in the preceding chapter. You’ll also learn how to allow developers to customize the behavior of your control by adding control attributes.

Using LightSwitch Extensions

To help you understand how LightSwitch extensions work, let’s begin by looking at how to use extensions that other people have created. By the end of this chapter, you’ll be able to create your own extensions that you can use in your LightSwitch projects.

Installing LightSwitch Extensions

To demonstrate how to install and use extensions, this section shows you how to use a control that was developed by Microsoft: the “Many-to-Many control.” This control allows users to view and edit Many-to-Many data by using a check-box list control. To use this control, you’ll need first to download it from the following web page:

http://code.msdn.microsoft.com/Many-to-Many-Control-for-52cd8c6c

LightSwitch extensions are packaged in Visual Studio Extension files—these are files that end with a VSIX file extension. To install the extension, simply double-click the VSIX file that you’ve downloaded in Windows Explorer. After the installer finishes, you can manage the extensions that you’ve installed by using Visual Studio’s Extensions And Updates dialog. (You’ll find a shortcut to this dialog in Visual Studio’s Tools menu.) This dialog allows you to disable or uninstall extensions, as shown in Figure 12-1.

9781430250715_Fig12-01.jpg

Figure 12-1. Extensions And Updates dialog

To use an extension in your LightSwitch project, you’ll need to first enable it in the properties of your LightSwitch project. On the Extensions tab (shown in Figure 12-2), use the check boxes to enable the extensions that you want to use. You can also check the Use In New Projects check box to automatically enable the extension in any new projects that you create. This saves you from having to go to the Extensions tab and manually enable the extension each time you create a new project.

9781430250715_Fig12-02.jpg

Figure 12-2. Enabling extensions in your LightSwitch project

Using the Many-to-Many Data Control

Once you install the Many-to-Many Control control, you can use it on your screens like any other built-in control. The HelpDesk system allows managers to enter skills and assign those skills to an engineer. To demonstrate this control, here’s how to modify the Engineer Details screen to allow managers to assign skills to engineers. To set up your application, you’ll need to carry out the following pre-requisites (you may have already completed some of these in earlier chapters):

  1. Create the two tables: Skills and EngineerSkills. Create the relationships between these two tables, and also create the relationship between the EngineerSkills and Engineer tables. Refer to Chapter 2 (Figure 2-14) for full details.
  2. Create an Editable Grid Screen for the Skills table. This will allow you to enter skill records at runtime.

Once you’ve set up your tables, you’ll need to create an Engineer Details screen (if you haven’t done so already). In the Add New Screen dialog, make sure that you’ve included the EngineerSkills data by checking the Engineer Skills check box in the Additional Data section. If you’ve already created an Engineer Details screen, you can click on the AddEngineerSkills link that appears inside your Engineer property in the screen member list.

In the screen designer, drag the EngineerSkills collection onto your screen. To lay out your screen more tidily, you can add the collection to a tab, as shown in Figure 12-3. By default, LightSwitch displays the data collection by using a Data Grid. Use the drop-down box to change the control type from a Data Grid to Checkbox List.

9781430250715_Fig12-03.jpg

Figure 12-3. Using the Many-to-Many Control

That’s all there is to using a custom control extension. You can now run your application. Figure 12-4 shows what the control looks like at runtime.

9781430250715_Fig12-04.jpg

Figure 12-4. Many-to-Many Control as shown on screen at runtime

EXERCISE 12.1 – USING CUSTOM EXTENSIONS

Besides the Many-to-Many control, Microsoft has produced several other great extensions. Check out the following two extensions:

Preparing Your Computer to Develop Extensions

Now that you know how to install and use extensions, let’s prepare your computer so that you can develop your own extensions. To get started, you’ll need to install the Visual Studio SDK, followed by the LightSwitch Extensibility Toolkit. You can download these components from the following web sites:

Once you install the Extensibility Toolkit, you’ll need to complete the installation by copying a file called Microsoft.LightSwitch.Toolkit.targets into your MSBuild folder. The Extensibility Toolkit’s readme file provides full instructions. After you install both components, Visual Studio’s File image New Project dialog will include the C# and VB Extension Library Templates (as shown in Figure 12-5).

9781430250715_Fig12-05.jpg

Figure 12-5. Adding a new Extension Library project

Once you create an Extension Library Project, you’ll find that it contains seven individual projects. The starting point that allows you to extend your library is the LSPKG project. This allows you to create extension items by choosing the right-click Add New Item option (shown in Figure 12-6). This opens the Add New Item dialog, which allows you to create one of the six extension items. You’ll use this dialog throughout the next two chapters to create extension items.

9781430250715_Fig12-06.jpg

Figure 12-6. Right-click the LSPKG project to create a new item

In addition to the LSPKG project, the remaining projects and their purposes are as follows:

  • Client: This contains the Silverlight custom control XAML and .NET code.
  • Client.Design: This contains custom code that extends the Silverlight runtime designer.
  • Common: This contains the lsml metadata that describes your extension and the common .NET code that runs on both the client and server.
  • Design: This contains custom code that extends the Visual Studio design surface. Custom screen templates are also stored here.
  • Vsix: This contains the build output that allows users to install your extension.

Understanding Custom Control Types

There are five custom control types that you can create, which are shown in Table 12-1. You’ll be familiar with these control types because you’ll have used controls that match these control types throughout your day-to-day use of LightSwitch.

Table 12-1. Custom Control Extension Types

Control Type Purpose and Example Control
Value Represents a single scalar value (text box, label)
Details Represents an entity (AutoCompleteBox)
Command A control that initiates an action (button, hyperlink)
Collection Represents a collection of data (Datagrid)
Group A container for other controls (RowsLayout)

Let’s take a look at the general process that you would follow to create a control extension. The first step is to right-click your LSPKG project and choose the option to add a new control item (shown in Figure 12-6). Next, you would carry out the following tasks, which are fully described in this chapter:

  1. Set the Control Type, Display Name, and Description for your control extension.
  2. Define your Supported Data Types (for collection and value types only).
  3. Write the XAML that defines the UI for your control.
  4. Set the icons for your control.

After you create a custom extension, you can extend it further by adding custom properties and property editors. An example of a custom property is the Show As Link and Target Screen properties that you’ll find in Visual Studio’s property sheet for the label control. You can also add read only support, handle keyboard navigation, and allow developers to access your control in code.

Creating Value Controls

Value controls allow users to view or edit a single value. In this section, you’ll find out how to wrap the duration control from Chapter 11 in a custom control extension. You’ll learn how to allow developers to access your control in code and find how to optimize the performance of your control when it’s used on a data grid.

To create the Duration Editor control extension, carry out the following steps:

  1. Start Visual Studio, create a new Extension Library Project, and name it ApressExtension.
  2. Right-click your LSPKG project, create a new Control item, and name it DurationEditor.xaml.
  3. Right-click your Client project, select the Add Reference option, and add a reference to the ApressControls.dll assembly that you created in Chapter 11.
  4. Modify your DurationEditor.xaml file as shown in Listing 12-1.

Listing 12-1.  XAML Markup for a Value Control

File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsDurationEditor.xaml
  
<UserControl
   x:Class="ApressExtensionVB.Presentation.Controls.DurationEditor"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:apControls="clr-namespace:ApressControlsVB;
       assembly=ApressControlsVB"                                                   images
   xmlns:converter="clr-namespace:ApressExtensionVB.Presentation.Controls"          images
   xmlns:slu=
       "clr-namespace:Microsoft.LightSwitch.Utilities.SilverlightUtilities;
         assembly=Microsoft.LightSwitch.Client"
   xmlns:framework="clr-namespace:Microsoft.LightSwitch.Presentation.Framework;
      assembly=Microsoft.LightSwitch.Client">                                       images
  
    <UserControl.Resources>
        <converter:SplitMinutes x:Key="formatter"/>                                 images
    </UserControl.Resources>
  
   <framework:StatesControl HorizontalAlignment="Stretch" >                         images
        <apControls:DurationEditorInternal
        Duration="{Binding Value, Mode=TwoWay}"                                     images
            HorizontalAlignment="Stretch"
            Name="DurationControl"
            ToolTipService.ToolTip="{Binding Description}" />
    </framework:StatesControl>
</UserControl>
    

After you complete step one, Visual Studio opens your XAML file in the code editor window. The Add New Item dialog creates a custom control that includes a TextBox control. If you’re in a hurry and want to skip some of the steps in this section, you can create a control extension that uses a TextBox control rather than the duration control from the ApressControls.dll assembly.

This XAML defines the presentation logic for your custom control and contains the following functionality:

  • Support for the duration control: This code includes the duration control from Chapter 11, although as mentioned earlier, you can use a TextBox control instead to shortcut this example.
  • State Handling: LightSwitch can hook into your control and show loading and error states. When a user enters invalid data in a screen, LightSwitch displays a red border around your control. This example includes the logic that supports this behavior.
  • Data Grid Optimization:  If you add this control to a data grid, the control appears in read-only mode and becomes active only when the user selects the cell that contains your control.

The first part of this code adds a namespace reference to the ApressControls assembly images. This allows you to use the duration control from Chapter 11 in your control extension. The second reference images refers to a value converter that converts integer durations into their hour and minute representations. You’ll create the value converter in Listing 12-5 so that, at this moment, Visual Studio will highlight the line as an error. The reference to the Microsoft.LightSwitch.Presentation.Framework assembly images allows LightSwitch’s State Handling mechanism to work. Note that namespace references mustn’t contain line breaks—we’ve added these in the book to allow the code to fit the page. For LightSwitch’s State Handling to work correctly, you must enclose the contents of your control inside a StatesControl element images.

The main part of this control defines an instance of a DurationEditorInternal control. It uses the data-binding path of Value to set the Duration dependency property images. Value represents the data context value of your custom control (that is, the value of the data item that your custom control binds to). The code also data binds the tooltip of the Duration Editor control to the description of your data item.

The UserControl.Resources tag images defines the value converter that converts integer durations into a format that includes hours and minutes. At the moment, you won’t find any XAML in the control that uses this value converter—you’ll use this value converter later when you write the code that optimizes your control for data-grid use. You’ll also receive an error on this line because it refers to the value converter that you’ll create in Listing 12-5.

image Note  The XAML examples in this book feature the VB version of the source code, and the listings refer to the namespace ApressExtensionVB. If you’re re-creating these examples in C#, make sure to replace the references that include a VB suffix with the correct name.

Specifying Which Data Types to Support

Value custom controls are designed to work with specific data types and, in our case, the Duration Editor works only with numeric data. The supported data types for a custom control are defined in an LSML file that corresponds with your control. You’ll find this file in the Metadata folder of your Common project.

To specify that your control should work only with integer data, open the DurationEditor.lsml file and modify the SupportedDataType element as shown in Listing 12-2 images.

Listing 12-2.  Specifying Control Data Types

File:ApressExtensionVBApressExtensionVB.CommonMetadataControlsDurationEditor.lsml
  
  
<?xml version="1.0" encoding="utf-8" ?>
<ModelFragment
  xmlns="http://schemas.microsoft.com/LightSwitch/2010/xaml/model"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Control Name="DurationEditor"
    SupportedContentItemKind="Value"
    DesignerImageResource="ApressExtensionVB.DurationEditor::ControlImage">
    <Control.Attributes>
      <DisplayName Value="DurationEditor" />
    </Control.Attributes>
    <Control.SupportedDataTypes>
      <SupportedDataType DataType=":Int32"/>                      images
    </Control.SupportedDataTypes>
  </Control>
</ModelFragment>

If you want your control to support additional data types, you can define this by adding additional SupportedDataType elements. The SupportedDataType values that you can use include :String, :Boolean, and :Date . Appendix B shows a full list of valid values that you can use.

Supporting the FindControl Method

Developers should be able to access the underlying Silverlight control in their screen code by calling the IContentItemProxy object’s FindControl method. Refer to Chapter 7 if you want to remind yourself how this works.

To add support for the FindControl method, open the code file for your control. (You can do this by opening the DurationEditor.xaml file in the editor, and choosing the right-click View Code option.) Now edit your code by adding the code that’s shown in Listing 12-3.

Listing 12-3.  Adding Support for the FindControl Method

VB:
File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsDurationEditor.xaml.vb
  
  
Partial Public Class DurationEditor
    Inherits UserControl
    Implements IContentVisual                                                  images
  
    '1. Implement the Control property
    Public ReadOnly Property Control As Object
        Implements Microsoft.LightSwitch.Presentation.IContentVisual.Control
        Get
            Return Me.DurationControl                                          images
        End Get
    End Property
  
    '2. Implement the Show method
    Public Sub Show() Implements
       Microsoft.LightSwitch.Presentation.IContentVisual.Show
          Throw New NotImplementedException(
              "Show method not implemented")                                   images
    End Sub
  
    'the rest of the DurationEditor class appears here...
  
C#:
File:ApressExtensionCSApressExtensionCS.ClientPresentationControlsDurationEditor.xaml.cs
  
public partial class DurationEditor : UserControl,
    IContentVisual                                                             images
{
    //1. Implement the Control property
    object IContentVisual.Control
    {
        get { return this.DurationControl;}                                    images
    }
  
    //2. Implement the Show method
    void IContentVisual.Show()
    {
        throw new NotImplementedException(
           "Show method not implemented");                                     images
    }
  
    //the rest of the DurationEditor class appears here...

When you first add a control item, the Add New Item dialog creates two classes in your custom control’s code file: a class that represents your user control, and a supporting factory class. The code that’s shown in Listing 12-3 modifies your user control class so that it implements the IContentVisual interface images. The next block of code implements the Control property. This returns a reference to your Silverlight control and, in this example, DurationControl images refers to the name of the DurationEditorInternal control that’s defined in Listing 12-1. At this moment, Visual Studio will show a warning against the reference to DurationControl (if you’re using VB). This is because you haven’t yet built your project, and you can safely ignore this warning for the time being.

If you were writing a control that contained hidden UI elements (for example, an expander control), the Show method images would include code that reveals the hidden content. Because this doesn’t apply to this example, the code raises an exception if a developer attempts to call the Show method.

Setting the Control Icon

By default, LightSwitch’s screen designer would identify your custom control with a green spherical icon (as shown in Figure 12-7). The Extension Toolkit’s control template generates a PNG icon file for every custom control that you create and names it after the name of your control.

9781430250715_Fig12-07.jpg

Figure 12-7. Default green icon

To change this icon, replace the DurationEditor.png file that you’ll find in your Design project beneath the following path: ApressExtensionVB.DesignResourcesControlImages.

Optimizing Controls for Data-grid Use

If you add the control that you’ve built so far to a Data Grid, LightSwitch creates an editable instance of your control on every row. (See Figure 12-8.) There are three problems with this behavior. First, it doesn’t match the default behavior of the standard LightSwitch controls. In Figure 12-8, notice how the grid displays the Entry Date as a read-only piece of text and makes the control editable only when the cell has the focus. The second relates to performance—it isn’t efficient to show multiple editable controls on every row in a data grid. And the final problem relates to usability. Each editable control introduces a tab stop. If the user isn’t using a mouse and attempts to navigate your screen by using the keyboard, the process can be very awkward. Let’s suppose that your Data Grid contains 45 records (the default page size), and a user sets the focus to the first row in the grid. To get to the Save button that’s shown after the grid, the user would have to press the tab key 90 times! (This assumes of course that unlike you, your user doesn’t know how to use the <shift> <tab> key combination to navigate a screen in reverse order.)

9781430250715_Fig12-08.jpg

Figure 12-8. The Duration Editor control is shown as Editable for each row

Because of these problems, it’s a good idea to write custom controls that display only the text value when they’re rendered on a data grid. To do this, you’ll need to create a display-mode template that contains the XAML that presents your data by using read-only controls. You would do this by modifying the return value of a method called GetDisplayModeDataTemplate, which you’ll find in the factory class for your control. To make it simple for you to implement this logic, LightSwitch automatically creates a method stub that identifies where you need to write this code.

Before you write your display-mode template, you need to create a custom Silverlight control that displays your duration data using a Silverlight text-block control. In your Client project, add a new Silverlight User Control in your Presentation image Controls folder and name it DurationViewer.xaml. Now modify the contents of this file, as shown in Listing 12-4.

Listing 12-4.  Creating a Display Mode Control

File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsDurationViewer.xaml
  
<UserControl x:Class="ApressExtensionVB.Presentation.Controls.DurationViewer"
    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:converter="clr-namespace:ApressExtensionVB.Presentation.Controls"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <converter:SplitMinutes x:Key="formatter" />                           images
    </UserControl.Resources>
    <StackPanel
        Width="{Binding Properties[Microsoft.LightSwitch:RootControl/Width]}"
        Height="{Binding Properties[Microsoft.LightSwitch:RootControl/Height]}"
        VerticalAlignment ="{Binding
            Properties[Microsoft.LightSwitch:RootControl/VerticalAlignment]}">

        <TextBlock                                                             images
           Text="{Binding Value, Mode=OneWay,                                  images
                  Converter={StaticResource formatter}}"
           TextAlignment="{Binding
                  Properties[Microsoft.LightSwitch:RootControl/TextAlignment]}">
        </TextBlock>
    </StackPanel>
</UserControl>

The custom control in Listing 12-4 displays the duration text in a TextBlock control images. It uses the data-binding path of Value to bind the Text property to the underlying content item. The data binding applies a value converter images to convert the integer duration values to a string value that’s split into hours and minutes. For example, it converts the integer value 90 into the string value 1hr 30min. The code in the first part of this file defines the value converter images.

To create this value converter, create a new class called SplitMinutes in the Presentation image Controls folder of your Presentation project. (Make sure to add your class file to the Presentation image Controls folder—if you do not, your code might not compile properly.) Now add the code that’s shown in Listing 12-5.

Listing 12-5.  Value Converter That Returns a String Representation of a Time Duration

VB:
File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsSplitMinutes.vb
  
Imports System.Windows.Data
Namespace Presentation.Controls
Public Class SplitMinutes
    Implements IValueConverter
  
    Public Function Convert(
            value As Object, targetType As System.Type,
            parameter As Object, culture As System.Globalization.CultureInfo
        ) As Object Implements System.Windows.Data.IValueConverter.Convert
  
        Dim ts As TimeSpan = TimeSpan.FromMinutes(CInt(value))
        Return String.Format(
           "{0}hrs {1}mins", Math.Floor(ts.TotalHours), ts.Minutes)
  
    End Function
  
    Public Function ConvertBack(value As Object, targetType As System.Type,
       parameter As Object, culture As System.Globalization.CultureInfo) As ←
       Object Implements System.Windows.Data.IValueConverter.ConvertBack
           Return Nothing
    End Function
  
End Class
  
End Namespace
  
C#:
File:ApressExtensionCSApressExtensionCS.ClientPresentationControlsSplitMinutes.cs

using System;
using System.Windows.Data;
  
namespace ApressExtensionCS.Presentation.Controls
{
    public class SplitMinutes : IValueConverter
    {
        public object Convert(object value, Type targetType,
          object parameter, System.Globalization.CultureInfo culture)
        {
            TimeSpan ts = TimeSpan.FromMinutes(int.Parse(value.ToString() ));
            return String.Format(
                "{0}hrs {1}mins", Math.Floor(ts.TotalHours), ts.Minutes);
        }
  
        public object ConvertBack(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }
}

This code contains the standard logic that you’d find in a value converter. If you need help in understanding how this code works, Chapter 11 (Listing 11-3) provides further explanation.

Once you’ve created your DurationViewer control and SplitMinutes value converter, open the code file for your DurationEditor control. Now add the code that’s shown in Listing 12-6 to the IControlFactory Members region of your file.

Listing 12-6.  Returning a Read-Only Display-Mode Template

VB:
File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsDurationEditor.xaml.vb
  
#Region "IControlFactory Members"
  
Private Const DisplayModeControlTemplate As String =                          images
"<DataTemplate" & _
   " xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""" & _
   " xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""" & _
   " xmlns:ctl=""clr-namespace:ApressExtensionVB.Presentation.Controls;
                   assembly=ApressExtensionVB.Client"">" & _
    "<ctl:DurationViewer/>" & _
    "</DataTemplate>"
  
Private cachedDisplayDataTemplate As DataTemplate
  
Public Function GetDisplayModeDataTemplate(                                   images
   ByVal contentItem As IContentItem) As DataTemplate Implements ←
      IControlFactory.GetDisplayModeDataTemplate
  
      ' provide the display mode template
      If Me.cachedDisplayDataTemplate Is Nothing Then
         Me.cachedDisplayDataTemplate =
            TryCast(XamlReader.Load(
               DurationEditorFactory.DisplayModeControlTemplate),
                  DataTemplate)
      End If
   Return Me.cachedDisplayDataTemplate
  
End Function
  
#End Region
  
C#:
File:ApressExtensionCSApressExtensionCS.ClientPresentationControlsDurationEditor.xaml.cs
  
#region IControlFactory Members
  
private const string DisplayModeControlTemplate =                              images
"<DataTemplate" +
   @" xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""" +
   @" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""" +
   @" xmlns:ctl=""clr-namespace:ApressExtensionCS.Presentation.Controls;
                assembly=ApressExtensionCS.Client"">" +
   @"<ctl:DurationViewer/>" +
   @"</DataTemplate>";
  
private DataTemplate cachedDisplayDataTemplate;

public DataTemplate GetDisplayModeDataTemplate(IContentItem contentItem)      images
{
    // provide the display mode template
    if (null == this.cachedDisplayDataTemplate)
    {
        this.cachedDisplayDataTemplate =
            XamlReader.Load(
               DurationEditorFactory.DisplayModeControlTemplate) as DataTemplate;
    }
    return this.cachedDisplayDataTemplate;
}

The first part of this code defines a constant variable called DisplayModeControlTemplate images. This variable contains the data template that defines the Display Mode content of your custom control. The XAML in this data template presents your data by using the DurationViewer control that you created in Listing 12-4.

In simpler scenarios, you could create a DisplayModeControlTemplate that displays your data by using a TextBlock control and save yourself the effort of creating a custom viewer control. In this scenario, however, defining the logic in the DurationViewer control keeps your code more self-contained and allows you to more easily reference value converters.

GetDisplayModeDataTemplate images is the method that LightSwitch uses to obtain the display-mode template. Your code file already includes this method, but by default, it returns a null. The code that you add creates a DataTemplate object based on the content of the DisplayModeControlTemplate variable. To further improve performance, the code caches the DataTemplate in a variable called cachedDisplayDataTemplate.

This completes the DurationEditor control. Figure 12-9 shows how it would look at runtime when you apply it to a data grid. Notice that if a row doesn’t have the focus, the data grid shows the duration value by using the DurationViewer control. Otherwise, it uses the editable controls that you defined in your DurationEditor control.

9781430250715_Fig12-09.jpg

Figure 12-9. Display-mode template at runtime

Retrieving Height, Size, and Property Settings

You’ll already be familiar with the appearance properties that you can set on LightSwitch controls. These properties include height, size, and alignment values. You can configure your custom controls so that they apply the appearance values that a developer supplies through Visual Studio’s property sheet. Listing 12-4 already implements this and uses data binding to apply the settings that the developer supplies. Listing 12-7 highlights the specific code that does this.

Listing 12-7.  Binding to Values That the Developer Enters in the Screen Designer

<StackPanel
   Width="{Binding Properties[Microsoft.LightSwitch:RootControl/Width]}"        images
   Height="{Binding Properties[Microsoft.LightSwitch:RootControl/Height]}"
   VerticalAlignment ="{Binding
      Properties[Microsoft.LightSwitch:RootControl/VerticalAlignment]}">
  
   <TextBlock
           Text="{Binding Value, Mode=OneWay,
               Converter={StaticResource formatter}}"
           TextAlignment="{Binding                                              images
               Properties[Microsoft.LightSwitch:RootControl/TextAlignment]}">
  
                      

This code data binds the text and vertical alignment settings that the DurationViewer control uses. To retrieve the width that the developer enters in the properties sheet, you would use the binding syntax Properties[Microsoft.LightSwitch:RootControl/Width] images.

RootControl refers to the control that every custom control inherits as its top-most base control. Width refers to the width value that the developer enters in the properties sheet. The remaining lines of code bind the height and alignment properties by using similar syntax. The code that defines the TextBlock control binds the TextAlignment setting to the value that the developer enters in the properties sheet, as shown in images. You can find a full list of data-binding keys that you can use in Appendix C.

Running and Deploying Your Extension

You’ve now completed all of the tasks that are needed to create a non-trivial control extension—well done! To run and debug your work, you can simply debug your project in the usual way by pressing F5. This starts an experimental instance of Visual Studio that allows you to create or open an existing LightSwitch application. You can then debug your extension as normal by placing break points in your extension code. If you need to modify the debug settings for this experimental instance, you can do so by opening the properties of your VSIX project and editing the details on the debug tab.

To deploy your extension, simply distribute the VSIX file from the output of your VSIX project. In this example, you’ll find the output file in the following location: ApressExtensionVBApressExtensionVB.VsixinReleaseApressExtensionVB.vsix.

You can now install your VSIX file as you would any other extension. After you install your extension, remember to enable it in the properties window of your LightSwitch project. You can then use your Duration Editor control on your screens, as shown in Figure 12-10.

9781430250715_Fig12-10.jpg

Figure 12-10. Using the Duration Editor control

Note that if you created your Duration Editor control in VB, you might need to carry out an additional step before you build your project for the first time. The problem is that the DurationEditor and DurationViewer controls depend on the SplitMinutes value converter class. Visual Studio refuses to build the DurationEditor and DurationViewer controls if the SplitMinutes class hasn’t been built beforehand. The way to resolve this problem is to right-click the DurationEditor.xaml and DurationViewer.xaml files in Solution Explorer and choose the Exclude From Project item. Once you’ve excluded these two files, build your project. Once you’ve carried out your initial build (and compiled the SplitMinutes class in the process), re-enable the DurationEditor.xaml and DurationViewer.xaml files by choosing the right-click Include In Project item. You’ll now be able to carry on as normal.

Setting Product/Installation Attributes

Your custom extension includes attributes that are shown during the installation of your VSIX file and that also appear in Visual Studio’s Extensions And Updates dialog. These attributes include name, version, author, description, license details, icon, and a whole lot of other attributes. You can set these values through a file called vsixmanifest that you’ll find in your VSIX project. Double-click this file to open the editor, as shown in Figure 12-11.

9781430250715_Fig12-11.jpg

Figure 12-11. Setting the properties of your extension

EXERCISE 13.2 – CREATING A CUSTOM CONTROL

You now know how to create a value custom control, so try wrapping some of the other controls that you saw in Chapter 11 inside a custom control extension. For example, try creating a custom control extension for the PasswordBox or Slider controls.

Creating a Detail Control (ComboBox)

Now that you know how to create a value control and understand the principles of custom control design, let’s move on and look at how to create detail controls. Value controls (like the Duration Editor control) are designed to work with single, scalar values. Unlike value controls, detail controls allow users to view and select entities. The best way to describe a detail control is to give an example from the standard controls that ship with LightSwitch. The default detail controls include the AutoCompleteBox and Modal Window Picker controls.

In this section, you’ll learn how to create a ComboBox control extension that’s based on the ComboBox example that you saw in Chapter 11. As you’ll remember, the main benefit of a ComboBox control is that it restricts the values that users can choose (unlike the AutoCompleteBox control). And the advantage of an extension over a custom control is simplicity. It allows you to add an entity to your screen and set the control type to ComboBox. You don’t need to mess about with adding DLL references and writing custom data-binding code in your screen. Here’s an overview of the tasks that you need to carry out to create this control:

  1. Right-click your LSPKG project, create a new Control item, and name it ComboBox.xaml.
  2. Set the control type to Details in the control’s LSML file, and remove the Supported Data Types setting.
  3. Write XAML that defines the UI for your control.
  4. Add the dependency properties and .NET code that supports your control.

The custom ComboBox control will allow developers to choose the screen query that populates the control. In addition, developers can also choose what property to display in each row of the ComboBox. Figure 12-12 shows how the developer can enter these properties through the properties sheet once you’ve completed this example.

9781430250715_Fig12-12.jpg

Figure 12-12. ComboBox control at design time

Setting the Control’s Metadata

After you’ve added your ComboBox control item, open its corresponding LSML file (which you’ll find in the Common project) and make the modifications that are highlighted in Listing 12-8.

Listing 12-8.  LSML Settings for a Details Control

File:ApressExtensionVBApressExtensionVB.CommonMetadataControlsComboBox.lsml
  
  
<?xml version="1.0" encoding="utf-8" ?>
<ModelFragment
  xmlns="http://schemas.microsoft.com/LightSwitch/2010/xaml/model"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  
  <!--1 Set  SupportedContentItemKind to Details-->
  <Control Name="ComboBox"
    SupportedContentItemKind="Details"                                     images
    DesignerImageResource="ApressExtensionVB.ComboBox::ControlImage">
    <Control.Attributes>
      <DisplayName Value="ComboBox" />
    </Control.Attributes>
    
    <!--2 Remove this block of code                                        images
    <Control.SupportedDataTypes>
      <SupportedDataType DataType=":String"/>
    </Control.SupportedDataTypes>-->
  
    <!-- 3 - Add this block of code-->                                     images
    <Control.Properties>
       <!—3.1 - Property that defines the display property-->
      <ControlProperty Name="ComboDisplayItemProperty"
                       PropertyType=":String"
                       CategoryName="Appearance"
                       EditorVisibility="PropertySheet">
        <ControlProperty.Attributes>
          <DisplayName Value="Combo Display Property" />
          <Description Value="Enter the Property that's shown on each row" />
        </ControlProperty.Attributes>

        <ControlProperty.DefaultValueSource>
          <ScreenExpressionTree>
            <ConstantExpression ResultType=":String" Value="PriorityDesc"/>
          </ScreenExpressionTree>
        </ControlProperty.DefaultValueSource>
      </ControlProperty>
  
      <!—3.2 - Property that defines the Query -->
      <ControlProperty Name="ComboQueryProperty"
                   PropertyType=":String"
                   CategoryName="Appearance"
                   EditorVisibility="PropertySheet">
        <ControlProperty.Attributes>
          <DisplayName Value="Combo Query Property" />
          <Description Value="Enter the Screen Query - eg Screen.Priorities" />
        </ControlProperty.Attributes>
  
        <ControlProperty.DefaultValueSource>
          <ScreenExpressionTree>
            <ConstantExpression ResultType=":String"
                Value="Screen.EnterYourQuery"/>
          </ScreenExpressionTree>
        </ControlProperty.DefaultValueSource>
      </ControlProperty>
    </Control.Properties>

  </Control>
</ModelFragment>

The first change that you need to make is to mark the control as a Details control. To do this, set the SupportedContentItemKind attribute to Details images. In value-type controls (such as the Duration Editor control), the Control.SupportedDataTypes element allows you to specify the data types that are supported by your control. Because detail controls are designed to work with entities, you’ll need to delete the entire Control.SupportedDataTypes XML block images.

To create the custom properties that allow developers to enter the screen query and display properties, add the two Control.Property elements that are shown in images. Each Control.Property element includes additional attributes that you can set and the purposes of these attributes are shown in Table 12-2.

Table 12-2. Property Attributes

Attribute Name Purpose of Attribute
Name The name that uniquely identifies your property.
PropertyType Defines the data type of your property. The list of values that you can use are shown in Appendix B.
CategoryName Defines the Group where the property will be shown (for example, General, Appearance, Sizing).
Attributes – DisplayName This defines the label text that’s shown next to the data-entry control for your property in Visual Studio’s property sheet.
Attributes – Description This defines the text that appears when a developer hovers the mouse over the data-entry control for your property.

The next step is to define the user interface for your ComboBox control by adding the XAML that’s shown in Listing 12-9 to your ComboBox.xaml file.

Listing 12-9.  XAML Code for the ComboBox Control

File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsComboBox.xaml
  
<UserControl x:Class="ApressExtensionVB.Presentation.Controls.ComboBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="DetailRoot">                                                  images
    <ComboBox Name="Combo" />                                             images
</UserControl>

This XAML specifies the namespace DetailRoot images. This allows you to simplify your data-binding code later on. The next line of code defines the XAML that represents a System.Windows.Controls.ComboBox control images.

The next step is to write the .NET code that supports your custom control. The Extension Toolkit’s Control item template automatically creates ComboBox and ComboBoxFactory classes in your Client project. Open your ComboBox control’s code file, and modify the code as shown in Listing 12-10. Make sure to add the necessary Imports (VB) or using (C#) statements.

Listing 12-10.  ComboBox Code File

VB:
File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsComboBox.xaml.vb
  
Imports System
Imports System.ComponentModel.Composition
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Markup
Imports System.Windows.Data
Imports Microsoft.LightSwitch.Presentation
Imports Microsoft.LightSwitch.Model
  
Partial Public Class ComboBox
    Inherits UserControl
    Implements IContentVisual
  
#Region "1. INITIALIZE CONTROL & RETRIEVE PROPERTY SHEET VALUES"
  
    Public Sub New()
        InitializeComponent()
  
        MyBase.SetBinding(ComboBox.ComboBoxQueryProperty,                      images
         New Binding(
          "Properties[ApressExtensionVB:ComboBox/ComboQueryProperty]"))
  
        MyBase.SetBinding(ComboBox.ComboDisplayItemProperty,                   images
          New Binding(
           "Properties[ApressExtensionVB:ComboBox/ComboDisplayItemProperty]"))
  
        MyBase.SetBinding(ComboBox.ContentItemProperty, New Binding())
  
    End Sub
  
#Region "2. DEFINE DEPENDENCY PROPERTIES"
  
    Public Property ComboDisplayItem As String
        Get
            Return MyBase.GetValue(ComboBox.ComboDisplayItemProperty)
        End Get
        Set(value As String)
            MyBase.SetValue(ComboBox.ComboDisplayItemProperty, value)
        End Set
    End Property

    Public Shared ReadOnly ComboDisplayItemProperty As DependencyProperty =    images
        DependencyProperty.Register(
           "ComboDisplayItem", GetType(String), GetType(ComboBox),
           New PropertyMetadata(AddressOf ComboBox.ComboDisplayItemChanged))

    Public Property ComboBoxQuery As String
        Get
            Return MyBase.GetValue(ComboBox.ComboBoxQueryProperty)
        End Get
        Set(value As String)
            MyBase.SetValue(ComboBox.ComboBoxQueryProperty, value)
        End Set
    End Property
  
    Public Shared ReadOnly ComboBoxQueryProperty As DependencyProperty =       images
        DependencyProperty.Register(
           "ComboBoxQuery", GetType(String), GetType(ComboBox),
           New PropertyMetadata(AddressOf ComboBox.ComboBoxQueryChanged))
    Public Property ContentItem As IContentItem
       Get
          Return MyBase.GetValue(ComboBox.ContentItemProperty)
       End Get
       Set(value As IContentItem)
          MyBase.SetValue(ComboBox.ContentItemProperty, value)
       End Set
    End Property
  
    Public Shared ReadOnly ContentItemProperty As DependencyProperty =
          DependencyProperty.Register("ContentItem",
             GetType(IContentItem), GetType(ComboBox),
             New PropertyMetadata(AddressOf ComboBox.ComboDisplayItemChanged))
  
#End Region
  
#Region "3. HANDLE PROPERTY SHEET CHANGES"
    Private Shared Sub ComboBoxQueryChanged(d As DependencyObject,
       e As DependencyPropertyChangedEventArgs)
           CType(d, ComboBox).SetComboContentDataBinding()
    End Sub
  
    Private Shared Sub ComboDisplayItemChanged(d As DependencyObject,
       e As DependencyPropertyChangedEventArgs)
           CType(d, ComboBox).SetContentDataBinding()
    End Sub

    Private Sub SetContentDataBinding()                                        images
  
        If Not String.IsNullOrEmpty(Me.ComboDisplayItem) Then
            Dim str = "<DataTemplate
             xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
                <TextBlock Text=""{Binding " & Me.ComboDisplayItem &
                 "}"" /> </DataTemplate>"
  
            Combo.ItemTemplate =
                CType(XamlReader.Load(str), DataTemplate)
  
            Dim selectedBinding As New Data.Binding("Value")
            selectedBinding.Mode = BindingMode.TwoWay
            Combo.SetBinding(                                                  images
               System.Windows.Controls.ComboBox.SelectedValueProperty,
               selectedBinding)
  
        End If
  
    End Sub
  
#End Region
  
#Region "4. SET COMBO DATA SOURCE AND VALUE"
  
    Private Sub SetComboContentDataBinding()                                   
        If Not String.IsNullOrEmpty(Me.ComboBoxQuery) Then
            Dim dataSourceBinding As New Data.Binding(Me.ComboBoxQuery)
            dataSourceBinding.Mode = BindingMode.OneTime
            Combo.SetBinding(
                System.Windows.Controls.ComboBox.ItemsSourceProperty,
                dataSourceBinding)
        End If
    End Sub
  
#End Region
  
End Class

C#:
File:ApressExtensionCSApressExtensionCS.ClientPresentationControlsComboBox.xaml.cs
  
using System;
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Data;
using Microsoft.LightSwitch.Presentation;
using Microsoft.LightSwitch.Model;
using System.Collections.Generic;
using System.Linq;
  
namespace ApressExtensionCS.Presentation.Controls
{
    public partial class ComboBox : UserControl
    {
  
        //1 . INITIALIZE CONTROL & RETRIEVE PROPERTY SHEET VALUES
        public ComboBox()
        {
            InitializeComponent();
  
            this.SetBinding(ComboBox.ComboBoxQueryProperty,                    images
              new Binding(
              "Properties[ApressExtensionCS:ComboBox/ComboQueryProperty]"));
  
            this.SetBinding(ComboBox.ComboDisplayItemProperty,                 images
             new Binding(
             "Properties[ApressExtensionCS:ComboBox/ComboDisplayItemProperty]"));
  
            this.SetBinding(ComboBox.ContentItemProperty, new Binding());
        }
  
  
        //2. DEFINE DEPENDENCY PROPERTIES
        public string ComboDisplayItem
        {
            get { return (string)GetValue(ComboDisplayItemProperty); }
            set { SetValue(ComboDisplayItemProperty, value); }
        }
  
        public static readonly DependencyProperty ComboDisplayItemProperty =   images
            DependencyProperty.Register("ComboDisplayItem", typeof(string),
               typeof(ComboBox), new PropertyMetadata(ComboDisplayItemChanged));

        public string ComboBoxQuery
        {
            get { return (string)GetValue(ComboBoxQueryProperty); }
            set { SetValue(ComboBoxQueryProperty, value); }
        }
  
        public static readonly DependencyProperty ComboBoxQueryProperty =      images
            DependencyProperty.Register("ComboBoxQuery", typeof(string),
                typeof(ComboBox), new PropertyMetadata(ComboBoxQueryChanged));
  
        public IContentItem ContentItem
        {
            get { return (IContentItem)GetValue(ContentItemProperty); }
            set { SetValue(ContentItemProperty, value); }
        }
  
        public static readonly DependencyProperty ContentItemProperty =
           DependencyProperty.Register("ContentItem",
              typeof(IContentItem), typeof(ComboBox),
              new PropertyMetadata(ComboDisplayItemChanged));
  
        //3. HANDLE PROPERTY SHEET CHANGES
        private static void ComboDisplayItemChanged(DependencyObject d,
              DependencyPropertyChangedEventArgs e)
        {
            ((ComboBox)d).SetContentDataBinding();
        }
  
        private static void ComboBoxQueryChanged(DependencyObject d,
              DependencyPropertyChangedEventArgs e)
        {
            ((ComboBox)d).SetComboContentDataBinding();
        }
  
        //4. SET COMBO DATA SOURCE AND VALUE
  
        private void SetContentDataBinding()                                   images
        {
            if (!String.IsNullOrEmpty(ComboDisplayItem))
            {
                string str = @"<DataTemplate
            xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
            <TextBlock Text=""{Binding " + ComboDisplayItem + @"}"" />
            </DataTemplate>";
  
                Combo.ItemTemplate = (DataTemplate)XamlReader.Load(str);

                Binding selectedBinding = new Binding("Value");
                selectedBinding.Mode = BindingMode.TwoWay;
                Combo.SetBinding(                                              images
                  System.Windows.Controls.ComboBox.SelectedValueProperty,
                  selectedBinding);
  
            }
        }
  
        private void SetComboContentDataBinding()                              
        {
            if (!string.IsNullOrEmpty(this.ComboBoxQuery))
            {
                Binding dataSourceBinding = new Binding(this.ComboBoxQuery);
                dataSourceBinding.Mode = BindingMode.OneTime;
                Combo.SetBinding(
                   System.Windows.Controls.ComboBox.ItemsSourceProperty,
                   dataSourceBinding);
            }
        }
  
    }
}

Although this listing contains a lot of code, you can separate the logic into four distinct sections:

  1. Initialization of the control, and the retrieval of property sheet values
  2. The definition of dependency properties
  3. Code that handles changes when the property sheet values change
  4. Code that sets the ComboBox control’s selected value and item choices

Let’s begin by examining the first two sections. This code relies on the two dependency properties: ComboDisplayItemProperty and ComboBoxQueryProperty. The purpose of these dependency properties is to allow the control to access the screen query and display properties that the developer sets in Visual Studio’s property sheet. You’ll find out how to use the ContentItemProperty dependency property later in this chapter.

You might wonder why it’s even necessary to use dependency properties. After all, you could simply retrieve your property sheet values without using any dependency properties, and save yourself a whole load of complexity, as well as reduce the code by about a third. The reason you need to use dependency properties is to support the Silverlight runtime designer. Developers can change the property sheet values at runtime, and dependency properties enable your control to react to the changes that a developer makes at runtime.

Let’s take a closer look at these two dependency properties:

  • ComboDisplayItemProperty: This dependency property stores the Display Item property that the developer sets in Visual Studio’s property sheet. The control’s constructor defines the binding between this dependency property and the property sheet value images. The definition of this dependency property images defines a value callback method called ComboDisplayItemChanged. This method calls the SetContentDataBinding images method, which builds the display data template that defines each row in the ComboBox. Building this data template in code rather than statically defining it in XAML allows the control to change the contents at runtime. The next part of the code binds the selected item in the ComboBox to the value of the underlying data item images.
  • ComboBoxQueryProperty: This dependency property stores the Combo Query property that the developer sets in Visual Studio’s property sheet. This defines the name of a screen query that populates the ComboBox control’s ItemsSource. The control’s constructor defines the binding between this dependency property and the property sheet value images. The definition of this dependency property images defines a value callback method called ComboQueryChanged. This method calls a method called SetComboContentDataBinding  that binds the ItemsSource of the ComboBox to the screen query.

image Tip  Your ComboBox control will now compile and run. It’s possible to now build and install this extension, but we’ll carry on and extend the control further.

Finding the Summary Property for an Entity

When you’re writing a details custom control, you might want to implement some functionality that uses the summary property of the entity that’s bound to your control. For instance, you might want to know this so that you can show the summary property value on your control.

At present, the screen designer’s properties sheet for the ComboBox control allows the developer to enter the display property that’s shown in each row of the ComboBox. For example, the developer could enter Surname, and the ComboBox would display the surname value in each ComboBox row. You’ll now modify your control so that if a developer doesn’t supply a display property, the control uses the summary property. The purpose of this example is to teach you how to determine the summary property for an entity, and here’s an overview of the steps that you’ll carry out to add this feature to your ComboBox control extension:

  1. In your custom control code, create a dependency property that binds to the data context of your control. This allows you to access the entity that your custom control binds to, and it allows you to determine its data type. In Listing 12-10, you’ll notice that you’ve already done this by creating a dependency property called ContentItemProperty.
  2. Write a method that accepts an entity type as an input and returns the summary property.
  3. Modify the UI on your custom control so that it uses the summary property if the developer hasn’t supplied a display property through the properties sheet.

Creating a Helper Class

To begin, let’s create a helper class to help you with this task. The helper class defines methods that you’ll reuse later in this chapter. The helper methods that you’ll add to this class are:

  • GetSummaryProperty: This method returns the summary property for an entity.
  • GetFirstEntityProperty: The developer might not have defined a summary property for an entity. This method returns the first property.
  • IsTextProperty: This method accepts a property and returns true if you can represent it as a string. For example, if you pass in a binary property, this method returns false.
  • GetBaseSystemType: This method supports the IsTextProperty method. It’s designed to return the underlying data type of a business type.
  • GetTextPropertiesForEntity: This method returns a list of text properties for an entity.

To create this class, add a new class called CustomEditorHelper to your Common project and enter the code that’s shown in Listing 12-11.

Listing 12-11.  CustomEditorHelper

VB:
File:ApressExtensionVBApressExtensionVB.CommonCustomEditorHelper.vb
  
Imports System.Linq
Imports Microsoft.LightSwitch.Model
  
Public Module CustomEditorHelper
  
    ' This method returns the summary property
    Public Function GetSummaryProperty(
       entityType As IEntityType) As IEntityPropertyDefinition
  
        Dim attribute As ISummaryPropertyAttribute =
            entityType.Attributes.OfType(
               Of ISummaryPropertyAttribute)().FirstOrDefault()
  
        If attribute IsNot Nothing AndAlso
           attribute.Property IsNot Nothing Then
               Return attribute.Property
        End If
  
        ' There's no summary property – return the first property
        Return GetFirstEntityProperty(entityType)
  
    End Function
  
    Private Function GetFirstEntityProperty(
       entityType As IEntityType) As IEntityPropertyDefinition
  
        'Simple type properties are non business types/ non navigation properties
        Dim simpleTypeProperties As IEnumerable(Of IEntityPropertyDefinition) =
            entityType.Properties.Where(
               Function(p) TypeOf p.PropertyType Is ISimpleType)

        ' Find the first string property, or the first one that can
        ' be represented as a string
        Dim defaultSummaryProperty As IEntityPropertyDefinition =
            simpleTypeProperties.FirstOrDefault(
                Function(p) GetBaseSystemType(p.PropertyType) Is GetType(String))
  
        If defaultSummaryProperty Is Nothing Then
            simpleTypeProperties.FirstOrDefault(Function(p) IsTextProperty(p))
        End If
  
        Return defaultSummaryProperty
  
    End Function
  
    ' This returns true if a property can be represented by a string
    Public Function IsTextProperty(
       propertyDefinition As IPropertyDefinition) As Boolean
  
        Dim dataType As ISimpleType =
           TryCast(propertyDefinition.PropertyType, ISimpleType)
        If dataType IsNot Nothing Then
            Dim clrType As Type = GetBaseSystemType(dataType)
            Return clrType IsNot Nothing AndAlso
                clrType IsNot GetType(Byte())
        End If
        Return False
    End Function
  
   ' This returns the underlying type of a business type
    Public Function GetBaseSystemType(dataType As ISimpleType) As Type
        While dataType IsNot Nothing
            If TypeOf dataType Is IPrimitiveType Then
                ' Primitive types are foundation LightSwitch data types like:
                  String/Int32/Decimal/Date/...
                Return DirectCast(dataType, IPrimitiveType).ClrType
            ElseIf TypeOf dataType Is INullableType Then
                ' NullableType represents a Nullable version of
                  any primitive or semantic (business) type.
                dataType = DirectCast(dataType, INullableType).UnderlyingType
            ElseIf TypeOf dataType Is ISemanticType Then
                dataType = DirectCast(dataType, ISemanticType).UnderlyingType
            End If
        End While
        Return Nothing
    End Function

    ' This returns a collection of properties for an entity
    Public Function GetTextPropertiesForEntity(
       dataType As IDataType) As IEnumerable(Of IPropertyDefinition)
  
        If dataType IsNot Nothing Then
            Return dataType.Properties _
                .Where(Function(p) CustomEditorHelper.IsTextProperty(p)) _
                .Cast(Of IPropertyDefinition)()
        End If
        Return Enumerable.Empty(Of IPropertyDefinition)()

    End Function
  
End Module
  
C#:
File:ApressExtensionCSApressExtensionCS.CommonCustomEditorHelper.cs
  
using System;
using System.Linq;
using Microsoft.LightSwitch.Model;
using System.Collections.Generic;
  
public static class CustomEditorHelper
{
    // This method returns the summary property
    private static IEntityPropertyDefinition GetSummaryProperty(
       IEntityType entityType)
    {
        ISummaryPropertyAttribute attribute =
            entityType.Attributes.OfType
            <ISummaryPropertyAttribute>().FirstOrDefault();
  
        if (attribute != null && attribute.Property != null)
        {
            return attribute.Property;
        }
  
        // There's no summary property – return the first property
        return GetFirstEntityProperty(entityType);
    }
  
    private static IEntityPropertyDefinition GetFirstEntityProperty(
       IEntityType entityType)
    {
        // Simple types are non business types/ non navigation properties
        IEnumerable<IEntityPropertyDefinition> simpleTypeProperties =
            entityType.Properties.Where(p => p.PropertyType is ISimpleType);
  
        // Find the first string property, or the first one that can
        // be represented as a string
        IEntityPropertyDefinition defaultSummaryProperty =
            simpleTypeProperties.FirstOrDefault(
               p => CustomEditorHelper.GetBaseSystemType(
                   (ISimpleType)p.PropertyType) == typeof(string)) ??
            simpleTypeProperties.FirstOrDefault(
               p => CustomEditorHelper.IsTextProperty(p));

        return defaultSummaryProperty;
    }
  
    // This returns true if a property can be represented by a string
    public static bool IsTextProperty(IPropertyDefinition propertyDefinition)
    {
        ISimpleType dataType =
            propertyDefinition.PropertyType as ISimpleType;
        if (dataType != null)
        {
            Type clrType = GetBaseSystemType(dataType);
            return clrType != null &&
                !object.ReferenceEquals(clrType, typeof(byte[]));
        }
        return false;
    }
  
    // This returns the underlying type of a business type
    public static Type GetBaseSystemType(ISimpleType dataType)
    {
        while (dataType != null)
        {
            if (dataType is IPrimitiveType)
            {
                // Primitive types are foundation LightSwitch data types like:
                // String/Int32/Decimal/Date/...
                return ((IPrimitiveType)dataType).ClrType;
            }
            else if (dataType is INullableType)
            {
                // NullableType represents a Nullable version of
                // any primitive or semantic (business) type.
                dataType = ((INullableType)dataType).UnderlyingType;
            }
            else if (dataType is ISemanticType)
            {
                dataType = ((ISemanticType)dataType).UnderlyingType;
            }
        }
        return null;
    }

    // This returns a collection of properties for an entity
    public static IEnumerable<IPropertyDefinition>
        GetTextPropertiesForEntity(IDataType dataType)
    {
        if (dataType != null)
        {
            return dataType.Properties.Where(
                p => CustomEditorHelper.IsTextProperty(p)
                ).Cast<IPropertyDefinition>();
        }
        return Enumerable.Empty<IPropertyDefinition>();
    }
}

One of the benefits of a reusable helper class is that you can reuse it in other projects in your LightSwitch Extension library. You created the CustomEditorHelper class in your Common project, and to use the helper class from your Client project (the project where your Silverlight ComboBox control belongs), you’ll need to add a link to this class from your Client project.

To do this, right-click your Client project and choose the Add Existing Item option. When the File Browser dialog appears, select the CustomEditorHelper file from your Common project. Expand the drop-down list next to the Add button, and choose Add As Link (as shown in Figure 12-13).

9781430250715_Fig12-13.jpg

Figure 12-13. Linking to your CustomEditorHelper class

The final step is to modify the SetContentDataBinding method in your ComboBox control, as shown in Listing 12-12.

Listing 12-12.  ComboBox Control Code

VB:
File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsComboBox.xaml.vb
  
Private Sub SetContentDataBinding()
    If ContentItem IsNot Nothing Then
  
        Dim entityType As IEntityType = ContentItem.ResultingDataType
  
        If entityType IsNot Nothing Then
  
            Dim displayProperty As String = Me.ComboDisplayItem
  
            If String.IsNullOrEmpty(displayProperty) Then                     images
                displayProperty =
                   CustomEditorHelper.GetSummaryProperty(entityType).Name     images
            End If
  
            If Not String.IsNullOrEmpty(displayProperty) Then
                Dim str = "<DataTemplate
             xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
              <TextBlock Text=""{Binding " & displayProperty &
             "}"" /> </DataTemplate>"
  
                Combo.ItemTemplate =
                    CType(XamlReader.Load(str), DataTemplate)
  
                Dim selectedBinding As New Data.Binding("Value")
                selectedBinding.Mode = BindingMode.TwoWay
                Combo.SetBinding(
                  System.Windows.Controls.ComboBox.SelectedValueProperty,
                  selectedBinding)

            End If
        End If
  
    End If
End Sub
  
C#:
File:ApressExtensionCSApressExtensionCS.ClientPresentationControlsComboBox.xaml.cs
  
private void SetContentDataBinding()
{
    if (ContentItem != null)
    {
  
        IEntityType entityType = ContentItem.ResultingDataType as IEntityType;
        if (ContentItem != null)
        {
            string displayProperty = ComboDisplayItem;
            if (string.IsNullOrEmpty(displayProperty))                                      images
            {
                displayProperty =                                                           images
                   CustomEditorHelper.GetSummaryProperty(entityType).Name;
            }
  
            if (!string.IsNullOrEmpty(displayProperty))
            {
                string str = @"<DataTemplate
                      xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
                      <TextBlock Text=""{Binding " +
                    displayProperty + @"}"" /> </DataTemplate>";
  
                Combo.ItemTemplate = (DataTemplate)XamlReader.Load(str);
  
              Binding selectedBinding = new Binding("Value");
              selectedBinding.Mode = BindingMode.TwoWay;
              Combo.SetBinding(
                 System.Windows.Controls.ComboBox.SelectedValueProperty,
                 selectedBinding);
            }
        }
    }
}

The changes that you’ve applied to the SetContentDataBinding method in Listing 12-12 (compared to Listing 12-10) are as follows. The code checks whether the developer has specified a display property in Visual Studio’s property sheet images. If the developer hasn’t specified a display property, the code calls your helper class’s GetSummaryProperty method images to return the summary property of the entity that’s bound to your custom control.

Creating Custom Property Editors

Your ComboBox control is starting to take shape. You’ve added a feature that allows developers to configure the display property that’s shown in each row of the ComboBox. And if your developer hasn’t supplied a display property, your control uses the summary property of the underlying entity instead. Although the control works well, there’s still room for further improvement. At the moment, developers must use a text box to enter the display property (as shown in Figure 12-14). This method of entering a display property name is difficult because developers have to remember and enter the exact property names. Worst of all, if the developer makes a mistake, your custom control will throw an exception at runtime and break your application.

9781430250715_Fig12-14.jpg

Figure 12-14. By default, developers must use a text box to alter the display property

In this section, you’ll learn how to customize Visual Studio’s properties sheet by modifying the Combo Display Property data entry control so that it uses a drop-down box, instead of a text box. There are two parts to this piece of work. In the first part, you’ll customize the Combo Display Property entry control in Visual Studio’s property sheet. However, remember that LightSwitch also allows developers to customize controls through the runtime screen designer. So, in part two, you’ll learn how to apply the same customization to LightSwitch’s Silverlight runtime designer.

Customizing Visual Studio’s Property Editor

When Microsoft released Visual Studio 2010, it wanted to demonstrate its confidence in Windows Presentation Foundation (WPF) and, as a result, much of Visual Studio is built with WPF. This includes the window management system that includes the toolbars, context menus, and status bar areas of the IDE. Therefore, the method of extending Visual Studio’s properties sheet involves building a WPF user control.

As you’ve seen, the LSML file for your custom control contains the definition for your custom Combo Display Property, along with the definition of any other custom properties that you’ve created. The LSML file also allows you to specify the control that developers would use to edit your Combo Display Property in the properties sheet. To use a custom editor, you’d specify the name of an editor class in your LSML file. (In the example that follows, you’ll name your editor class EntityPropertyDropdownEditor.) This is a class that implements an interface called IPropertyValueEditorProvider and provides an implementation of a method called GetEditorTemplate.

Now imagine that you’ve installed your control extension and have added the ComboBox control to a screen. You’re in the screen designer, you’ve selected an instance of your ComboBox, and you open your properties sheet. At this point, Visual Studio builds the property sheet, and it does this by searching your ComboBox control’s LSML file to work out what custom properties there are and what editor control it should use. By using the LSML metadata, Visual Studio finds out that your ComboBox control’s Combo Display Property is associated with the EntityPropertyDropdownEditor class. With this knowledge, Visual Studio calls the EntityPropertyDropdownEditor class’s GetEditorTemplate method, which returns a WPF control that plugs into Visual Studio’s property sheet.

This WPF control contains a ComboBox that displays a list of properties for the underlying entity. The WPF control binds to an object that represents the property value, which is an object that implements the IBindablePropertyEntry interface. Figure 12-15 illustrates the process in the form of a diagram.

9781430250715_Fig12-15.jpg

Figure 12-15. Building the properties sheet

Now that you understand how this works, here’s an overview of how you’d create a custom editor for your ComboBox control’s Combo Display Property:

  1. Set the target .NET Framework version of your project to version 4.5, and add the required references.
  2. Create a link to the CustomEditorHelper class from your Design project.
  3. Create the WPF user control that contains the drop-down list of properties.
  4. Create a supporting editor class for your control (EntityPropertyDropdownEditor).
  5. Add value converters to assist with data binding.

Preparing Your Project

There are a few tasks that you need to carry out before you begin. First, you’ll need to set the .NET Framework versions of your LSPKG and Design projects to .NET 4.5. To do this, right-click your project and use the Target Framework drop-down menu that you’ll find in the Application tab.

Next, you’ll need to add a reference to the Microsoft.LightSwitch.Design.Designer.dll file from your Design project. You’ll find this file in Visual Studio’s private assembly folder. On a 64-bit computer, the default location of this folder is

C:Program Files (x86)Microsoft Visual Studio 11.0Common7IDEPrivateAssemblies

The code for your custom editor control uses the methods that are in your helper class. Therefore, you’ll need to add a link to the CustomEditorHelper class from your Design project, in the same way you did for your Client project.

Creating a Custom Control Editor

Let’s begin by creating the WPF editor control that plugs into Visual Studio’s property sheet. To do this, create a new folder in your Design project and call it Editors. Create a WPF User Control in this folder, and name it EntityPropertyDropdown. Modify the contents of this file as shown in Listing 12-13.

Listing 12-13.  Editor Control XAML

File:ApressExtensionVBApressExtensionVB.DesignEditorsEntityPropertyDropdown.xaml
  
  
<UserControl x:Class="Editors.EntityPropertyDropdown"                         images
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   mc:Ignorable="d"
   xmlns:e="clr-namespace:ApressExtensionVB.Editors">                         images
  
    <UserControl.Resources>
        <e:GetAllEntityPropertiesConverter
             x:Key="GetAllEntityPropertiesConverter" />
        <e:AppendColonConverter x:Key="AppendColonConverter" />
        <e:EmptyStringToSummaryConverter x:Key="EmptyStringToSummaryConverter" />
    </UserControl.Resources>
  
    <!--3 Set fonts -->                                                       images
    <Grid TextBlock.FontSize="{DynamicResource DesignTimeFontSize}"
          TextBlock.FontFamily="{DynamicResource DesignTimeFontFamily}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!-- 4  This shows the Property Label -->                             images
        <TextBlock x:Name="ComboBoxLabel"
           Text="{Binding Entry.DisplayName, Mode=OneWay,
                  Converter={StaticResource AppendColonConverter}}"
           TextWrapping="WrapWithOverflow"
           ToolTip="{Binding Entry.Description, Mode=OneWay}"
           Margin="0,0,0,2"/>
  
        <!-- 5 This is the ComboBox -->                                       images
        <ComboBox x:Name="ComboBox"
           Grid.Row ="1"
           SelectedItem="{Binding Entry.PropertyValue.Value}"
           ItemsSource="{Binding Entry.PropertyValue.ModelItem,
               Mode=OneWay,
               Converter={StaticResource GetAllEntityPropertiesConverter}}">
  
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <!-- 6 This shows the ComboBox data items-->              images
                    <TextBlock Text="{Binding Converter=
                       {StaticResource EmptyStringToSummaryConverter}}"/>
                </DataTemplate>
            </ComboBox.ItemTemplate>
  
        </ComboBox>
    </Grid>
  
</UserControl>

In the XAML code, set the parent namespace of your control to Editors images. Add an XML reference to the Editors namespace as shown in images. (Make sure to replace ApressExtensionVB with the correct namespace for your extension project.)

The initial part of the code images defines the font attributes that the editor control uses. So that your editor control doesn’t look out of place, it’s important to use fonts that are consistent with the rest of the Visual Studio IDE. DesignTimeFontSize and DesignTimeFontFamily are design-time public resource items that allow you to reference the font settings that the developer chooses in Visual Studio’s Tools image Option menu.

ComboBoxLabel images defines the label that shows your property name. In this example, your label would show the text “Combo Display Property.” Visual Studio binds your editor control to an IBindablePropertyEntry object. Entry.DisplayName is the binding path that allows you to show the property name.

ComboBox images defines the drop-down box that will show the entity’s properties, and the ComboBox object’s ItemTemplate images defines the content that’s shown on each row of the ComboBox.

This code data binds to the IBindablePropertyEntry object with the help of the following three value converters:

  • GetAllEntityPropertiesConverter: This returns a list of properties for an entity, and its purpose is to fill the ComboBox.
  • AppendColonConverter: This appends a colon to the end of the display name label.
  • EmptyStringToSummaryConverter: This sets the display text of the empty string that appears at the top of the ComboBox to <Summary >.

The next step is to create the .NET code for your custom editor control as shown in Listing 12-14.

Listing 12-14.  Custom Editor Control Code

VB:
File:ApressExtensionVBApressExtensionVB.DesignEditorsEntityPropertyDropdown.xaml.vb
  
Imports System.Windows.Controls
Namespace Editors                                                   images
    Public Class EntityPropertyDropdown
        Inherits UserControl
  
        Public Sub New()
            InitializeComponent()
        End Sub
  
    End Class
End Namespace
  
C#:
File:ApressExtensionCSApressExtensionCS.DesignEditorsEntityPropertyDropdown.xaml.cs
  
using System.Windows.Controls;
  
namespace Editors                                                   images
{
    public partial class EntityPropertyDropdown : UserControl
    {
        public EntityPropertyDropdown()
        {
            InitializeComponent();
        }
    }
}

The code in Listing 12-14 contains the standard code that you’d find in any user control. The only change that you’d make to this code is to change the namespace to Editors images. The next step is to add the code that contains the value converters. To do this, create a new class in your Editors folder called CustomEditorValueConverters, and add the code that’s shown in Listing 12-15.

Listing 12-15.  Value Converter Code

VB:
File:ApressExtensionVBApressExtensionVB.DesignEditorsCustomEditorValueConverters.vb
  
Imports System
Imports System.Globalization
Imports System.Windows.Data
Imports Microsoft.LightSwitch.Model
  
Namespace Editors
  
    ' This appends ':' to the end of the property name label
    Public Class AppendColonConverter
        Implements IValueConverter
  
        Public Function Convert(value As Object, targetType As System.Type,
            parameter As Object, culture As System.Globalization.CultureInfo)
               As Object Implements System.Windows.Data.IValueConverter.Convert
  
            If value IsNot Nothing Then
                Return String.Format(CultureInfo.CurrentCulture, "{0}:", value)
            End If
            Return String.Empty

        End Function
  
        Public Function ConvertBack(value As Object, targetType As System.Type,
           parameter As Object, culture As System.Globalization.CultureInfo)
             As Object Implements System.Windows.Data.IValueConverter.ConvertBack
               Throw New NotSupportedException()
        End Function
  
    End Class
  
    ' This gets a collection of property names for an entity type and the result
    ' includes an entry that contains an empty string.
    ' The return value fills the values in the ComboBox.
    Public Class GetAllEntityPropertiesConverter
        Implements IValueConverter
  
        Public Function Convert(value As Object, targetType As System.Type,
            parameter As Object, culture As System.Globalization.CultureInfo) As
                Object Implements System.Windows.Data.IValueConverter.Convert
  
            Dim textProperties As List(Of String) = New List(Of String)()
  
            textProperties.Add(String.Empty)
  
            Dim contentItemDefinition As IContentItemDefinition =
              TryCast(value, IContentItemDefinition)
            If contentItemDefinition IsNot Nothing Then
                Dim entityType As IEntityType =
                    TryCast(contentItemDefinition.DataType, IEntityType)
                If entityType IsNot Nothing Then
                    For Each p As IPropertyDefinition In
                       CustomEditorHelper.GetTextPropertiesForEntity(entityType)
                           textProperties.Add(p.Name)
                    Next
                End If
            End If
            Return textProperties
  
        End Function
  
        Public Function ConvertBack(value As Object, targetType As System.Type,
           parameter As Object, culture As System.Globalization.CultureInfo) As
               Object Implements System.Windows.Data.IValueConverter.ConvertBack
                 Throw New NotSupportedException()
        End Function
  
    End Class
  

    ' If the developer hasn't entered a display name, this converts the display
    ' text of the empty value to '<Summary>'
    Public Class EmptyStringToSummaryConverter
        Implements IValueConverter
  
        Public Function Convert(value As Object, targetType As System.Type,
           parameter As Object, culture As System.Globalization.CultureInfo) As
              Object Implements System.Windows.Data.IValueConverter.Convert
  
            If TypeOf value Is String AndAlso
                String.IsNullOrEmpty(DirectCast(value, String)) Then
                   Return "<Summary>"
            End If
            Return value
  
        End Function
  
        Public Function ConvertBack(value As Object, targetType As System.Type,
            parameter As Object, culture As System.Globalization.CultureInfo) As
               Object Implements System.Windows.Data.IValueConverter.ConvertBack
            Throw New NotSupportedException()
        End Function
  
    End Class
  
End Namespace

C#:
File:ApressExtensionCSApressExtensionCS.DesignEditorsCustomEditorValueConverters.cs
  
using System;
using System.Globalization;
using System.Windows.Data;
using Microsoft.LightSwitch.Model;
using System.Collections.Generic;
  
namespace ApressExtensionCS.Editors
{
    //This appends ':' to the end of the property name label
    public class AppendColonConverter : IValueConverter
    {
        public object Convert(object value, System.Type targetType,
           object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null)
            {
                return string.Format(CultureInfo.CurrentCulture, "{0}:", value);
            }
            return string.Empty;
        }
  
        public object ConvertBack(object value, System.Type targetType,
           object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
  
    // This gets a collection of property names for an entity type and the result
    // includes an entry that contains an empty string.
    // The return value fills the values in the ComboBox.
    public class GetAllEntityPropertiesConverter : IValueConverter
    {
        public object Convert(object value, System.Type targetType,
          object parameter, System.Globalization.CultureInfo culture)
        {
            List<string> textProperties = new List<string>();
  
            textProperties.Add(string.Empty);
  
            IContentItemDefinition contentItemDefinition =
                value as IContentItemDefinition;
            if (contentItemDefinition != null)
            {
                IEntityType entityType =
                   contentItemDefinition.DataType as IEntityType;
                if (entityType != null)
                {
                    foreach (IPropertyDefinition p in
                       CustomEditorHelper.GetTextPropertiesForEntity(entityType))
                    {
                        textProperties.Add(p.Name);
                    }
                }
            }
            return textProperties;
        }
  
        public object ConvertBack(object value, System.Type targetType,
           object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
  
    // If the developer hasn't entered a display name, this converts the display
    // text of the empty value to '<Summary>'
    public class EmptyStringToSummaryConverter : IValueConverter
    {
        public object Convert(object value, System.Type targetType,
           object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is string && string.IsNullOrEmpty((string)value))
            {
                return "<Summary>";
            }
            return value;
        }
        public object ConvertBack(object value, System.Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}

The next step is to create the class that returns your WPF user control to Visual Studio. In the Editors folder of your Design project, create the EntityPropertyDropdownEditorProvider class, and add the code that’s shown in Listing 12-16.

Listing 12-16.  EntityPropertyDropdownEditorProvider Class

VB:
File:ApressExtensionVBApressExtensionVB.DesignEditors
        EntityPropertyDropdownEditorProvider.vb
  
Imports System.ComponentModel.Composition
Imports System.Windows
Imports System.Windows.Markup
Imports Microsoft.LightSwitch.Designers.PropertyPages
Imports Microsoft.LightSwitch.Designers.PropertyPages.UI
  
Namespace ApressExtensionVB.Editors

    <Export(GetType(IPropertyValueEditorProvider))>
    <PropertyValueEditorName("EntityPropertyDropdown")>                        images
    <PropertyValueEditorType("System.String")>
    Public Class EntityPropertyDropdownEditorProvider                          images
        Implements IPropertyValueEditorProvider
  
        Public Function GetEditor(entry As IPropertyEntry) As
           IPropertyValueEditor Implements IPropertyValueEditorProvider.GetEditor
            Return New Editor()
        End Function
  
        Private Class Editor
            Implements IPropertyValueEditor
  
            ' The screen designer calls this method to create the UI control on
            ' the property sheet. Note that you should define the
            ' GetEditorTemplate and Context declarations in a single line.
            Public Function GetEditorTemplate(entry As IPropertyEntry) As      images
              System.Windows.DataTemplate Implements
               IPropertyValueEditor.GetEditorTemplate
                Return XamlReader.Parse(
                   EntityPropertyDropdownEditorProvider.ControlTemplate)
            End Function

            Public ReadOnly Property Context As Object Implements              images
              IPropertyValueEditor.Context
                Get
                    Return Nothing
                End Get
            End Property
  
        End Class
  
#Region "Constants"
        Private Const ControlTemplate As String =                              images
            "<DataTemplate" +
         " xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""" +
         " xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""" +
         " xmlns:editors=""clr-namespace:ApressExtensionVB.Editors;
           assembly=ApressExtensionVB.Design"">" +
         "   <editors:EntityPropertyDropdown/>" +
         "</DataTemplate>"
#End Region
  
    End Class
  
End Namespace
  
C#:
File:ApressExtensionCSApressExtensionCS.DesignEditors
        EntityPropertyDropdownEditorProvider.cs
  
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Markup;
using Microsoft.LightSwitch.Designers.PropertyPages;
using Microsoft.LightSwitch.Designers.PropertyPages.UI;
  
namespace ApressExtensionCS.Editors
{
  
    [Export(typeof(IPropertyValueEditorProvider))]
    [PropertyValueEditorName("EntityPropertyDropdown")]                                     images
    [PropertyValueEditorType("System.String")]
    public class EntityPropertyDropdownEditorProvider                                       images
        : IPropertyValueEditorProvider
    {
        public IPropertyValueEditor GetEditor(IPropertyEntry entry)
        {
            return new Editor();
        }
  
        private class Editor : IPropertyValueEditor
        {
            // The screen designer calls this method to create the UI control on
            //  the property sheet.
  
            public DataTemplate GetEditorTemplate(IPropertyEntry entry)                     images
            {
                return XamlReader.Parse(ControlTemplate) as DataTemplate;
            }
  
            public object Context                                                           images
            {
                get
                {
                    return null;
                }
            }
        }

        #region Constants
  
        private const string ControlTemplate =                                              images
            "<DataTemplate" +
            " xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"" +
            " xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"" +
            " xmlns:editors="clr-namespace: Editors;assembly=ApressExtensionCS.Design">" +
            "   <editors:EntityPropertyDropdown/>" +
            "</DataTemplate>";
  
        #endregion
    }
}

EntityPropertyDropdownEditorProvider images is the component that allows Visual Studio to create a custom property editor. Make a note of the PropertyValueEditorName images attribute that you specify here. This is the unique “key” that associates this component with your custom property.

When Visual Studio builds the property sheet for your custom control, it calls the GetEditorTemplate method images to generate the UI for your custom property. This method returns the contents of the ControlTemplate variable images, a string constant that defines the XAML for the WPF control (EntityPropertyDropdown) that you defined in Listing 12-13. If you named your WPF control differently, you’ll need to amend the contents of the ControlTemplate variable to take account of this. It’s important that you get this correct because if you make a mistake, Visual Studio will freeze when a developer opens the property sheet for your control.

When you’re extending Visual Studio’s property sheet, IBindablePropertyEntry’s EditorContext member allows you to access an additional Context object images. This allows the property editor to store and access additional data. However, the Silverlight runtime designer doesn’t support the Context object, so it remains unused in this example. But if you want to find out more, Chapter 13 shows how you can use the Context object to extend the property sheet for the table designer.

Linking Your Property with the Editor

Your WPF editor control is now complete, so the final step is to associate it with your custom control’s ComboDisplayItemProperty property. To make this association, open your custom control and edit the UIEditorId attribute as shown in Listing 12-17.

Listing 12-17.  Linking Your Property with Your Custom Editor

File:ApressExtensionVBApressExtensionVB.CommonMetadataControlsComboBox.lsml
  
<Control.Properties>
   <ControlProperty Name="ComboDisplayItemProperty"
      PropertyType=":String"
      CategoryName="Appearance"
      UIEditorId="EntityPropertyDropdown"                                                   images
      EditorVisibility="PropertySheet">

The UIEditorId attribute images allows you to specify the control that Visual Studio uses to present your property. The value that’s used here (EntityPropertyDropdown) matches the PropertyValueEditorName value that you defined in your EntityPropertyDropdownEditorProvider class. (See Listing 12-16.) If you omit this attribute, Visual Studio chooses the most suitable property editor for the data type of your property.

You’ll find a list of editors that you can use in the Microsoft.LightSwitch.Designers.PropertyPages.UI.CommonPropertyValueEditorNames class, and the list below shows some of the choices.

  • BooleanEditor
  • CheckBoxEditor
  • CiderStringEditor
  • CodeCollectionEditor
  • CodeRuleLinkEditor
  • CollectionEditor
  • ColorEditor
  • DesignerCommandLinkEditor

Customizing the Runtime Designer

Customizing Visual Studio’s property sheet is just one half of the story. Developers can also set property values by using the Silverlight runtime designer, so it’s equally important to apply the same customization to this part of your application. Fortunately, the steps that you’ll need to carry out are similar to the previous example, and you can also reuse the code that you’ve already written. You’ll carry out all of your work in the ClientDesign project. This is the project that allows you to customize the Silverlight runtime designer. Here’s an overview of what you’ll need to do:

  1. Create a link to your Common project’s CustomEditorHelper class from your ClientDesign project.
  2. Create a link to your Design project’s CustomEditorValueConverters file from your Design project.
  3. Create a Silverlight user control that contains your custom editor control.
  4. Create a supporting editor class that returns your Silverlight editor control to the runtime designer.

Once you’ve linked to the CustomEditorHelper and CustomEditorValueConverters classes from your ClientDesign project, create a new folder in your ClientDesign project called Editors and add a Silverlight User Control named SilverlightEntityPropertyDropdown.xaml. Now modify the contents of this file, as shown in Listing 12-18.

Listing 12-18.  Silverlight Custom Editor Control

VB:
File:ApressExtensionVBApressExtensionVB.Client.DesignEditors
         SilverlightEntityPropertyDropdown.xaml
  
<UserControl x:Class="ApressExtensionVB.Editors.SilverlightEntityPropertyDropdown"
    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:e="clr-namespace:ApressExtensionVB.Editors;
               assembly=ApressExtensionVB.Client.Design"                       images
    mc:Ignorable="d">
  
    <UserControl.Resources>
        <e:GetAllEntityPropertiesConverter
            x:Key="GetAllEntityPropertiesConverter" />
        <e:AppendColonConverter x:Key="AppendColonConverter" />
        <e:EmptyStringToSummaryConverter x:Key="EmptyStringToSummaryConverter" />
    </UserControl.Resources>
  
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
  
        <!-- 2  This shows the Property Label -->                              images
        <TextBlock x:Name="EditorLabel"
            Text="{Binding Path=DisplayName,
            Converter={StaticResource AppendColonConverter}}"/>

        <!-- 3 This is the ComboBox -->                                        images
        <ComboBox Margin="0,1,0,0" Grid.Row="1"
           ItemsSource="{Binding Path=PropertyValue.ModelItem,
               Converter={StaticResource GetAllEntityPropertiesConverter}}"
           SelectedItem="{Binding Path=PropertyValue.Value, Mode=TwoWay}"
           AutomationProperties.LabeledBy="{Binding ElementName=EditorLabel}"
           HorizontalAlignment="Stretch">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <!-- 4 This shows the ComboBox data items-->               images
                   <TextBlock Text="{Binding
                    Converter={StaticResource EmptyStringToSummaryConverter}}" />
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </Grid>
</UserControl>

When you added a custom editor to Visual Studio’s property sheet, you did this by adding a WPF user control. Customizing the runtime designer involves adding a custom Silverlight control instead, and you’ll notice that the code in Listing 12-18 looks almost identical to the code in Listing 12-13.

Listing 12-18 defines a custom editor that includes a TextBlock and a ComboBox. Your Silverlight editor control binds to an IPropertyEntry object, and you can reuse the value converters that you created in the previous example. Because you’ve added a link to the CustomEditorValueConverters, which belongs in your Design project, you’ll need to add a namespace reference to this project images.

The TextBlock images control shows the custom property’s display name and uses the AppendColonConverter value converter to append a colon to the end of the text (for example, ComboBox Display Property:). The ComboBox images uses the GetAllEntityPropertiesConverter to display the valid choices, and uses the data-binding path of PropertyValue.Value to set the selected item. In the markup for your ComboBox, it’s very important to specify the ItemsSource property before the SelectedItem property. If you don’t do this, your control won’t correctly show the selected item.

The next step is to create the .NET code for your Silverlight editor control as shown in Listing 12-19.

Listing 12-19.  SilverlightEntityPropertyDropdown Code

VB:
File:ApressExtensionVBApressExtensionVB.Client.DesignEditors
         SilverlightEntityPropertyDropdown.xaml.vb
  
Namespace Editors                                                      images
    Partial Public Class SilverlightEntityPropertyDropdown
        Inherits UserControl
        Public Sub New()
            InitializeComponent()
        End Sub
    End Class
End Namespace
  
C#:
File:ApressExtensionCSApressExtensionCS.Client.DesignEditors
         SilverlightEntityPropertyDropdown.xaml.cs
  
using System.Windows;
using System.Windows.Controls;
  
namespace ApressExtensionCS.Editors                                    images
{
    public partial class SilverlightEntityPropertyDropdown : UserControl
    {
        public SilverlightEntityPropertyDropdown()
        {
            InitializeComponent();
        }
    }
}

The code in Listing 12-19 contains the Silverlight equivalent of the code from Listing 12-14. Once again, the important point is to set the namespace of the control to Editors images. Now create a new code file in the Editors folder of your Client.Design project, and add the SilverlightEntityPropertyDropdownEditor class that’s shown in Listing 12-20.

Listing 12-20.  SilverlightEntityPropertyDropdownEditor Class

VB:
File:ApressExtensionVBApressExtensionVB.Client.DesignEditors
         SilverlightEntityPropertyDropdownEditor.vb
  
Imports System.ComponentModel.Composition
Imports System.Windows
Imports System.Windows.Markup
Imports Microsoft.LightSwitch.Designers.PropertyPages
Imports Microsoft.LightSwitch.RuntimeEdit
  
Namespace ApressExtensionVB.Editors
  
    <Export(GetType(IPropertyValueEditorProvider))>
    <PropertyValueEditorName("EntityPropertyDropdown")>                        images
    <PropertyValueEditorType("System.String")>
    Public Class SilverlightEntityPropertyDropdownEditor
        Implements IPropertyValueEditorProvider

        Public Function GetEditor(entry As IPropertyEntry) As
           IPropertyValueEditor Implements IPropertyValueEditorProvider.GetEditor
               Return New Editor()
        End Function
  
        Private Class Editor
         Implements IPropertyValueEditor
            Public Function GetEditorTemplate(entry As IPropertyEntry)
               As DataTemplate Implements IPropertyValueEditor.GetEditorTemplate
                Return XamlReader.Load(
                   SilverlightEntityPropertyDropdownEditor.ControlTemplate)
            End Function
  
        End Class
  
        Private Const ControlTemplate As String =
         "<DataTemplate" +
         " xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""" +
         " xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""" +
         " xmlns:editors=""clr-namespace:ApressExtensionVB.Editors;
              assembly=ApressExtensionVB.Client.Design"">" +
         "       <editors:SilverlightEntityPropertyDropdown/>" +
         "</DataTemplate>"
  
    End Class
End Namespace
  
C#:
File:ApressExtensionCSApressExtensionCS.Client.DesignEditors
         SilverlightEntityPropertyDropdownEditor.cs
  
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Markup;
using Microsoft.LightSwitch.Designers.PropertyPages;
using Microsoft.LightSwitch.RuntimeEdit;

namespace ApressExtensionCS.Editors
{
    [Export(typeof(IPropertyValueEditorProvider))]
    [PropertyValueEditorName("EntityPropertyDropdown")]                       images
    [PropertyValueEditorType("System.String")]
    public class SilverlightEntityPropertyDropdownEditor
        : IPropertyValueEditorProvider
    {
        public IPropertyValueEditor GetEditor(IPropertyEntry entry)
        {
            return new Editor();
        }
  
        private class Editor : IPropertyValueEditor
        {
            public DataTemplate GetEditorTemplate(IPropertyEntry entry)
            {
                return (DataTemplate)XamlReader.Load(ControlTemplate);
            }
        }
  
        #region Constants
  
        private const string ControlTemplate =
         "<DataTemplate" +
         " xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"" +
         " xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"" +
         " xmlns:editors="clr-namespace:ApressExtensionCS.Editors;
              assembly=ApressExtensionCS.Client.Design">" +
         "      <editors:SilverlightEntityPropertyDropdown/>" +
         "</DataTemplate>";
  
        #endregion
    }
}

SilverlightEntityPropertyDropdownEditor is the component that’s used by the run-time designer to load your custom editor. Once again, you need to set your PropertyValueEditorName images so that it matches the UIEditor choice that you specified in your LSML file—this setting was shown in Listing 12-17.

This completes the runtime designer example. When you install and run your extension, you’ll be able to modify your ComboBox control’s display property in the runtime designer, as shown in Figure 12-16.

9781430250715_Fig12-16.jpg

Figure 12-16. Silverlight runtime designer

Creating a Group Control Extension

The built-in group controls that you’ll find in LightSwitch include Rows Layout and Columns Layout. Group control extensions allow you to create controls that arrange child items in a custom way. You can build a group control only through an extension.

To demonstrate how to create a group control, this section shows you how to create a control that includes a Show/Hide button. When the user clicks on this button, the control toggles the visibility of the data items that are shown inside the control. Here are the steps to create this control:

  1. Right-click your LSPKG project, create a new Control item, and name it ToggleControl.xaml.
  2. In your Client project, add a reference to the Microsoft.LightSwitch.Client.Internal assembly. The default location of this file on a 64-bit computer is C:Program Files (x86)Microsoft Visual Studio 11.0Common7IDELightSwitchClient. This reference is needed to support the ContentItemPresenter control that you’ll add to your custom control.
  3. Modify your ToggleControl.xaml file by adding the contents from Listing 12-21.

    Listing 12-21.  XAML for ToggleControl Control

    File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsToggleControl.xaml
      
    <UserControl
        x:Class="ApressExtensionVB.Presentation.Controls.ToggleControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:framework ="clr-namespace:Microsoft.LightSwitch.Presentation.Framework;
           assembly=Microsoft.LightSwitch.Client"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <StackPanel>
      
            <StackPanel  Orientation="Horizontal" >
                <TextBlock Text="{Binding DisplayName}"></TextBlock>                  images
                <Button Name="ToggleButton" Content="Hide"                            images
                    Click="ToggleButton_Click"/>
            </StackPanel>
      
            <StackPanel Name="ContentPanel">                                          images
                <!-- ItemsControl binds to the child items of your control-->
                <ItemsControl ItemsSource="{Binding ChildItems}" >
      
                    <!-- This arranges your child items in a rows layout fashion -->
                    <ItemsControl.ItemsPanel>                                         images
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Vertical" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
      
                    <!-- ContentItemPresenter chooses the most suitable control
                          for your data item -->
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <framework:ContentItemPresenter                           images
                               ContentItem="{Binding}"  Margin="4"/>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </StackPanel>
        </StackPanel>
    </UserControl>
  4. Set the SupportedContentItemKind to Group in your control’s LSML file, and remove the Supported Data Types element.

This control consists of two main parts:

  • Logic that defines the Show/Hide button
  • Logic that arranges the child data items that the developer adds to the control

The first part of the XAML defines a TextBlock control that shows the Display Name images. This allows the developer to set a title for the group control, and can help users to identify the purpose of the controls that are shown within the control. The next line of code defines a button called ToggleButton images. This button toggles the visibility of a StackPanel called ContentPanel images. This panel acts as the parent container for the child items that are shown in the control.

The ContentPanel control contains an ItemsControl. This is a Silverlight control that binds to a data source, and it allows you to customize the appearance of each data item by defining a layout in an ItemTemplate. You’ll notice that the ItemsControl defines a data source that specifies a binding string of ChildItems. This is because the underlying IContentitem object that the toggle control (and all other custom group controls) binds to exposes the child data items through a collection called ChildItems.

ItemsControl.ItemsPanel images allows you to define the parent control that contains the ItemsControl.ItemTemplate contents. The ItemsPanelTemplate element contains a StackPanel with its orientation set to vertical. This means that the toggle control displays the child items in Rows Layout style (that is, vertically stacked). If you want to lay out the child items differently, you can use a different layout control here.

The controls that have been described so far define the framework for you control. But you still need to define the controls that render your child data items. One way not to do this is to specify a TextBox control in your ItemsControl.ItemTemplate. Although this can work, a TextBox control isn’t the best control to display every single data item type. For example, your users won’t be impressed if your group control uses a TextBox to display an image. So a much better approach is to use a special control called a ContentItemPresenter images. This control renders the most suitable UI control for the bound data item.

The final task is to add the .NET code that toggles the visibility of ContentPanel, and this is shown in Listing 12-22.

Listing 12-22.  Group Control Code

VB:
File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsToggleControl.xaml.vb
  
Private Sub ToggleButton_Click(sender As Object, e As RoutedEventArgs)
    If ContentPanel.Visibility = Windows.Visibility.Visible Then
        ContentPanel.Visibility = Windows.Visibility.Collapsed
        ToggleButton.Content = "Show"
    Else
        ContentPanel.Visibility = Windows.Visibility.Visible
        ToggleButton.Content = "Hide"
    End If
End Sub
  
C#:
File:ApressExtensionCSApressExtensionCS.ClientPresentationControlsToggleControl.xaml.cs
  
private void ToggleButton_Click(object sender, RoutedEventArgs e)
{
    if (ContentPanel.Visibility == Visibility.Visible)
    {
        ContentPanel.Visibility = Visibility.Collapsed;
        ToggleButton.Content = "Show";
    }
    else
    {
        ContentPanel.Visibility = Visibility.Visible;
        ToggleButton.Content = "Hide";
    }
}

Setting the Visibility of Labels

By default, LightSwitch displays a label next to any control that you add to a screen. Although this is very useful for controls such as text boxes and labels, you generally don’t want labels to appear next to group or custom controls. LightSwitch controls the label settings in the LSML file for your control, as shown in Listing 12-23.

Listing 12-23.  LSML Changes

File:ApressExtensionVBApressExtensionVB.CommonMetadataControlsToggleControl.lsml
  
<?xml version="1.0" encoding="utf-8" ?>
<ModelFragment
  xmlns="http://schemas.microsoft.com/LightSwitch/2010/xaml/model"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  
  <Control Name="ToggleControl"
    SupportedContentItemKind="Group"                                          images
    DesignerImageResource="ApressExtensionVB.ToggleControl::ControlImage"
            AttachedLabelSupport="DisplayedByControl">                        images
    <Control.Attributes>
      <DisplayName Value="Toggle Layout" />
    </Control.Attributes>
    <Control.PropertyOverrides>
      <!-- Override AttachedLabelPosition to allow it to be shown on the
          property sheet. -->
      <ControlPropertyOverride                                                images
          Property=":RootControl/Properties[AttachedLabelPosition]"
          EditorVisibility="PropertySheet">
      </ControlPropertyOverride>
    </Control.PropertyOverrides>
  </Control>
</ModelFragment>

Before you begin to modify the label properties, you should set the SupportedContentItemKind attribute to Group images (to identify your custom control as a group control).

LightSwitch determines the label and other property settings by using Property Value Inheritance. The “Creating a Dependency Property” section in Chapter 11 describes how this works. If a developer doesn’t set an Attached Label Position, LightSwitch uses the setting that’s assigned to the parent control. If an Attached Label Position isn’t set at that level, the resolution system carries on searching parent items until it finds a value. The default attached label position of a screen is left aligned. Unless a developer manually sets the Attached Label Position, your group control will most likely inherit the default label position of left aligned.

To prevent LightSwitch from displaying a label next to your group control, set the AttachedLabelSupport setting to DisplayedByControl images. (The other valid setting is DisplayedByContainer.) Although this switches off the label that appears next to your group control, you’ll still want labels to appear next to child items that are shown inside your control. To do this, the LSML defines a ControlPropertyOverride element images to make certain that Visual Studio includes the Attached Label Position control in the properties sheet for your group control. By allowing developers to set your group control’s Attached Label Position, Silverlight’s Property Value Inheritance behavior will apply the label position to your group control’s child items.

This completes the example, and you can now build and run your extension. Figure 12-17 shows screenshots of the final control at design time, and run time.

9781430250715_Fig12-17.jpg

Figure 12-17. Toggle layout control

In Figure 12-17, you’ll notice that your custom control doesn’t left align the address labels, and because of this, the appearance of this control looks a bit jagged. If you don’t like this appearance, you can resolve this by creating a Rows Layout beneath your Toggle Layout control and adding your data controls beneath your Rows Layout.

Creating a Command Control Extension

The final control extension that I’ll show you in this chapter is a command extension. To demonstrate how to create a command extension, this example shows you how to create a customized button that includes a green background. This control functions in the same way as the custom button that you created in Chapter 11. However, the big advantage of the command extension is that it provides a much better implementation. The custom button in Chapter 11 contains a hard-coded reference to the screen method that the button calls. This imposes a dependency between the custom control and screen, and it results in an approach that isn’t very extensible. By creating an extension, developers can data bind your custom button to the underlying screen method. This allows you to implement custom commands in the way that the LightSwitch designers at Microsoft intended it to work.

Here are the steps to carry out this example:

  1. Right-click your LSPKG project, create a new Control item, and name it HighlightButton.xaml.
  2. Modify your HighlightButton.xaml file by adding the contents from Listing 12-24.

    Listing 12-24.  Custom Button Code

    File:ApressExtensionVBApressExtensionVB.ClientPresentationControlsHighlightButton.xaml
      
    <UserControl x:Class="ApressExtensionVB.Presentation.Controls.HighlightButton"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      
       <Button Content="{Binding DisplayName}" Foreground="White"                     images
          Width="{Binding Properties[Microsoft.LightSwitch:RootControl/Width]}"
          Height="{Binding Properties[Microsoft.LightSwitch:RootControl/Height]}"
          MinWidth="{Binding Properties[Microsoft.LightSwitch:RootControl/MinWidth]}"
          MaxWidth="{Binding Properties[Microsoft.LightSwitch:RootControl/MaxWidth]}"
          MinHeight=
             "{Binding Properties[Microsoft.LightSwitch:RootControl/MinHeight]}"
          MaxHeight=
             "{Binding Properties[Microsoft.LightSwitch:RootControl/MaxHeight]}"
          Click="CustomButton_Click" >
         <Button.Template>
            <ControlTemplate TargetType="Button">
               <Border x:Name="Border" CornerRadius="4">
                  <Border.Background>                                                 images
                     <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#FF294008" Offset="0"/>
                        <GradientStop Color="#FF74BB20" Offset="1"/>
                     </LinearGradientBrush>
                  </Border.Background>
                  <ContentPresenter VerticalAlignment="Center"
                      HorizontalAlignment="Center" />
                  </Border>
              </ControlTemplate>
          </Button.Template>
       </Button>
     </UserControl>
  3. Set the SupportedContentItemKind to Command in your control’s LSML file, remove the Supported Data Types settings, and modify the default label settings.

This XAML defines a normal Silverlight button images. This custom control allows the developer to set the dimensions of control by binding the presentation attributes such as Width and Height to the property sheet values. The button template code allows you to specify the look of your control, and defines the gradient green background color images. The next step is to add the CustomButton_Click method to your CustomButton class, as shown in Listing 12-25.

Listing 12-25.  Custom Button Code

VB:
File:ApressExtensionVBApressExtensionVB.ClientPresentationControls
       HighlightButton.xaml.vb
  
Private Sub CustomButton_Click(sender As Object, e As RoutedEventArgs)
    Dim cmd As Microsoft.LightSwitch.IExecutable =
       CType(Me.DataContext, IContentItem).Details
    If cmd IsNot Nothing AndAlso cmd.CanExecuteAsync Then
        cmd.ExecuteAsync()                                                   images
    End If
End Sub
  
C#:
File:ApressExtensionCSApressExtensionCS.ClientPresentationControls
       HighlightButton.xaml.cs
  
using Microsoft.LightSwitch;
  
private void CustomButton_Click(object sender, RoutedEventArgs e)
{
    IExecutable cmd = (IExecutable)((IContentItem)this.DataContext).Details;
    if (cmd != null && cmd.CanExecuteAsync)
    {
        cmd.ExecuteAsync();                                                  images
    }
}

The Details member of the IContentItem object that’s bound to your control allows you to access the underlying screen method—this is an object that implements the IExecutable interface. You can simply call the ExecuteAsync method images to asynchronously call the method that’s defined on the LightSwitch screen. The next step is to make the necessary changes to your LSML file, as shown in Listing 12-26.

Listing 12-26.  Custom Button LSML File

File:ApressExtensionVBApressExtensionVB.CommonMetadataControlsHighlightButton.lsml
  
<?xml version="1.0" encoding="utf-8" ?>
<ModelFragment
  xmlns="http://schemas.microsoft.com/LightSwitch/2010/xaml/model"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Control Name="CustomButton"
    SupportedContentItemKind="Command"                                       images
    DesignerImageResource="ApressExtensionVB.CustomButton::ControlImage"
    AttachedLabelSupport="DisplayedByControl">                               images
    <Control.Attributes>
      <DisplayName Value="Highlight Button" />
    </Control.Attributes>
  </Control>
</ModelFragment>

This code sets the SupportedContentItemKind to Command images to identify the control as a command control. Just like the preceding group control example, you’ll want to hide the default label that LightSwitch displays to the left of your custom button. This is achieved by setting the AttachedLabelSupport attribute to DisplayedByControl images.

This completes the custom button control sample. Figure 12-18 shows screenshots of the final control at design time and run time.

9781430250715_Fig12-18.jpg

Figure 12-18. Custom button

Summary

Extensions are components that can extend the Visual Studio IDE and allow you to add extra capabilities to your Silverlight LightSwitch applications. To use an extension, the first step is to install it in Visual Studio. Extensions are packaged into files with a VSIX extension. To install an extension, simply run the VSIX file. When the installation completes, your extension will appear in Visual Studio’s Extensions And Updates dialog. This dialog allows you to manage and uninstall extensions from Visual Studio. Before you can use an extension in a LightSwitch project, you have to first enable it through the properties window of your LightSwitch project. LightSwitch provides six extensibility types: business types, controls, data sources, screen templates, shells, and themes. This chapter concentrated on how to create control extensions, and the next chapter (Chapter 13) shows you how to create the remaining extension types.

Instead of directly using Silverlight controls as I showed you in Chapter 11, developers can more easily work with controls that are installed as an extension. If a developer wants to use a control extension on a LightSwitch screen, there’s no need to select the control assembly from a separate dialog and write custom data-binding code. Developers can simply use the data item’s drop-down menu to select the custom control and they’re done. Another benefit of control extensions is that they support layout controls that can’t be implemented in any other way (like RowsLayout and ColumnsLayout). When you’re writing a custom control extension, you can create custom properties that developers can set through Visual Studio’s properties sheet.

There are five types of control extension that you can create: value, details, command, collection, and group. You’ll already be familiar with these control types because you’ll have used controls that match these control types in your day-to-day use of LightSwitch. For instance, a value control displays a single value—just like a text box or label. A details control displays an entity—just like an autocomplete box or model window picker.

To create extensions, you need to install the Visual Studio SDK and the Microsoft LightSwitch Extensibility Toolkit. You can download these components free of charge from the Microsoft web site. Once you’ve set up your computer, you can add an Extension Library Project by using Visual Studio’s Add New dialog. An Extension Library Project contains seven individual projects, one of which is an LSPKG project. The LSPKG project allows you to create one of the six extension items.

The first step in creating a control extension is to right-click your LSPKG project and select the New Item option. This creates all of the files that are needed. To complete your control extension, you can simply flesh out these files with the functionality that you want to add. The main file in a control extension is a XAML file (a Silverlight user control) that defines the UI for your control. A custom control binds to an IContentItem object on a LightSwitch screen, and this allows you to use Silverlight data-binding techniques to bind the UI elements on your custom control to the underlying screen data item. The metadata for your control is stored in a corresponding LSML file. This metadata allows you to set the control type (for example, value, details, collection, and so forth), display name, description, and supported data types.

To debug an extension, you can simply press F5. This starts an experimental instance of Visual Studio, and you can debug your extension project by adding breakpoints and opening a LightSwitch project in your experimental instance. The output from your VSIX project allows you to deploy and distribute your extension.

To demonstrate how to create a details control, this chapter shows you how to create a ComboBox control. This control includes custom properties that allow the developer to set the ComboBox data source and the data property that the control shows on each row of the ComboBox. You’ve learned how to extend the Visual Studio property sheet and Silverlight runtime designer to allow developers to set these custom properties. To extend the Visual Studio property sheet, you would create a WPF user control. You use WPF because Visual Studio itself is built with WPF. You’d then create an editor class that implements the IPropertyValueEditorProvider interface and returns the XAML markup that represents your WPF custom editor in a method that’s called GetEditorTemplate. The UIEditorId setting in the control’s LSML file allows you to connect your custom property with your custom editor. Customizing the runtime designer uses a very similar process, except that you create a Silverlight user control rather than a WPF user control.

This chapter has showed you the code that allows you to access the summary property. You’ve learned how to encapsulate this code in a helper class and to include logic that returns the first text property, or a property that can be represented as a string if the developer hasn’t defined a summary property.

Group control extensions allow you to develop controls that arrange child items in a custom way. You’ve learned how to build a group control that includes a button to allow the user to toggle the visibility of the items that are shown in the control. The XAML for a group control contains an ItemsControl. This is Silverlight control that allows you to bind to data and to customize the appearance of each data item by defining a layout in an ItemTemplate. The ItemTemplate contains a ContentItemPresenter control. This control allows LightSwitch to render the child data items by using the Silverlight control that most suits the data type of the bound item.

Finally, this chapter has showed you how to create a command control Extension that features a button with a green background. A command control is designed to bind to a LightSwitch screen method. Within your Command Control, you can call the underlying screen method by using the Details member of an IContentItem object that’s bound to your control. The Details member exposes an IExecutable object that allows you to run the underlying screen method by calling the IExecutable object’s ExecuteAsync method.

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

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