Tuesday, July 22, 2008

PanoCube in Webdesigner Magazine


For the panocube project, created by Martijn Croezen and me, we teamed up again and wrote a tutorial for Webdesigner Magazine.

It's in store starting today, so get it while it's hot!

Monday, June 23, 2008

Creating a sketch application in Silverlight - Part 2, Saving the sketch on the server

In the second part of this tutorial we're going to pick up where we left off. So we can open the SketchApplicationPart1 solution from the first part of the tutorial (or you can download the source of the second part of this one at the end of this tutorial).
Now we're going to create a way to save the drawing to the server using SharpZipLib in Silverlight, to zip the XML file before sending it to a webservice.

1. Add Click eventhandler for the Save button


and a Helper class for creating the XML.
First we need an eventhandler for the Save button. This is added in the Page_Loaded event of the Page.xaml.cs:
SaveButton.Click += new RoutedEventHandler(SaveButton_Click);


In the Lib Folder, which I created, I add a class "XMLHelpers.cs".
This class only contains a single method to create the XML file. I used LINQ (both to objects and XML) to iterate through the StrokeCollection and create the XML. The class returns an XElement object.
Add the following method "CreateXElementsFromStrokeCollection" to the class:

public static XElement CreateXElementsFromStrokeCollection(StrokeCollection DrawingStrokeCollection)
{
var xmlElements =
new XElement("Strokes", DrawingStrokeCollection
.Select(stroke =>
new XElement("Stroke",
new XElement("Color",
new XAttribute("A", stroke.DrawingAttributes.Color.A),
new XAttribute("R", stroke.DrawingAttributes.Color.R),
new XAttribute("G", stroke.DrawingAttributes.Color.G),
new XAttribute("B", stroke.DrawingAttributes.Color.B)
),
new XElement("Points", stroke.StylusPoints
.Select(point =>
new XElement("Point",
new XAttribute("X", point.X),
new XAttribute("Y", point.Y),
new XAttribute("PressureFactor", point.PressureFactor)
)
)
),
new XElement("Width", stroke.DrawingAttributes.Width),
new XElement("Height", stroke.DrawingAttributes.Height)
)
)
);

return xmlElements;
}


This is an example of the xml the function returns:






13
13







13
13



Looks nice with a minimal amount of effort thanks to LINQ.
By the way, don't forget to add a reference to System.Xml.Linq and add both the using's for System.Linq and System.Xml.Linq.

2. Create a webservice in your WebAppication Project

Name it SaveDrawingService.asmx.
In this tutorial I used an ASMX webservice, but it's almost just as easy to implement a WCF service.
This webservice should contain 2 methods, one for saving a new drawing and one for saving a already saved drawing.
These methods are uncomplicated because all we have to do here is to save the byte[] that is sent from the Silverlight application to the server:

[WebMethod]
public Guid SaveNewDrawing(byte[] zippedFileBytes)
{
Guid drawingID = Guid.NewGuid();
DirectoryInfo drawingFolder = new DirectoryInfo(Server.MapPath("~/App_Data/Drawings/"));
if (!drawingFolder.Exists)
drawingFolder.Create();
FileInfo fi = new FileInfo(Server.MapPath("~/App_Data/Drawings/" + drawingID.ToString() + ".zip"));
if (fi.Exists)
fi.Delete();

FileStream fileStream = new FileStream(Server.MapPath("~/App_Data/Drawings/" + drawingID.ToString() + ".zip"), FileMode.OpenOrCreate);

fileStream.Write(zippedFileBytes, 0, zippedFileBytes.Length);
fileStream.Flush();
fileStream.Close();
return drawingID;
}

[WebMethod]
public bool SaveEditedDrawing(Guid DrawingID, byte[] zippedFileBytes)
{
FileInfo fi = new FileInfo(Server.MapPath("~/App_Data/Drawings/" + DrawingID.ToString() + ".zip"));
if (fi.Exists)
fi.Delete();

FileStream fileStream = new FileStream(Server.MapPath("~/App_Data/Drawings/" + DrawingID.ToString() + ".zip"), FileMode.OpenOrCreate);

fileStream.Write(zippedFileBytes, 0, zippedFileBytes.Length);
fileStream.Flush();
fileStream.Close();
return true;
}


3. Add a Service Reference to the ASMX webservice


Before doing this, always try if the ASMX webservice opens in the browser by right-clicking the asmx file of the webservice in VS2008 and clicking "View in browser".
If it opens correctly, it can be added to the Silverlight project using the namespace SaveDrawingService.

4. Add the SharpZipLib library to your Silverlight project


