Please visit http://www.ningzhang.org!

This is a backup site. Please go to http://www.ningzhang.org for the latest content and best viewing. Thanks!
Showing newest posts with label WPF. Show older posts
Showing newest posts with label WPF. Show older posts

Nov 30, 2009

Silverlight Design Time Assemblies

Introduction

If you write Silverlight controls, you should consider writing design time assemblies for your controls too, for two simple reasons:

  • developer productivity: try to imagine Silverlight development without tools like Visual Studio or Blend! For custom controls, you may need to provide much of the design time experience for your controls in Visual Studio or Blend yourself.
  • designers: XAML and tools like Blend enable developers and designers work together. A key design criteria for Silverlight controls is to make sure designers can use them without writing a single line of code.

Design time experience usually includes (but not limited to) the following:

  1. Metadata for property window, like category, infotip, property editor, binding etc.
  2. Metadata for design surface, like initializer, adorner, context menu, adapters etc.
  3. Toolbox integration, like icons, control registration
  4. intellisense for code editor

Except intellisense (please see Add Rich Intellisense for Your Silverlight Controls) and control registration (please see Register Silverlight Controls with visual Studio and Blend), above design time experience are usually delivered through design time assemblies. Below I will discuss various approaches of delivering design time experiences, in increasing complexity and flexibility, and gradually introduce pieces of the naming convention of design time assemblies.

Runtime Assembly Only

The simplest way to deliver design time experience is to package design time code into the runtime assemblies, especially when design time metadata are meaningful at runtime too, like TypeConverterAttribute.

The pros and cons of this approach:

  • Pro: simple
    • no separate design time assemblies, simpler setup
    • design time attributes are specified directly on the runtime code, easier to maintain
  • Con: tight coupling of runtime and design time code
    • perf degradation because of useless design time code at runtime
    • design time dependencies (like MWDs and other VS/Blend assemblies) get dragged into runtime unnecessarily
    • can’t service runtime or design time independently
    • can’t support multiple designers (like both VS2008/Blend2 and VS2010/Blend3)
    So this approach is highly discouraged, unless there is strong justification for it.

Shared Design Time Assembly

So the revolutionary step forward is to decouple design time and runtime code, release and service them with separate assemblies. This opens up all kinds of possibilities. Of course, we need a way to link design time assemblies to their corresponding runtime assemblies, without introducing any unnecessary dependency or perf degradation to runtime assemblies. Hence the naming convention: if a runtime assembly Foo.dll is referenced in a Silverlight project, the designer (like Visual Studio and Blend) will first try to load design time information like icons (via another naming convention, see How to Add an Toolbox Icon for Your Silverlight Control) and design time metadata (via interface, like IRegisterMetadata for VS2008 and Blend2, or IProvideAttributeTable for VS2010 and Blend3. Please see How to Write Silverlight Design Time for All Designers: Visual Studio 2008, Blend 2; Blend 3, and Visual Studio 2010) from the runtime assembly; it will then look for a design time assembly by the name Foo.Design.dll in the same directory as Foo.dll; if found, the designer will try to load design time information from Foo.Design.dll as well.

Designer Specific Design Time Assembly

Visual Studio is mostly for developers, while Blend is mostly for designers, so they have difference requirements for design time experiences. Putting all design time code in one shared design time assembly introduces tight coupling among designers. So the naming convention is enhanced: for runtime assembly Foo.dll, there is a shared design time assembly Foo.Design.dll that’s loaded by all designers; each designer will also try to load its own design time assemblies, like Foo.VisualStudio.Design.dll for Visual Studio, and Foo.Expression.Design.dll for Blend. The designer specific design time assembly is loaded after the shared design time assembly. Third party designers can define their own designer specific design time assembly. 

Design Sub Folder

So to support both Visual Studio and Blend, each runtime assembly has three design time assemblies, in the same directory, and a package (like Silverlight SDK or Silverlight Toolkit) usually contains several runtime assemblies. So the directory gets a bit crowded. A minor improvement is to put the design time assemblies under a Design subfolder. So the naming convention is further enhanced: if a designer (like Visual Studio or Blend) can’t find corresponding design time assemblies in the same directory as a runtime assembly, it will look for them in the Design subfolder, if it exists. 

Support Multiple Versions of MWDs

Design time are built on top of designer extensibility framework, which consists of several dlls like Microsoft.Windows.Design.Extensibility.dll and Microsoft.Windows.Design.Interaction.dll. We usually call them collectively as MWDs. To make life more interesting, sometimes we have to introduce breaking changes to the extensibility framework, like VS2008 and Blend2 use MWD version 3.5, VS2010 and Blend3 use MWD version 4.0, and they are incompatible. So if you have a runtime assembly Foo.dll, and you want your users to be able to develop against it with both VS2008 and VS2010, you have to provide two sets of design time assemblies: one set built against v3.5 MWDs and used by VS2008, and another set built against v4.0 MWD and used by VS2010. The two sets of design time assemblies probably have a lot of code in common, while the one for VS2010 may have some new code to leverage the new functionalities exposed by VS2010. Since design time assemblies are loaded by name and you can’t have two files with the same name, so the naming convention is enhanced yet again:

for a runtime assembly Foo.dll, the shared design time assembly is named Foo.Design*.dll, the Visual Studio specific design time assembly is named Foo.VisualStudio.Design*.dll, and the Blend specific design time assembly is named Foo.Expression.Design*.dll, where * can be zero or more valid characters for file names. When a designer (like Visual Studio or Blend) tries to load a design time assembly and several fit the naming convention, zero or one will be loaded:

  • If the MWD version referenced by the design-time assembly has a different major version number than the designer’s MWD version, then the design-time assembly will not load and is bypassed.
  • If more than one design-time assembly is compatible with the designer’s MWD version, the Designer loads the one compiled against the highest MWD version that is less than or equal to the designer’s MWD version.

