Archives

Showing posts with label silverlight. Show all posts
Showing posts with label silverlight. Show all posts

Monday, April 18, 2011

Specifying Content Types from the SharePoint 2010 Silverlight Client Object Model

Cross-posted from Jason Lee's Blog

A few weeks ago, I wrote about how you can use the Silverlight client object model to upload files to a SharePoint document library. One of the limitations of this process is that it doesn't allow you to specify a content type or provide any metadata for the document you're uploading. In this post, I look at how you can programmatically provide this missing information.

As with most client-side operations for SharePoint 2010, the process is a little more complex from a Silverlight client than from a managed .NET client, as many useful methods and properties are unavailable. From a Silverlight client, you need to use the following high-level steps:

  • Upload the file
  • Retrieve the list item corresponding to the file
  • Update the field values of the list item to set the content type and any other required metadata

Let's take a look at how this works in code. Because we're working with a document library, you must upload the file as the first step –SharePoint won't allow you to create a list item first and upload the document when you're finished providing metadata. I covered uploading a document in a fair amount of detail last time, so let's assume we've done that already. The next step is to retrieve the list item that SharePoint created when we uploaded the document.

Since we need to execute more than one query, it's easier to queue our logic to run on a background thread. This means we can execute queries synchronously rather than creating multiple nested callbacks, which get difficult to untangle after a while.

ClientContext context = ClientContext.Current; System.Threading.ThreadPool.QueueUserWorkItem(
   new System.Threading.WaitCallback(UpdateMetadata), context);

In the callback method, the first step is to submit a CAML query that retrieves the list item corresponding to our document. Notice that we also load the collection of available content types. You'll see why in a bit.

private void UpdateMetadata(object state)
{
   ClientContext context = (ClientContext)state;
   Web web = context.Web;
   List list =
      context.Web.Lists.GetByTitle("My Document Library");
   CamlQuery query = new CamlQuery();
   query.ViewXml = @"
      <View>
         <Query>
            <Where>
               <Eq>
                  <FieldRef Name='FileLeafRef'/>
                  <Value Type='Text'>Sample.txt</Value>
               </Eq>
            </Where>
         </Query>
         <RowLimit>10</RowLimit>
      </View>";
   ListItemCollection items = list.GetItems(query);
   context.Load(items);
   ContentTypeCollection contentTypes =
      context.Web.AvailableContentTypes;
   context.Load(cts);
   context.ExecuteQuery();

Let's assume we want to assign an arbitrary content type named "Chapter" to our list item. To set the content type of a list item, we need to set the value of the ContentTypeId field. In the Silverlight client object model, the ContentTypeCollection class doesn't allow you to use the name of the content type as an indexer. Instead, we can use a simple LINQ expression to get the ID of our Chapter content type.

   var ctid = from ct in contentTypes
              where ct.Name == "Chapter"
              select ct.Id;

We can now set the content type of our document and provide any required metadata.

   ListItem item = items[0];
   item["ContentTypeId"] = ctid;
   item["PublishingContactName"] = "Jason L";
   item["PublishingContactEmail"] = "jason@example.com";
   item.Update();
   context.ExecuteQuery();
}

In a real-world application, you'd obviously need to check that your query returned one unique list item, build in error handling, and so on. However, hopefully this provides enough information to get you started.

Read More >>

Wednesday, March 23, 2011

Using the SharePoint 2010 Silverlight Client Object Model to Update Documents

Cross-posted from Jason Lee's Blog

Earlier this month, I blogged on how you can use the Silverlight client object model to retrieve files from a SharePoint document library. This time, let's take a look at how you can add or update files in a document library from Silverlight.

Just like the process for retrieving files, the process for adding or updating files differs between managed .NET clients and Silverlight clients. The Silverlight client object model does not support the File.SaveBinaryDirect method, so the recommended approach for managed clients is not available to us. From a Silverlight client, the high-level process is as follows:

  • Convert the contents for your new file to a byte array
  • Create a FileCreationInformation instance to represent the new file
  • Add the file to a folder in a document library

The code should resemble the following:

ClientContext context = ClientContext.Current;
String fileContents = "This is the contents of my file";
String fileUrl = String.Format(@"{0}/{1}/{2}/{3}",
   new String[]
      {context.Url, libraryPath, folderName, filename});

//Convert the file contents to a byte array
System.Text.UTF8Encoding encoding =
   new System.Text.UTF8Encoding();
Byte[] fileBytes = encoding.GetBytes(fileContents);

//Create an object to represent the file
FileCreationInformation fileCreationInfo =
   new FileCreationInformation();
fileCreationInfo.Url = fileUrl;
fileCreationInfo.Content = fileBytes;
//Overwrite the file if it exists, create if it doesn't
fileCreationInfo.Overwrite = true;

//Add the file to a library
List targetList =
   context.Web.Lists.GetByTitle("My Document Library");
targetList.RootFolder.Files.Add(fileCreationInfo);
targetList.Update();
context.ExecuteQueryAsync(SaveFileSucceeded,
   SaveFileFailed);

And that's how you save a file to a SharePoint document library. You don't need to do anything specific in the callback methods, other than check for errors or report success back to the user. Note that you don't need to add your file to a specific folder in the document library—you can simply add it to the root folder, and SharePoint will use the URL you provided to put it in the right place. Unlike the server-side object model, the Silverlight client object model doesn't expose a collection of files on the Web object.

One limitation of this approach is that it doesn't allow you to specify a content type or provide any metadata for the file. I plan to look a little deeper into this in a later post.

Read More >>

Thursday, March 17, 2011

Using the SharePoint 2010 Silverlight Client Object Model to Retrieve Documents

Cross-posted from Jason Lee's Blog

This week I've been working on migrating a Silverlight application to SharePoint 2010. The application in question uses some fairly complex XML files as a data source, and currently relies on a custom Web service to retrieve and update these files. We want to modify the application to retrieve the XML files from a SharePoint 2010 document library. MSDN provides a good article on how to use the managed .NET client object model for SharePoint 2010 to retrieve and update documents in a SharePoint document library. However, this scenario becomes a little more challenging from a Silverlight client, as some of the required classes are unavailable in the Silverlight version of the client object model.

When you work with the managed client object model, the recommended approach for retrieving the contents of a file is to call the synchronous File.OpenBinaryDirect method. This returns a FileInformation instance that exposes the contents of the file as a stream. However, the FileInformation class is not included in the Silverlight client object model. Instead, the Silverlight client object model includes an alternative, asynchronous version of the File.OpenBinaryDirect method. This returns null, but exposes the contents of the file as a stream through the event arguments in a callback method.

Let's take a look at the code. Suppose we want to retrieve both the metadata for the file and the contents of the file.

ClientContext context = ClientContext.Current;
List targetList =
context.Web.Lists.GetByTitle("My Document Library");
CamlQuery query = new CamlQuery();
query.ViewXml =
   @"<View Scope='RecursiveAll'>
      <Query>
         <Where>
            <Eq>
               <FieldRef Name='FileLeafRef' />
               <Value Type='Text'>input.xml</Value>
            </Eq>
         </Where>
      </Query>
   </View>";

ListItemCollection targetListItems = targetList.GetItems(query);
context.Load(targetListItems);
context.ExecuteQuery();

We can now retrieve document metadata from the list item. For example, we could use the following code to establish when the document was created.

