Wednesday, January 28, 2009

Creating a ColorFillToy in Silverlight - Part 2

Click here to view a demo of the application after part 2 of the tutorial/
Click here to download the source

In part 1 of this tutorial we focused on the creation of the XAML for the ColorFillToy application. In this part, we will create the code to set the cursor and to detect the FillArea that we need to fill.

Terence Tsang from shinedraw.com did the preparation for us, so we're going to reuse some of his code from his flash-vs-silverlight post on the ColorFillToy.

I'm also going to use the ColorPicker from the SilverlightContrib project. So we'll need a reference in our project for that.
As a little extra I'm going to create a ColorPickerButton control which triggers the ColorPicker and shows the selected color. Page Brooks was pretty excited about this and we're probably going to include it in the SilverlightContrib project as an addition to the ColorPicker control. I'll get around to this in the next and final part of this serie.

Finalizing the XAML

First of all we're going to finish up our XAML in Expression Blend by creating a ColorPickerButtonControl and creating an image which we'll use as our cursor. There is no bucket cursor available in Silverlight so we'll have to create one ourselves.

Using Expression Blend we can create a button looking a lot like the one Terence used in his Flash example.

First create the button on the LayoutRoot of the ColorFillToy. It's just a bunch of rectangles and paths making it look like this:


The most important thing of this control is the Rectangle (second object in our Canvas) named ButtonBackground. ButtonBackground is used to "fill" the button with the currently selected color of the ColorPicker.

Once we're pleased with the looks of the ColorPickerButtonControl we can right-click the container Canvas and select "Make Control...".
Now Blend will do most of the work for us. Create the control, create the reference and and instance of the control where we had our container Canvas.

The new Control will open in a new window in Blend. Close it and return to the Page.xaml.

Now lets not forget to add an image to the LayoutRoot canvas of 16x16 and select the paintbucket image. I placed the image right outside of the canvas so it won't be visible if the application loads.



This rounds up the design part.
Right-click the Page.xaml in Expression Blend and select "Edit in Visual Studio" to continue in VS2008.
Like I said earlier, we're going to use most of the code Terence created for us. You can download his code here: http://www.shinedraw.com/?dl_id=113

Making it work

Most of the important stuff is done in the RootVisual_MouseLeftButtonDown handler:

void RootVisual_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// generate random color
Random r = new Random();
byte red = (byte)(255 * r.NextDouble());
byte green = (byte)(255 * r.NextDouble());
byte blue = (byte)(255 * r.NextDouble());
Color color = Color.FromArgb(255, red, green, blue);

// get the ui
Border border = sender as Border;
Canvas canvas = (Canvas)border.Child;

// create ellipse
Ellipse ellipse = new Ellipse() { Fill = new SolidColorBrush(color), Width = DEFAULT_SIZE, Height = DEFAULT_SIZE };
ellipse.SetValue(Canvas.LeftProperty, e.GetPosition(canvas).X);
ellipse.SetValue(Canvas.TopProperty, e.GetPosition(canvas).Y);

// add to stage
canvas.Children.Add(ellipse);
ScaleTransform scale = new ScaleTransform() { CenterX = DEFAULT_SIZE / 2, CenterY = DEFAULT_SIZE / 2 };
ellipse.RenderTransform = scale;

// expand the ellipse
DoubleAnimation doubleAnimatoin1 = new DoubleAnimation() { From = 1, To = SCALE_MAX };
doubleAnimatoin1.Duration = new Duration(new TimeSpan(0, 0, 3));
Storyboard.SetTarget(doubleAnimatoin1, scale);
Storyboard.SetTargetProperty(doubleAnimatoin1, new PropertyPath("(ScaleX)"));

DoubleAnimation doubleAnimatoin2 = new DoubleAnimation() { From = 1, To = SCALE_MAX };
doubleAnimatoin2.Duration = new Duration(new TimeSpan(0, 0, 3));
Storyboard.SetTarget(doubleAnimatoin2, scale);
Storyboard.SetTargetProperty(doubleAnimatoin2, new PropertyPath("(ScaleY)"));