Please see Extensibility Series – WPF & Silverlight Design-Time Code Sharing – Part I and How to Write Silverlight Design Time for All Designers: Visual Studio 2008, Blend 2; Blend 3, and Visual Studio 2010 for more information.

Support Both WPF and Silverlight

To complicate life further, since WPF and Silverlight are so awfully similar, you may be tempted to try to write it once and run with both WPF and Silverlight. You are not alone. There are many articles on how to share source code and/or binaries across Silverlight and WPF. We’d like to do that for design time assemblies too. One approach is to make most of the design time assemblies platform agnostic, and limit platform specific (WPF or Silverlight) code and references to a small platform specific assembly. Please see Extensibility Series – WPF & Silverlight Design-Time Code Sharing – Part I for more information on this.

Last One Wins

The same design time metadata for a class or its member can be specified multiple times in multiple design time assemblies, which allows the shared design time assembly to specific the default/common behavior and then designer specific design time assemblies to override it if necessary. The same design time metadata can also be specified multiple times within a single design time assembly (please see Design Time Feature Implementation in Silverlight Toolkit as an example, where DescriptionAttribute for a class or its property can be added by AddDescriptions, AddAttributes and AddTables methods). So we need to know which metadata wins. The simplest and most logic design is that last one wins. This is mostly true but not always. Sometimes the result is un-deterministic when the same design time metadata is specified several times but with different values.

Feedback

Devil is in the details! Feedbacks are always appreciated. Please let me know what issues you run into, what requests/improvements you’d like to make, for both design times experience and designer extensibility framework. Thanks!

 

Dec 24, 2008

Quick Blend Tutorial for Developers

Introduction

XAML is an major invention of WPF, and XMAL "programming" is an important part of WPF/Silverlight development. Even though XAML is XML and can be authored manually, it is mostly designed for tools, so good authoring tools are more important to XAML than to other languages like C# or HTML. Blend to XAML is what Visual Studio is to C#. There are many good tutorials and documents for Blend, like Jesse Liberty's Expression Blend for Developers. This post is more like "Learn Blend in 15 Minutes", aiming to give an quick overview and tutorial of Blend to developers who are authoring XAML mostly in Visual Studio or notepad, and so they will be interested and know how to get started in using Blend.

Project Management

Blend duplicates a lot of Visual Studio functionality and has great integration with it. It is very common (or even recommended, should I say) to have a WPF/Silverlight project open in both Blend and Visual Studio, using Blend for XAML authoring and Visual Studio for C# coding (or whatever language you prefer), and using either or both for project management.

Project manage in Blend is very similar to that in Visual Studio, via menu, Project Panel and Results Panel:
Blend: Project Management

  • Menu: the File, Edit, Project and Help menus are very similar to those in Visual Studio. Through those menus, you can create/open WPF/Silverlight projects, add items/references etc, and even build and run the solution, just like in Visual Studio.
    File Menu Project Menu
  • Project Panel: it is very similar to Visual Studio's Solution Explorer. You can right click on any .xaml/.cs file in the project panel and select "Edit in Visual Studio", and the project and the selected file will be open in Visual Studio for editing.
    Edit in Visual Studio Context Menu
  • Result Panel: the Output tab and Error tab are similar to Output window and Error List window in Visual Studio.

UI Authoring

UI authoring is done mostly via three panels: 
Blend: UI Authoring

  • Objects and Timeline Panel: the objects panel makes the logical tree structure very clear. It is easy to navigate the logical tree by expanding/collapsing/selecting an element in the tree, or restructure the tree by dragging and dropping elements along the tree. The selected element is highlighted in the objects panel, and in both the design view and xaml view of the documents panel; the properties panel displays the properties and events of the selected element. The internal node with yellow border (LayoutRoot in above screenshot) is the current default container.
  • Documents Panel: the documents panel is a tabbed window, with one tab for each open xaml file. It has a design view and a xaml view for each file.
    • You can use the tabs at the top of the documents window to switch among open documents or close documents(only one tab in above screenshot, since only one file page.xaml is open.).
    • You can switch among Design view, XAML view, or Split view of the selected document. You can also set the default document view in Tools -> Options -> Documents:
      Documents View Options
    • There is a breadcrumb control at the top of the design view (also called artboard) to show the logic tree path of the selected element. This is another way to navigate the logical tree besides the objects panel, and to switch among authoring modes as we will see shortly.
    • You can zoom the design view by
      • select the zoom icon from the toolbox, then click or alt click on the artboard;
      • use the zoom combobox right below the artboard: zoom combobox
      • use the View menu items or their shortcut keys: View menu
    • The design view is a WYSIWYG designer, where you can drag and drop an element from the toolbox or Asset Library along the left border of Blend window:
      Toolbox  Asset Library
    • The XAML view allows direct XAML authoring. It doesn't have IntelliSense yet, but you can use the design view and the error tab in the Results panel below to correct errors while you type.
  • Properties Panel: properties panel has two views: properties and events, indicated by two buttons at the top right corner of the panel.
    • In properties view: 
      image
      • properties of the selected element is grouped into categories, like Layout, Common Properties;
      • properties usually have helpful tooltip, like Content property tooltip in above screenshot;
      • properties usually have inline/extended/dialog editor(s) to help user pick a value, like the value of HorizontalAlignment property in above screenshot can be set by clicking one of the four buttons, instead of typing in values like "Center" in XAML view directly;
      • if you give a property a value (so it no longer takes the default value), the little square to its right will become white, like the Alignment properties in above screenshot;
      • you can click the down arrow (which has "Show advanced properties" tooltip) in most categories to see more properties of that category;
      • you can find a property quickly by typing part of its name in the Search box at the top of the properties panel:
        Search box in Properties Panel
    • In events view:
      Properties Panel: Events view
      • you can type the name of the event handler for an event, and Blend will open the project in Visual Studio, with a prototype implementation of the event handler in the code behind file:
        Blend opens Visual Studio for event handler coding
         