if(targetListItems.Count == 1)
{
   ListItem item = targetListItems[0];
   DateTime createdDate =
      Convert.ToDateTime(item["Created_x0020_Date"]);
}

To get the contents of the file, we use the Microsoft.SharePoint.Client.File.OpenBinaryDirect method and specify callback methods:

String serverRelativeUrl =
   @"/sitename/libraryname/foldername/input.xml";
File.OpenBinaryDirect(context, serverRelativeUrl,
   OnOpenSucceeded, OnOpenFailed);

In the callback method, we can read the contents of the file from the stream and do something useful with it.


private void OnOpenSucceeded(object sender, OpenBinarySucceededEventArgs args)
{
   StreamReader strReader = new StreamReader(args.Stream);
   String fileContents = strReader.ReadToEnd();
   strReader.Close();

   //Do something with the file contents
}

In a nutshell, that's how you retrieve SharePoint 2010 documents from a Silverlight client. Note that I used the synchronous ExecuteQuery method, rather than the asynchronous ExecuteQueryAsync method, to send my queries to the server. Silverlight will not allow you to block the UI thread, so if you want to use this approach you need to run your code on a background thread (for example, by using ThreadPool.QueueUserWorkItem to invoke your logic). You might find this approach preferable if you need to send multiple queries to the server—otherwise you can end up with a tangled web of nested callback methods.

Next time, I'll take a look at creating, updating, and deleting documents from a Silverlight client.

Read More >>

Saturday, January 23, 2010

First Steps with the Silverlight Bing Maps Control

A while back, I posted an article about displaying spatial data from SQL Server with what was then called the Virtual Earth Maps control. The article demonstrated an application that retrieves information about locations visited by a toy stuffed bear named Beanie, and displays those locations on a map. Since then, the Virtual Earth Map control has been renamed Bing Maps, and a Silverlight version of the map control is now available – so naturally, the time has come to update the Beanie Tracker application.

Unlike the Javascript version of the Bing Maps control, to use the Silverlight Bing Maps control, you need to sign up at the Bing Maps Account Center and obtain a key.  However, this is a straightforward process (and free!). Once you have a key, you can create Silverlight applications that display and manipulate the Bing Maps control. To do this, download and install the Bing Maps control.  Then create a new Silverlight application and add a reference to the assemblies provided with the control as shown here:

Ref

Now that you have a reference to the Map control, you can add its namespace to a XAML UserControl and include a map object in the Silverlight user interface as shown here, referencing the key you obtained from the Bing Maps Account Center:

<UserControl x:Class="BeanieTracker.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
    mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="500" Width="700" Height="400">
  <Grid x:Name="LayoutRoot">

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200" />
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0" Grid.Row="0" Orientation="Vertical">
            <Image Name="imgBeanie" Source="Beanie.jpg"></Image>
            <Button Cursor="Hand" Width="195" Height="25" HorizontalAlignment="Left" Content="Show Locations" x:Name="b1" Margin="2,10,0,1" Click="b1_Click"></Button>
       </StackPanel>

        <m:Map Name="map" Grid.Column="1" Grid.Row="0" CredentialsProvider="YOUR_KEY" Width="475" Height="300" />

    </Grid>
</UserControl>

Adding the map control displays a Bing Maps map in Silverlight user interface, enabling users to view the map and interact with it through it’s built in controls for changing the zoom level or view, and moving around the map. However, to add custom functionality, you need to write some code to manipulate the map control.

The Silverlight map control exposes a number of objects with properties and methods you can control programmatically, though some of the functionality in the Javascript version of the control has not been implemented in the Silverlight version. Unfortunately, the functionality that enables you to import a GeoRSS feed as a ShapeLayer onto the map is not implemented in the Silverlight control, so a simpler version of the Beanie Tracker application is required. In this version, I’ve written code to retrieve the GeoRSS feed, and then parse the XML feed and create a pushpin for each GML pos element, as shown here:

private void b1_Click(object sender, RoutedEventArgs e)
{
     Uri url = new Uri("../Feed.aspx?data=locations", UriKind.Relative);
     WebClient client = new WebClient();
     client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
     client.DownloadStringAsync(url);
}

void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
     if (e.Error == null)
     {
         StringReader stream = new StringReader(e.Result);
         XmlReader reader = XmlReader.Create(stream);
         string gmlURI = "http://www.opengis.net/gml";

         while (reader.Read())
         {
             if (reader.NodeType == XmlNodeType.Element)
             {
                 if (reader.NamespaceURI == gmlURI && reader.Name == reader.Prefix + ":pos")
                 {
                     string[] loc = reader.ReadInnerXml().Split(" ".ToCharArray());
                     double lat = Double.Parse(loc[0]);
                     double lon = double.Parse(loc[1]);
                     Pushpin p = new Pushpin();
                     p.Location = new Location(lat, lon);
                     map.Children.Add(p);
                 }
             }
         }
     }
}

You can see the resulting application at http://www.graemesplace.com/beanietracker.aspx.

Read More >>

Monday, January 11, 2010

Multi-touch in Silverlight 3: Part 1

 

1. Introduction

1.1 Introduction

One of the cool features in Silverlight 3 is the Multi-touch capabilities.   This feature allows Silverlight developers to create fun and interactive applications that can be used with standard inputs, such as mouse and keyboard, as well as with touch-enabled devices. 

If you have seen Minority Report, you are already aware of the gesture-based technology.  The user can interact with the device to drag objects around, flick to navigate, and pinch to adjust the size of an object.  These concepts have become a standard feature on a variety of devices, including Microsoft Surface, Windows 7, and mobile phones.

Silverlight 3 provides the  fundamentals of receiving touch events from the hardware to the application.  By default, the touch events are fired from Windows 7 to the browser (IE / Firefox).  If the application is not processing the touch events, these events are registered as standard mouse events.

Here are two sample applications that utilize Multi-touch concepts.  You will need a touch-enabled device and Windows 7 to get the full experience.

SlideView

3D SlideView | SlideView

In this two-part blog series, I’ll show you the process that I went through in developing a Silverlight class library with Multi-touch gestures and events.  This process was used in SharePoint Silverview to add Silverlight / Windows 7 touch functionality in a SharePoint environment.  You can find the complete source code at http://code.msdn.microsoft.com/ssv.

1.2 Online Research

Jesse Bishop, Program Manager on the Microsoft Silverlight team, wrote an amazing article about Multi-touch Gesture recognition in Silverlight 3.  The article illustrates on how to register touch events and process standard gestures on individual elements.  The primary concept is to have a single entity that reads the incoming events and determine which element that is being accessed.  After reading Jesse’s article, I got inspired to extend his concept with a variety of gestures and events that can be used in a standard Silverlight application.

1.3 Hardware

Before you can develop or run Silverlight application with Multi-touch, you need the right equipment.  I have been developing on the Dell SX2210T and the HP TouchSmart IQ586.  The HP TouchSmart TX2 is also a good choice.  One interesting thing that I learned was the HP TouchSmart IQ586 only had support for a single touch point.  There is an updated driver to ensure that the monitor utilizes two touch points.  You can check for this by viewing the Pen and Touch information under System Information.  It is ideal to have 2 Touch Points, if you plan to support two-finger gestures such as Scale and Rotate. With the recent release of Windows 7, there may be drivers for existing hardware that will need to be updated. 