// start the story board
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(doubleAnimatoin1);
storyboard.Children.Add(doubleAnimatoin2);
storyboard.Begin();
}


All that is actually done in this code is the following:
- Generate a random color
- Get the border object that is clicked on
- Create an Ellipse and add it to the Canvas located inside the Border
- Use a Storyboard to expand the Ellipse

So most of this stuff is reusable. We're going to make the following changes to this code to make it work in our application:
- Instead of Border objects we're going to use a method to find the fillarea that was clicked on.
- The cursor paintbucket needs to be added when hovering over ColorFillToy.
- We need to finish up our ColorPickerButtonControl to expand the ColorPickerControl when it's clicked and set the background of the button to the selected color. This selectedcolor is used to fill the Ellipse instead of the randomized color

Finding the FillArea


To locate the parent FillArea, we have are going to use the MouseLeftButtonDown event of the FillArea Canvas or of the objects on the FillArea Canvas.
We can use a recursive method to go through the collection of user controls from top to bottom. The first FillArea we encounter will be the parent FillArea of the clicked object.
I created an extension method for the FrameWorkElement controls:

public static Panel FindFillArea(this FrameworkElement originalSource)
{
if (originalSource == null) throw new ArgumentNullException("originalSource");
if (originalSource.Name.IndexOf("fillarea") == 0 || originalSource == Application.Current.RootVisual)
return (Panel)originalSource;

FrameworkElement parent = (FrameworkElement)originalSource.Parent;
if (parent != null)
{
if (parent.Name.IndexOf("fillarea") == 0 || parent == Application.Current.RootVisual)
{
return (Panel)parent;
}
return FindFillArea(parent);
}

return (Panel)Application.Current.RootVisual;
}


We can use this extensionmethod in the MouseLeftButtonDownHandler.
Instead of the line:
Canvas canvas = (Canvas)border.Child;

We can use:
Panel panel = originalSource.FindFillArea();


Adding the paintbucket cursor


The add the paintbucket cursor add the following lines to the Loaded event of the control:

LayoutRoot.MouseMove += ColorFillToy_MouseMove;
cursor.IsHitTestVisible = false;
cursor.Visibility = Visibility.Collapsed;

And add the following MouseMove handler:

private void ColorFillToy_MouseMove(object sender, MouseEventArgs e)
{
cursor.Visibility = Visibility.Visible;
this.Cursor = Cursors.None;
Canvas.SetLeft(cursor, e.GetPosition(null).X - 10);
Canvas.SetTop(cursor, e.GetPosition(null).Y - 10);
}


By doing this the image will replace the normal cursor and it will appear to be a real cursor.

In this part we finished up the XAML, created an extension method to find the fillarea we need and filled the area with a random color.
In the next and final part we'll add the ColorPickerButton and the ColorPicker of the SilverlightContrib project and make sure we can pick any color we like.

Click here to view a demo of the application after part 2 of the tutorial/

Click here to download the source

Cheers!

Rob.

Sunday, January 25, 2009

Creating a ColorFillToy in Silverlight - Part 1

Hi,

It's been a while since my last blogpost. It's been a bit of a hectic period lately.

But not blogging doesn't mean not doing anything :) I've been working on some stuff which I'll blog about in the coming period. Like a deepzoom application for my aunt which enables her to show the bronze sculpture work she does in detail.