Animation Authoring

To create an animation, click the plus sign in Objects and Timeline panel, the "Create Storyboard Resource" dialog will pop up, asking for the name of the new storyboard:
Create Storyboard Resource

Blend then enters timeline recording mode. Let's create an animation that will double the size of the button. The key steps in creating an animation:

  1. create a keyframe, by first dragging the yellow timeline to a keytime, and then clicking the "Record Keyframe" green plus button;
  2. edit properties of UI elements at keytime, as described in the above UI Authoring section;
  3. repeat steps 1 and 2 for each keyframe;

As we can see in below screenshot:

  • in Objects and Timeline Panel:
    • the name of the timeline being edited: Storyboard1;
    • the keyframe being edited: keytime is 1 second, indicated by the yellow timeline;
    • the double arrow icons to the left of the nodes in the objects panel (button, RenderTransform, Scale, ScaleX, ScaleY in below screenshot) and the white dots along the timeline in the timeline panel show property changes for objects at the keytime ;
    • we can use the play controls at the top of the timeline panel, or drag the yellow timeline, to run the animation;
  • in Artboard of Documents Panel:
    • the red border, red recording button, and the red label "Timeline recording is on" all indicate Blend is in timeline recording mode;
    • you can click the red recording button to stop the recording mode and switch back to UI authoring mode;
  • In Properties Panel:
    • value of target properties: like ScaleTransform.ScaleX is set to 2;

Blend: Animation Authoring

Blend uses XXXAnimationUsingKeyFrames and generates one keyframe for each object/property/keyframe trio. We can fine tune the properties of a keyframe itself by first selecting it in the Objects Panel, and then setting its properties in the properties panel, like in below screenshot:

  • KeyTime is "00:00:01"
  • target object is button, target path is roughly RenderTransform.ScaleTransform.ScaleX
  • Value is "2"
  • KeySpline is "0.5,0.1,0.5,0.9"

Blend: Animation Authoring

we can also fine tune the properties of the storyboard in the same way, like in below screenshot:

  • Storyboard1 is selected
  • AutoReverse and RepeatBehavior properties are changed

Blend: Animation Authoring

This is the XAML code Blend generated for above simple animation:

  1: <Storyboard AutoReverse="True" RepeatBehavior="Forever" x:Name="Storyboard1">
  2: 	<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="button" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
  3: 		<SplineDoubleKeyFrame KeySpline="0.495999991893768,0.104000002145767,0.5,0.898999989032745" KeyTime="00:00:01" Value="2"/>
  4: 	</DoubleAnimationUsingKeyFrames>
  5: 	<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="button" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
  6: 		<SplineDoubleKeyFrame KeyTime="00:00:01" Value="2"/>
  7: 	</DoubleAnimationUsingKeyFrames>
  8: </Storyboard>
  9: 

Clearly it is much easier to author animations in Blend instead of writing XAML manually.

Template Authoring

The beauty and power of WPF/Silverlight is the capability to re-template a control. It is rather easy to do that in Blend: select an menu item from the "Edit Control Parts (Template) -> Edit Template" context menu, either from Objects and Timeline Panel, or from the breadcrumb in Artboard:

Blend: Template Authoring

"Edit a Copy..." menu item is also an easy way to get the default template of a control. In below screenshot, we can see:

  • default template components in Objects and Timeline Panel
  • visual state groups, visual states, and transitions in Interaction Panel
  • properties of template components in Properties Panel. The orange square to the right of Background property indicates it is template bound.

Blend: Template Authoring

Drill in further: select the Disabled visual state, and we can see that the disabled visual in artboard is generated by the visual state transition storyboard that animates the Opacity property of the DisabledVisualElement from 0% to 55% in 0 second:

Blend: Template Authoring

Please note that near the top of the Object and Timeline panel, it shows Disabled State instead of a storyboard, indicating Blend is in state recording mode.

Now let's make some change to the default template by changing the focus visual state transition to skew the button when it is focused:

Template Authoring

  • select the Focused visual state in Interaction Panel, click the arrow plus icon on its right, select one of the three options (those options are for visual state transitions to and from this state, and between particular two states in the visual state group);
  • Blend is in visual state recording mode: focused is the selected visual state;
  • add a new skew transform to the top grid as discussed in Authoring Animations section. Notice that the button in artboard is now skewed. The WYSIWYG artboard makes animation authoring easy and intuitive.
  • delete the FocusVisualElement template component in Objects panel since we don't need it any more. Notice the tooltip at the top indicating Blend automatically deleted the visual state transition that shows the FocusVisualElement as well.

The Blend generated XAML for above custom template is 102 lines, 6179 characters! Imagine the difficulty in authoring that by hand!

Once we have a template, we can apply it to UI elements of the style's target type (or its sub type):

Apply Template

Style Authoring

Using style is usually better than setting properties on objects directly, since styles can be reused, are easy to maintain, and help separate presentation from content (the UI elements). Style authoring is similar to template authoring discussed above, except it is much simpler. You have to use the Object -> Edit Style menu to enter the style authoring mode. There is no context menu for it, which makes style authoring less discoverable.