1.4 Touch 101

The Touch framework has two primary concept: the TouchPoint object and the FrameReported event.

The TouchPoint object stores data related to a single touch point.  This data is passed from the hardware as the user interacts with the touch device.  The number of touch points vary on the supported hardware.   The first touch point that is fired to the application is called the primary touch point.  The object has two important properties: Position and Action.  The Action property stores the current action state, such as Up, Down, and Move.  The Up and Down actions are similar to the mouse’s MouseLeftButtonUp and MouseLeftButonDown events.  The Move action is the transition action that is constantly called until the user releases the Touch Point, which also invokes the Up action.

The FrameReported event is an application-wide event that sends data from the hardware to the application.  One thing to note is that this event is global to the application rather than an individual element.  The application doesn’t directly know the element that the event is processing.  It is possible to retrieve this information using the VisualTreeHelper.GetPointsUnderCoordinates method.  The TouchFrameEventArgs argument has methods of getting information about touch points.  You can get the current touch points by calling the GetPrimaryTouchPoint and GetTouchPoint methods.

2. Basic Implementation

Demo | Source Code

2.1 Creating the MultiTouch Class Library

Now, let’s start by creating a class library.  The library will be useful for  rapidly developing touch applications.  We will only need two objects for the library: TouchElement control and TouchProcessor (explained in Jesse’s article).

In Visual Studio 2008 (or Visual Studio 2010 Beta 2), create a new Silverlight Application.  Right-click on the Solution in the Solution Explorer and select Add New Project.  In the Project Dialog box, select Silverlight Class Library and name it TouchLibrary.  Remove the default Class1.cs file. 

2.2 TouchElement

We will define the TouchElement control as a content control to allow you to place any element, such as an image, movie clip, or UIElement, into the control.   This is not a requirement for deploying Touch applications, but rather a personal choice for development.  One useful scenario is having a couple of TouchElement controls with images to allow users to drag and scale images around the user interface.

Right-click on the TouchLibrary and select Add New Item.  In the Add New Item Dialog box, select User Control and name it TouchElement.  Replace the TouchElement.xaml content with the following:

<ContentControl x:Class="TouchLibrary.TouchElement"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
RenderTransformOrigin="0.5,0.5"
>
<ContentControl.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scale" />
<TranslateTransform x:Name="translate" />
</TransformGroup>
</ContentControl.RenderTransform>
<ContentControl.Template>
<ControlTemplate>
<ContentPresenter Margin="0" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}">
</ContentPresenter>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>





Note that this is a Content Control rather than a UserControl.  We have added TranslateTransform and ScaleTransform transforms to render the control’s transformation based on the touch events.



Now it’s time for some code action.  In Jesse Bishop’s article, he implements the concept using ITouchElement as an interface that describes the supported gestures and ManipulateElement as a UserControl that implements the ITouchElement interface.  I decided to merge the code from his elements into the TouchElement content control.   I highly recommend checking out his article if you want to learn the details of the concept.



We will implement the Translate and Scale gestures.  The Translate gesture occurs when the user drags the element from one position to another.  The Scale gesture occurs when the user pinch and scales using two touch points.  There may be scenarios when the developer wants to create their own custom translate and scale events.  For this reason, we will add support for default transforms as well as add custom events that will send information back to the application.



The TransformEventArgs class stores basic data for both Translate and Scale events.




public class TransformEventArgs : EventArgs
{
public TouchElement.TransformType Type { get; set; }
public Point Translate { get; set; }
public double Scale { get; set; }

public TransformEventArgs(TouchElement.TransformType type, Point translate)
{
Type = type;
Translate = translate;
Scale = 1;
}

public TransformEventArgs(TouchElement.TransformType type, double scale)
{
Type = type;
Scale = scale;
Translate = new Point(0, 0);
}
}





The TransformHandler delegate is used for both events, with the TransformEventArgs object as the second parameter.




public delegate void TransformHandler(object sender, TransformEventArgs e);
public event TransformHandler ScaleChanged;
public event TransformHandler TranslateChanged;





We will add three properties to the TouchElement class.  The TouchEnabled property will be used in the TouchProcessor class to check which TouchElement objects need to be processed.  The TouchDrag and TouchScale boolean properties enable the default touch behaviors for drag (translate) and scale events.  By default, the properties are set to false to allow the user to create their own events.




public bool TouchEnabled
{
get { return (bool)GetValue(TouchEnabledProperty); }
set { SetValue(TouchEnabledProperty, value); }
}

public bool TouchDrag
{
get { return (bool)GetValue(TouchDragProperty); }
set
{
SetValue(TouchDragProperty, value);

if (value)
this.TranslateChanged += new TransformHandler(TouchElement_TranslateChanged);
else
this.TranslateChanged -= new TransformHandler(TouchElement_TranslateChanged);
}
}

public bool TouchScale
{
get { return (bool)GetValue(TouchScaleProperty); }
set
{
SetValue(TouchScaleProperty, value);

if (value)
this.ScaleChanged += new TransformHandler(TouchElement_ScaleChanged);
else
this.ScaleChanged -= new TransformHandler(TouchElement_ScaleChanged);
}
}

public static readonly DependencyProperty TouchEnabledProperty =
DependencyProperty.Register("TouchEnabled", typeof(bool), typeof(TouchElement), new PropertyMetadata(true));

public static readonly DependencyProperty TouchDragProperty =
DependencyProperty.Register("TouchDrag", typeof(bool), typeof(TouchElement), new PropertyMetadata(false));

public static readonly DependencyProperty TouchScaleProperty =
DependencyProperty.Register("TouchScale", typeof(bool), typeof(TouchElement), new PropertyMetadata(false));





The TouchDrag and TouchScale properties connect the default event handlers, which validates the input value and directly updates TouchElement’s transform values.




void TouchElement_ScaleChanged(object sender, TransformEventArgs e)
{
double scale = e.Scale;

double s = this.scale.ScaleX + scale;
double MIN_SCALE = 0.25;
double MAX_SCALE = 5.0;

if (s > MIN_SCALE && s < MAX_SCALE)
{
this.scale.ScaleX += scale;
this.scale.ScaleY += scale;
}
else if (s < MIN_SCALE)
{
this.scale.ScaleX = MIN_SCALE;
this.scale.ScaleY = MIN_SCALE;
}
else if (s > MAX_SCALE)
{
this.scale.ScaleX = MAX_SCALE;
this.scale.ScaleY = MAX_SCALE;
}
}

void TouchElement_TranslateChanged(object sender, TransformEventArgs e)
{
this.translate.X += e.Translate.X;
this.translate.Y += e.Translate.Y;
}





The TranslateElement and ScaleElement methods calculate the appropriate transforms from the positions based on the touch events.  After the value is calculated, the event is fired with the TransformEventArgs as a parameter.  This will process the default transformation as well as any custom work that is connected to the event.




private void TranslateElement(Point oldPosition, Point newPosition)
{
double xDelta = newPosition.X - oldPosition.X;
double yDelta = newPosition.Y - oldPosition.Y;

if (yDelta != 0 || xDelta != 0)
RaiseTranslate(new TransformEventArgs(TransformType.TRANSLATE, new Point(xDelta, yDelta)));
}

