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!

 

Nov 28, 2009

Silverlight Drag and Drop API

Introduction

Drag and drop is a common UX paradigm and a top requested feature. In Silverlight 4 Beta, we introduced a basic set of API to enable the most common scenario of dragging pictures files and dropping them to Silverlight applications, and we designed the API to allow us to expose more drag and drop functionalities down the road without API change, hopefully.

API

Below is the core of the new API:

namespace System.Windows
{
public abstract class UIElement : DependencyObject
{
// Fields
public static readonly DependencyProperty AllowDropProperty;

// Events
public event DragEventHandler DragEnter;
public event DragEventHandler DragLeave;
public event DragEventHandler DragOver;
public event DragEventHandler Drop;

// Properties
public bool AllowDrop { get; set; }
}

public delegate void DragEventHandler(object sender, DragEventArgs e);

public sealed class DragEventArgs : RoutedEventArgs
{
// Methods
public Point GetPosition(UIElement relativeTo);

// Properties
public IDataObject Data { [SecuritySafeCritical] get; }
public bool Handled { get; set; }
}
}

namespace System.Windows.Controls
{
public abstract class Control : FrameworkElement
{
protected virtual void OnDragEnter(DragEventArgs e);
protected virtual void OnDragLeave(DragEventArgs e);
protected virtual void OnDragOver(DragEventArgs e);
protected virtual void OnDrop(DragEventArgs e);
}
}

  • AllowDrop: this is dependency property, indicating whether an element is a drop target.
  • DragEnter, DragLeave, DragOver & Drop: these are routed events. They bubble up, and fire only on elements with AllowDrop set to true.
  • OnDragEnter, OnDragLeave, OnDragOver & OnDrop: these are protected virtual functions for subclasses to override. 
  • DragEventArgs: this class allows drop target event handlers or method overrides to access the data being dragged or dropped.

Usage

There are primarily two ways to use Silverlight’s drag and drop API:

  • Silverlight applications can handle those drop target events and process the files dropped in the event handlers.
  • Silverlight controls can override those drop target virtual functions to process the data being dragged and dropped, and enable themselves to be drop targets.

Limitations