Blend: Style Authoring

Select Create Empty... menu item, and the Create Style Resource dialog pops up, allow you to specify the name and location of the new style resource:

Create Style Resource Dialog

Then Blend enters style authoring mode:

  • the Objects and Timeline panel displays highlighted Style element instead of the UI logical tree;
  • the breadcrumb on top of artboard also indicates we are authoring LayoutRoot's style with a palette icon;
  • let's add something to the style by setting Background via the brush editor, notice the artboard reflects the change immediate;
  • the XAML view of documents panel shows the simple XAML generated for style authoring;

Blend: Style Authoring

Once we have a style resource, applying it is easy: just select the object and the use Object -> Edit Style -> Apply Resource menu item.

Apply Style

Resource Management

Storyboard, styles and templates authored above are all resources. We can use the Resources Panel to manage resources:

Resources Panel

  • we can see all the resources in both app.xaml (global resources) and current xaml file page.xaml that is being edited (local resources).
    • Not sure why Storyboard1 created in Animation Authoring section does not show up. Blend bug or user error?
  • we can edit a resource via its context menu or edit resource button:
    Resource Context Menu
  • we can also drag and drop a resource along the tree to change its location (and visibility), like moving between app.xaml and page.xaml, or [UserControl] and button.

Conclusion

To recap, Blend makes UI authoring easy and intuitive:

  • toolbox (and asset library) and artboard allows WYSIWYG UI authoring;
  • objects panel helps to organize all the UI elements in the logical tree;
  • properties panel simplifies property setting with editors;

Building on top of the intuitive UI authoring, Blend simplifies animation authoring by introducing the timeline recording mode:

  • Blend always uses XXXAnimationUsingKeyFrames, so a storyboard is always a collection of keyframes;
  • Blend generates keyframes indirectly by letting user set UI element properties at keytimes via its intuitive UI authoring interface;
  • Blend always generates all four transforms like below, to make its keyframe generation uniform for transforms:

<Button.RenderTransform>
    <TransformGroup>
        <ScaleTransform/>
        <SkewTransform/>
        <RotateTransform/>
        <TranslateTransform/>
    </TransformGroup>
</Button.RenderTransform>

On top of its UI and animation authoring, Blend simplifies template authoring by introducing the template authoring mode and state recording mode. A template consists of template parts and visual states. Template parts are just a logical tree of UI elements, and visual state transitions are storyboards.

Style authoring is even simpler: a style is just a collection of property setters. Blend introduces style authoring mode to distinguish between regular UI authoring and style authoring.

So Blend is a great XAML authoring tool. It is not just for designers, but can also give developers big productivity boost. Hopefully this post gives you a good overview of Blend and get you started in exploring Blend, enthusiastically :-). Thanks!

Dec 13, 2008

Design Time Features in Silverlight Toolkit

Introduction

The Silverlight Toolkit December 2008 Release added design time features for controls. I wrote these design time features, and will write a series of posts about how to implement them. This is the first, giving an overview of those design time features.

Binaries

For each of the four control assemblies, there are three design time assemblies:

C:\mesh\SLTK\Binaries>filever /s /e /a
        c:\mesh\sltk\binaries\*.*
W32i   DLL   -  2.0.21024.1838 shp    286,720 12-09-2008 microsoft.windows.controls.datavisualization.dll
W32i   DLL   -  2.0.21024.1838 shp    200,704 12-09-2008 microsoft.windows.controls.dll
W32i   DLL   -  2.0.21024.1838 shp     77,824 12-09-2008 microsoft.windows.controls.input.dll
W32i   DLL   -  2.0.21024.1838 shp     40,960 12-09-2008 microsoft.windows.controls.theming.dll
        c:\mesh\sltk\binaries\design\*.*
W32i   DLL   -  2.0.21024.1838 shp    387,072 12-09-2008 microsoft.windows.controls.datavisualization.design.dll
W32i   DLL   -  2.0.21024.1838 shp     11,264 12-09-2008 microsoft.windows.controls.datavisualization.expression.design.dll
W32i   DLL   -  2.0.21024.1838 shp     13,824 12-09-2008 microsoft.windows.controls.datavisualization.visualstudio.design.dll
W32i   DLL   -  2.0.21024.1838 shp    259,584 12-09-2008 microsoft.windows.controls.design.dll
W32i   DLL   -  2.0.21024.1838 shp     10,752 12-09-2008 microsoft.windows.controls.expression.design.dll
W32i   DLL   -  2.0.21024.1838 shp     81,408 12-09-2008 microsoft.windows.controls.input.design.dll
W32i   DLL   -  2.0.21024.1838 shp     11,264 12-09-2008 microsoft.windows.controls.input.expression.design.dll
W32i   DLL   -  2.0.21024.1838 shp     11,264 12-09-2008 microsoft.windows.controls.input.visualstudio.design.dll
W32i   DLL   -  2.0.21024.1838 shp     48,640 12-09-2008 microsoft.windows.controls.theming.design.dll
W32i   DLL   -  2.0.21024.1838 shp     11,264 12-09-2008 microsoft.windows.controls.theming.expression.design.dll
W32i   DLL   -  2.0.21024.1838 shp     11,264 12-09-2008 microsoft.windows.controls.theming.visualstudio.design.dll
W32i   DLL   -  2.0.21024.1838 shp     11,264 12-09-2008 microsoft.windows.controls.visualstudio.design.dll