private void ScaleElement(Point primaryPosition, Point oldPosition, Point newPosition)
{
double prevLength = GetDistance(primaryPosition, oldPosition);
double newLength = GetDistance(primaryPosition, newPosition);

if (prevLength == double.NaN || newLength == double.NaN)
return;

double scale = (newLength - prevLength) / newLength;

if (scale != 0)
RaiseScale(new TransformEventArgs(TransformType.SCALE, scale));
}





The TouchProcessor class calls the TouchPointReported method to process the touch event based on the TouchPoint’s Action property.  On the Down action, we collect information on the TouchPoint.  If it is the initial touch point, then the position is later used for distance calculation.  In the next blog post, I will go into additional functionality that can be added in the TouchDown method, such as a start time for timing touch event for other gestures.  On the Touch Up action, we free up the current point.  The TouchUp method can also be used to process any gestures, which will be detailed in the next blog post.  The Touch Move is where the fun’s at.  After the Down action occurs, the Move action constantly runs until the Up action is called.  If there is only a single touch point, the TranslateElement method is called.  The ScaleElement method is called if there are multiple points.  In Jesse’s article, he also has Rotate functionality, which is processed with multiple points.




public void TouchPointReported(TouchPoint touchPoint)
{
switch (touchPoint.Action)
{
case TouchAction.Down:
TouchDown(touchPoint.TouchDevice.Id, touchPoint);
break;
case TouchAction.Move:
TouchMove(touchPoint.TouchDevice.Id, touchPoint);
break;
case TouchAction.Up:
TouchUp(touchPoint.TouchDevice.Id, touchPoint);
break;
};
}

private void TouchDown(int id, TouchPoint touchPoint)
{
_points.Add(id, touchPoint.Position);

if (_points.Count == 1)
{
_startPoint = touchPoint.Position;
_primaryId = id;
}
}

private void TouchMove(int id, TouchPoint touchPoint)
{
Point oldPoint = _points[id];
Point newPoint = touchPoint.Position;

if ((_points.Count == 1 || id == _primaryId))
TranslateElement(oldPoint, newPoint);
else
ScaleElement(_points[_primaryId], oldPoint, newPoint);

_points[id] = newPoint;
}

private void TouchUp(int id, TouchPoint touchPoint)
{
Point oldPoint = _points[id];
Point newPoint = touchPoint.Position;
_points.Remove(id);
}





2.3 TouchProcessor



The TouchProcessor class manages the global touch functionality by connecting the Touch’s FrameReported event.  Jesse goes into detail about the TouchProcessor class in his article.  Add the following class to the Class Library.  The primary difference in this version is the RootElement property, which is used to connect to the current application’s visual root.  This property is required for the VisualTreeHelper.FindElementsInHostCoordinates method.




public class TouchProcessor
{
#region Data Members
private static Dictionary<int,TouchElement> touchControllers = new Dictionary<int, TouchElement>();
private static bool _istouchEnabled = false;
#endregion

#region Properties
public static UIElement RootElement
{
get;
set;
}

public static bool IsTouchEnabled
{
get { return _istouchEnabled; }

set
{
if (value && !_istouchEnabled)
Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
else if (!value && _istouchEnabled)
{
Touch.FrameReported -= new TouchFrameEventHandler(Touch_FrameReported);
touchControllers.Clear();
}

_istouchEnabled = value;
}
}
#endregion

#region Constructor
public TouchProcessor()
{ }
#endregion

#region Events
private static void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
TouchPointCollection touchPoints = e.GetTouchPoints(null);

foreach (TouchPoint touchPoint in touchPoints)
{
int touchDeviceId = touchPoint.TouchDevice.Id;

switch (touchPoint.Action)
{
case TouchAction.Down:
TouchElement hitElement = GetTouchElement(touchPoint.Position);

if (hitElement != null)
{
if (!touchControllers.ContainsKey(touchDeviceId))
touchControllers.Add(touchDeviceId, hitElement);

// Prevent touch events from being promoted to mouse events (only if it is over a TouchElement)
TouchPoint primaryTouchPoint = e.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null && primaryTouchPoint.Action == TouchAction.Down)
e.SuspendMousePromotionUntilTouchUp();

ProcessTouch(touchPoint);
}

break;
case TouchAction.Move:
ProcessTouch(touchPoint);
break;
case TouchAction.Up:
ProcessTouch(touchPoint);

touchControllers.Remove(touchDeviceId);
break;
};
}
}
#endregion

#region Touch Process Events
private static void ProcessTouch(TouchPoint touchPoint)
{
TouchElement controller;
touchControllers.TryGetValue(touchPoint.TouchDevice.Id, out controller);

if (controller != null)
controller.TouchPointReported(touchPoint);
}

private static TouchElement GetTouchElement(Point position)
{
foreach (UIElement element in VisualTreeHelper.FindElementsInHostCoordinates(position, RootElement))
{
if (element is TouchElement)
return (TouchElement)element;

// immediately-hit element wasn't an TouchElement, so walk up the parent tree
for (UIElement parent = VisualTreeHelper.GetParent(element) as UIElement; parent != null; parent = VisualTreeHelper.GetParent(parent) as UIElement)
{
if (parent is TouchElement)
return (TouchElement)parent;
}
}

// no TouchElement found
return null;
}
#endregion
}





3. Test the TouchLibrary



In the Silverlight Project, add TouchLibrary to the project’s References.  This will allow you to add the custom touch functionality to your project. 



In MainPage.xaml, add the following namespace to connect to the new library.




xmlns:touch="clr-namespace:TouchLibrary;assembly=TouchLibrary"             





Add the following to MainPage.xaml to create three TouchElements (two with color rectangles and one with an image).  The image source has the project name pre-pended to ensure that it loads the image from the correct project.  The TouchDrag and TouchScale properties are enabled to allow you to manipulate the items.




<Grid Background="Black">
<touch:TouchElement TouchDrag="True" TouchScale="True" HorizontalAlignment="Left">
<Rectangle Fill="Red" Width="200" Height="200" />
</touch:TouchElement>

<touch:TouchElement TouchDrag="True" TouchScale="True" HorizontalAlignment="Right">
<Rectangle Fill="Blue" Width="200" Height="200" />
</touch:TouchElement>

<touch:TouchElement TouchDrag="True" TouchScale="True" HorizontalAlignment="Center" VerticalAlignment="Bottom">
<Image Source="/TouchApp;component/Rocks.jpg" Width="200" Height="200" />
</touch:TouchElement>
</Grid>





In MainPage.xaml.cs, add the following code to initialize the TouchProcessor object.




TouchProcessor.RootElement = this;
TouchProcessor.IsTouchEnabled = true;





One thing to note is that the Touch functionality doesn’t work in Windowless mode.  By default, Windowless mode is set to false and it is a feature that should be used with caution.  For more information on the limitations of Windowless mode, check out this article.



Compile and run the application.  As mentioned before, you will need the required hardware to recognized the Touch events.  You can drag the three items across the screen as well as scale them to different sizes.  The item is also brought to the front of the screen after release the touch point.



The demo of this version can be found here



Screenshot



4. Wrapping Up



Thanks for checking out this article.  Stay tuned for my next blog post, where I introduce more customization for the TouchLibrary, including Gesture support, parented selection, and touch events in a ChildWindow.



http://www.silverlighttoys.com/Articles.aspx?Article=SilverlightMultiTouch

Read More >>

Monday, March 23, 2009