The drag and drop functionality shipped in Silverlight 4 Beta only enables the most common scenario: Silverlight plugin as a file drop target. Please notice the following limitations with current implementation:

  • there is no drop source support (QueryContinueDrag & GiveFeedback).
  • there is no DragDropEffects or DragDropKeyStates in DragEventArgs.
  • there is no visual for the dragged object or DragDropEffects.
  • only file drag and drop is supported:
    • all drop target events fire only when files are being dragged and dropped.
    • IDataObject, DataObject and DragEventArgs.Data support only one format: “FileDrop”, and the data is of type FileInfo[].
  • most of IDataObject methods throw NotImplementedException.
  • for drag and drop to work on Windows, Silverlight plugin must be windowed. In another word, if <param name="windowless" value="true"/> is specified, drop events won’t fire.
  • since Silverlight plugin on Mac is always windowless, you need to hook up JavaScript drop events to trigger Silverlight drop events. In another word, if you want your Silverlight application to support file drop on Mac as well as Windows, you will need to add following script and Silverlight plugin attributes to the hosting html or aspx page:
    <!-- add following script before end of HEAD tag -->
    <script type="text/javascript">
    function handleDragEnter(oEvent) {
    // Prevent default operations
    oEvent.preventDefault();

    var flag = silverlightControl.dragEnter(oEvent);

    // If we handled it successfully then stop propagation of the event
    if (flag)
    oEvent.stopPropagation();
    }

    function handleDragLeave(oEvent) {
    // Prevent default operations
    oEvent.preventDefault();

    var flag = silverlightControl.dragLeave(oEvent);

    // If we handled it successfully then stop propagation of the event
    if (flag)
    oEvent.stopPropagation();
    }

    function handleDragOver(oEvent) {
    // Prevent default operations
    oEvent.preventDefault();

    var flag = silverlightControl.dragOver(oEvent);

    // If we handled it successfully then stop propagation of the event
    if (flag)
    oEvent.stopPropagation();
    }

    function handleDropEvent(oEvent) {

    // Prevent default operations
    oEvent.preventDefault();

    var flag = silverlightControl.dragDrop(oEvent);

    // If we handled it successfully then stop propagation of the event
    if (flag)
    oEvent.stopPropagation();
    }
    </script>
    <!-- use JavaScript drop target events to trigger Silverlight's drop target events -->
    <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%"
    id="silverlightControl" ondragenter="handleDragEnter(event)" ondragover="handleDragOver(event)"
    ondragleave="handleDragLeave(event)" ondrop="handleDropEvent(event)" >


  • DragEventArgs.Data is only accessible in Drop event. Accessing DragEventArgs.Data in DragEnter, DragOver and DragLeave may cause SecurityException. And accessing Directory, DirectoryName or FullName property of the FileInfo object from DragEventArgs.Data may cause ArgumentNullException. 

    Demo App

    Below is a demo app:
  • the top left nested grids use drop target event handler to display DragEventArgs info.
  • the top right nested grids contain an ImageDropTarget user control, which overrides OnDrop method to render the dropped file if it is an image.
  • the bottom listbox shows the sequence of drop target events, their bubbling up, and DragEventArgs info.
    DragDropDemo

    you can find the complete source here.

    Feedback

    While we are excited to add drag and drop to Silverlight, we know there are a lot to finish/improve, and what we released is just a Beta. As always, your feedback is highly appreciated, and will be used as an important data point to help us prioritize the work. Thanks!

     

    Technorati Tags: ,

    Nov 18, 2009

    Silverlight 4 Adds Arabic Support

    Silverlight 4 adds support for Arabic culture:

    • Install, configuration and runtime UI are in Arabic and RTL layout on Arabic culture
    • Support mixed BiDi/ComplexScript text
    • Support RTL layout with FlowDirection attribute and implemented by all controls

    Below screenshots demonstrate some of the support:

    • Install UI:
      Silverlight developer runtime install UI
    • Configuration UI:
      Silverlight Configuration UI
    • Controls with FlowDirection set to RightToLeft:
      RTL Layout and Controls Demo

    For those curious, you may notice that:

    • there is a new ar directory under %programfiles%\Microsoft Silverlight\4.0.41108.0, besides the 10 directories that have been there since SL3: de, en-us, es, fr, it, ja, ko, zh-Hans, zh-Hint. The ar directory contains Arabic resources for Silverlight runtime assemblies.
    • the MUI resource dlls contain Arabic resources:
      image

    We will continue adding more support to more cultures in Silverlight. The Arabic support debuted in Silverlight 4 Beta is particularly important, since it is the first RTL language Silverlight supports. If you notice any bug or have suggestions, please let me know.

    Technorati Tags: ,

    Silverlight Clipboard API

    Clipboard API in Silverlight

    Silverlight 4 adds Clipboard support. Check out the amazing demos by Scott Guthrie and Brian Goldfarb today at PDC that showcase scenarios enabled by Silveilght4 Clipboard support.

    Silverlight’s Clipboard API is a subset of WPF’s Clipboard API: 

    public static class Clipboard
    {
    public static bool ContainsText();
    public static string GetText();
    public static void SetText(string text);
    }
    It allows copy/paste Unicode strings to/from system clipboard. GetText and SetText require either of following to succeed:
    1. the calling Silverlight application is an elevated trust application, or
    2. it is user initiated and user grants clipboard access
    For the second case, when GetText or SetText is first called in an user initiated event handling, Silverlight pops up a dialog like the one below (UI may change at RTW):
      Clipboard Prompt
      If user clicks yes, Silverlight will allow this and later GetText/SetText calls to succeed for this application during this session; if user clicks no, the default, Silverlight will throw a SecurityException. In another word, the setting is per application, per session, for both get and set access to system clipboard, and it is not persisted.

    Clipboard API in JavaScript and WPF

    We need to consider many factors while designing Clipboard API for Silverlight. Among them, the Clipboard APIs of JavaScript and WPF are particularly interesting, because we need to study the security lessons in JavaScript and try to be compatible with WPF.

    There is no cross browser, cross platform JavaScript API for clipboard access. IE provides clipboardData object that allows get/set/clear strings in system clipboard:

    bResult = window.clipboardData.setData(sDataFormat, sData);
    sData = window.clipboardData.getData(sDataFormat);
    bResult = window.clipboardData.clearData([sDataFormat]);

    where sDataFormat is a string constant of "Text" or "URL", sData is a string, and bResult is a Boolean.

    The first time the clipboadData object is accessed, IE will prompt user to allow the script to access clipboard, and the setting is valid for the session only.

    WPF’s Clipboard API supports more and extensible data format via IDataObject, and has helper functions for common formats like text, image, audio, and filedrop.

    public static class Clipboard
    {
    // Methods
    [SecurityCritical]
    public static void Clear();
    public static bool ContainsAudio();
    public static bool ContainsData(string format);
    public static bool ContainsFileDropList();
    public static bool ContainsImage();
    public static bool ContainsText();
    public static bool ContainsText(TextDataFormat format);
    public static Stream GetAudioStream();
    public static object GetData(string format);
    [SecurityCritical]
    public static IDataObject GetDataObject();
    public static StringCollection GetFileDropList();
    public static BitmapSource GetImage();
    public static string GetText();
    public static string GetText(TextDataFormat format);
    public static bool IsCurrent(IDataObject data);
    public static void SetAudio(byte[] audioBytes);
    public static void SetAudio(Stream audioStream);
    public static void SetData(string format, object data);
    [SecurityCritical]
    public static void SetDataObject(object data);
    [SecurityCritical]
    public static void SetDataObject(object data, bool copy);
    public static void SetFileDropList(StringCollection fileDropList);
    public static void SetImage(BitmapSource image);
    public static void SetText(string text);
    public static void SetText(string text, TextDataFormat format);
    }

    Clipboard API in Windows and Mac OS X

    Silverlight’s clipboard API is ultimately implemented on top of the clipboard APIs of the underlying operating system. Windows’ clipboard APIs are documented at MSDN, below excerpt gives an overview on how to set/get clipboard data:

    Cut and Copy Operations

    To place information on the clipboard, a window first clears any previous clipboard content by using the EmptyClipboard function. This function sends the WM_DESTROYCLIPBOARD message to the previous clipboard owner, frees resources associated with data on the clipboard, and assigns clipboard ownership to the window that has the clipboard open. To find out which window owns the clipboard, call the GetClipboardOwner function.

    After emptying the clipboard, the window places data on the clipboard in as many clipboard formats as possible, ordered from the most descriptive clipboard format to the least descriptive. For each format, the window calls the SetClipboardData function, specifying the format identifier and a global memory handle. The memory handle can be NULL, indicating that the window renders the data on request. For more information, see Delayed Rendering.

    Paste Operations

    To retrieve paste information from the clipboard, a window first determines the clipboard format to retrieve. Typically, a window enumerates the available clipboard formats by using the EnumClipboardFormats function and uses the first format it recognizes. This method selects the best available format according to the priority set when the data was placed on the clipboard.

    Alternatively, a window can use the GetPriorityClipboardFormat function. This function identifies the best available clipboard format according to a specified priority. A window that recognizes only one clipboard format can simply determine whether that format is available by using the IsClipboardFormatAvailable function.

    After determining the clipboard format to use, a window calls the GetClipboardData function. This function returns the handle to a global memory object containing data in the specified format. A window can briefly lock the memory object in order to examine or copy the data. However, a window should not free the object or leave it locked for a long period of time.

    For now, Silverlight Clipboard API only supports CF_UNICODETEXT format for copy/paste Unicode text to/from clipboard.

    Mac OS X uses Pasteboard Manager. It is documented at Mac Dev Center. Below excerpt (with APIs added) gives an overview:

    The copying application is responsible for placing copied or cut data onto the pasteboard:

    1. The user selects some data and invokes the Copy (or Cut) menu item.

    2. If the application doesn’t already have a reference to the Clipboard pasteboard, it creates one (PasteboardCreate).

    3. The application then takes ownership of the pasteboard and clears the current contents (PasteboardClear).

    4. The application assigns item IDs to the selected data.

    5. If any data is to be promised, the application must register a promise keeper callback function to supply the promised data (PasteboardSetPromiseKeeper).

    6. The application adds one or more flavors of each item to the pasteboard, including either the actual flavor data or a promise with each flavor (PasteboardPutItemFlavor).

    The receiving application has a slightly different set of tasks to handle the Paste action:

    1. When the application becomes active, it checks to see if the pasteboard has been modified (PasteboardSynchronize). If so, it obtains a listing of the flavors on the pasteboard. If there are any flavors the application supports, it can enable its Paste menu item.

    2. The user invokes the Paste menu item.

    3. The application requests the item (or items) on the pasteboard in the flavors that it supports (PasteboardGetItemCount, PasteboardGetItemIdentifier, PasteboardCopyItemFlavors, PasteboardCopyItemFlavorData).

    4. If the pasted data is to be stored as a file, the receiving application needs to set a paste location before requesting any flavor data. In any other case, the receiving application doesn’t need to worry about whether the paste data was promised or not.

    If the copying application’s promise keeper is called, the callback must do the following:

    • If the data is to be stored as a file, determine the paste location specified by the receiving application.

    • Generate or otherwise prepare the promised data for transfer.

    • If the promised data is not be stored as a file, add the flavor and data to the pasteboard. Otherwise, transfer the promised data to the specified file location.

    Some time later, when the application quits, or when it no longer needs the pasteboard, the application can release the pasteboard reference.

    As on Windows, Silverlight Clipboard API only uses kPasteboardClipboard and kUTTypeUTF16PlainText flavor to support copy/paste Unicode text to/from clipboard.

    Feedback

    We are excited to add clipboard support to Silverlight, and see all the important scenarios enabled by this simple API. I am interested in hearing your feedbacks, especially:

    • any security concerns over Silverlight clipboard API design and implementation
    • how important it is to add support for other data type/format, and in what priority order
    • how important it is to make the API extensible, like supporting IDataObject, allowing custom format/flavor and delayed rendering/promised data

    Thanks!

    -Ning

    Technorati Tags: ,