The name and location of corresponding run time and design time assemblies are important. Take microsoft.windows.controls.dll for example:

  • its design time assemblies must be named microsoft.windows.controls.design.dll, microsoft.windows.controls.expression.dll, microsoft.windows.controls.visualstudio.dll.
  • Its design time assemblies must be either in the same directory as the run time assembly, or in a sub directory named design, as is the case here.
  • With above name and location arrangement, Blend is able to automatically find and load microsoft.windows.controls.design.dll and microsoft.windows.controls.expression.dll, and in that order, while Visual Studio is able to find and load microsoft.windows.controls.design.dll and microsoft.windows.controls.visualstudio.dll, and in that order.

 

Design Time Features for Blend

To demonstrate how to use Silverlight Toolkit, and the design time features of its controls, let's create a new Silverlight application project from Blend:

New Silverlight Application Project

And add all four run time assemblies to references:

Add References... 

Pop up Asset Library, select Custom Controls tab: 

Asset Library, Tooltip

  • the Custom Controls tab is populated with controls from the four run time assemblies of Silverlight Toolkit;
  • each control has a tooltip explaining what it is;
  • clicking the control will add it to the selected container, with the right xmlns registered;
  • Category, Tooltip

  • each property has a helpful tooltip, like above tooltip for MinimumPopulateDelay property shows its type and unit (milliseconds);
  • we added new categories (like Auto Complete property category for AutoCompleteBox above) to group custom properties better instead of having them all under the Miscellaneous category;
  • Hide Attributes, Common Properities Category, Custom Editor

  • some properties are hidden, like Background, Foreground, BorderBrush etc for Viewbox above;
  • some properties are moved to Common Properties category, like Child, Stretch, StretchDirection for Viewbox above;
  • clicking the New button for Child property will pop up the Select Object dialog;
  • All those make the design time experience of those Silverlight controls that exist in WPF as well to be as similar as possible to their WPF counterpart, just like the run time experience.

     

    Design Time Features for Visual Studio

    Launch Visual Studio by right click the project in Blend and then select Edit in Visual Studio context menu item:

    Edit in Visual Studio

    To add Silverlight Toolkit controls to Visual Studio toolbox:

  • create a new tab, name it Silverlight Toolkit, or anything you want:
  • Add Tab

  • right click on the new tab and choose Choose Items...:
  • Choose Items...

  • select Silverlight Components tab, and then Browse... button:
  • Choose Silverlight Components

  • navigate to where the run time assemblies are, and add them one by one:
  • image

  • below is what it looks like after adding all four run time assemblies of Silverlight Toolkit. Please note that:
    • I added a filter to only show controls from Silverlight Toolkit assemblies;
    • Microsoft.Windows.Control.Theming.dll doesn't have any controls to be added to Visual Studio toolbox, so the error dialog after selecting Microsoft.Windows.Control.Theming.dll is expected and OK;
    • we expose a smaller set of controls to Visual Studio than to Blend;
    • you can further remove some controls from Visual Studio toolbox by uncheck it below;

    Choose Toolbox Items 

    After adding Silverlight Toolkit controls to the toolbox, we can see below:

  • each control has a nice custom icon;
  • double clicking a control in the toolbar will add it to the xaml where cursor is, with the right xmlns registered;
  • Properties window and tooltip don't work, for now, limitation of Visual Studio xaml editor for Silverlight;
  • custom icons

     

    Conclusion

    As control developer, we serve two types of customers:

    • developers who use our controls to develop Silverlight applications. For developers, we need to provide:
      • good run time APIs (properties/methods/events, class inheritance and containment etc) and UI (control contract, default template), to make controls easy to use, customize, extend and evolve;
      • good design time UI to improve developer productivity;
      • good documentation, samples, tutorials, community support etc;
    • end users who use controls as part of the applications developed by developers. For end users, we need to provide good UI (control rendering, its keyboard and mouse interface etc): rich, intuitive, consistent, reliable, performant, and secure.

    So the design time experience of Silverlight Toolkit is an important part of our overall deliverables. This post is an overview of the design time features in the December 2008 release. I will write follow up posts explaining how to develop design time features for Blend and Visual Studio, and provide some general framework/code that hopefully you can use in your own development.

    As always, we are eager to hear your feedback, and quick in addressing your concerns and incorporating your suggestions. We strive to make Silverlight the best development platform, and make your investment and experience with Silverlight the most pleasant, productive, and rewarding! Thanks!

    Nov 26, 2008

    Customize Silverlight Toolkit Controls: Expander

    Introduction

    This is the second post of the Customize Silverlight Toolkit Controls series. This post demonstrates how to customize Expander, and provides the most asked customizations from Silverlight Toolkit forum.

    Expander Customization

    To customize Expander, it is very important to understand its API and default template, and know how to use Blend to re-template a control. I'd highly recommend you read my previous post Expander Control in Silverlight Toolkit if you haven't yet. The key to customize Expander:

    • understand Expander interface, especially its control contract:
      • Expander expects one template part, a toggle button named "ExpanderButton", in its template. Its expand/collapse function depends on the existence of this template part, because Silverlight doesn't support two way binding.
      • Expander exposes ExpansionStates visual state group (including Expanded and Collapsed two visual states) for users to customize its expand and collapse behavior.
      • Expander exposes ExpandDirectionStates visual state group (including ExpandDown, ExpandUp, ExpandLeft, ExpandRight) for users to customize the layout for each of the four expand direction.
      • Expander also exposes HeaderTemplate and ContentTemplate properties for users to customize its header and content part. You can find this kind of customization from Silverlight Control Toolkit Sample.

    image

    • understand Expander's default template. The screen shot below shows how the default template handles the layout of the four expand directions and the behavior of expand/collapse by providing StoryBoard for the ExpansionStates and ExpandDirectionStates visual state groups:
      • The whole Header is a templated ToggleButton, so clicking anyway on the Header may expand/collapse the Content area.
      • It uses a 2x2 Grid to layout Header and Content area.
      • It lays out the Expander according to ExpandDirection by having state animation for ExpandDirectionStates:
        • It lays out Header and Content areas by animating their Grid.Row and Grid.Column properties.
        • It draws the header toggle button correctly by changing its template.
      • It expands/collapses content areas by animating its Visibility attribute in ExpanionStates state transitions.

    ExpandDirection state animation

    Below screen shot shows the most asked customizations. If you have Silverlight 2.0 installed and your reader supports live Silverlight application, you can play with it here too:

    Expander Customization

    • PDC08 shows the Expander in current release on codeplex. There is a bug in ExpanderRightHeaderTemplate (used if you set ExpandDirection to Right) that causes high CPU and memory usage. This is fixed and the fix will be in our next release in a few weeks. I apologize for the inconvenience. Expander is my very first Silverlight control. I didn't know anything about Silverlight or WPF before I joined Shawn's team a few months back, and I still can't believe how fragile xaml and animation are. It took me a long time to track down the one character fix: one (out of dozens) ObjectAnimationKeyFrames has Duration attribute accidentally set to "1" instead of "0". That drove Silverlight runtime nuts and hog cpu/memory.
    • New (ExpanderStyle) shows the new template to be released soon.
    • Fade In/Out (ExpanderFadeStyle) customizes the expand/collapse behavior by animating Opacity to fade the content in and out.
    • Scale In/Out (ExpanderScaleStyle) customizes the expand/collapse behavior by animating ScaleTransform.ScaleX and ScaleTransform.ScaleY between 1 and 0 to expand and shrink the content.
    • No Button (ExpanderNoButtonStyle) customizes the layout by removing the circle with arrow toggle button. You can still expand/collapse content with mouse or keyboard.
    • Bottom/Right Button (ExpanderBottomRightButtonStyle) customizes the layout by putting the the circle with arrow toggle button to the bottom/right part of the header.

     

    Source Code

    You can find the zipped project file:

    Conclusion

    Expander has been the top keyword to my blog. It is a very useful control and relatively hard to customize. Hopefully this post will help you in using and customizing Expander. Thanks!

    Nov 14, 2008

    DependencyProperty: Validation, Coercion & Change Handling (Part III: Dependency Property Code Snippet)

    Introduction

    This is the last part of the three part series on how to implement dependency property with validation, coercion and eventing on WPF and Silverlight. In Part I, I implemented a simple dependency property and debugged through to demonstrate how dependency property works on WPF and the common pattern for implementing dependency property on WPF. In Part , I did the same on Silverlight. Because Silverlight only supports PropertyChangedCallback, but not CoerceValueCallback and ValidateValueCallback, so validation, coercion and change handling all have to be implemented with PropertyChangedCallback. This causes PropertyChangedCallback being called recursively when validation or coercion changes the effective value of the dependency property. Plus other limitations of Silverlight property system, it can get very tricky to implement dependency property correctly. This post I will show some obscure behaviors of Silverlight RangeBase controls and their causes to further demonstrate how tricky this can be. And at last, I will provide a code snippet that implements the complete pattern of dependency property implementation I discussed in Part , as a reward to those who read through the series :-)

    Silverlight RangeBase Controls

    Overview

    ScrollBar, ProgressBar and Slider inherits from RangeBase, which implements Minimum, Maximum and Value dependency properties, with the coercion constraint that Minimum <= Value <= Maximum.

    RangeBase

    This sounds simple :-), but RangeBase controls' behavior can get very odd. Take a few examples:

    1. <Slider x:Name="sl"/> gives you a slider with Minimum = 0, Value = 0, and Maximum = 10, while <Slider x:Name="sl2" Minimum="-1"/> produces a slider with Minimum = -1, Value = 0, and Maximum = 0.
    2. Click on slider sl2's drag thumb to make sl2 in focus, hit right or up arrow key, then change sl2.Maximum to a positive number 10, sl2.Value magically changes from 0 to 0.1.
    3. Set sl2's property like Value to double.NaN, an invalid value, an exception will throw, but sl2.Value is changed to double.NaN anyway, and no ValueChanged event is fired.
    Source Code

    Below is the code we will debug and experiment with:

    • page.xaml:
    <UserControl x:Class="SLApp2.Page"
    
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    
        xmlns:my="clr-namespace:SLApp2"
    
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    
        <StackPanel x:Name="LayoutRoot" Background="White">
    
            <Slider x:Name="sl"/>
    
            <my:Slider2 x:Name="sl2" Minimum="-1"/>
    
            <Button x:Name="btn" Content="Break!"
    
                    Click="btn_Click" HorizontalAlignment="Center"/>
    
        </StackPanel>
    
    </UserControl>
    • page.xaml.cs:

     

    using System.Windows;
    
    using System.Windows.Controls;
    
    using System.Windows.Controls.Primitives;
    
    namespace SLApp2
    
    {
    
        // subclass Slider to easily break at property change and key down events.
    
        public class Slider2 : Slider
    
        {
    
            protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
    
            {
    
                base.OnMinimumChanged(oldMinimum, newMinimum);
    
            }
    
            protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
    
            {
    
                base.OnMaximumChanged(oldMaximum, newMaximum);
    
            }
    
            protected override void OnValueChanged(double oldValue, double newValue)
    
            {
    
                base.OnValueChanged(oldValue, newValue);
    
            }
    
            protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
    
            {
    
                base.OnKeyDown(e);
    
            }
    
        }
    
        public partial class Page : UserControl
    
        {
    
            public Page()
    
            {
    
                InitializeComponent();
    
            }
    
            // the button click event handler provides a chance to break into debugger 
    
            // and experiment with RangeBase validation, coercion and change handling.
    
            private void btn_Click(object sender, RoutedEventArgs e)
    
            {
    
            }
    
        }
    
    }
    
    Debug & Experiment

    Build and run above simple Silverlight application, you will see below screen shot. Please notice the thumb location difference between the two sliders:

    SilverlightApplication1

    Click the Break! button to break into its event handler in Visual Studio, check the value of slider sl and sl2:

    • <Slider x:Name="sl"/>: sl has the correct value of Mininum = Value = 0 and Maximum = 10, while <Slider x:Name="sl2" Minimum="-1"/> sl2 has Minimum = -1 but Maximum = Value = 0;

    image

    For those who read Part II and are now familiar with the implementation pattern, this is the root cause of sl2's odd behavior:

    • RangeBase leaves _initialMax, _initialVal, _requestedMax, _requestedVal to be initialized by CLR to default value 0. This is OK for Value property, since it defaults to zero anyway; but it is wrong for Maximum property, which defaults to 1 in its DependencyProperty.Register call and uses its default value as its effective value:

    RangeBase not initialize its fields

    • Setting sl2.Minimum to -1 triggers OnMinimumPropertyChanged -> CoerceMaximum, which uses the CLR initialized _requestedMax to coerce Maximum. Since _requestedMax is 0 instead of Maximum's current effective value of 1, and 0 happens to be greater than Minimum's current value of -1, so Maximum is change to 0:

    RangeBase.CoerceMaximum

    • The reason <Slider x:Name="sl"> has a Maximum value of 10 instead of 1 by default is that its default template has a <Setter Property="Maximum" Value="10"/>. This line also fixes the problem that _requestedMax isn't initialized. This is why sl._requestedMax is 10 in above screen shot.

    Slider default template

    Next, set a break point at each function, let the application run. Select sl2's thumb to make it in focus, hit right or up arrow key, it breaks into OnKeyDown event handler. Check sl2's value: _requestedVal is 0; step through base.OnKeyDown(e); check sl2 again: _requestedVal becomes 0.1. This is because base.OnKeyDown handles the right/up arrow key stroke and tries to increase Value by SmallIncrement of 0.1; this triggers OnValuePropertyChanged -> CoerceValue, which then coerces Value back to 0, since Maximum is 0; but it remembers the request of increment by setting _requestedVal to 0.1.

    image

    While still inside Immediate window, set sl2.Maximum to 10. This triggers OnMaxiumPropertyChanged:

    image

    which in turns triggers CoerceValue, which checks _requestedVal, whose value is now 0.1. Since 0.1 is in the range of [-1, 10], so Value is changed to 0.1:

    image

    Last, let's set Value to double.NaN. This triggers an ArgumentException, as expected:

    image

    but Value is changed to double.NaN anyway, and no ValueChanged event is fired because of the ArgumentException:

    image

    Dependency Property Code Snippet

    By now you probably have got the idea that it is pretty tricky to implement dependency property with validation, coercion and change handling correctly on Silverlight. To make this easier, below is a code snippet that provides most of the code for above mentioned implementation pattern. This code snippet was originally authored by Ted Glaza, and I modified it to provide the complete pattern. You can remove stuff you don't need for simpler dependency properties, like those that don't need validation, coercion, or change handling. You do still need to fill in your own validation, coercion, and change handling logic by modifying functions like IsValid$property$, Coerce$property$, On$property$PropertyChanged, or On$property$Changed etc, and add a single definition of private int _nestLevel. But at least you don't have to worry about most of differences between WFP and Silverlight property systems, and focus only on validation, coercion and change handling logic themselves.

    <?xml version="1.0" encoding="utf-8" ?>
    
    <CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    
      <CodeSnippet Format="1.0.0">
    
        <Header>
    
          <Title>Silverlight Dependency Property</Title>
    
          <Shortcut>sdp</Shortcut>
    
          <Description>Code snippet for a dependency property with validation, coercion and changed event.</Description>
    
          <Author>Ning Zhang</Author>
    
          <SnippetTypes>
    
            <SnippetType>Expansion</SnippetType>
    
          </SnippetTypes>
    
        </Header>
    
        <Snippet>
    
          <Declarations>
    
            <Literal>
    
              <ID>property</ID>
    
              <ToolTip>Property name</ToolTip>
    
              <Default>MyProperty</Default>
    
            </Literal>
    
            <Literal>
    
              <ID>type</ID>
    
              <ToolTip>Property type</ToolTip>
    
              <Default>object</Default>
    
            </Literal>
    
            <Literal>
    
              <ID>defaultValue</ID>
    
              <ToolTip>Default value</ToolTip>
    
              <Default>null</Default>
    
            </Literal>
    
            <Literal Editable="false">
    
              <ID>classname</ID>
    
              <ToolTip>Class name</ToolTip>
    
              <Function>ClassName()</Function>
    
              <Default>MyClass</Default>
    
            </Literal>
    
            <Literal Editable="false">
    
              <ID>SystemWindowsDependencyProperty</ID>
    
              <Function>SimpleTypeName(global::System.Windows.DependencyProperty)</Function>
    
            </Literal>
    
            <Literal Editable="false">
    
              <ID>SystemWindowsDependencyObject</ID>
    
              <Function>SimpleTypeName(global::System.Windows.DependencyObject)</Function>
    
            </Literal>
    
            <Literal Editable="false">
    
              <ID>SystemWindowsDependencyPropertyChangedEventArgs</ID>
    
              <Function>SimpleTypeName(global::System.Windows.DependencyPropertyChangedEventArgs)</Function>
    
            </Literal>
    
            <Literal Editable="false">
    
              <ID>SystemWindowsPropertyMetadata</ID>
    
              <Function>SimpleTypeName(global::System.Windows.PropertyMetadata)</Function>
    
            </Literal>
    
          </Declarations>
    
          <Code Language="csharp">
    
            <![CDATA[#region public $type$ $property$
    
            /// <summary>
    
            /// Gets or sets the value of $property$ dependency property.
    
            /// </summary>
    
            public $type$ $property$
    
            {
    
                get { return ($type$)GetValue($property$Property); }
    
                set { SetValue($property$Property, value); }
    
            }
    
            /// <summary>
    
            /// Identifies the $property$ dependency property.
    
            /// </summary>
    
            public static readonly $SystemWindowsDependencyProperty$ $property$Property =
    
                $SystemWindowsDependencyProperty$.Register(
    
                    "$property$",
    
                    typeof($type$),
    
                    typeof($classname$),
    
                    new $SystemWindowsPropertyMetadata$($defaultValue$, On$property$PropertyChanged));
    
            /// <summary>
    
            /// $property$Property property changed handler.
    
            /// </summary>
    
            /// <param name="d">$classname$ that changed its $property$.</param>
    
            /// <param name="e">Event arguments.</param>
    
            private static void On$property$PropertyChanged($SystemWindowsDependencyObject$ d, $SystemWindowsDependencyPropertyChangedEventArgs$ e)
    
            {
    
                $classname$ source = ($classname$)d;
    
                $type$ newValue = ($type$)e.NewValue;
    
                $type$ oldValue = ($type$)e.OldValue;
    
                
    
                // validate newValue
    
                if (!IsValid$property$(newValue))
    
                {
    
                    // revert back to e.OldValue
    
                    source._nestLevel++;
    
                    source.SetValue(e.Property, e.OldValue);
    
                    source._nestLevel--;
    
                    
    
                    // throw ArgumentException
    
                    throw new ArgumentException("Invalid $property$Property value", "e");
    
                }
    
                
    
                if (source._nestLevel == 0)
    
                {
    
                    // remember initial state
    
                    source._initial$property$ = oldValue;
    
                    source._requested$property$ = newValue;
    
                }
    
                
    
                source._nestLevel++;
    
                
    
                // coerce newValue
    
                $type$ coercedValue = ($type$)Coerce$property$(d, e.NewValue);
    
                if (newValue != coercedValue)
    
                {
    
                    // always set $property$Property to coerced value
    
                    source.$property$ = coercedValue;
    
                }
    
                
    
                source._nestLevel--;
    
                
    
                if (source._nestLevel == 0 && source.$property$ != source._initial$property$)
    
                {
    
                    // fire changed event only at root level and when there is indeed a change
    
                    source.On$property$Changed(oldValue, source.$property$);
    
                }
    
            }
    
            /// <summary>
    
            /// $property$Property validation handler.
    
            /// </summary>
    
            /// <param name="value">New value of $property$Property.</param>
    
            /// <returns>
    
            /// Returns true if value is valid for $property$Property, false otherwise.
    
            /// </returns>
    
            private static bool IsValid$property$($type$ value)
    
            {
    
                return true;
    
            }
    
            /// <summary>
    
            /// $property$Property coercion handler.
    
            /// </summary>
    
            /// <param name="d">$classname$ that changed its $property$.</param>
    
            /// <param name="value">Event arguments.</param>
    
            /// <returns>
    
            /// Coerced effective value of $property$Property from input parameter value.
    
            /// </returns>
    
            private static object Coerce$property$($SystemWindowsDependencyObject$ d, object value)
    
            {
    
                $classname$ source = ($classname$)d;
    
                $type$ newValue = ($type$)value;
    
                return newValue;
    
            }
    
            /// <summary>
    
            /// $property$Property changed event.
    
            /// </summary>
    
            public event RoutedPropertyChangedEventHandler<$type$> $property$Changed;
    
            
    
            /// <summary>
    
            /// Called by On$property$PropertyChanged static method to fire $property$Changed event.
    
            /// </summary>
    
            /// <param name="oldValue">The old value of $property$.</param>
    
            /// <param name="newValue">The new value of $property$.</param>
    
            protected virtual void On$property$Changed($type$ oldValue, $type$ newValue)
    
            {
    
                RoutedPropertyChangedEventArgs<$type$> e = 
    
                    new RoutedPropertyChangedEventArgs<$type$>(oldValue, newValue);
    
                if ($property$Changed != null)
    
                {
    
                    $property$Changed(this, e);
    
                }
    
            }
    
            
    
            /// <summary>
    
            /// Cached previous value of $property$Property.
    
            /// </summary>
    
            private $type$ _initial$property$ = $defaultValue$;
    
            /// <summary>
    
            /// Cached originally requested value of $property$Property by user.
    
            /// </summary>
    
            private $type$ _requested$property$;
    
            #endregion public $type$ $property$
    
    ]]>
    
          </Code>
    
        </Snippet>
    
      </CodeSnippet>
    
    </CodeSnippets>

     

    Conclusion

    I hope the series helped you in understanding and implementing dependency properties on WPF and Silverlight. Both WPF and Silverlight are great platforms, and will be fundamental for software development, possibly more than what Win32 did before.

    As always, feedback, suggestions, corrections are welcome. Thanks!