Silverlight 3 Released

Silverlight 3 Beta was announced at the MIX 09 event in Las Vegas. The new version pushes the envelope in providing richer online applications through its various new features, controls, and methods of development. Some of the great Silverlight 3 features include the Out-Of-Browser experience, Search Engine Optimization, and improved graphic support. In this article, I will show you some of the cool features from Silverlight 3.

Developer Version

The current version of Silverlight 3 Beta is intended for developer use only. This means that you will need the Developer runtime to view Silverlight 3 applications. There is no “Go Live” support and not intended for public viewing. The end user runtime will be available later this year. Developers will need to ensure that they define the install experience in an appropriate manner to inform the end user that the application is in the beta stage. Tim Heuer wrote a great blog article about the Silverlight 3 Install Experience.

Let’s Get Started

Before you start developing in Silverlight 3, you will need to be aware that this is a Beta version and must treated as such. Silverlight 2 applications can be viewed, but not developed, with the new runtime. If you plan on developing Silverlight 3 applications, it is recommended to install Silverlight 3 on its own machine or virtual machine. Once you have installed Silverlight 3, you won’t be able to deploy Silverlight 2 applications on the same machine.

The following tools are the bare minimum needed to develop Silverlight 3 applications:

For more information on getting started with Silverlight, check out the Microsoft Silverlight 3 Site. The site includes the necessary tools, tutorials, and information to rapidly get you started.

Silverlight Toolkit

During MIX 09, the Silverlight Toolkit March 2009 was released with additional controls. One of the major change in the new Toolkit is the move from Microsoft.Windows.Controls to System.Windows.Controls namespace. The Toolkit includes two new themes: BubbleCreme & TwilightBlue. Some of the great new controls in the Toolkit include the Accordion and DomainUpDown. The Accordion control stores a list of collapsed and expanded AccordionItem controls. This concept is similar to a grouped collection of Expander controls, which allows you to organize data or XAML elements in a clean manner. The DomainUpDown control allows the user to cycle through a list of values using the TextBox and Spinner controls.

To learn more about the new features, check out the Silverlight Toolkit Breaking Changes.

New Controls

Silverlight 3 is shipped with new controls, along with a mature set of controls from Silverlight Toolkit. Some of the mature controls from Silverlight Toolkit include DockPanel, WrapPanel, Label, TreeView, Expander, and DataGrid. If you have used any of these controls in older applications, they will still work with Silverlight 3. Silverlight 3 also utilizes new controls for data, search engine optimization, and overall development.

Two new Data controls, DataForm and DataPager, can be used to render data in Silverlight applications. The DataForm control displays data for a single entity and allows for traditional data manipulation, including edit and update. The DataPager control allows the navigation through a data set.

Silverlight 3 introduces the navigation framework (System.Windows.Control.Navigation), which is composed of the Page and Frame controls. The Frame control hosts a single Page control utilizes the Navigation APIs to allow the user to navigate between the pages. The Page control resembles the commonly used UserControl control to hold the contents of the respected page. This feature allows you to create applications that resemble a web page using a single xap file. The primary benefit of the framework is the ability to communicate with the browser in regards to the Address Bar and Browser History. The currently loaded XAML file is stored in the Address Bar, which allows for deep linking in Silverlight applications and providing SEO. The framework also maintains the history of navigated pages, which can be access using the browser’s back and forward functionality.

The ChildWindow control makes its way to Silverlight 3. Child Windows, also known as modal windows, are used to draw attention to important information or to halt the application flow for user input. The child window blocks the workflow until the window is closed. The window stores the result in DialogResult to inform the application of its status upon closing. Unlike the traditional modal window, Silverlight renders the child window with an animation sequence and renders an overlay background to ensure the user focuses on the window.

Graphic Enhancements

Silverlight 3 sports significant graphical enhancements including 3D perspective transforms, pixel shaders, GPU acceleration, and animation easing. The new version also has a Bitmap API for manipulating bitmap pixels.

Perspective Transforms in Silverlight 3 is the next step towards developing 3D Silverlight applications. In previous versions of Silverlight, transforms were processed in the X and Y axis using the UIElement’s RenderTransform property. Silverlight 3 uses the PlaneProjection class to render 3D-like effects by applying content to a 3D plane. All elements, that derive from UIElement, have the Projection property that allows the element to simulate translation and rotation transformations in a 3D space. This feature allows for 3D-like user interfaces, flipping animations, and transition effects.

Silverlight 3 has two built-in effects, Blur and DropShadow, and supports the development of custom effects. Pixel Shaders are a compiled set of software instructions that calculate the color of the pixels and executed on the GPU. The instructions are written in HLSL (High Level Shader Language). All elements, that derive from UIElement, have the Effect property that allows the element to render with the connected pixel shader. This feature allows for more rich and beautiful user interfaces and transition effects.

GPU hardware acceleration reduces the CPU processing workload by performing the tasks directly on GPU hardware. This allows for full screen HD renderings of video to run on your computer without taking up a large load of CPU. GPU rendering can also be used to cache XAML and image elements.

The animation system has been upgraded with Easing functions to provide more natural and advanced animation. Developers can create their own custom easing functions by modifying the built-in function or deriving from the EasingFunctionBase. Silverlight 3 comes with several built-in easing functions, including the following:

  • BackEase
  • BounceEase
  • CircleEase
  • CubicEase
  • ElasticEase
  • ExponentialEase
  • PowerEase
  • QuadraticEase
  • QuarticEase
  • QuinticEase
  • SineEase

The Bitmap API allows rendering XAML elements and data to bitmaps using the WriteableBitmap class. This can be used to modify images, capture a frame from a MediaElement control, and render the current state of a control.

Media Support

Silverlight 3 adds support for additional standard media types, including H.264 and AAC, and supports third party codecs to decode outside the runtime and render in the Silverlight application. This allows for a wide variety of video and audio files to be played in Silverlight. Using GPU hardware acceleration, applications can now deliver full-screen HD content.

Themes and Styles

Silverlight 3 improves the themes and styles system to allow designers and developers to customize the look and feel of their applications. Unlike previous versions of Silverlight, Silverlight 3 supports changing styles during runtime. Styles can be stored externally in resource dictionary to allow for organized and shareable code. This allows developers to easily share and merge their styles among different projects. Styles now support an inherited system to reduce necessary code for redundant styles.

Out of Browser Experience

Silverlight applications can run outside of the browser with simple modifications by the developer. The end user can choose whether to install the application on the user’ Start menu and Desktop. One major benefit is that the end user can run the application at home, at work, and on the go without the need of a web browser and online access. Installed applications can be automatically updated to ensure that the end user has the latest version. The new Network APIs allows the application to know whether the application is connected or not.

Element to Element Binding

Element to element data binding allows you to bind element properties to each other. In previous versions of Silverlight, this would require more work on the code side because the element would fire its changed method and have that update the necessary elements. Silverlight 3 simplifies this process by performing the task directly in XAML.

Conclusion

Silverlight 3 has a lot of fantastic features to create the next generation of interactive applications. This article has only scratched the surface on the Silverlight 3 features. For more information on Silverlight 3, check out http://silverlight.net/getstarted/silverlight3/default.aspx. Additional information on Silverlight 3 can be found on the many MIX 09, which can be viewed at http://sessions.visitmix.com/MIX09/.

Read More >>