The version I rebuilt is added in the sourcecode, so you can add the project, or compile a DLL and reference that.

5. Implement the save functionality in Silverlight (zipping and sending)


First, add the System.IO and System.Text namespaces in the using section of the Page.xaml.cs. Next, create a private field "_currentDrawing" of type Guid to save the guid of the drawing (this is returned by the webservice).
Now, the following code needs to be used for the click event:
void SaveButton_Click(object sender, RoutedEventArgs e)
{
SaveDrawingService.SaveDrawingServiceSoapClient wsclient = new SaveDrawingService.SaveDrawingServiceSoapClient();

// A memory stream to write to
using (MemoryStream zippedMemoryStream = new MemoryStream())
{
// A ZIP stream
using (ZipOutputStream zipOutputStream = new ZipOutputStream(zippedMemoryStream))
{
// Highest compression rating
zipOutputStream.SetLevel(9);

byte[] buffer;

// The string to use as file content
using (MemoryStream file = new MemoryStream(Encoding.UTF8.GetBytes(Lib.XMLHelpers.CreateXElementsFromStrokeCollection(inkpresenter.Strokes).ToString())))
{
buffer = new byte[file.Length];
file.Read(buffer, 0, buffer.Length);
}

// Write the data to the ZIP file
ZipEntry entry = new ZipEntry("drawing.xml");
zipOutputStream.PutNextEntry(entry);
zipOutputStream.Write(buffer, 0, buffer.Length);

// Finish the ZIP file
zipOutputStream.Finish();
}
if (_currentDrawing == Guid.Empty)
{
wsclient.SaveNewDrawingCompleted += new EventHandler(wsclient_SaveNewDrawingCompleted);
wsclient.SaveNewDrawingAsync(zippedMemoryStream.ToArray());
}
else
{
wsclient.SaveEditedDrawingCompleted += new EventHandler(wsclient_SaveEditedDrawingCompleted);
wsclient.SaveEditedDrawingAsync(this._currentDrawing, zippedMemoryStream.ToArray());
}
}
}

void wsclient_SaveEditedDrawingCompleted(object sender, SaveDrawingService.SaveEditedDrawingCompletedEventArgs e)
{
//Add functionality to show user drawing is saved
}

void wsclient_SaveNewDrawingCompleted(object sender, SaveDrawingService.SaveNewDrawingCompletedEventArgs e)
{
this._currentDrawing = e.Result;
//Add functionality to show user drawing is saved
}


An explanation of what happens here: First, an instance of the WebserviceSoapClient is created. Next, a new memorystream is created for writing and a ZipOutputStream is created for zipping the "XML string" returned by the XElement which is returned by the CreateXElementsFromStrokeCollection method.
A zipentry "drawing.xml" is created for the actual drawing in the zipfile. The byte array created by the zippedMemoryStream.ToArray() is then sent asynchronously to the server.

I'll call it a day for now. If I get around to it, I'll create a third part to add some more functionality to the sketching application.

Click here for the source

Creating a sketch application in Silverlight - Part 1, Sketching on the InkPresenter

As some of you know, I try to help people out on the Silverlight Forum. It's nice to help people and I have learned a lot by doing that.


One of the members of the forum (RamsZone) had a question about saving strokes that are drawn on the InkPresenter. Since I was already working on a sketch application and had already solved that puzzle, I promised him/her to post an article on my weblog explaining how to do this.
So I started a few hours ago to create a simple sketch application with save functionality. This article is the first part of describing how to create a sketch application. This part is focussed on the sketching itself. The next part will address the save issue. I hope to post the second part today as well.


1. Getting started


First, we need to create the XAML necessary for our sketch application. All we need right now is the InkPresenter and a button which the user can press to save the drawing:



2. Add eventhandlers


We need eventhandlers for the MouseLeftButtonDown, MouseLeftButtonUp and MouseMove events to make sure our sketch application responds to input the way we would expect it to:

LayoutRoot.MouseLeftButtonDown += new MouseButtonEventHandler(LayoutRoot_MouseLeftButtonDown);
LayoutRoot.MouseLeftButtonUp += new MouseButtonEventHandler(LayoutRoot_MouseLeftButtonUp);
LayoutRoot.MouseMove += new MouseEventHandler(LayoutRoot_MouseMove);


3. MouseLeftButtonDown and MouseLeftButtonUp handlers


We need very little code to produce the functionality we need in these handlers.
All we need to do here is set the value of private field to make sure that drawing is starting and stopped. This way we can determine if the visitor is keeping the mousebutton down so he/she is drawing:
void LayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this._drawing = false;
this._drawingContinuousLine = false;
}