But for now, I want to tell you something about the ColorFillToy I (re)created. Thanks Terence for the challenge :) (http://www.shinedraw.com/animation-effect/flash-vs-silverlight-colour-fill-toy/).

Terence of Shinedraw.com created a colorfilltoy in Flash and mentioned he didn't have enough knowledge on Expression Design to create a similar application in Silverlight.
I can say the same, I haven't worked with Design much, but I tried to recreate the application using Blend.

To achieve the shape I wanted, I created three circles. One large one and 2 smaller ones which I placed inside the larger one:


Now we need to remove the left half of the top circle and the right half of the bottom one. This is only possible if we convert the ellipse to a path object. You can achieve this by right clicking the object, choose the Path section and the Convert to Path option or by selecting the circles and choose the same option from the Object menu:


Now, by using the Direct selection tool (Shortcut key A in Blend) you can select parts of the ellipse which is now a path:


As said before, now delete the left part of the top circle and the right part of the bottom circle (or vice versa).
The shape you're getting now is exactly what you want.

Now, to create the fill ability I used wanted to use a clipping path so I need both of the shapes separately (the 2 halves of the yin-yang sign).

Next I converted the large circle to a path as well and copied it, and all of the other path's I created so far. We need to create 2 separate shapes, so the large circles need to be split up in 2 half circles. One left and one right side. You know the drill!

Visually there are no differences by making these changes, but you'll see next how these are really necessary to create the effect we need.
To glue the 3 shapes together (1 large half circle, and 2 smaller ones) we can use a compound path. Select the three paths, right-click and select Path, Make Compound Path:


Do the same with the other 3 shapes and we have our 2 halves of the yin-yang sign.
To make the filling effect possible we need to do 2 things:
1. Create a Canvas for the fill to be dropped on, make sure the Canvas has a fill, with the Alpha value of the fill set to 0% (not the opacity, but the alpha of the fill!)
2. Create a clipping path on the canvas to clip the ellipse if it gets any larger then the shape we had in mind.

To add the shape to a Canvas can easily be accomplished by right clicking the shape and selecting Group into, Canvas. Now a Canvas is created while the shape keeps its position and shape.
Creating a clipping path for this complex shape was a bit more difficult to achieve. At least the first time I tried :).

First, create a copy of the shape (the combined path). Now set the StrokeThickess to 0 in the Appearance section of the shape's Properties.
Next, by using the Direct selection tool make sure the shape surrounds the original shape:


Finally the path is ready to be used as a clipping path. Right-click the path and select Path, Make Clipping Path. In the pop-up that appears select the correct Canvas (Yin or Yang):


Note: When you notice a thin white line in the border you can adjust the path using the Direct selection tool. I cannot seem to get it aligned 100% accurately, even at 6400% zoom:


After creating these complex shapes, it's a piece of cake to create the small circles and the rectangle fill areas.
The end result should look something like this:


When creating these fillareas make sure they are prefixed with "fillarea". We are going to use this later on.

In the next part of this tutorial we'll do the coding. We can reuse a lot of the code Terence already created for us in his simple Silverlight version.


Hope this helps!

Friday, January 2, 2009

MVP: Expression

This morning when I read my e-mail I noticed I had a message in my junk e-mail.
When I opened it, it said:



Dear Rob Houweling,

Congratulations! We are pleased to present you with the 2009 Microsoft® MVP Award! This award is given to exceptional technical community leaders who actively share their high quality, real world expertise with others.


As you can imagine I am very proud of this and hope to prove that I'm worthy :)

Sunday, November 2, 2008

Another one on repacking XAPs to reduce filesize

I've seen quite some articles on repacking your XAP file to reduce the filesize. You can do this manually by using archiving utilities like 7-ZIP, WINRar or the Windows built in zip support.

Delay did a great article on this which inspired me to do the following:

I created a very small application called ReXapper.exe which can do exactly the same as Delay describes but there is no external archiving utility necessary. It uses the SharpZipLib to re-zip the file. So there's just a tiny (124 kb) command-line executable.

The executable requires .NET framework 2.0. It has only 1 parameter: -xap for the xap file.

To automatically re-xap your project after building follow these steps:
1. Copy the executable to a folder. I used d:\tools\rexapper\
2. Open you project in Visual Studio. Go to the properties of the project (using the Project menu) and locate the "Post-build event command line" box.
3. Paste the following in the box:
D:\tools\ReXapper\ReXapper.exe -xap "$(TargetDir)$(TargetName).xap"




Now build your project. In the Output window you will see the ReXapper.exe is called and it will output the following three things:
- filesize before ReXapping
- filesize after ReXapping
- time spent on ReXapping