Wednesday, February 11, 2009

Loading Files from a Remote Server in Silverlight

1. Introduction

Microsoft Silverlight uses the WebClient class to send HTTP requests to remote scripts, which allows the application to perform specific web server functions, such as retrieving files. Remote scripts, created with Active Server Pages (ASP) and Personal Hypertext Processor (PHP), add an additional layer of functionality with direct access to the remote server, such as the file system. In this article, I will demonstrate an application that utilizes the WebClient class to retrieve and display files from a remote server. The article also shows some fun features to enhance the application including Data Binding and a Sample Syntax Highlighter.

1.1 The Non-Silverlight Approach

I developed the Source Code Viewer application using PHP, AJAX, and MySQL. The application allows the end users to browse a collection of source code in a category-style manner. Using AJAX, the page remains stationary as the data is pulled and processed from a MySQL database. The users can browse through the categories to search for files.

There are a couple of concerns in regards to this implementation:

  • Modifying and adding files requires constant database changes
  • MIME types have to be modified to ensure that the file types are read
  • The files have direct hyperlinks

The original Source Code Viewer can be found at: http://www.bayprince.com/sourcecode.

1.2 The Silverlight Approach

I chose to use Silverlight to develop an interactive layer to the Source Code Viewer application.

The Silverlight version contains three primary components:

  • Display the list of allowed categories using a file path to category relationship.
  • Remote Script to display the list of files in the selected folder
  • Render the selected file based on file type

The Silverlight Toolkit has a TreeView control, which is perfect for showing hierarchy data. The control is easily customizable to develop a File Explorer style interface. The Toolkit is conveniently located at http://www.codeplex.com/Silverlight.

The Remote Script retrieves data from the server and renders its output in the form of xml, which can be imported into the Silverlight application. The script performs validations to protect the necessary files and stores the file details into the xml output.

Silverlight has a nice collection of built-in controls and supports rapid development of custom controls. Developing custom controls for specific file types, such as text files, images, and media, allows supporting those types without the need to configure the MIME type entries. For example, Python (py) files on my server are executable by default. It would be beneficial to render those files as text for viewing.

2. Introducing Remote File Viewer

Remote File Viewer is an online application that allows users to browse files on a remote server. I currently use the application to share my source code to students and fellow developers. I manage a single xml file for the category system to inform the application on what categories to display to the end user. The user chooses a category and is presented with a list of files in the category. The list of files is pulled from the remote script that finds the valid files in the respected directory. The user can select a file and view its contents, including basic file information such as File Size and Last Modified.

Remote File Viewer Flow Diagram Remote File Viewer Flow Diagram

2.1 Current Version

The current version of the Remote File Viewer renders source code files, images, and Deep Zoom output. Source Code files are assumed to be ASCII text files. Remote File Viewer has basic syntax highlighting for comments and strings. This allows the code files to appear more dynamic rather than the common text document. Images are rendered using the built-in Image control. The Deep Zoom approach is different because it searches specifically for the dzc_output.xml file to render its output using a standard Deep Zoom template. Additional file types, such as media, can easily be integrated using the framework.

2.2 Remote File Viewer Demo

You can check out the live demo here. The demo will allow you to browse source code text files, images, and Deep Zoom outputs located on my server.

The following file types are supported, along with their respected categories on my server:

  • Text: Everything except for the Images category
  • Images: Images / Raina Tracer / Scenes category
  • Deep Zoom: Images / 3D Modeling category

Online Demo

Download Source

3. Before Getting Started

3.1 Required Technologies

You will need the Silverlight Toolkit to use some of the necessary controls, including TreeView and DockPanel. The Toolkit can be downloaded at http://www.codeplex.com/Silverlight.

3.2 Testing Remote Code

You will need to run the application (xap) on a remote server otherwise the application will throw a SecurityException exception during any calls to the remote script. The supplied version of the project was designed for my domain, however you can customize the xml file to point to folders on your server.

3.3 Selecting a Server-Side Scripting Language

You can use ASP, PHP, or any other server-side scripting language that can perform tasks on the web server and produce an output. I chose to use PHP for this application, but the same concepts will work for other server-side scripting languages.

4. Implementing the Remote Script

The script must define its Content Type as text/xml to ensure that the output is rendered as XML content.

   1: header("Content-type: text/xml");




A single HTTP parameter is passed into the script to store the requested relative path, which will be appended to the script’s actual relative path to the files. The parameter is validated to ensure that the path exists on the web server.





   1: // HTTP Parameters


   2: $currentFolder = $_REQUEST['dir'];


   3:  


   4: // Prevent access to parent folders


   5: $i = stripos($currentFolder, "./", 0);


   6: if ($i > -1) 


   7:     $currentFolder = ""; 


   8:  


   9: $REL_PATH = "../sourcecode/files/";


  10: $dir = $REL_PATH . $currentFolder;


  11:  


  12: // Validate that the folder exists


  13: if (file_exists($dir) == FALSE) {


  14:     $currentFolder = "";        


  15:     $dir = $REL_PATH;


  16: }




The heart of the script is the generation of the XML content. The script loads the files in the path by calling the pathinfo and fileinfo APIs. The APIs may differ with other scripting languages. The file type is determined by comparing the file extension in the IGNORE_FILES and BINARY_FILES arrays. Each file is then entered as a single entry of the xml file.





   1: $IGNORE_FILES = array("exe", "swf", "zip", "vsd", "php", "inc", "mll","htm", "html");


   2: $BINARY_FILES = array("jpg", "png");


   3:  


   4: $xml = "<?xml version=\"1.0\"?>\n"; 


   5: $xml .= "<files dir=\"$currentFolder\">";


   6:  


   7: if ($handle = opendir($dir)) {


   8:     // Loop through files


   9:     while (false !== ($file = readdir($handle))) {


  10:         $p = $dir . "/" . $file;


  11:  


  12:         if ($file != "." && $file != ".." && is_dir($p) != 1) {


  13:             // Validate extensions


  14:             $pathInfo = pathinfo($p);


  15:             $file_ext = $pathInfo['extension'];    


  16:  


  17:             // Get file information


  18:             $fileInfo = stat($p);


  19:             $fileSize = $fileInfo['size'];


  20:  


  21:             $modifiedDate = date ("m:d:Y:H:i:s", $fileInfo['mtime']);


  22:  


  23:             // Validate if the application supports the file


  24:             if (in_array($file_ext, $IGNORE_FILES)) { continue; }


  25:  


  26:             // Determine file type


  27:             $type = "";


  28:  


  29:             if (in_array($file_ext, $BINARY_FILES)) { $type = "image"; }


  30:             else { $type = "text"; }


  31:         


  32:  


  33:             // Output File to XML


  34:               $xml .= "<file file=\"$file\" type=\"$type\" bytes=\"$fileSize\" lastModified=\"$modifiedDate\" />";


  35:           }


  36:        }


  37:     closedir($handle);


  38: }


  39:  


  40: $xml .= "</files>";


  41:  


  42: print $xml;




5. Designing the User Interface



The User Interface consists for three primary sections: Category Tree, File List and the Render Control. The Category Tree is the TreeView Control that is data bounded to the local category xml file. The File List is rendered after the user selects on the category and the data is brought back from the server. The Render Control is a UserControl object renders the output based on the file type.



5.1 Defining the Category Data