void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this._drawing = true;
}


4. MouseMove event


This is were the drawing is really done. Normally you would assume the code to be quite simple, something like this:
StylusPointCollection originalPoints = e.StylusDevice.GetStylusPoints(inkpresenter);
Stroke stroke = new Stroke();
stroke.DrawingAttributes.Color = Colors.Black;
stroke.DrawingAttributes.Width = 13.0;
stroke.DrawingAttributes.Height = 13.0;
stroke.StylusPoints = originalPoints;
inkpresenter.Strokes.Add(stroke);


So, this works.... There are strokes drawn on the inkpresenter. However... (bet you didn't see that one coming ;) ), the strokes are visible as dots. When moving the mouse very slow, it seems to be no problem. When moving very fast, there are gaps between the dots which makes it look like a dotted line instead of a smooth line.
We can fix this by using keeping track of the last detected point and if a continuous line is drawn. We do this by declaring 2 private field, so our private fields look like this:
public partial class Page : UserControl
{
private bool _drawing = false;
private bool _drawingContinuousLine = false;
private StylusPoint _lastDetectedPoint;


The MouseMove will look like this:
void LayoutRoot_MouseMove(object sender, MouseEventArgs e)
{
if (this._drawing)
{
StylusPointCollection originalPoints = e.StylusDevice.GetStylusPoints(inkpresenter);

StylusPointCollection newPoints = new StylusPointCollection();
StylusPointCollection moreNewPoints = new StylusPointCollection();

if (this._drawingContinuousLine)
{
StylusPoint point = new StylusPoint(this._lastDetectedPoint.X + 3, this._lastDetectedPoint.Y + 3);
point.PressureFactor = this._lastDetectedPoint.PressureFactor;
newPoints.Add(point);

StylusPoint morepoint = new StylusPoint(this._lastDetectedPoint.X + 6, this._lastDetectedPoint.Y + 6);
morepoint.PressureFactor = this._lastDetectedPoint.PressureFactor;
moreNewPoints.Add(morepoint);
}

foreach (StylusPoint thisPoint in originalPoints)
{
StylusPoint morepoint = new StylusPoint(thisPoint.X + 6, thisPoint.Y + 6);
morepoint.PressureFactor = thisPoint.PressureFactor;
moreNewPoints.Add(morepoint);

StylusPoint newpoint = new StylusPoint(thisPoint.X + 3, thisPoint.Y + 3);
newpoint.PressureFactor = thisPoint.PressureFactor;
newPoints.Add(newpoint);
}

Stroke stroke = new Stroke();
stroke.DrawingAttributes.Color = Colors.Black;
stroke.DrawingAttributes.Width = 13.0;
stroke.DrawingAttributes.Height = 13.0;
stroke.StylusPoints = newPoints;
inkpresenter.Strokes.Add(stroke);

Stroke morestroke = new Stroke();
morestroke.StylusPoints = moreNewPoints;

this._drawingContinuousLine = true;

this._lastDetectedPoint = originalPoints[originalPoints.Count - 1];
}
}


One thing that isn't handled in this application is resizing of the browser. If you remove the width and height in the UserControl in XAML and of the LayoutRoot Grid, you will be able to draw in the full available space of the browser. However, the application must be (re)loaded when the browserwindow is maximized.

That's it for now. Now you can start practising your sketching skills. In the next part I'll try to explain how to save the drawings on the server.


Click here for the demo

Click here for the source

Friday, June 20, 2008

Enabling browser back button in Silverlight 2 beta 2

Who hasn't encountered the problem of not being able to use the browser buttons when navigating a Flash, Silverlight or AJAX enabled website?
This articles describes how you can prevent this in your Silverlight application.

In this article I will explain how to use a class created by Mark Rideout for his TechEd presentation during this year's TechEd in the US.

1. Implement the NavigationManager


Include the NavigationManager.cs in your Silverlight application.
You can find it in the sourcecode of the project (see below).

Now implement the INavigationState interface on you main Silverlight UserControl(usually the Page UserControl).
public partial class Page : UserControl, INavigationState

#region INavigationState Members

public NavigationPropertyDictionary GetState()
{
throw new NotImplementedException();
}

public void LoadState(NavigationPropertyDictionary properties)
{
throw new NotImplementedException();
}

public string Id
{
get { throw new NotImplementedException(); }
}

#endregion

2. Instantiate the NavigationManager in the constructor


NavigationManager.Instance.Register(this);            
NavigationManager.Instance.LinkingMode = NavigationManager.LinkMode.None;

3. Declare a page indexer field and set it


This field is saved in the state of the NavigationManager so you know which page to show.
private int _pageIndex=-1;

On a state change, set the value of the page indexer field and save the current state:
_pageIndex = 1;
HtmlPage.Document.SetProperty("title", "Page 1");
NavigationManager.Instance.Save();


Now do this for all the state changes you want to have in your browser history:

_pageIndex = 2;
HtmlPage.Document.SetProperty("title", "Page 2");
NavigationManager.Instance.Save();

4. Create code for GetState()


When implementing the interface a number of methods were created.
The GetState() method sets the index of the NavigationPropertyIndex to the pageindex used in this example:

public NavigationPropertyDictionary GetState()
{
NavigationPropertyDictionary props = new NavigationPropertyDictionary(this);
props["index"] = _pageIndex.ToString();
return props;
}

5. Create code for LoadState


The retrieval of the pages from the state need to be implemented in this method.
By using the NavigationPropertyDictionary's index property we are able to retrieve the current index:

public void LoadState(NavigationPropertyDictionary properties)
{
ContentPlaceholder.Children.Clear();

if (properties.Count > 0)
{
UserControl pageToLoad = new UserControl();
switch (int.Parse(properties["index"]))
{
case 1:
HtmlPage.Document.SetProperty("title", "Page 1");
pageToLoad = new Subpage1();
break;
case 2:
HtmlPage.Document.SetProperty("title", "Page 2");
pageToLoad = new Subpage2();
break;
case 0:
HtmlPage.Document.SetProperty("title", "Home");
break;
default: break;
}
ContentPlaceholder.Children.Add(pageToLoad);
}
}

6. Create code for the ID property


This property is used for naming the ParentStateID in the NavigationManager. You'll also see this name in the querystring (depending on the settings).

public string Id
{
get { return "home"; }
}

7. Optional setting; LinkingMode


The property available on the NavigationManager which enables the address bar to be updated as well. So the querystring is set to the value necessary for deeplinking.
To set the LinkingMode property use the following code:
NavigationManager.Instance.LinkingMode = NavigationManager.LinkMode.None;


Click here for the demo

Click here for the source

Sunday, June 8, 2008

How to detect NO mouse movement

In some occasions you might want to show or hide stuff in your Silverlight application when the visitor stopped moving his/her mouse.
For instance, most media players use this method to hide the play/pause/forward/mute controlbar.

You can achieve this by doing following this:

1. Create a DispatcherTimer

private DispatcherTimer _nomousemoveTimer = new DispatcherTimer();


2. Use the constructor of your class to create eventhandlers for events listed below and set the interval of the DispatcherTimer (in this case I set it to 3 seconds):
this.MouseMove += new MouseEventHandler(Page_MouseMove);
this.Loaded += new RoutedEventHandler(Page_Loaded);
_nomousemoveTimer.Tick += new EventHandler(_nomousemoveTimer_Tick);
_nomousemoveTimer.Interval = new TimeSpan(0, 0, 3);


3. In the Page Loaded, start the timer:
_nomousemoveTimer.Start();


4. Now, in the Page_MouseMove event, stop the timer and start it immediately after stopping it:
_nomousemoveTimer.Stop();
_nomousemoveTimer.Start();


5. Finally, in the Tick event of the timer, execute your code:
private void _nomousemoveTimer_Tick(object sender, EventArgs e)
{
//do your stuff
}


Click here to view the demo

Click here to download the source

Tuesday, May 20, 2008

Manually controlling storyboard animation

For an animation I did for a customer I wanted it to start the storyboard when the mouse enters and to play backwards, until the state it started in, when the mouse leaves the object.

At this time, in Silverlight 2 beta 1, there is no method I found to play a storyboard backwards from a custom position in the timeline. There is an option to AutoReverse the storyboard but this only occurs when the Storyboard is finished.

So I needed to create a custom method to play a storyboard backwards. The only way we found to do this (thanks again Martijn ;) ) is to manually play the stoyboard. Forward as well as Reverse. By using this method you'll gain total control over the animation.
You can start wherever you want, reverse playback, play faster or slower, etc.

First of all I created a Gameloop (neverending loop) which comes down to a Storyboard which, in the Completed event, restarts the Storyboard animation so you get a neverending loop. There are a lot of posts from fellow Silverlight developers who describe this very well so I won't go any deeper into this. In the sourcecode download is the gameloop.cs file which I always use.

Setting variables and eventhandlers

Second, we need a couple of variables for remembering the state of the animation (is it in Reverse mode or in Forward mode, what's the progress, etc):

 private bool isMouseOver;
private enum PlayDirectionEnum { Forward, Reverse }
private PlayDirectionEnum playDirection = PlayDirectionEnum.Forward;
private TimeSpan progressTime = new TimeSpan(0);
private TimeSpan totalPlayTime;
private int frameRate = 60;

Next step is to create all of the eventhandlers in the constructor of your application for the mouseenter, mouseleave, page_loaded:


        public Page()
{
InitializeComponent();

this.MouseEnter += new MouseEventHandler(Page_MouseEnter);
this.MouseLeave += new MouseEventHandler(Page_MouseLeave);
this.Loaded += new RoutedEventHandler(Page_Loaded);

gameloop.GameloopDuration = new Duration(new TimeSpan(0, 0, 0, 0, 1000 / frameRate));
gameloop.Update += new Amercom.Silverlight.Game.GameLoop.UpdateDelegate(gameloop_Update);
gameloop.Attach(this);
}

MouseEnter and MouseLeave


Now the real coding begins ;)

In the MouseEnter event we need to do a couple of things to start the animation:


            
if (gameloop.IsAttached && progressTime == new TimeSpan(0))
{
gameloop.Start();
MoveBall.Begin();
MoveBall.Pause();
}
playDirection = PlayDirectionEnum.Forward;
isMouseOver = true;

Because we chose to use a gameloop and maintain the state of the animation using variables, the actions in the MouseLeave are minimal:


            isMouseOver = false;
playDirection = PlayDirectionEnum.Reverse;

Animating


Finally, we need to create the gameloop_Update method to play the animation:


        
void gameloop_Update(TimeSpan ElapsedTime)
{
if (isMouseOver || progressTime > new TimeSpan(0))
{
switch (playDirection)
{
case PlayDirectionEnum.Forward:
progressTime = progressTime.Add(new TimeSpan(0, 0, 0, 0, 1000 / frameRate));
break;
case PlayDirectionEnum.Reverse:
progressTime = progressTime.Subtract(new TimeSpan(0, 0, 0, 0, 1000 / frameRate));
if (progressTime == new TimeSpan(0))
resetAnimation();
break;
}
}

if (progressTime >= totalPlayTime)
{
progressTime = totalPlayTime;
}
MoveBall.Seek(progressTime);
}


As you can see is that the main 2 things that are done in this method are:


  1. 1. Set the progressTime (in the switch)

  2. 2. Tell the storyboard to jump to the position (progressTime) using the Seek method


Some of the other things that happen in this method is that we need to reset the animation back to default state (back to the beginning, Forward play instead of Reverse, etc.) when it's reached the beginning of the Storyboard (when playing backwards).

Furthermore I "pause" the animation when it reaches the end. You could also change this behaviour so it autoreverses when it reaches the end of the Storyboard.


Hope this is usefull!


Click here to view the demo
Click here to download the source

Thursday, April 24, 2008

Controlling video with dragging

To control video with dragging might sound a bit strange and useless but keep on reading and you'll see the the possibilities this creates.

For a project I'm working on, I wanted to create a rotating 3D image which the user van control with his mouse using dragging.
This kind of displaying is often used for displaying objects like cars:
http://www.autotropolis.com/car.review/2008/Ford/Mustang/exterior.htm
or purses?
http://www.cheathamlane.net/index.php?section=projects
or other products:
http://www.pioneer.fr/nl/black/products/kuro2007/360_PDP_508XD.html

Getting started


First I created a UserControl and added 2 properties:

  1. "AutoPlay" which plays the video once when loaded

  2. "MovieSource" which contains the Uri of the videofile

  3. "StretchMovie" which contains the Uri of the videofile



To be on the safe side I created a public method "Release" to stop the dragmode. If by some reason the mouse stays in dragmode it can be stopped by calling the Release method:




Custom MouseCursor


For a better drag experience a "custom" mousecursor is added when hovering over the control and when dragging.


To achieve this, add an image ("_cursor" in the code snippet below) to the page which you can position when the mousemove event is triggered. With this.Cursor = Cursors.None you can make sure the default cursor is no longer visible.





Controlling the video


In the eventhandler of the mousemove event, first check if the user is dragging:



If so, set the movieposition to the correct value:



The call to the UpdateVideoPlayer method makes sure the movie gets rendered on the right frame:


Click here to view the demo

Click here to download the source

One issue I experience (could be my laptop) is that it's a shocky. It's not rotating fluently. In Mediaplayer Classic it works fine, so I think it's a rendering issue with SL2. If someone has any ideas about this, please let me know!

That's it for now. I'm leaving to Spain tomorrow for a two week holiday, so no more for at least two weeks! :)