As you will see it is pretty fast so don't worry about it slowing you down.

If you want to use ReXapper.exe, you can download it here:

ReXapper

If you want the source to make some changes or additions, drop me a message.

Hope this helps.

Monday, October 13, 2008

Clipboard support in Silverlight 2

* UPDATE *
The demo now works with Silverlight 2 RTW and the ClipboardHelper has been added to the source of the SilverlightContrib project. It'll be in the next release.

By default, there is no clipboard support in Silverlight 2. Page Brooks, who is the project coordinator of the open source project SilverlightContrib I'm working on with a bunch of other developers (I feel really honered to work with them...), wanted to add this to the SilverlightContrib project as well so I decided to start off with a basic implementation.

You can create (limited) clipboard support in Silverlight by using Javascript and HTML DOM to read from, add to and clear the clipboard. Note that this will only work in Internet Explorer.

I created a class in which the following methods do the most important work:


public void SetData(string Value)
{
_window.Eval(string.Format("window.clipboardData.setData('text','{0}')", Value));
}

public string GetData()
{
this._currentValue = (string)_window.Eval("window.clipboardData.getData('text')");
return _currentValue;
}

public void ClearData()
{
_window.Eval("window.clipboardData.clearData()");
}


In the constructor of the class I create the possibility to add multiple UIElements as a parameter:


public ClipboardHelper(params UIElement[] UIElementsToCatchEventsOf)
{
if (UIElementsToCatchEventsOf != null)
{
foreach (UIElement element in UIElementsToCatchEventsOf)
{
element.KeyDown += new KeyEventHandler(UIElement_KeyDown);
element.KeyUp += new KeyEventHandler(UIElement_KeyUp);
}
}
}


By subscribing to the KeyUp and KeyDown event of the specific UIElement you can also add paste support to the elements. For example the Button:

private void UIElement_KeyUp(object sender, KeyEventArgs e)
{
if(e.Key == Key.Ctrl)
_ctrlPressed = false;
if (e.Key == Key.V && _ctrlPressed)
{
this.GetData();
if (sender.GetType() == typeof(Button))
{
Button buttonSender = (Button)sender;
buttonSender.Content = this.GetData();
}
}
}


You could also create your own implementation for ListBox and other elements. Be aware though that this will only work with items that can gain focus.


Watch the demo here

Download the project here

Saturday, September 20, 2008

Sneak preview

Don't tell Jeff, but here's a preview of another one of the projects in Foundation Silverlight 2 Animation.

I made a little modification to this compared to the one that you'll create from the instructions in the book, assuming you're going to get it of course :).
To make the dragon move more "natural" I used a DispatcherTimer with a random duration to make it change direction at a random time.



Click here to view the WMV file


Visit designwithsilverlight.com for more previews of the projects in the book.

Thursday, August 21, 2008

Authentication in Silverlight using ASP.NET FormsAuthentication

On the Silverlight forum I've seen a lot of questions regarding authentication and security.
I figured I'd write something down from my perspective as an ASP.NET developer. This isn't the only way to do it and probably not the best, but it does what I need it to do and it might get you started in the right direction.

IMPORTANT NOTE: This article is built with the assumption that all files requested are handled by ASP.NET and not by IIS (so let .net dll handle the wildcard extension *).

Click here to download the source

1. How and why

ASP.NET provides excellent methods for securing web applications using WindowsAuthentication or FormsAuthentication. And creating your own MembershipProvider allows you to authenticate using any type of database or other data storage.
For this example I chose to use FormsAuthentication because this gives you lots of freedom and flexibility to expand or adjust in a later stage of the build of your project.

2. Securing the ASP.NET application

In this example we will secure the application and will test it using 2 types of request:
- a file which is in a secured folder on the server
- a WCF webservice secured method

I used a standard Silverlight Application Project template in Visual Studio with a Web Application project, not a Website. This will probably also work with the Website, but to prevent problems better use the WebApplication type.

2.1 Changes to the Web.Config