The primary category data is the compiled hierarchical list of the categories that will be in the application. Each category has a name, file path, and a list of child categories. There will be some categories that only contain child categories and no files. By setting their file path to an empty string, the application will know to ignore server processing for those categories.



The following XML file shows two categories: C# and Silverlight Development. Both categories have a child categories with files. The dir attribute in the XML is the relative path of the files. This path will be appended to an absolute path location in the C# code. We will data bound this XML file to the Category Tree.





<categories>
<category name="C#" dir="">
<children>
<category name="TicTacToe" dir="csharp/tictactoe">
<children />
</category>
</children>
</category>
<category name="Silverlight Development" dir="">
<children>
<category name="Remote File Viewer" dir="silverlight/rfv">
<children />
</category>
</children>
</category>
</categories>




5.2 Category Tree



The TreeView control is part of the Silverlight Toolkit. The Microsoft.Windows.Controls.dll is required in your project’s References to use the control.



The following reference is added to the list of namespaces in Page.xaml.





   1: xmlns:y="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"




The following code snippet defines the TreeView Control and prepares it to be data bounded to the previously created xml. The TreeView Control uses the HierarchicalDataTemplate object to render the category children data. The SelectedItemChanged event is attached to the control to capture the user’s selection.





   1: <y:TreeView x:Name="listCategories" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" SelectedItemChanged="listCategories_SelectedItemChanged">


   2:     <y:TreeView.ItemTemplate>


   3:         <y:HierarchicalDataTemplate ItemsSource="{Binding Children}">


   4:             <TextBlock Margin="0" Text="{Binding Name, Mode=OneWay}" FontSize="12" />


   5:         </y:HierarchicalDataTemplate>


   6:     </y:TreeView.ItemTemplate>


   7: </y:TreeView>




5.3 File List



The File list becomes visible after the data is retrieved from the remote script. The list allows the user to select on the file to view in the Render Output control. This list is hidden if the category contains no files.



The following code snippet shows the data bounded ListBox.





   1: <ListBox x:Name="listFiles" Margin="20" Background="Transparent" BorderBrush="Transparent" SelectionChanged="listFiles_SelectionChanged" Visibility="Collapsed">


   2:     <ListBox.ItemTemplate>


   3:         <DataTemplate>


   4:             <TextBlock Margin="0" Text="{Binding File, Mode=OneWay}" Foreground="White" FontSize="10" />


   5:         </DataTemplate>


   6:     </ListBox.ItemTemplate>


   7: </ListBox>




5.4 Render Control



The Render Control is a UserControl that will render the file output based in the file type. The Remote File Viewer supports text, images, and deep zoom output. Text is processed through a lightweight syntax highlighting pass for string and comments notation. Images are rendered using the built-in Image control. Deep Zoom objects are loaded into the custom Deep Zoom application with the generic functionality. Other file formats can be supported by adding their output panels in this control.



The following code snippet shows the controls use to render the file outputs. The current file type refreshes the user interface by toggling the visibility of the other controls.





   1: <ScrollViewer x:Name="scrollViewer" BorderThickness="0" BorderBrush="Transparent" VerticalScrollBarVisibility="Auto">


   2:     <Grid>


   3:         <StackPanel x:Name="panelProgress" Margin="0" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed">


   4:             <TextBlock Text="Loading..." FontSize="16" />


   5:             <ProgressBar x:Name="progress" Width="200" Height="15" Minimum="0" Maximum="100" />


   6:         </StackPanel>


   7:         <TextBlock x:Name="txtOutput" Margin="5" Foreground="Black" FontSize="10" />


   8:         <Image x:Name="imageOutput" Stretch="Uniform" Width="Auto" Margin="0" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed"  />


   9:         <z:DZPanel x:Name="zoomOutput" Width="Auto" Margin="0" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed" />


  10:     </Grid>


  11: </ScrollViewer>




6. Implementing the User Interface



6.1 Defining the Remote Data Members



The following code snippet shows the two data members that store the values of the remote addresses. REMOTE_SCRIPT_PATH is the location of the remote script. REMOTE_ABS_PATH is the absolute path where the files are stored. The remote location is hidden from the end user thus allowing for another layer of security.





   1: private const string REMOTE_SCRIPT_PATH = "http://www.DOMAIN.com/RemoteFileViewer/xml.php";


   2: private const string REMOTE_ABS_PATH = "http://www.DOMAIN.com/sourcecode/files/";




6.2 Implementing the Web Server Connection



The following code snippet shows the implementation of wrapper function for the WebClient API. The function is called twice when retrieving the file list from the server and during the downloading file process. The function accepts the remote address and two callbacks as inputs. WebClient provides methods for sending and receiving data. The WebClient.DownloadStringAsync method is used to download the resource as a string format. DownloadProgressChanged and DownloadStringCompleted are two event handlers that will be use to handle the data transfer and completion. During the transfer, the application displays a progress bar to inform the user of its status. The DownloadStringCompleted validates that the download was successful and stores the results in the DownloadStringCompletedEventArgs member.





   1: private void CallWebService(


   2:     string remoteUrl,


   3:     DownloadProgressChangedEventHandler changeCallback,


   4:     DownloadStringCompletedEventHandler completeCallback


   5: )


   6: {


   7:     // Load service from the server


   8:     WebClient client = new WebClient();


   9:     client.DownloadProgressChanged += changeCallback;


  10:     client.DownloadStringCompleted += completeCallback;


  11:     client.DownloadStringAsync(new Uri(remoteUrl, UriKind.Absolute));


  12: }




6.3 Populate the TreeView Control



The following code snippet loads the Data.xml file and parses the data into the list of DataCategory object. The data is then bounded to the TreeView control.





   1: // Populate categories from local data source


   2: XElement root = XElement.Load("Data.xml");


   3: Categories = (from c in root.Elements("category")


   4:               select new DataCategory


   5:               {


   6:                   Name = (string)c.Attribute("name"),


   7:                   Children = LoadData(c),


   8:                   Folder = (string)c.Attribute("dir"),


   9:               }).ToList();


  10:  


  11: // Update categories' data


  12: listCategories.ItemsSource = Categories; 








6.4 Selection Events



The following code snippets show the implementation of the Category and File Selection Events. Both events call the CallWebService function to retrieve data from the server. The File Selection event parses the file based on its file type to provide the appropriate loading.





   1: private void listCategories_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)


   2: {


   3:     DataCategory category = (DataCategory)listCategories.SelectedItem;


   4:  


   5:     // Validate category (some categories don't have files)            


   6:     if (category == null  category.Folder == "")


   7:     {


   8:         listFiles.Visibility = Visibility.Collapsed;


   9:         return;


  10:     }


  11:  


  12:     // Connect to php service to get files in the folder


  13:     renderControl.ShowProgress(0);


  14:  


  15:     CallWebService(


  16:         String.Format("{0}?dir={1}", REMOTE_SCRIPT_PATH, category.Folder),


  17:         DirDownloadProgressChanged,


  18:         DirDataComplete


  19:     );


  20: }


  21:  


  22: private void listFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)


  23: {


  24:     DataFile file = (DataFile)listFiles.SelectedItem;


  25:  


  26:     // Load selected file


  27:     if (file == null  file.File == "")


  28:         return;


  29:  


  30:     // Get file path


  31:     string folder = ((DataCategory)listCategories.SelectedItem).Folder;


  32:     string path = String.Format("{0}{1}/{2}", REMOTE_ABS_PATH, folder, file.File);


  33:  


  34:     if (file.Type == "text")


  35:         LoadFile(path);


  36:     else if (file.Type == "deepzoom")


  37:         renderControl.RenderDeepZoom(file, new Uri(path, UriKind.Absolute));


  38:     else if (file.Type == "image")


  39:         renderControl.RenderImage(file, new Uri(path, UriKind.Absolute));


  40: }








