LightSwitch gives you a simpler and faster way to create high-quality business applications for the desktop and the cloud. When the team created LightSwitch, they also laid the groundwork for enterprise developers, IT departments, community contributors, ISV
’s (Independent Software Vendors), and even individual developers to extend it in various ways.
Out-of-the-box, LightSwitch is already a fairly compelling RAD (Rapid Application Development) experience, but there’s room to add significant value to the product, by creating extensions. In essence, the team has tried to give you what you need, while allowing you to add what you want.
LightSwitch provides the following extension points: themes, shells, screen templates, controls, business types, and data sources. Each of these extension points involves different LightSwitch tiers, as shown in Figure 10-1.
Extensions can enable users to increase their productivity by providing extra functionality beyond LightSwitch’s current capabilities. The six types of LightSwitch extensions fall into three broad categories:
When you create a LightSwitch application, it looks the same as any other LightSwitch application. You can spot a standard LightSwitch application immediately. Microsoft has taken a pretty conservative approach with the program’s default theme. Its internal name is BlueGlossy, even though it’s far from, well, “glossy.”
Actually, there are two built-in themes, but one of them isn’t available for you to choose unless you happen to know it exists (which you now do) and you specifically make it available. It’s a high-contrast theme that uses selected Windows system colors, and it’s automatically used if your Windows theme is set to a high contrast theme. Its internal name is simply Black.
By the time you finish reading this chapter, you should be able to create your own theme. Later in the chapter, after we’ve showed you how to create a theme extension, we’ll also show you how to activate this “hidden” theme (of course, it isn’t really hidden) as one of the selectable theme choices.
So, if you’d like to ensure that your LightSwitch application doesn’t look generic, you can add a bit of sophistication and polish to it by creating:
A theme is the basically the look of the application (the colors, the fonts, the control styles), while a shell can be thought of as the feel of it (the position and functionality of, say, the menu system for example, whether screens are exposed as a series of tabs or in some other way). By using both a theme extension and a shell extension together, you can completely customize the look and feel of a LightSwitch application, compared to the default appearance.
Screen templates enable you to save time by not having to create the same type of screen from scratch, over and over. For example, for every application that you write, you will probably want some type of home page for it. Later in this chapter, we’ll show you how you could create a standard home page by creating a screen template extension. The new template will then be displayed as another choice in the Add New Screen Wizard.
Chapter 9 showed you how to create custom controls. Using a custom control allows you to enter data in a way that isn’t available in LightSwitch out-of-the-box. Creating a control extension allows you package custom controls for use in multiple projects, or to share them with the community, or even sell them.
Business type extensions enable you to create new data types based on the existing data types that LightSwitch already understands. When you choose Money
as the type for a property, you’re really storing a Decimal
value. When you choose Phone Number
, you’re really dealing with a String
value. Each business type extends the data type that it’s based on, adding custom formatting and perhaps extra validation.
In Chapter 7, you learned how to create a custom RIA service, which enables you to access external data that LightSwitch doesn’t natively know how to talk to. The downside is that you’d have to include that code in any application that needs access to that particular type of data.
If you need to access a particular type of external data in several LightSwitch applications, you can encapsulate the code in a data source extension, and by doing that make it available to those applications, without having to duplicate the code in each one.
While extensions can be developed in-house, they can also be created by third-party vendors, ISVs, and even members of the LightSwitch community. Some extensions are available at no charge, some are offered as trial versions (free to try, but must be purchased after a predetermined amount of time for them to continue to provide full functionality), and others are paid products that you’ll have to buy before you can use them.
Installing a trial edition of an extension, if one’s available, can be a good way to test it out first, to see whether it does what you need before you actually buy it. Colleagues and friends can share free extensions and trial extensions via email by simply sending the setup file. You can go to the Visual Studio Gallery web site or use Visual Studio’s Extension Manager to search for, download, and then install any of the three extension types (free, trial, or paid).
The Visual Studio Gallery web site (http://visualstudiogallery.msdn.microsoft.com/site/search
) is a kind of marketplace for all types of Visual Studio extensions, including LightSwitch extensions. Being a website, you can access it with your favorite browser. On the site, you can either browse through the myriad of available extensions (there are thousands of them!), or use search criteria to narrow your search to the particular type of extension that you’re interested in.
Figure 10-2 shows several free LightSwitch controls, made available to the LightSwitch community by one of the book’s authors.
The Extension Manager that’s built into Visual Studio (see Figure 10-3) provides a more integrated way to access the gallery contents, without the need for a browser. You’ll notice, though, that there are no criteria selections, as there are on the web site, apart from a text search. This is also the place (in fact, it’s the only place) to select any extension that you’ve previously installed and either disable it or uninstall it altogether.
There are two methods that you can use to access the Extension Manager.
You can access the Extension Manager via the Extensions tab (see Figure 10-4) of the LightSwitch project’s properties:
Properties
folder in Solution Explorer.You can also access the Extension Manager via the Tools menu in Visual Studio (see Figure 10-5):
After the Extension Manager is open, as shown in Figure 10-6:
Several LightSwitch community members have made extensions available. These extensions fill gaps that exist in the current version or offer new functionality not provided by the team. In both cases, it’s possible that the team just didn’t have the time to include everything that they would have liked to include in the first version of the product.
Two of the first contributors, and the contents of their contributions, are listed below. Other community members have since followed their example.
Spursoft LightSwitch Extensions is the most polished of the currently available third-party commercial extensions. They started life humbly as a small suite of free controls, which has now grown into quite a professional offering. Although now a paid product, it’s available at a very modest price.
http://www.spursoft.com/Extensions.htm
Twilight Blue
Twilight Green
Hill Country
(inspired by the colors in the Hill Country area in Texas)Several extensions are currently available for free from Luminous Software, (although though donations are welcome to help cover development costs).
http://lightswitchcentral.com.au/Extensions/LuminousSoftware.aspx
Luminous Dark Purple
Luminous Dark Blue
TextBlock
(allowing static text to be added to a screen, with no code)GroupLayout
(allowing controls to be grouped, with a title, a themed background, and a border, all with no code)Percent
type (stores value as decimal, displays it as a percent)ISBN
type (validates ISBN-10, and ISBN-13 numbers)Link
type (clicking on the URL launches a web browser)Password
type (stores a string, displays a masked value)Close And Save
(closes the current screen, saving any changes)Close And Cancel
(closes the current screen, cancelling any changes)Log Off
(logs off the current user, but only in web applications)Exit
(exits the application completely)Several third-party vendors claim to support LightSwitch. As of this writing, only some have developed native LightSwitch controls, while others provide tutorials on how to use their Silverlight offerings as LightSwitch custom controls. We suspect that they may be waiting for version 2 of LightSwitch before making a full commitment.
http://lightswitchcentral.net.au/Extensions/ThirdParty.aspx#infragistics
TileView
navigation)DateTime
, Numeric, DateTime
range, Numeric range)http://lightswitchcentral.net.au/Extensions/ThirdParty.aspx#firstfloor
http://lightswitchcentral.net.au/Extensions/ThirdParty.aspx#componentone
BI
(Business Intelligence) functionality)
http://lightswitchcentral.net.au/Extensions/ThirdParty.aspx#rssbus
http://lightswitchcentral.net.au/Extensions/ThirdParty.aspx#devexpress
URL
or a database field)URL
s and launch them in a browser)http://lightswitchcentral.net.au/Extensions/ThirdParty.aspx#telerik
If you didn’t install the extension from either the Visual Studio Gallery or Visual Studio Extension Manager (maybe you saved it instead, or received it in an email from a colleague), installation is as simple as clicking (or double-clicking) the extension’s setup file (VSIX) and following the installation prompts.
But to actually use the extension in your current LightSwitch project, you need to activate it for the project by selecting the extension’s check box in the column to the left of the Name column in the Extensions tab of the project’s properties, as shown in Figure 10-7.
If you want to have an extension activated by default for all future projects, select its check box in the Use in New Projects column, also shown in Figure 10-7.
If the extension that you’ve activated is a shell or a theme, you’ll also need to make a selection on the General Properties tab (see Figure 10-8).
Advanced developers can extend the functionality of LightSwitch by creating their own extensions.
LightSwitch uses MEF (Managed Extensibility Framework) to enable extension elements to be defined by the developer at design-time (exported) and then consumed by LightSwitch at runtime (imported).
You’ll need three prerequisites before you can start creating extensions. The last item in the list is optional, but it will make your extension-creating life a lot easier.
The minimum version of Visual Studio that you can use to create extensions is Visual Studio Professional. You can use any of the higher versions (Premium or Ultimate), but you can’t use any of the Express versions. If you have an MSDN
(Microsoft Developer Network) subscription, you should be able to download LightSwitch as part of your subscription benefits.
The Visual Studio 2010 SP1 (SDK (Software Development Kit)) provides tools and templates for building Visual Studio extensions. The SDK is available for free download from the Microsoft Download Center:
www.microsoft.com/download/en/details.aspx?id=21835.
Note Because Visual Studio SP1 has to be installed for you to be able to install LightSwitch, make sure you install the SP1 version of the SDK, not the earlier RTM version.
To test your extensions, you’ll need either a trial version of LightSwitch or the retail version. You can obtain a copy of LightSwitch by:
http://www.microsoft.com/visualstudio/en-us/lightswitch
)http://www.microsoftstore.com/store/msstore/en_US/pd/productID.230090400
)Shortly after the release of the RTM (Release to Manufacturing) version of LightSwitch, the LightSwitch 2011 Extensibility Toolkit was made available as a free download. With the release of this toolkit, the creation of extensions became a reality for more developers than ever before. It’s a huge improvement over the Extensibility Cookbook that was available in the Beta 2 time frame.
When you create an extension library (explained in an upcoming section), you can add any type of LightSwitch extension to it, or even different types of extensions. You might decide to create a separate extension library for each type of extension, or you might decide to create a suite of extensions, all contained in the one library. The choice is up to you.
The toolkit provides both VB and C# project types for creating LightSwitch extension libraries, as well as individual templates for creating themes, shells, screen templates, controls, business types, and data sources. The templates are really just an automated process to make adding an extension type to a library easier for you.
After a while, when you get used to what files make up a particular extension, you may find that it’s just as easy to create a new extension by copying and pasting the various files, especially if you decide to make modifications to the automatically generated folders and/or code.
Tip A good example of a type of extension that you might want to put in its own project is a theme extension. A developer can choose only one theme at any particular time. The end user can’t currently change that, so including several themes in one extension may not be the best way to distribute them.
To install the Extensibility Toolkit, assuming that you already have Visual Studio Professional (or higher) installed on your machine (make sure to follow the order listed below):
www.microsoft.com/download/en/details.aspx?id=21835
.http://visualstudiogallery.msdn.microsoft.com/0dfaa2eb-3951-49e7-ade7-b9343761e1d2
.LightSwitch Extensibility Toolkit.exe
file to your local machine (be sure to remember where you extracted it).Microsoft.LightSwitch.Toolkit.vsix
file.Microsoft.LightSwitch.Toolkit.targets
file into one of these folders:
Caution Failure to install the SP1 SDK or to copy the target files will mean that the toolkit will not perform as expected.
We’ll be creating a single extension project to hold all of the examples in this chapter, but as mentioned earlier, you could just as easily create one project per extension type or even one project per individual extension if you really wanted to do it that way.
To create the project (see Figure 10-9):
Central.Extensions
.You should now have a Visual Studio solution that initially contains seven projects, as shown in Figure 10-10.
Caution Be careful not to give your extension project a name that is too long.The extensibility toolkit project template can run into a character limit when the whole path is taken into consideration. Long names can result in an error message:
Let’s look at each of the projects in detail, so you know why they exist, as well as which parts of your extensions go into which projects, and why.
The Client
project is a Silverlight project that contains client implementations that should be deployed with a LightSwitch application but are not found in the Common
project (for example, controls, shells, and themes). The classes/files in this project are used to display the extension in the client application.
When the Client
project is initially created by the template, it has no folders or files:
Each extension type can create the Presentation
folder if it hasn’t already been created by another extension. However, no extension type adds files directly to the actual folder itself.
Each extension type can add its own subfolder to the Presentation
folder:
The Client.Design
project is a Silverlight project that contains the implementations necessary during the debugging of a LightSwitch application (for example, control images or custom property editors for the runtime screen designer). The classes/files in this project are used in the Design Screen experience.
When the project is initially created, it has no folders:
As with the Presentation
folder in the Client
project, some extension types will add a Resources
folder if it doesn’t already exist, but no extension type adds files directly to the folder itself.
A control extension or business type extension will add a ControlImages
subfolder to the Resources
folder. You can add further subfolders to it manually, as shown in Figure 10-11, in order to better organize the elements that get added to it by the extension templates (remember to manually move the files that the templates create, though).
For a single extension, this might not really be an improvement, but the more extensions that you add to an extension library, the more this will help. (Being able to collapse folders that you’re not working on is a big advantage here.)
The Common
project is a Silverlight project that primarily contains the extension’s metadata definitions, along with any code that runs on the middle tier (on both the client and on the server), such as metadata loaders and validators.
The metadata definitions are contained in LSML files, each of which represents a model fragment that is merged with all other model fragments at runtime.
When first created, the Common
project has two folders, Metadata
and Resources
:
The Metadata
folder contains files that define and load metadata, both for the extension library itself and for any extension types that are added to it. The Metadata
folder initially contains two files, Module.lsml
and ModuleLoader.vb
/cs
:
The Module.lsml
file simply contains a small XML model fragment that declares a module name for the extension. There’s only one of these files per extension library.
The purpose of the ModuleLoader
class is to define the ResourceManager
that’ll be used by the extension to find its resources, and to define a ModuleLoader
class to load any model fragments (LSML files) that have been added to the extension’s assembly as a resource.
The ModuleLoader
class has a Friend
/Internal
1 modifier, meaning that it can be accessed by code that’s in the same assembly, but not from code that’s outside the assembly. There’s only a single instance of this class per extension library.
The class is decorated with two attributes:
Export
attribute, to alert the LightSwitch application that this class exports an implementation of IModuleDefinitionLoader
ModuleDefinitionLoader
attribute, which provides the String
value of the extension’s name (Central.Extensions
)It implements a single interface, IModuleDefinitionLoader
, which contains two methods:
GetModelResourceManager
sub (void function in C#), which returns the instance of a ResourceManager
that the extension should use to find its resourcesLoadModelFragments
function, which returns an IEnumerable(Of Stream)
that gets populated with any file with an LSML file extensionThe Metadata
folder doesn’t initially contain any subfolders, but each extension type can add its own subfolder:
________________________
1Access modifiers are keywords used to specify the declared accessibility of a member, or a type:
Public / public:
access is not restrictedProtected / protected:
access is limited to the containing class, or types derived from the containing classFriend / internal:
access is limited to the current assemblyProtected Friend / protected internal:
access is limited to the current assembly, or types derived from the containing classPrivate / private:
access is limited to the containing typeThe Resources
folder contains resource files, both for the extension library and for any extension types that are added to it. The Resources
folder is created with only a single file in it:
The ModuleResources.resx
file contains any embedded resources, such as localized strings, which are added via the Resource Editor. Initially there aren’t any resources in it.
The Design
project is a WPF project, and contains elements such as design-time controls, images, and supporting code (for example, a screen template). The classes/files in this project are used for the design-time experience in the IDE (such as designing a screen).
At first, it may seem like the files in this project are redundant duplicates of the files in the Client.Design
project, but the difference is that the Client.Design
project is a Silverlight project, used for the display of the extension’s UI elements in the Silverlight client, and the Design
project is a WPF project, used for the display of the extension’s elements in the Visual Studio IDE.
When the project is initially created, it has no folders:
As with the Resources
folder in the Client.Design
project, some extension types will add a Resources
folder if it doesn’t already exist, but no extension type adds files directly to the folder itself.
A control extension or business type extension will add a ControlImages
subfolder. You can add further subfolders to it manually, as shown in Figure 10-12, to better organize the elements that are added to it by the extension templates (you’ll have to manually move the files that the templates create, though).
The Server
project is a .NET project that contains server implementations that should be deployed with a LightSwitch application but are not found in the Common
project (for example, data sources). It has access to assemblies of the full .NET Framework.
When the project is initially created, it has no folders:
A data source extension item creates a DataSources
folder if it hasn’t already been created by another data source extension, but no files are added directly to the folder itself.
This project exists only as a place to pull all of the other projects together, for packaging into the VSIX file. It packages the previous five projects so that LightSwitch can unpack and reference them when the package is installed.
If you watch carefully, you may notice that files and folders are created in this project, and then they’re moved to one of the other projects. Interesting technique!
Occasionally you might also see errors displayed that tell you that you can’t mix platform types. The error message states:
The project ‘ExtensionName.ProjectName’ cannot be referenced. The referenced project is targeted to a different framework family (Silverlight).
These particular errors can safely be ignored, because the project doesn’t generate any code. You’ll also find that a project/solution rebuild will usually make them go away. But they can be a bit disconcerting when you see them for the first time.
When the extension project is first created, the Lspkg
project contains no folders or files:
You add a template item by right-clicking the Lspkg
project and then selecting Add New Item. The Add New Item dialog box opens, as shown in Figure 10-13.
When you add a template item, one or more folders or files may be added to some of the other projects, depending on the type of item you’re adding.
This project packages the generated LSPKG
file into a VSIX
package so that it can be added to LightSwitch through the Extension Manager. It initially contains only a single file:
Any VSIX-supported project template, including a LightSwitch extension library, generates a manifest file and calls it source.extension.vsixmanifest
. When Visual Studio builds the project, it copies the content of this file into Extension.VsixManifest
in the VSIX
package. The package file is actually a simple ZIP file that uses the Open Packaging Conventions.
There’s usually no need to edit the manifest file manually. You can set the properties by using the VSIX Manifest Designer to modify the source.extension.vsix
manifest file in the VSIX
project. Simply double-click the file to open it in the editor (see Figure 10-14).
The VSIX Manifest Designer goes further than just providing an easy way to edit the VSIX manifest’s XML contents. It integrates into Visual Studio to simplify the process of adding content to the VSIX file.
When the Icon setting, the Preview Image setting, or the License Terms setting is specified via the Manifest Editor, the corresponding files are added to the project if they are not already present. The Include in VSIX property for those files is also set to true, so they’ll be copied into the VSIX file that’s generated when the project is built (or rebuilt).
Let’s look at each of the VSIX’s manifest settings individually. We’ve sorted them into two categories:
The values for these manifest settings determine what information appears for your extension in:
The value for the License Terms setting is also displayed in the Visual Studio Extension Installer (see Figure 10-16), as the extension is being installed.
The value of this setting is displayed as the Name value in the Extensions tab of the LightSwitch project, and in the Extension Manager. The default is the name that you supplied for the extension library (solution name), but it can be changed at any time, to whatever you want to call your product.
The maximum length for this setting is 60 characters.
The value of this setting is displayed as the Created By value in the Extensions tab, and in the Extensions Manager. The default is the Company Name that was entered when you installed Visual Studio. You can change this property to anything you like, but it’s a good idea to be consistent.
The maximum length is 50 characters.
The version number that you enter here will be used by the Extension Manager to determine whether there is a new version (update) of the extension. The Extension Manager won’t allow the VSIX to be installed if there’s already an extension installed with the same version number (or higher).
The maximum length is 23 characters.
SAMPLE VERSIONING SYSTEM
Tip If you have the Visual Studio Productivity Power Tools extension installed, you might find it helpful to pin the manifest file in Visual Studio, so it’s always available for you during development and debugging of your extension. You can find yourself changing the build number often as you test or fix your extension’s functionality.
The value of this setting is displayed as the Description value for the selected extension in the Extensions tab, as well as in the Extension Manager. The default is the solution name plus the word description.
It’s best to keep the description fairly short, if possible, because although the gallery web page will display several lines, as will the Extensions tab, the Extension Manager will display only two lines. It may look odd if your description is cut off after two lines. You can change the Description setting at any time.
The maximum length is 1,024 characters.
Here you can specify an ICO, BMP, JPG, GIF, JPG, PNG, or TIFF file that will be displayed beside the extension name. Specifying an icon file is optional but highly recommended. A default image will be used if you don’t supply one.
This optional setting allows you to specify a BMP, JPG, GIF, PNG, or TIFF file that shows a preview of what the extension looks like. The same image that was specified for the Icon setting will be used if you don’t supply a value here.
Microsoft’s documentation for this setting suggests that you should use an image that is 200200 pixels, but you might want to find out for yourself what gives you the best result.
Here you can enter an HTTP/HTTPS URL that points to a web page that has more information about your extension. A More Information link will be displayed in the Extension Manager and in the Visual Studio Gallery.
If you don’t specify a URL, the hyperlink is disabled.
This setting allows you to enter an HTTP/HTTPS URL that points to a web page indicating how to get started with your extension. A Getting Started link will be displayed in the Extension Manager and in the Visual Studio Gallery.
If you don’t specify a URL, the hyperlink is disabled.
Internal settings aren’t exposed to the consumer of the extension. They’re used internally by the Extension Manager.
This setting is the extension’s unique identifier. This value is used by the Extension Manager to identify when a new version of the extension is being deployed. You can’t install two extensions that have the same ID in the same installed instance of Visual Studio.
Be careful what you use here, because if you change it later, any future versions of your extension won’t be recognized as an update to the original extension. We learned this lesson the hard way, but now you know about it, so you can avoid this problem.
The value is initially set to the name that you supplied for the extension library (the solution name), but it can be changed to anything you like. You won’t see it anywhere, though, because it is used only internally.
The maximum length is 100 characters.
This setting determines the intended locale of your extension. The default value is the locale of the installed version of Visual Studio. Only a limited number of locales are currently available (see Figure 10-17).
Figure 10-18 shows a hierarchy diagram of the various Visual Studio editions that an extension can normally support. The Express editions aren’t supported for LightSwitch extensions, of course.
The default values for this setting are as follows:
Caution Don’t change the values in this property. LightSwitch will recognize your extensions only if the default values haven’t been changed.
The Extension Manager will allow the extension to be installed only if the version of Visual Studio that’s currently installed on the machine (or a higher version) has been specified in this dialog box. The LightSwitch extension project template automatically selects Visual Studio 2010, as well as Additional Visual Studio Products. It also adds VSLS in the additional versions field, as you can see in Figure 10-19.
This setting allows you to include a TXT or RTF file that describes the extension’s terms of use or licensing agreement. This will be displayed in the Visual Studio Extension Installer’s dialog box during installation.
It’s an optional setting, and the default is empty. If you don’t enter the name of a file, the installer will display Your license terms go here.
These two values are automatically set for you. In LightSwitch 2011:
In the previous section, we explored the different projects that a LightSwitch extension is composed of. As you saw, not all projects are used by all extension types. However, each extension type has its own unique set of requirements. The most basic of these are certain attributes and interface implementations.
On the one hand, attributes enable LightSwitch to know what features your extension is offering, whether it’s a theme extension or a shell extension, and so on. Attributes can also provide some basic information about the extension itself, such as an ID, a name, or even a version number. LightSwitch imports extensions, via MEF, that are made known to it through the use of the Export
attribute and a contract value. This contract must match the contract that LightSwitch is expecting for a particular extension type.
On the other hand, interfaces give LightSwitch a way of consuming your extension by being able to make assumptions about their behavior in a consistent manner. The interfaces that LightSwitch provides determine what an extension can do, without having to have exact knowledge of how that’s achieved.
Because of publishing deadlines, and to keep the chapter to a manageable length, we weren’t able to include step-by-step instructions for each of the extension types, as much as we would have liked to. There’s a huge amount of information to absorb regarding to how create extensions. Each extension type could easily be its own chapter. Each of the control type extensions could also fill a chapter of their own as well.
So although we could have written much, much more on actually creating extensions, we’ve chosen to concentrate on helping you understand them and hopefully provide the “missing” information that’s hard to find anywhere else, or that’s scattered all over the Internet. Microsoft and a few others have written articles that walk through creating various types of extensions, but without the background information, these articles can be hard to follow, or worse, hard to understand.
We hope that we’ll able to follow up this book with more-specific extension-related articles. Be sure to visit LightSwitch Central (www.lightswitchcentral.net.au
) for related blog posts, articles, or downloads.
Let’s now have a look at understanding each of the extension types in a bit more detail.
As mentioned earlier in the chapter, a theme extension allows you to change your application’s visual impact (the look part of look and feel). You do this by changing the font and color values, and by specifying alternative values for the various brushes that are used by your LightSwitch application’s shell, screens, and controls. You could create a theme to make your application match your company’s colors, for example.
A theme extension can contain two implementation types: a Theme
and a somewhat unfortunately named ThemeExtension
(not to be confused with an actual theme extension). It probably would have been a lot less confusing if it had been called something like a StyleExtension
or a ControlStyleExtension
, but its name comes from the fact that it’s an extension to a theme, in the form of additional control styles.
Web sites often use what’s called a style sheet, usually referred to as CSS (Cascading Style Sheets) to define the colors and fonts, as well as the control styles, for the entire site in one place. LightSwitch defines its color and font information in a Theme
, which is stored in a ResourceDictionary
in a XAML file. It can also define control styles in a ThemeExtension
, which is also stored in ResourceDictionaries
in one or more XAML files.
By using themes, it’s easy to change the colors for the entire application in one place, just by selecting a different theme. By using control styles, it’s easy to change the way various controls look in the application, again all defined in one place.
The attributes and interfaces that a theme extension requires are found in the Microsoft.LightSwitch.Theming
namespace.
There are a few simple steps required to create a theme extension:
LSPKG
project (Central.Extensions.lspkg
)CentralTheme
).Two attributes must be applied to any class that defines a theme, for it to be recognized by LightSwitch. Two attributes also need to be applied to any class that defines a ThemeExtension
(not to be confused with a Theme
extension), for it to be recognized by LightSwitch. Table 10-1 lists these four attributes.
Listing 10-1 shows these attributes being used in code, to decorate the class that defines a theme.
VB:
File: N/A
Friend Const ThemeId As String = "Central.Extensions:CentralTheme"
Friend Const ThemeVersion As String = "1.0"
<Export(GetType(ITheme))>
<Theme(CentralTheme.ThemeId, CentralTheme.ThemeVersion)>
Public Class CentralTheme
Implements ITheme
Implements IThemeExtension
'implementation code goes here
End Class
C#:
File: N/A
internal const string ThemeId = "Central.Extensions:CentralTheme";
internal const string ThemeVersion = "1.0";
[Export(typeof(ITheme))]
[Theme(CentralTheme.ThemeId, CentralTheme.ThemeVersion)]
public class CentralTheme : ITheme, IThemeExtension
{
//implementation code goes here
}
In addition to being decorated with the two attributes just described, a class that defines a Theme item must also implement the ITheme
interface. You can also optionally implement IThemeExtension
.
A theme extension can have one or more ITheme
implementations, as well as one or more IThemeExtension
implementations, though in practice you’d probably really want only one of either in a single theme extension. You can, of course, have multiple theme extensions in a single extension library.
The ITheme
interface consists of three properties, shown in Table 10-2. The default values are based on the extension library name (CentralExtensions
) and the theme extension pathname (Central/CentralTheme
).
The IThemeExtension
interface has only one method to implement, GetControlStyleResources
, which must return an IEnumerable(Of Uri)
. Its purpose is to expose one or more URI
values that each point to a ResourceDictionary
(stored in a XAML file) that contains control style definitions. The method has three parameters: themeId
, themeVersion
, and modules
.
A LightSwitch application will load theme-related information in the following order:
ColorAndFontScheme
ITheme
implementation’s ColorAndFontScheme
ControlStyleResources
IThemeExtension
’s ControlStyleResources
Let’s look at what happens when you use the Extensibility Toolkit to add a theme. As we mentioned before, once you know what the template is doing for you, it can be almost as quick to copy/paste/edit an existing theme. This is especially true if you decide to change any of the locations of the various files, as we will in the example.
The following paragraphs assume that we’ve called our theme CentralTheme
.
When we add a theme, using the Extensibility Toolkit’s Theme template, as we mentioned earlier in the chapter, a Themes
folder is added to the Client
project’s Presentation
folder for us (see Figure 10-21).
We recommend creating a separate folder for each theme in the Themes
folder, and moving the files created by the template into it. You don’t have to do this, but it helps keep the project more organized. For our example theme, we’ll create a folder called Central
and move the files into that folder.
The CentralTheme
class contains the ITheme
implementation (see Listing 10-2) that we discussed earlier. Because we moved the files for the theme into their own folder, we also have to make a small change to the ColorAndFontScheme
property’s value, changing /Themes/
to /Themes/Central/
in order to reflect the folder change.
You could also put the code for any IThemeExtension
implementations in this file, or you could put them in a file of their own, or even a file per ThemeExtension
.
Caution If you move the XAML file to a different folder than the one the template put it in, as we have, make sure that you edit the Theme
class so that the ColorAndFontScheme
property points to the correct location.
VB:
File: Central.ExtensionsCentral.Extensions.ClientPresentationThemesCentralCentralTheme.vb
Imports System
Imports System.ComponentModel.Composition
Imports System.Collections.Generic
Imports Microsoft.LightSwitch.Theming
Imports Microsoft.LightSwitch.Model
<Export(GetType(ITheme))>
<Theme(CentralTheme.ThemeId, CentralTheme.ThemeVersion)>
<Export(GetType(IThemeExtension))>
<ThemeExtension(CentralTheme.ThemeId)>
Public Class CentralTheme
Implements ITheme
Implements IThemeExtension
'constants
Friend Const ThemeId As String = "Central.Extensions:CentralTheme"
Friend Const ThemeVersion As String = "1.0"
'ITheme members
Public ReadOnly Property ColorAndFontScheme As Uri
Implements ITheme.ColorAndFontScheme
Get
'this is a modified version of what appears in the file created by the template
Dim uriString = String.Format("{0}{1}{2}"
, "/Central.Extensions.Client;"
, "component/Presentation/Themes/Central/"
, "CentralTheme.xaml"
)
Return New Uri(uriString, UriKind.Relative)
End Get
End Property
Public ReadOnly Property Id As String
Implements ITheme.Id
Get
Return CentralTheme.ThemeId
End Get
End Property
Public ReadOnly Property Version As String
Implements ITheme.Version
Get
Return CentralTheme.ThemeVersion
End Get
End Property
'IThemeExtension members
Public Function GetControlStyleResources(
ByVal themeId As String
, ByVal themeVersion As String
, ByVal modules As IEnumerable(Of IModuleDefinition)
) As IEnumerable(Of Uri)
Implements IThemeExtension.GetControlStyleResources
'this is a modified version of what appears in the file created by the template
Dim uriString =String.Format("{0}{1}{2}"
, "/Central.Extensions.Client;"
, "component/Presentation/Themes/"
, "CentralTemplates.xaml"
)
Dim result as new List(Of Uri) From
{
New Uri(uriString, UriKind.Relative)
}
Return result
End Function
End Class
C#:
File: Central.ExtensionsCentral.Extensions.ClientPresentationThemesCentralCentralTheme.cs
using System;
using System.ComponentModel.Composition;
using System.Collections.Generic;
using Microsoft.LightSwitch.Theming;
using Microsoft.LightSwitch.Model;
[Export(typeof(ITheme)), Theme(CentralTheme.ThemeId, CentralTheme.ThemeVersion)]
[Export(typeof(IThemeExtension)), ThemeExtension(CentralTheme.ThemeId)]
public class CentralTheme : ITheme, IThemeExtension
{
//constants
internal const string ThemeId = "Central.Extensions:CentralTheme";
internal const string ThemeVersion = "1.0";
//ITheme members
public Uri ColorAndFontScheme
{
get
{
//this is a modified version of what appears in the file created by the template
var uriString = string.Format("{0}{1}{2}"
, "/Central.Extensions.Client;"
, "component/Presentation/Themes/Central/"
, "CentralTheme.xaml"
);
return new Uri(uriString, UriKind.Relative);
}
}
public string Id
{
get
{
return CentralTheme.ThemeId;
}
}
public string Version
{
get
{
return CentralTheme.ThemeVersion;
}
}
//IThemeExtension members
public IEnumerable<Uri> GetControlStyleResources(
string themeId
, string themeVersion
, IEnumerable<IModuleDefinition> modules
)
{
//this is a modified version of what appears in the file created by the template
var uriString = string.Format("{0}{1}{2}"
, "/Central.Extensions.Client;"
, "component/Presentation/Themes/"
, "CentralTemplates.xaml"
);
List<Uri> result = new List<Uri>()
{
new Uri(uriString, UriKind.Relative)
};
return result;
}
}
The XAML file contains a ResourceDictionary
, which defines the colors and brushes for the theme, but is far too large to include here in its entirety. Instead, we’ll examine the various categories of styles that are defined, along with any details that you’ll need to know but that might not be so obvious.
There are 11 predefined text style definitions, as shown in Table 10-3.
Each of these styles has a number of font substyles that can be also set for it, as shown in Table 10-4.
The rest of the rather large XAML file consists of a series of brush definitions, grouped into style groups, as shown in Table 10-5. A color value is provided for every brush that’s used in a theme by default. (Custom themes can add new style definitions, but it’s not advisable to do so, because most of the themes that will be available won’t be aware of your custom definitions.)
The hardest part of creating a theme extension is deciding and testing which colors to use for the various brush definitions, and then entering them into the XAML. A tool such as Microsoft Expression Blend can be useful here, but it’s still a fairly tedious process.
Table 10-5 shows the groups of brushes that are found in the XAML file that defines a theme’s colors. In most cases, you can derive the actual brush name by taking the value from the Group column, and then adding the value from the Brush column, plus one of the states mentioned in the Description column (except for Normal
), and finally adding the word Brush on the end.
So, for the Screen
group, you’d end up with three brushes called ScreenBackgroundBrush
, ScreenLoadingBackgroundBrush
, and ScreenControlBorderBrush
. For the Button
group, you’d have five brushes called ButtonBorderBrush
, ButtonBorderFocusedBrush
, ButtonBorderMouseOverBrush
, ButtonBorderDisabledBrush
, and ButtonBorderPressedBrush
.
In the XAML file that the template produces for you, you’ll see that some brushes are SolidColorBrushes
, some are LinearGradientBrushes
, and a few are RadialGradientBrushes
. You don’t have to keep the same type of brush that’s defined in the template’s XAML. You can change a SolidColorBrush
into a LinearGradientBrush
, or vice versa. As long as you provide some type of brush, you have complete freedom to create whatever effects that you want to achieve.
When we add a theme, using the Extensibility Toolkit’s Theme template, as described earlier in the chapter, a Themes
folder is added to the Common
project’s Metadata
folder for us (see Figure 10-22).
Because only one file is added per theme, there’s not really any need to create a separate folder for each theme’s LSML file, but of course you could if you wanted to. The file for our theme is called CentralTheme.lsml.
The CentralTheme.lsml
file contains a model fragment that defines the theme’s details, as shown in Listing 10-3. Two attributes are defined in it (see Table 10-6).
XAML:
File: Central.Extensions.CommonMetadataThemesCentralTheme.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">
<Theme Name="CentralTheme">
<Theme.Attributes>
<DisplayName Value="Central Theme"/>
<Description Value="Central Theme description"/>
</Theme.Attributes>
</Theme>
</ModelFragment>
Earlier, we mentioned the so-called hidden theme that uses Windows system colors to create a high- contrast theme. Now that we’ve explained the steps required to create a theme, for a bit of fun we can show you how to activate this theme:
Lspkg
project).HiddenTheme
.URI
in ColorAndFontScheme
(in the HiddenTheme.vb/cs
file, located in the Client
project) to match Listing 10-4.HiddenTheme.lsml
(located in the Common
project) to put a space between Hidden and Theme (for display purposes).HiddenTheme.xaml
file that was created by the template (it’s not needed).We don’t actually need the XAML file that the template created for us this time, because the brush definitions have already been defined in one of LightSwitch’s assemblies, Microsoft.LightSwitch.Client.Internal
. All we need to do is just point to that XAML resource in our code.
Caution Be aware that the next version of LightSwitch might not contain that particular piece of XAML in that particular assembly, or even at all.
VB:
File: Central.UtilitiesPresentationThemesHiddenHiddenTheme.vb
<Export(GetType(ITheme))>
<Theme(HiddenTheme.ThemeId, HiddenTheme.ThemeVersion)>
Friend Class HiddenTheme
Implements ITheme
'constants
Friend Const ThemeId As String = "Central.Extensions:HiddenTheme"
Friend Const ThemeVersion As String = "1.0"
'ITheme members
Public ReadOnly Property ColorAndFontScheme As Uri
Implements ITheme.ColorAndFontScheme
Get
'this is a modified version of what appears in the file
'created by the template
Dim uriString = String.Format("{0}{1}{2}"
, "/Microsoft.LightSwitch.Client.Internal;"
, "component/Screens/ScreenPresentation/Implementations/Resources/Themes/"
, "Black/Black_VisualPalette.xaml"
)
Return New Uri(uriString, UriKind.Relative)
End Get
End Property
Public ReadOnly Property Id As String
Implements ITheme.Id
Get
Return HiddenTheme.ThemeId
End Get
End Property
Public ReadOnly Property Version As String
Implements ITheme.Version
Get
Return HiddenTheme.ThemeVersion
End Get
End Property
End Class
C#:
File: Central.UtilitiesPresentationThemesCentral.cs
[Export(typeof(ITheme)), Theme(HiddenTheme.ThemeId, HiddenTheme.ThemeVersion)]
internal class HiddenTheme : ITheme
{
//constants
internal const string ThemeId = "Central.Extensions:HiddenTheme";
internal const string ThemeVersion = "1.0";
//ITheme members
public Uri ColorAndFontScheme
{
get
{
//this is a modified version of what appears in the file
//created by the template
var uriString = string.Format("{0}{1}{2}"
, "/Microsoft.LightSwitch.Client.Internal;"
, "component/Screens/ScreenPresentation/Implementations/Resources/Themes/"
, "Black/Black_VisualPalette.xaml"
);
return new Uri(uriString, UriKind.Relative);
}
}
public string Id
{
get
{
return HiddenTheme.ThemeId;
}
}
public string Version
{
get
{
return HiddenTheme.ThemeVersion;
}
}
}
Sometimes, after you’ve added a theme, you might need to rename it for some reason. Although you could just delete the original theme and add a new one, we’ll show you the various places where the name needs to be changed. This will also be helpful if you decide to copy/paste/edit a theme, instead of using the template to create a new theme.
These instructions assume that you’ve followed our previous advice and have moved the theme to its own folder.
ClientPresentationThemesOldFolderName
toClientPresentationThemesNewFolderName
ClientPresentationThemesNewFolderNameOldThemeName.vb
/cs
toClientPresentationThemesNewFolderNameNewThemeName.vb
/cs
Theme
class’s ThemeId
from"Central.Extensions:OldThemeName"
to"Central.Extensions:NewThemeName"
Theme
class’s ColorAndFontScheme
URI
path from"/Central.Extensions.Client;component/Presentation/Themes/
OldTemplatesName.xaml"
to"/Central.Extensions.Client;component/Presentation/Themes/
NewTemplatesName.xaml"
XAML
file fromClientPresentationThemesNewFolderNameOldThemeName.xaml
toClientPresentationThemesNewFolderNameNewThemeName.xaml
Microsoft has made a sample theme available for download in the MSDN Samples Gallery: http://code.msdn.microsoft.com/LightSwitch-Metro-Theme-b1bfce24
. It’s quite a complex theme that not only defines fonts and colors, but also adds a number of ThemeExtensions
, which define new appearances and behaviors for some of the built-in LightSwitch controls.
You might find that visually this theme is quite a jarring experience, with uppercase and lowercase fonts appearing in unusual places. However, it’s actually based on the new Metro style that Windows 8 and Windows Phone 7 have introduced. Some people like it, others not so much.
Hidden away in the theme (well, not really hidden, but you do have to go looking) are an abundance of interesting and helpful techniques for anyone who wants to explore the theme in more depth.
In addition to theme extensions, shell extensions also allow you to change your application’s visual impact (the feel part of look and feel), by creating an application shell with a potentially completely different layout, and even different capabilities than the standard application shell that comes with LightSwitch.
The attributes and interfaces required by the shell extension classes are found in the Microsoft.LightSwitch.Runtime.Shell
namespace.
There are a few simple steps required to create a shell extension:
LSPKG
project (Central.Extensions.lspkg
)CentralShell
).Two attributes must be applied to any class that defines a Shell, for it to be recognized by LightSwitch (see Table 10-7).
Listing 10-5 show the attributes being used in code, to decorate the class that defines a shell extension.
VB:
File: N/A
Friend Const ShellId As String = "Central.Extensions:CentralShell"
<Export(GetType(IShell))>
<Shell(CentralShell.ShellId)>
Public Class CentralShell
Implements IShell
'implementation code goes here
End Class
C#:
File: N/A
internal const string ShellId = "Central.Extensions:CentralShell";
[Export(typeof(IShell))]
[Shell(CentralShell.ShellId)]
public class CentralShell : IShell
{
//implementation code goes here
}
In addition to being decorated with the two attributes just described, a class that defines a shell must also implement the IShell
interface.
The IShell
interface consists of two properties, listed in Table 10-8.
A shell enables users to interact with the application. It provides the navigation menu items and displays screens, associated commands, current user information, and other useful information. Let’s look at how some of that is defined.
The following paragraphs assume that we’ve called our theme CentralShell
.
When we add a shell, using the Extensibility Toolkit’s Shell item, as we mentioned earlier, a Shells
folder and a ShellsComponents
folder are added to the Client
project’s Presentation
folder for us, as shown in Figure 10-24.
We recommend creating a separate folder for each theme in the Shells
folder and moving the files created by the template into it. Once again, you don’t have to do this, but it helps keep the project more organized. For our example theme, we’ll create a folder called Central
and move the files into that folder (the Components
folder seems unnecessary, so we’ve deleted it, but it’s up to you what you do with it in your own projects).
Three files get added to the Shells
folder.
The CentralShell
class contains the IShell
implementation (see Listing 10-6) that we discussed earlier.
Because we moved the files for the shell into their own folder, we also had to make a small change to the ShellUri
property’s value, changing /Shells/
to /Shells/Central/
in order to reflect this folder change.
VB:
File: Central.Extensions.ClientPresentationShellsCentralCentralShell.vb
Imports System
Imports System.ComponentModel.Composition
Imports Microsoft.LightSwitch.Runtime.Shell
Namespace Presentation.Shells.Components
Friend Const ShellId As String = "Central.Extensions:CentralShell"
<Export(GetType(IShell))>
<Shell(CentralShell.ShellId)>
Friend Class CentralShell
Implements IShell
Public ReadOnly Property Name As String
Implements IShell.Name
Get
Return CentralShell.ShellId
End Get
End Property
Public ReadOnly Property ShellUri As Uri
Implements IShell.ShellUri
Get
'this is a modified version of what appears in the file
'created by the template
Dim uriString = String.Format("{0}{1}{2}"
, "/Central.Extensions.Client;"
, "component/Presentation/Shells/Central/"
, "CentralShell.xaml"
)
Return New Uri(uriString, UriKind.Relative)
End Get
End Property
End Class
End Namespace
C#:
File: Central.Extensions.ClientPresentationShellsCentralCentralShell.cs
using System;
using System.ComponentModel.Composition;
using Microsoft.LightSwitch.Runtime.Shell;
namespace Presentation.Shells.Components
{
internal const string ShellId = "Central.Extensions:CentralShell";
[Export(typeof(IShell)), Shell(CentralShell.ShellId)]
internal class CentralShell : IShell
{
public string Name
{
get
{
return CentralShell.ShellId;
}
}
public Uri ShellUri
{
get
{
//this is a modified version of what appears in the file
//created by the template
var uriString = string.Format("{0}{1}{2}";
, "/Central.Extensions.Client;"
, "component/Presentation/Shells/Central/"
, "CentralShell.xaml"
);
return new Uri(uriString, UriKind.Relative);
}
}
}
}
Caution If you move the XAML file to a different folder than the one the template put it in, as we have, make sure that you edit the Shell
class so that the ShellUri
property points to the correct location.
The CentralShell.xaml
file contains the XAML that represents the controls and the layout of the shell itself (see Listing 10-7). By default, the shell is based on a UserControl
that simply contains an empty Grid
. You can replace the grid with the XAML that’s required to display your intended components.
The x:Class
name has to match the name of the class in the code-behind file. The xmlns
definitions are the XML namespaces that Silverlight requires, so they must be present in your XAML. They’re always the same, and you won’t have any need to change them.
As you can see, for various elements in your shell, you can refer to brushes that have been defined in the theme’s resource dictionary, simply by specifying it as a static resource binding, using the syntax ElementName={StaticResource BrushName}
.
XAML:
File: Central.ExtensionsCentral.Extensions.ClientPresentationSehllsCentralCentralShell.xaml
<UserControl x:Class="Central.Extensions.Presentation.Shells.CentralShell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid Background="{StaticResource NavShellBackgroundBrush}">
</Grid>
</UserControl>
The CentralShell
class inherits from UserControl
. The only method in the class is the constructor, which calls InitializeComponent
. Most of the time, you won’t need to add any code to this file.
The Shell template also adds a Shells
folder to the Common
project’s Metadata
folder, as you can see in Figure 10-25. Only a single file gets added to the Shells
folder for our shell. Based on the name we gave the shell, it’s called CentralShell.lsml
.
The CentralShell.lsml
file contains a model fragment that defines the shell’s details (see Listing 10-8). Two attributes are defined in it (see Table 10-9).
LSML:
File: Central.Extensions.CommonMetadataShellsCentralShell.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">
<Shell Name="CentralShell">
<Shell.Attributes>
<DisplayName Value="CentralShell"/>
<Description Value="CentralShell description"/>
</Shell.Attributes>
</Shell>
</ModelFragment>
Microsoft has made a simplified sample shell available for download in the MSDN Samples Gallery, at http://code.msdn.microsoft.com/LightSwitch-Shell-1869646f
.
LightSwitch’s built-in templates make it easy to implement various commonly required data display and maintenance screens, but in some situations, the built-in templates don’t do quite what you need them to do. A screen template extension allows you to create (and share) templates that have common control layout patterns and are exactly suited to your particular needs.
The attributes and interfaces required by the screen template classes are found in the Microsoft.LightSwitch.Designers.ScreenTemplates.Model
namespace.
There are a few simple steps required to create a screen template extension:
LSPKG
project (Central.Extensions.lspkg
)CentralScreen
).Generate
method, to add the screen template elements.Two attributes must be applied to any class that defines a screen template, for it to be recognized by LightSwitch: the Export
attribute and the Template
attribute (see Table 10-10).
Listing 10-9 shows these attributes being used in code, to decorate the class that defines a screen template.
VB:
File: N/A
Friend Const TemplateId As String = "Central.Extensions:CentralScreen"
<Export(GetType(IScreenTemplateFactory))>
<Template(CentralScreen.TemplateId)>
Friend Class CentralScreenFactory
Implements IScreenTemplateFactory
'implementation code goes here
End Class
C#:
File: N/A
internal const string TemplateId = "Central.Extensions:CentralScreen";
[Export(typeof(IScreenTemplateFactory))]
[Template(CentralScreen.TemplateId)]
internal class CentralScreenFactory : IScreenTemplateFactory
{
//implementation code goes here
}
Two classes are required to define a screen template extension. One must implement the IScreenTemplate
interface, and the other must implement the IScreenTemplateFactory
interface.
The IScreenTemplate
interface consists of eight properties and one method, listed in Table 10-11.
Table 10-12 shows the available values for RootDataSourceType
, which are also found in the Microsoft.LightSwitch.Designers.ScreenTemplates.Model
namespace.
The IScreenTemplateFactory
interface consists of a single method, CreateScreenTemplate
, which simply returns an instance of the screen template class (in our example, the CentralScreen
class).
A common requirement is to have a screen that you can use to both add and edit entities. With this, you would be able to replace the two separate screen templates that come with LightSwitch with just one. By doing this, you could encapsulate all of the code changes that you might need to write over and over, in just one screen template.
The following paragraphs assume that we’ve called our screen template CentralScreen
.
When we add a screen template, using the Extensibility Toolkit’s Screen Template item, a ScreenTemplates
folder is added to the Design
project, and a ScreenTemplateImages
folder is added to the project’s Resources
folder for us, as shown in Figure 10-27.
We recommend creating a separate folder for each screen template in the ScreenTemplateImages
folder and moving the files created by the extension template into it. You don’t have to do this, but it helps keep the project more organized. There’s no need to create a subfolder for each screen template in the ScreenTemplates
folder, because there’s only one file added per screen template. But you could if you really wanted to.
Two default image files, CentralScreenLarge.png
(245178) and CentralScreenSmall.png
(2424), are added to the project for you. These represent the images that will be displayed for the template in the Add New Screen Wizard.
The small image is used in Select a Screen Template section, shown at the left of Figure 10-28. The large image is displayed for the selected screen template, shown in the center of Figure 10-28.
You’ll need to replace the default images with images that show the user what your screen template looks like.
When it’s created by the template, the CentralScreen
file will contain implementations for both the IScreenTemplate
(CentralScreen
class) and IScreenTemplateFactory
(CentralScreenFactory
class) that we discussed earlier (see Listing 10-10). Because we moved the files for the shell into their own folder, we also had to make a small change to the ShellUri
property’s value to reflect this folder change.
Out of the nine namespaces that are listed in the Import
/Using
statements, only two (three for C#, if you include System
) are required (the rest of them can safely be removed). The System.ComponentModel.Composition
namespace is required for the Export
attribute, and the Microsoft.LightSwitch.Designers.ScreenTemplates.Model
namespace is required for the IScreenTemplate
and IScreenTemplateFactory
interfaces.
The CentralScreen
class contains the IScreenTemplate
’s implementation code that we discussed earlier (see Listing 10-10). Most of the actual code for a screen template is found in the Generate
method. This method can be used to add any data, code, or controls required for screens that are based on the screen template.
You can modify the template’s hierarchy of content items and create additional elements by using storage model classes such as ContentItem
and all of its related classes. Generating a screen content tree can consist of any combination the following:
Caution Remember that if you’ve changed the folder structure, as we have, you also need to update the ImageProvider
code that refers to the elements that have been moved.
The CentralScreen
class contains the IScreenTemplateFactory
’s implementation code as well (see Listing 10-10). The only method in the class is the CreateScreenTemplate
method, which simply returns a new instance of the CentralScreen
class.
VB:
File: Central.Extensions.DesignScreenTemplatesCentralScreen.vb
Imports System.ComponentModel.Composition
Imports Microsoft.LightSwitch.Designers.ScreenTemplates.Model
Namespace ScreenTemplates
Friend Class CentralScreen
Implements IScreenTemplate
Friend Const TemplateId As String = "Central.Extensions:CentralScreen"
Public ReadOnly Property TemplateName As String _
Implements IScreenTemplateMetadata.TemplateName
Get
Return CentralScreen.TemplateId
End Get
End Property
Public ReadOnly Property DisplayName As String _
Implements IScreenTemplateMetadata.DisplayName
Get
Return "CentralScreen"
End Get
End Property
Public ReadOnly Property Description As String _
Implements IScreenTemplateMetadata.Description
Get
Return "CentralScreen description"
End Get
End Property
Public ReadOnly Property ScreenNameFormat As String _
Implements IScreenTemplateMetadata.ScreenNameFormat
Get
Return "{0}CentralScreen"
End Get
End Property
Public ReadOnly Property PreviewImage As Uri _
Implements IScreenTemplateMetadata.PreviewImage
Get
'this is a modified version of what appears in the file
'created by the template
Dim uriString = String.Format("{0}{1}{2}"
, "/Central.Extensions.Design;"
, "component/Resources/ScreenTemplateImages/Central/"
, "CentralScreenLarge.png"
)
Return New Uri(uriString, UriKind.Relative)
End Get
End Property
Public ReadOnly Property SmallIcon As Uri _
Implements IScreenTemplateMetadata.SmallIcon
Get
'this is a modified version of what appears in the file
'created by the template
Dim uriString = String.Format("{0}{1}{2}"
, "/Central.Extensions.Design;"
, "component/Resources/ScreenTemplateImages/Central/"
, "CentralScreenSmall.png"
)
Return New Uri(uriString, UriKind.Relative)
End Get
End Property
Public ReadOnly Property RootDataSource As RootDataSourceType _
Implements IScreenTemplateMetadata.RootDataSource
Get
Return RootDataSourceType.Collection
End Get
End Property
Public ReadOnly Property SupportsChildCollections As Boolean _
Implements IScreenTemplateMetadata.SupportsChildCollections
Get
Return True
End Get
End Property
Public Sub Generate(host As IScreenTemplateHost) _
Implements IScreenTemplate.Generate
End Sub
End Class
<Export(GetType(IScreenTemplateFactory))>
<Template(CentralScreen.TemplateId)>
Friend Class CentralScreenFactory
Implements IScreenTemplateFactory
Public Function CreateScreenTemplate() As IScreenTemplate _
Implements IScreenTemplateFactory.CreateScreenTemplate
Return New CentralScreen()
End Function
End Class
End Namespace
C#:
File: Central.Extensions.DesignScreenTemplatesCentralScreen.cs
using System.ComponentModel.Composition;
using Microsoft.LightSwitch.Designers.ScreenTemplates.Model;
namespace ScreenTemplates
{
internal class CentralScreen : IScreenTemplate
{
internal const string TemplateId = "Central.Extensions:CentralScreen";
public string TemplateName
{
get
{
return CentralScreen.TemplateId;
}
}
public string DisplayName
{
get
{
return "CentralScreen";
}
}
public string Description
{
get
{
return "CentralScreen description";
}
}
public string ScreenNameFormat
{
get
{
return "{0}CentralScreen";
}
}
public Uri PreviewImage
{
get
{
//this is a modified version of what appears in the file
//created by the template
var uriString = string.Format("{0}{1}{2}”;
, "/Central.Extensions.Design;”
, "component/Resources/ScreenTemplateImages/Central/";
, "CentralScreenLarge.png"
);
return new Uri(uriString, UriKind.Relative);
}
}
public Uri SmallIcon
{
get
{
//this is a modified version of what appears in the file
//created by the template
var uriString = string.Format("{0}{1}{2}";
, "/Central.Extensions.Design;";
, "component/Resources/ScreenTemplateImages/Central/";
, "CentralScreenSmall.png"
);
return new Uri(uriString, UriKind.Relative);
}
}
public RootDataSourceType RootDataSource
{
get
{
return RootDataSourceType.Collection;
}
}
public bool SupportsChildCollections
{
get
{
return true;
}
}
public void Generate(IScreenTemplateHost host)
{
}
}
[Export(typeof(IScreenTemplateFactory)), Template(CentralScreen.TemplateId)]
internal class CentralScreenFactory : IScreenTemplateFactory
{
public IScreenTemplate CreateScreenTemplate()
{
return new CentralScreen();
}
}
}
Tip Remember that if you change the name of either of the image files, you need to change any code that references them as well.
Microsoft’s screen template sample can be found at http://code.msdn.microsoft.com/LightSwitch-Screen-1b414903
.
Control extensions are created by combining Silverlight user controls with additional attributes to allow them to be integrated into LightSwitch. So there’s no need to manually select Custom Control as the control type, find the assembly that contains the custom control, and then select and bind it each time you want to use that particular custom control. Using control extensions make custom controls as easy to select as any of the controls that LightSwitch provides out-of-the-box.
Several types of control extensions can be created, as shown in Table 10-13.
From their name, custom controls may sound like they just deliver extra user interface/presentation layer capabilities. But they can provide packages of much broader functionality. Custom controls can be thought of as little applications within your bigger LightSwitch application. For example, a reporting control might not just display reports, it may also generate them. An analysis control might not just display a cross tab view of data, it might also provide filtering and aggregation capabilities.
The attributes and interfaces required by the control extension classes are found in the Microsoft.LightSwitch.Presentation
namespace.
There are a few simple steps required to create a control extension:
LSPKG
project (Central.Extensions.lspkg
)CentralControl
).Several attributes must be applied to any class that defines a control, for it to be recognized by LightSwitch (see Table 10-14).
Listing 10-11 shows these attributes being used in code.
VB:
File: N/A
'in the Client project
<Export(GetType(IControlFactory))>
<ControlFactory("Central.Extensions:CentralControl")>
Friend Class CentralControlFactory
Implements IControlFactory
'implementation code goes here
End Class
'in the Client.Design and Design projects
<Export(GetType(IResourceProvider))>
<ResourceProvider("Central.Extensions.CentralControl")>
Friend Class CentralControlImageProvider
Implements IResourceProvider
'implementation code goes here
End Class
C#:
File: N/A
//in the Client project
[Export(typeof(IControlFactory)), ControlFactory("Central.Extensions:CentralControl")]
internal class CentralControlFactory : IControlFactory
{
//implementation code goes here
}
//in the Client.Design and Design projects
[Export(typeof(IResourceProvider)), ResourceProvider("Central.Extensions.CentralControl")]
internal class CentralControlImageProvider : IResourceProvider
{
//implementation code goes here
}
A class that defines a control item must implement two interfaces, the IControlFactory
interface and the IResourceProvider
interface.
The IControlFactory
interface must be implemented in both the Client
project and the Client.Design
project. It consists of one property and one method (see Table 10-15).
The IResourceProvider
interface consists of just a single method, GetResource
, which returns a BitmapImage
from a URI that’s constructed using the name of the image file path and the assembly that contains the image.
When you define a control extension, you use a Silverlight control to display the data. You can also include custom design-time properties that you can later refer to in code.
Just as adding a theme extension added a Themes
folder to the Client
project’s Presentation
folder, adding a control extension will add a Controls
folder to it as well. Three files get added to the Controls
folder.
We recommend creating a separate folder for each control in the Controls
folder and moving the files created by the template into it. You don’t have to do this, but it helps keep the project more organized. For our example control, we’ll create a folder called CentralControl
and move the files into that folder, as shown in Figure 10-30.
Caution Remember that if you’ve changed the folder structure, as we have, you also need to update any code that refers to the elements that have been moved.
The CentralControl.xaml
file contains the XAML definitions that represent the control itself (see Listing 10-12). By default, it simply contains a TextBox
, with a two-way binding to the StringValue
control’s property. You need to replace this with the XAML required to display your extension’s control.
XML:
File: Central.Extensions.ClientPresentationControlsCentralControlCentralControl.xaml
<UserControl x:Class="Central.Extensions.Presentation.Controls.CentralControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<TextBox Text="{Binding StringValue, Mode=TwoWay}"/>
</UserControl>
The class declaration and the two namespace declarations will be added for you. There’s no need to change either of the xmlns
definitions, and in fact if you do change them in any way, your control will no longer work.
If you change the namespace where the control’s code is found, or the name of the control itself, you’ll have to change the class definition to match.
There are three sections to take note of in the XAML’s code-behind file.
Out of the sixteen namespaces that are listed in the Import
/Using
statements, only three (four for C#, if you include System
) are required; the rest of them can safely be removed. The System.ComponentModel.Composition
namespace is required for the Export
attribute, the System.Windows.Markup
namespace is required for XamlReader
, and Microsoft.LightSwitch.Presentation
is required for IControlFactory
and IContentItem
.
The control’s class inherits from UserControl
. The only method in the class is the constructor, which calls InitializeComponent
. As noted earlier, the name of this class must match the x:Class
name in the XAML file.
Curiously, the generated code in the XAML file’s code-behind file contains not only the usual code that you’d find for a UserControl
, but also the actual control’s IControlFactory
implementation (see Listing 10-13). This class contains the XAML definition for the control’s DataTemplate
, as well as an optional display mode DataTemplate
.
We advise you to extract this class out into its own file, to give the class a bit more visibility, making it easier to find when you want to make changes to the data template definitions. This is especially helpful when using the copy/paste method of creating extensions.
VB:
File: CentralExtensions.ClientPresentationControlsCentralControlCentralControl.vb
Imports System.ComponentModel.Composition
Imports System.Windows.Markup
Imports Microsoft.LightSwitch.Presentation
Namespace Presentation.Controls
Partial Public Class CentralControl
Inherits UserControl
Public Sub New()
InitializeComponent()
End Sub
End Class
'the template put this class in this file, but you can move it
'(and the namespaces above) to a file of its own
'as we discussed, it really doesn’t belong in here
<Export(GetType(IControlFactory))>
<ControlFactory("Central.Extensions:CentralControl")>
Friend Class CentralControlFactory
Implements IControlFactory
'Constants
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:ctl="'clr-namespace:Central.Extensions.Presentation.Controls;" &
"assembly=Central.Extensions.Client'>" &
"<ctl:CentralControl/>" &
"</DataTemplate>"
'IControlFactory Members
Private cachedDataTemplate As DataTemplate
Public ReadOnly Property DataTemplate As DataTemplate
Implements IControlFactory.DataTemplate
Get
If Me.cachedDataTemplate Is Nothing
Then
Me.cachedDataTemplate = TryCast(
XamlReader.Load(CentralControlFactory.ControlTemplate)
, DataTemplate
)
End If
Return Me.cachedDataTemplate
End Get
End Property
Public Function GetDisplayModeDataTemplate(
ByVal contentItem As IContentItem
) As DataTemplate
Implements IControlFactory.GetDisplayModeDataTemplate
Return Nothing
End Function
End Class
End Namespace
C#:
File: CentralExtensions.ClientPresentationControlsCentralControlCentralControl.cs
using System;
using System.ComponentModel.Composition;
using System.Windows.Markup;
using Microsoft.LightSwitch.Presentation;
namespace Presentation.Controls
{
public partial class CentralControl : UserControl
{
public CentralControl()
{
InitializeComponent();
}
}
//the template put this class in this file, but you can move it
//(and the namespaces above) to a file of its own
//as we discussed, it really doesn’t belong in here
[Export(typeof(IControlFactory)), ControlFactory("Central.Extensions:CentralControl")]
internal class CentralControlFactory : IControlFactory
{
//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:ctl="clr-namespace:Central.Extensions.Presentation.Controls;" +
"assembly=Central.Extensions.Client">" + "<ctl:CentralControl/>" +
"</DataTemplate>";
//IControlFactory Members
private DataTemplate cachedDataTemplate;
public DataTemplate DataTemplate
{
get
{
if (this.cachedDataTemplate == null)
{
this.cachedDataTemplate =XamlReader.Load(
CentralControlFactory.ControlTemplate
) as DataTemplate;
}
return this.cachedDataTemplate;
}
}
public DataTemplate GetDisplayModeDataTemplate(IContentItem contentItem)
{
return null;
}
}
}
A ResourcesControlImages
folder, shown in Figure 10-31, also will have been added to the Client.Design
project by the template. This is another case where we’d suggest adding a folder for each of the control extensions that you add, if there’s more than one, or if you plan on adding more control extensions later.
Two files get added to the ControlImages
folder.
By default, the CentralControl.png
file will be a generic image (a 3D green sphere). You need to replace this with the image that you want to display for your control.
The file should be:
The CentralControlImageProvider
file contains the IResourceProvider
implementation, as shown in Listing 10-14.
VB:
File: ResourcesControlImagesCentralControlCentralControlImageProvider.vb
Imports System
Imports System.ComponentModel.Composition
Imports System.Globalization
Imports System.Windows.Media.Imaging
Imports Microsoft.LightSwitch.BaseServices.ResourceService
Namespace Resources
<Export(GetType(IResourceProvider))>
<ResourceProvider("Blank.Extension.CentralControl")>
Friend Class CentralControlImageProvider
Implements IResourceProvider
Public Function GetResource(
ByVal resourceId As String
, ByVal cultureInfo As CultureInfo
) As Object _
Implements IResourceProvider.GetResource
Return New BitmapImage(New Uri(
"/Blank.Extension.Client.Design;component/Resources/ControlImages/
CentralControl.png"
, UriKind.Relative)
)
End Function
End Class
End Namespace
C#:
File: ResourcesControlImagesCentralControlCentralControlImageProvider.cs
using System;
using System.ComponentModel.Composition;
using System.Globalization;
using System.Windows.Media.Imaging;
using Microsoft.LightSwitch.BaseServices.ResourceService;
namespace Resources
{
[Export(typeof(IResourceProvider)), ResourceProvider("Blank.Extension.CentralControl")]
internal class CentralControlImageProvider : IResourceProvider
{
public object GetResource(string resourceId, CultureInfo cultureInfo)
{
return new BitmapImage(new Uri(
"/Blank.Extension.Client.Design;component/Resources/ControlImages/
CentralControl.png"
, UriKind.Relative)
);
}
}
}
Caution Remember that if you’ve changed the folder structure, as we have, you also need to update any code that refers to the elements that have been moved.
The template will have added a Controls
folder to the Common
project’s Metadata
folder, as shown in Figure 10-32. Only a single file is added to this folder for each control extension, so there’s no need for separate folders.
The CentralControl.lsml
file contains a model fragment that defines the control’s details, as shown in Listing 10-15. This includes several attributes and properties. By default there are four (see Table 10-16).
LSML:
File: Central.Extensions.CommonMetadataControlsCentralControl.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="CentralControl"
SupportedContentItemKind="Value"
DesignerImageResource="Central.Extensions.CentralControl::ControlImage">
<Control.Attributes>
<DisplayName Value="CentralControl" />
</Control.Attributes>
<Control.SupportedDataTypes>
<SupportedDataType DataType=":String"/>
</Control.SupportedDataTypes>
</Control>
</ModelFragment>
The LightSwitch controls used in its shell and in its screens are all based on Silverlight controls. Table
10-17 contains the mappings of the LightSwitch control elements to their underlying Silverlight controls.
Microsoft has provided the following control extension samples (usually with a link to a walk-through):
http://code.msdn.microsoft.com/LightSwitch-Value-Control-600cd408
http://code.msdn.microsoft.com/LightSwitch-Detail-Control-a77ab62a
http://code.msdn.microsoft.com/LightSwitch-Smart-Layout-ed3bedae
http://code.msdn.microsoft.com/LightSwitch-Stack-Panel-530f630a
Business types allow you wrap a base data type in what’s called a semantic type. This enables you to present data in a way that’s most appropriate for your application, adding extra validation and display capabilities in LightSwitch, while continuing to store the actual data as its underlying data type in the database.
The attributes and interfaces required for a business type extension are found in the Microsoft.LightSwitch.Presentation
namespace.
Out-of-the-box, LightSwitch comes with several predefined business types, listed in Table 10-18. For those business types to be active, the Microsoft LightSwitch Extensions extension must be activated for the project (it’s active by default).
You can think of business types as an extra layer of validation and formatting that sits on top of a normal database type. This allows LightSwitch’s data models to be even smarter than they would be if they could work with only the base data types.
Table 10-19 shows some examples of what a business type could be used for.
Using a business type makes it easy to ensure that only valid data is entered, while enabling the work that went into creating the validation code, for example, to be done just once, rather than needing to be duplicated in any new application. So business types make it easy to share and reuse code that may have taken quite some time and effort to get working correctly, saving you both time and money. For example, an IT department could do all the hard work and then simply make the business type available to be installed and used by various other departments, knowing that the data will be handled correctly. They could even make its use compulsory.
Without a business type, each department could well end up creating their own implementations, wasting time re-creating the wheel, as they say, and possibly introducing bugs into the application that will also take time to track down and fix. So you can view business types as both a productivity tool and a data governance tool.
There are a few simple steps required to create a business type extension:
LSPKG
project (Central.Extensions.lspkg
)CentralType
).Several attributes are used by classes that define a business type extension, in order for its elements to be recognized by LightSwitch. Two attributes are used in the Client
project: Export
and ControlFactory
. Both the Client.Design
project and the Design
project also use two attributes: Export
and ResourceProvider
.
The Export
attribute supplies the contracts that LightSwitch requires for an extension to identify itself as a business type extension. These are IControlFactory
and IResourceProvider
(see Listing 10-16).
The ControlFactory
attribute then provides further information about the business type’s control. The information required is a String
Id value, in the form of ExtensionLibraryName:ControlName
.
VB:
File: N/A
'in the Client project
<Export(GetType(IControlFactory))>
<ControlFactory("Central.Extensions:CentralTypeControl")>
Friend Class CentralTypeControlFactory
Implements IControlFactory
'implementation code goes here
End Class
'in the Client.Design and Design projects
<Export(GetType(IResourceProvider))>
<ResourceProvider("Central.Extensions.CentralTypeControl")>
Friend Class CentralTypeControlImageProvider
Implements IResourceProvider
'implementation code goes here
End Class
C#:
File: N/A
//in the Client project
[Export(typeof(IControlFactory))]
[ControlFactory("Central.Extensions:CentralTypeControl")]
internal class CentralTypeControlFactory : IControlFactory
{
//implementation code goes here
}
//in the Client.Design and Design projects
[Export(typeof(IResourceProvider))]
internal class CentralTypeControlImageProvider : IResourceProvider
{
//implementation code goes here
}
A class that defines a business type extension must implement two interfaces: IControlFactory
and IResourceProvider
.
The IControlFactory
interface must be implemented in both the Client
project and the Client.Design
project. As we noted in the control extension section, it consists of one property and one method, listed in Table 10-20.
Also as noted in the control extensions section, the IResourceProvider
interface consists of just a single method, GetResource
, which returns a BitmapImage
from a URI
that’s constructed using the name of the image file path, and the assembly that contains the image.
When you define a business type, you can add custom design-time properties that you can later refer to in code. A business type can also have a control associated with it (and in most cases, it does), so some of the implementation code is very similar to a control extension’s implementation code. But we’ll still include all of the implementation details in this section, so that each extension section is self-contained.
Just as adding a control extension added a Controls
folder to the Client
project’s Presentation
folder, adding a business type extension will also add a Controls
folder to it if it doesn’t already exist, as shown in Figure 10-34. Three files get added to the Controls
folder. Once again, we recommend creating a separate folder for each business type and moving the files that get created into it.
Caution Remember that if you’ve changed the folder structure, as we have, you also need to update any code that refers to the elements that have been moved.
The CentralTypeControl.xaml
file contains the XAML that represents the business type’s control itself, as you can see in Listing 10-17. By default, it simply contains a TextBox
, with a two-way binding to the StringValue
control’s property. You need to replace this with the XAML required to display your intended control.
XML:
File: Central.Extensions.ClientPresentationControlsCentralTypeCentralTypeControl.xaml
<UserControl x:Class="Central.Extensions.Presentation.Controls.CentralTypeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<TextBox Text="{Binding StringValue, Mode=TwoWay}"/>
</UserControl>
The class declaration and the two namespace declarations will be added for you. There’s no need to change either of the xmlns
definitions, and in fact if you do change them in any way, your control will no longer work.
There are three sections to take note of in the XAML’s code-behind file.
Out of the sixteen namespaces that are listed in the Import
/Using
statements, only three (four for C#, if you include System
) are required (the rest of them can safely be removed). The System.ComponentModel.Composition
namespace is required for the Export
attribute, the System.Windows.Markup
namespace is required for XamlReader
, and the Microsoft.LightSwitch.Presentation
namespace is required for IControlFactory
and IContentItem
.
The control’s class inherits from UserControl
. The only method in the class is the constructor, which calls InitializeComponent
.
As with the CentralControlFactory
class in the control extension, the generated code in the XAML file’s code-behind file contains not only the usual code that you’d find for a UserControl
, but also the actual business type control’s IControlFactory
implementation (see Listing 10-18). This class contains the XAML definition for the control’s DataTemplate
, as well as an optional display mode DataTemplate
.
We advise you to extract this class out into its own file, to give the class a bit more visibility, making it easier to find when you want to make changes to the data template definitions. This is especially helpful when using the copy/paste method of creating extensions.
VB:
File: CentralExtensions.ClientPresentationControlsCentralTypeCentralTypeControl.vb
Imports System.ComponentModel.Composition
Imports System.Windows.Markup
Imports Microsoft.LightSwitch.Presentation
Namespace Presentation.Controls
Partial Public Class CentralTypeControl
Inherits UserControl
Public Sub New()
InitializeComponent()
End Sub
End Class
'the template put this class in this file, but you can move it
'(and the namespaces above) to a file of its own
'as we discussed, it really doesn’t belong in here
<Export(GetType(IControlFactory))>
<ControlFactory("Central.Extensions:CentralTypeControl")>
Friend Class CentralTypeControlFactory
Implements IControlFactory
'Constants
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:ctl="'clr-namespace:Central.Extensions.Presentation.Controls;" &
"assembly=Central.Extensions.Client'>" &
"<ctl:CentralTypeControl/>" &
"</DataTemplate>"
'IControlFactory Members
Private cachedDataTemplate As DataTemplate
Public ReadOnly Property DataTemplate As DataTemplate
Implements IControlFactory.DataTemplate
Get
If Me.cachedDataTemplate Is Nothing
Then
Me.cachedDataTemplate = TryCast(
XamlReader.Load(CentralTypeControlFactory.ControlTemplate)
, DataTemplate
)
End If
Return Me.cachedDataTemplate
End Get
End Property
Public Function GetDisplayModeDataTemplate(
ByVal contentItem As IContentItem
) As DataTemplate
Implements IControlFactory.GetDisplayModeDataTemplate
Return Nothing
End Function
End Class
End Namespace
C#:
File: CentralExtensions.ClientPresentationControlsCentralControlCentralTypeControl.cs
using System;
using System.ComponentModel.Composition;
using System.Windows.Markup;
using Microsoft.LightSwitch.Presentation;
namespace Presentation.Controls
{
public partial class CentralTypeControl : UserControl
{
public CentralTypeControl()
{
InitializeComponent();
}
}
//the template put this class in this file, but you can move it
//(and the namespaces above) to a file of its own
//as we discussed, it really doesn’t belong in here
[Export(typeof(IControlFactory)), ControlFactory("Central.Extensions:CentralTypeControl")]
internal class CentralTypeControlFactory : IControlFactory
{
//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:ctl='clr-namespace:Central.Extensions.Presentation.Controls;" +
"assembly=Central.Extensions.Client'>" +
"<ctl:CentralTypeControl/>" +
"</DataTemplate>";
//IControlFactory Members
private DataTemplate cachedDataTemplate;
public DataTemplate DataTemplate
{
get
{
if (this.cachedDataTemplate == null)
{
this.cachedDataTemplate = XamlReader.Load(
CentralTypeControlFactory.ControlTemplate
) as DataTemplate;
}
return this.cachedDataTemplate;
}
}
public DataTemplate GetDisplayModeDataTemplate(IContentItem contentItem)
{
return null;
}
}
}
A ResourcesControlImages
folder (see Figure 10-35) will have been also added to the Client.Design
project by the template. Two files get added to the ControlImages
folder. This is another case where we’d suggest adding a folder for each of the control extension that you add, if there’s more than one, or if you plan on adding more business type extensions later.
By default, the CentralTypeControl.png
file will be a generic image (a 3D green sphere). You need to replace this with the image that you want to display for your control.
The file should be:
The CentralTypeControlImageProvider
file contains the IResourceProvider
implementation (see Listing 10-19).
VB:
File: ResourcesControlImagesCentralTypeCentralTypeControlImageProvider.vb
Imports System
Imports System.ComponentModel.Composition
Imports System.Globalization
Imports System.Windows.Media.Imaging
Imports Microsoft.LightSwitch.BaseServices.ResourceService
Namespace Resources
<Export(GetType(IResourceProvider))>
<ResourceProvider("Blank.Extension.CentralTypeControl")>
Friend Class CentralTypeControlImageProvider
Implements IResourceProvider
Public Function GetResource(
ByVal resourceId As String
, ByVal cultureInfo As CultureInfo
) As Object _
Implements IResourceProvider.GetResource
Return New BitmapImage(New Uri(
"/Blank.Extension.Client.Design;component/Resources/ControlImages/
CentralType/CentralType.png"
, UriKind.Relative)
)
End Function
End Class
End Namespace
C#:
File: ResourcesControlImagesCentralTypeCentralTypeControlImageProvider.cs
using System;
using System.ComponentModel.Composition;
using System.Globalization;
using System.Windows.Media.Imaging;
using Microsoft.LightSwitch.BaseServices.ResourceService;
namespace Resources
{
[Export(typeof(IResourceProvider)), ResourceProvider("Blank.Extension.CentralTypeControl")]
internal class CentralTypeControlImageProvider : IResourceProvider
{
public object GetResource(string resourceId, CultureInfo cultureInfo)
{
return new BitmapImage(new Uri(
"/Blank.Extension.Client.Design;component/Resources/ControlImages/
CentralType/CentralType.png"
, UriKind.Relative)
);
}
}
}
Caution Remember that if you’ve changed the folder structure, as we have, you also need to update any code that refers to the elements that have been moved.
The Business Type template will have added a Controls
folder and a Types
folder to the Common
project’s Metadata
folder, as shown in Figure 10-36. Only a single file is added to these folders for each business type extension, so there’s no need for separate folders.
The LSML file contains some XML, which defines a model fragment, shown in Listing 10-20. This model fragment specifies various attributes and properties for the business type. By default there are two (see Table 10-21).
Tip A single colon (:
) can often be used as a shortcut for Microsoft.LightSwitch:
.
LSML:
File: Central.Extensions.CommonMetadataTypesCentralType.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">
<SemanticType Name="CentralType"
UnderlyingType=":String">
<SemanticType.Attributes>
<DisplayName Value="CentralType" />
</SemanticType.Attributes>
</SemanticType>
<DefaultViewMapping
ContentItemKind="Value"
DataType="CentralType"
View="CentralTypeControl"/>
</ModelFragment>
You can create multiple controls for your business type. An Editor and a Viewer are the most common.
LightSwitch applications can natively connect to SQL Server databases, SharePoint lists, and WCF RIA Services. If you need to connect to some other type of data source, that’s where the extensibility features come in handy. A data source extension is essentially an adapter class (a domain service) that enables LightSwitch to work with data sources that it normally wouldn’t understand.
By using RIA Services as service layer, you can write code that connects with just about any external data source. LightSwitch then interfaces with the service layer, allowing the data to appear as standard LightSwitch data entities. After the data source extension has been written, connecting with external services is transparent for LightSwitch application developers who are consuming the data source.
Data source extensions work particularly well for in-house use. An IT department can build a RIA service around data from a particular legacy application and wrap it in a data source extension. This enables it to be shared for use in other LightSwitch applications. Data source extensions can also be included as companions to other extension types in the same extension library. For example, the DevExpress XtraReports control provides its own data source extension to enable report preview screens to communicate with the report server.
The classes that are required for a data source extension come predominantly from:
There are a few simple steps required to create a data source extension:
LSPKG
project (Central.Extensions.lspkg
).CentralSource
).From the point of view of writing extensions, a data source extension is the simplest of all the extension types. There are no special attributes that LightSwitch requires to advertise its presence as an extension. And there are no interfaces that are required to be implemented.
A data source extension is simply a custom RIA service (you learned about creating and using RIA services in Chapter 7). If you know how to create a RIA service, you know how to create a data source extension.
LightSwitch uses the custom DomainService
class that you write as a kind of in-memory data adapter, calling the instance directly from its data service implementation to perform query and submit operations.
Using this mechanism, you can create a DomainService
class that exposes entity types and implements query, insert, update, and delete methods. LightSwitch infers a LightSwitch entity model based on the exposed entity types and infers an entity set based on the presence of a query decorated with the Query
attribute (with IsDefault
set to true
). A primary-key property also has to be defined by decorating it with the Key
attribute.
When we add a screen template, using the Extensibility Toolkit’s Data Source item, a DataSources
folder is added to the Server
project for us, as shown in Figure 10-38. Unlike several of the other extension item types, there’s no real need to create a subfolder for each data source in the DataSources
folder, because only one file is added per data source. But again, you could if you really wanted to.
There are two sections to take note of in the CentralSource
class file.
Out of the five namespaces that are listed in the Import
/Using
statements, only one (two for C#, if you include System
) is required, plus one that you’ll need when you start writing the code for the data source (the rest of them can safely be removed). The System.ServiceModel.DomainServices.Server
is required for DomainService
, and System.ComponentModel.DataAnnotations
is required for the Key attribute.
The data source’s class inherits from DomainService
. No other code is added to the class by the template, just a TODO
comment that acts as a reminder that this is the place to create methods that contain the logic required for your data source.
The Microsoft recommended way of debugging an extension is to use what’s called the experimental instance of Visual Studio:
There are two methods that you can use to distribute your extension, depending on whether you want to make it available to the general public or just to people you know personally.
When you’re happy that your extension is behaving the way that you want it to, you should make sure that the solution configuration setting is set to Release, as shown in Figure 10-39. If you leave it set to Debug, you’ll be publishing debug symbols with your extension, and the download time for your users may also be longer than necessary.
If the solution configuration isn’t available on your Visual Studio toolbar, you can access it by clicking Build Configuration Manager to access the dialog box shown in Figure 10-40.
Tip Before distributing your extension, you should make sure that you’ve entered at least a name, description, and author in the VSIX project’s settings, as described earlier in the VSIX project section.
To share the setup file, follow these steps (replacing Central.Extensions
with the name you gave your extension):
Central.Extensions.vsixinRelease
folder.Central.Extensions.vsix
To publish your extension so that everyone can access it:
Central.Extensions.vsixinRelease
folder and select the VSIX file and click Next.There’s been a lot to learn in this chapter. First we explored why you might need an extension:
Then you examined the two ways of finding extensions, through either the Visual Studio Gallery web site or with the built-in Extension Manager. You also learned how to install them.
You looked briefly at some extensions made available by community members as well as the offerings of some third-party commercial vendors.
Further on in the chapter, you learned about:
We then gave you some background information to help you understand each of the six extension types:
Finally, we showed you how to debug your extensions, as well as how you can distribute them to other users.