First of all we're going to configure the ASP.NET Web Application to use FormsAuthentication to secure a folder.
Create a folder named "Secure" in the root of the ASP.NET Web Application (website from here on).
Open the Web.Config and locate the following section:

Change it to this:








Second thing to do in the Web.Config is to add the following section just after the tag:








That's it. We're done securing the folder Secure in our website. To verify this, create an XML file in the Secure folder with the following content:



You can only see this if you're logged in.



Now run your website pressing F5 and try to open the XML file. If everything went as planned you should see the application redirected you to a login.aspx which doesn't exist. We won't create one in this tutorial because we'll be using Silverlight to do the logging in and not ASP.NET.

Next thing we're going to do is to create the WCF webservice that we'll use for logging in the website.

2.2 Create a WCF Authentication Service


We'll be using a standard Silverlight WCF Service for this. I always like to keep my application tidy so I created a folder in the root of my website named "WebServices".
In that folder, create a "Silverlight-enabled WCF Service" which you can find in the dialog that appears when you click on Add => New Item and when you select the Silverlight Category. Name the service "AuthenticationService".

In the created WCF service there is a method already created for you:
[OperationContract]
public void DoWork()
{
// Add your operation implementation here
return;
}

Replace this method with the following code:
[OperationContract]
public bool Authenticate(string Username, string Password)
{
if (FormsAuthentication.Authenticate(Username, Password))
{
FormsAuthentication.SetAuthCookie(Username, false);
return true;
}
return false;
}


As you can see is that all we did was create a method with a username and password parameter which uses the two default FormsAuthentication methods, Authenticate and SetAuthCookie, to enable security. The reason we need the SetAuthCookie is because it fills the
HttpContext.Current.User
object which we can read out later on to verify if a user is logged in and which user it is.

That covers the authentication part for now. Next, we'll create the Silverlight Application with the login form.

2.3 Creating the login form in Silverlight


Open the Page.xaml in Expression Blend. In the LayoutRoot, add three Grids:
- LoginForm
- ResultForm
- TestForm

Make sure the ResultForm is positioned at about the same position as the LoginForm and the the visibility of the ResultForm is set to Collapsed.

On the LoginForm, add the textblocks (labels), textboxes and a button needed to create login form so that it looks like this:



On the ResultForm, add a textblox with a default text of:
Login succeeded. You can continue with the application

On the TestForm, add 2 buttons and a large Textblock:
- ButtonGetMyBalance
- ButtonGetXmlFile
- TextBlockResult

When you're done you should have something similar to this:


Now we're done with preparing the interface and we'll proceed to the coding of the application. Open the Page.xaml.cs with Visual Studio 2008.

2.4 Calling the AuthenticationService


Before calling the service, we need to add the reference to the service to the Silverlight Application.
Right-click on the Service Reference folder in the Silverlight Application project, and select "Add Service Reference".
Next, click on the "Discover" button that is in the pop-up window that will appear.
After the discovery is completed, you'll see the AuthenticationService appear in the box "Services".
Select it and change the "Namespace" to: "AuthenticationService". Now press OK and the service will be added to you project.
Now, let's continue in the Page.xaml.cs.

In the Page() constructor, add the following lines of code after the InitializeComponent() method:
ButtonLogin.Click += new RoutedEventHandler(ButtonLogin_Click);
ButtonGetXmlFile.Click += new RoutedEventHandler(ButtonGetXmlFile_Click);


Next, add the code for getting the Xml file using a WebClient:
void ButtonGetXmlFile_Click(object sender, RoutedEventArgs e)
{
WebClient webclient = new WebClient();
webclient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webclient_DownloadStringCompleted);
webclient.DownloadStringAsync(new Uri("../Secure/TestFile.xml", UriKind.Relative));
}

void webclient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null && !string.IsNullOrEmpty(e.Result))
{
TextBlockResult.Text = e.Result;
}
else
{
if (e.Error != null)
TextBlockResult.Text = e.Error.Message;
else
TextBlockResult.Text = "Please login first";
}
}