7. Icing on the Cake: Syntax Highlighter



Syntax highlighting provides for a visual addition when viewing source code. I decided to add a basic implementation of Syntax Highlighting to the Remote File Viewer to make the source code stand out. The current version supports string and comment syntax highlighting.



The TextBlock object can hold a collection of Run objects, each with their own style properties. The ApplyHighlighting function accepts the string input from the file and parses the file based on lines. The function performs string checks to find particular tokens, such as comment tokens and string quotations, and render them in separate Run objects. Additional tokens can be supported by comparing to a keyword hash or applying regular expressions.





   1: #region Syntax Highlighting


   2: //================================================


   3: private string AddOutput(string output)


   4: {


   5:     if (output != "")


   6:     {


   7:         AddRun(output, Colors.Black);


   8:         output = "";


   9:     }


  10:  


  11:     return output;


  12: }


  13:  


  14: //================================================


  15: private void AddRun(string txt, Color color)


  16: {


  17:     Run run = new Run();


  18:     run.Text = txt;


  19:     run.Foreground = new SolidColorBrush(color);


  20:     txtOutput.Inlines.Add(run);


  21: }


  22:  


  23: //================================================


  24: private void ApplyHighlighting(string txt)


  25: {


  26:     string output = "";


  27:     bool commentMode = false;


  28:  


  29:     // Clean line spacing


  30:     txt = txt.Replace("\t", "    ");


  31:     txt = txt.Replace("   ", "    ");


  32:  


  33:     // Clear UI Elements


  34:     txtOutput.Inlines.Clear();


  35:     txtOutput.Text = "";


  36:  


  37:     // Parse file contents into lines


  38:     List<string> lines = txt.Split('\n').ToList();


  39:  


  40:     foreach (string line in lines)


  41:     {


  42:         // Blank line (append to current output)


  43:         if (line.Trim() == "")


  44:         {


  45:             output = String.Format("{0}\n", output);


  46:             continue;


  47:         }


  48:         


  49:         // Check for comments if we are not in a comment


  50:         if (!commentMode)


  51:         {


  52:             // Visual Studio Region coloring


  53:             if (line.Contains("#region")  line.Contains("#endregion"))


  54:             {


  55:                 int index = line.IndexOf("#");


  56:  


  57:                 // Add words before the comment


  58:                 output = String.Format("{0}{1}", output, line.Substring(0, index));


  59:                 output = AddOutput(output);


  60:  


  61:                 // Add the comment


  62:                 AddRun(String.Format("{0}\n", line.Substring(index)), Colors.Blue);


  63:                 continue;


  64:             }


  65:  


  66:             // Single line comments (// and # symbols)


  67:             if ((line.Contains("//") && !line.Contains("http://"))  (line.Contains("#") && !line.Contains("\"#") && !line.Contains("#\"")))


  68:             {


  69:                 int index = 0;


  70:  


  71:                 if (line.Contains("//"))


  72:                     index = line.IndexOf("//");


  73:                 else


  74:                     index = line.IndexOf("#");


  75:  


  76:                 // Add words before the comment


  77:                 output = String.Format("{0}{1}", output, line.Substring(0, index));


  78:                 output = AddOutput(output);


  79:  


  80:                 // Add the comment


  81:                 AddRun(String.Format("{0}\n", line.Substring(index)), Colors.Green);


  82:                 continue;


  83:             }


  84:  


  85:             // Apply string highlighting


  86:             if (line.Contains("\""))


  87:             {


  88:                 output = ApplyStringHighlighting(output, line);


  89:                 continue;


  90:             }


  91:         }


  92:  


  93:         // Comments Highlighting


  94:         if (line.Contains("/*"))


  95:         {


  96:             if (line.Contains("*/"))


  97:             {


  98:                 try


  99:                 {


 100:                     int index = line.IndexOf("/*");


 101:  


 102:                     if (index > 0)


 103:                         output = String.Format("{0}{1}", output, line.Substring(0, index));


 104:                     else


 105:                         index = 0;


 106:  


 107:                     // Add words before the comment


 108:                     output = AddOutput(output);


 109:  


 110:                     // Add the comment


 111:                     int commentIndex = line.IndexOf("*/");


 112:                     AddRun(line.Substring(index, commentIndex + 2 - index), Colors.Green);


 113:  


 114:                     // Add the words after the comment


 115:                     output = AddOutput(String.Format("{0}\n", line.Substring(commentIndex + 2)));


 116:                 }


 117:                 catch { }


 118:  


 119:                 continue;


 120:             }


 121:             else


 122:             {


 123:                 // Append to the output data (not the UI)


 124:                 output = AddOutput(output);


 125:                 commentMode = true;


 126:             }


 127:         }


 128:  


 129:         // Append string


 130:         output = String.Format("{0}{1}\n", output, line);


 131:  


 132:         // Close comments (if applicable)


 133:         if (line.Contains("*/") && commentMode)


 134:         {


 135:             AddRun(output, Colors.Green);


 136:             output = "";


 137:             commentMode = false;


 138:         }


 139:     }


 140:  


 141:     // Add remainder output


 142:     output = AddOutput(output);


 143:  


 144:     // Update scroll viewer to its top position


 145:     scrollViewer.ScrollToVerticalOffset(0);


 146: }


 147:  


 148: //================================================


 149: private string ApplyStringHighlighting(string output, string line)


 150: {


 151:     string curLine = line;


 152:     bool openString = false;


 153:     int index = 0;


 154:  


 155:     output = AddOutput(output);


 156:  


 157:     while (curLine.Length > 1 && curLine.Substring(1).IndexOf('\"') != -1)


 158:     {


 159:         index = curLine.IndexOf('\"');


 160:  


 161:         if (openString)


 162:         {


 163:             AddRun(String.Format("\"{0}", curLine.Substring(0, index + 1)), Colors.Red);


 164:             openString = false;


 165:         }


 166:         else


 167:         {


 168:             output = AddOutput(curLine.Substring(0, index));


 169:             openString = true;


 170:         }


 171:  


 172:         curLine = curLine.Substring(index + 1);


 173:     }


 174:  


 175:     return AddOutput(String.Format("{0}\n", curLine));


 176: }


 177: #endregion Syntax Highlighting




8. Conclusion



Silverlight provides a wide variety of features to develop rich interactive applications. The WebClient class extends the application with web server functionality by downloading data from the remote web address.



In this article, I demonstrated how the Remote File Viewer application interacts with the remote script using the WebClient class. The basic framework of the Remote File Viewer will allow you to show files to your end users. Other file formats, such as video and audio, can be easily implemented by extending the remote script and the Render Control. The Syntax Highlighting feature is a fun addition that can be further enhanced to handle a variety of scenarios.



Hopefully this article has helped start you along the path of using Silverlight and the WebClient class to create interactive data-driven web applications.

Read More >>