Now create the event handler for the ButtonLogin.Click event:
private void ButtonLogin_Click(object sender, RoutedEventArgs e)
{
AuthenticationService.AuthenticationServiceClient authService = new AuthenticationService.AuthenticationServiceClient();
authService.AuthenticateCompleted += new EventHandler(authService_AuthenticateCompleted);
authService.AuthenticateAsync(TextBoxUsername.Text, TextBoxPassword.Text);
}


As you can see in the previous code, we need to create an eventhandler for the AuthenticateCompleted event.

void authService_AuthenticateCompleted(object sender, SilverlightAuthentication.AuthenticationService.AuthenticateCompletedEventArgs e)
{
if (e.Result)
{
LoginForm.Visibility = Visibility.Collapsed;
ResultForm.Visibility = Visibility.Visible;
TextBlockResult.Text = "";
}
else
{
TextBlockResult.Text = "Unable to log you in. Invalid username or password.";
}
}


In this piece of code we hide the LoginForm once the authentication is succeeded. If authentication fails, an errormessage is displayed in the TextBlockResult control.

When you press F5 to run the application, you should be able to login using the credentials we added in the Web.Config:
username: myUser
password: secret

If you use an incorrect username, the errormessage should appear in the TextBlockResult.

You can also try to use the "Get Xml File" button which should only work if you're logged in.


2.5 Create a secure WCF Webservice


For the retrieval of your balance (don't worry, it's not your real balance) we are going to create another Silverlight-enabled WCF service. Let's call it "BalanceService". Next, we'll create the method "GetMyBalance" in there which will return a decimal and a boolean value.
Again, replace the default generated method with the following code:
[OperationContract]
public bool GetMyBalance(out decimal Balance)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
Balance = 1000.00M;

return true;
}
else
{
Balance = decimal.MinValue;
return false;
}
}


As you can see, we use the User.Identity object, which was filled using the SetAuthCookie method we called earlier on in this tutorial, to check if the user calling the method is authenticated. If the user is authenticated, the webservice will return true and will fill the Balance output paramater with the correct value, else it will return false and the decimal.MinValue in the Balance parameter.

Next well add the functionality to call the WCF Service in the Silverlight application.

2.6 Add functionality in Silverlight for the BalanceService


First we need to add the Service Reference to the project again. Since we've done this before, I'll just sum it up:
- Right-click the Service References folder
- Click on Add Service Reference
- Click on Discover
- Select the service "BalanceService"
- Change the namespace to "BalanceService"
- Click OK

Once we're done with that it's time to do some coding.

Add the following line to the Page() constructor, just before the closing curly brace:
ButtonGetMyBalance.Click += new RoutedEventHandler(ButtonGetMyBalance_Click);


Next, create the event handler for the Click event:
void ButtonGetMyBalance_Click(object sender, RoutedEventArgs e)
{
BalanceService.BalanceServiceClient balanceService = new SilverlightAuthentication.BalanceService.BalanceServiceClient();
balanceService.GetMyBalanceCompleted += new EventHandler(balanceService_GetMyBalanceCompleted);
balanceService.GetMyBalanceAsync();
}


What we see here is that it is not necessary to add the out parameter of our method to the webservice call. In the next step we'll see what happens with that.

All we need to do now is to create the handler for the GetMyBalanceCompleted event:
private void balanceService_GetMyBalanceCompleted(object sender, SilverlightAuthentication.BalanceService.GetMyBalanceCompletedEventArgs e)
{
if (e.Result == true)
{
TextBlockResult.Text = string.Format("Your balance: {0}", e.Balance.ToString());
}
else
{
TextBlockResult.Text = "Please login first";
}
}


What we see now is that our out parameter is included in the GetMyBalanceCompletedEventArgs. This way we can use multiple parameters without creating a class for this.

If you press F5 now, and try the button without logging in, you'll see the "Please login first" text in the resultbox.
After you're logged in and press the button the Balance will appear in the resultbox.



That's it for now. I hope this helps to get you started using this type of authentication.

Click here to download the source