Rick Barraza
Silverlight UX Development
Sunday April 20, 2008
Bitmap Images and Particle Engines with Silverlight
Needless to say, its been one hectic year so far! Between Project Maestro at SXSW, Teaching at MIX08, Winning the PhizzPop nationals, Artists in Residency and getting ready for the Web 2.0 Expo, this is the first chance I've had to push up a new post! But this is a great time for Silverlight development and I'll be keeping you updated with some amazing things just over the horizon!
First things first, we're going to talk about Streaming data and particles today. Here's the project the code snippet comes from:
Instructions: Move the slider to increase the particle buzz. Upload any 24 bit .BMP file. All binary streams are processed client side, there is no server manipulation required.
Here is the main loop that takes the BMP file from a client side dialog box, parses it on the client side to extract the important informatio, and populate an array of custom color objects that will be used to draw the tiles:
[...]
using System.Windows.Media.Imaging;
using System.IO;
using System.Windows.Browser;
using System.Windows.Resources;
drawSqaure[] tiles;
double totalCol = 0;
double totalRow = 0;
double imageWidth = 0;
double imageHeight = 0;
[...]
private void OpenBMP()
{
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog() == DialogResult.OK)
{
Stream stream = ofd.SelectedFile.OpenRead();
output = ofd.SelectedFile.Name;
// Find out how big the file is. Create a Byte array to hold it.
// Read the values you need from the Bitmap data stream...
int nBytes = Convert.ToInt32(stream.Length);
byte[] ByteArray = new byte[nBytes];
int nBytesRead = stream.Read(ByteArray, 0, nBytes);
stream.Close();
// BMP Header files start with B and M in Ascii
if ((ByteArray[0] == 66) && (ByteArray[1] == 77))
{
output += " [24bit BMP File] ";
}
else
{
output = " ERROR: File is not a BMP";
txtOut.Text = output;
return;
}
// BMP files will pad the stream with leading zeros if the
// image width in pixels is not divisble by 4
imageWidth = (double)(convert4Bytes(ByteArray[18], ByteArray[19],
ByteArray[20], ByteArray[21]));
double rowPad = imageWidth % 4;
imageHeight = (double)(convert4Bytes(ByteArray[22], ByteArray[23],
ByteArray[24], ByteArray[25]));
int dataOffset = convert4Bytes(ByteArray[10], ByteArray[11],
ByteArray[12], ByteArray[13]);
int bitDepth = convert2Bytes(ByteArray[28], ByteArray[29]);
int imageDataSize = convert4Bytes(ByteArray[34], ByteArray[35],
ByteArray[36], ByteArray[37]);
if ( (imageWidth > 600) || (imageHeight > 600) ) {
output = "ERROR: Image too large. Please select another file.";
txtOut.Text = output;
return;
}
if (bitDepth != 24)
{
output += "ERROR: Color depth must be set at 24 bits.";
txtOut.Text = output;
return;
}
// I will be calculating out the color average of 10x10 pixel
// squares from the image and storing them in a drawSqaure object.
// A 24bit BMP uses 3 bytes per pixel for the color. This code
// can easily be expanded to support other bit depths as well.
int resolution = 10;
int bytesPerPixel = 3;
totalCol = Math.Floor( imageWidth / resolution);
totalRow = Math.Floor( imageHeight / resolution);
int totalTiles = (int)(totalCol * totalRow);
tiles = new drawSqaure[totalTiles];
double rowOffset = 0;
double pointer = 0;
int index = 0;
// Here is the important part. I have now stored the Bitmap
// image's stream in my byte array and I have created an array
// of 'drawSquare' objects to be populated with the average
// of the 10 x 10 pixel square color values I will next calculate.
for (int r = 0; r < totalRow; r++)
{
// BMP files send their pixel data from the bottom left corner
// of the image UP. Also, while their pixel data is traditional
// 0-255 per RGB color, the ORDER is reversed, so it comes in
// BGR, not RGB.
rowOffset = 54 + (r*(resolution *
((imageWidth * bytesPerPixel) + rowPad) ));
for (int c = 0; c < totalCol; c++)
{
pointer = rowOffset + (c * resolution * bytesPerPixel);
drawSqaure d = new drawSqaure();
int b = 0;
int g = 0;
int red = 0;
for (int i = 0; i < resolution; i++)
{
for (int y = 0; y < resolution; y++)
{
int calc = Convert.ToInt32(pointer +
(i*((imageWidth*bytesPerPixel)+rowPad)) + (y*3));
b += (int)ByteArray[calc];
g += (int)ByteArray[calc+1];
red += (int )ByteArray[calc + 2];
}
}
// Since the above nested for loops will sample a 10 x 10 pixel
// square, I need to divide the sum of each Color by 100 to
// get its color average per channel.
d.red = red / 100;
d.blue = b / 100;
d.green = g / 100;
tiles[index] = d;
index++;
}
}
txtOut.Text = output;
ByteArray = null;
buildUI();
}
}
The drawSquare object simply holds the averaged color values per canvas tile we will add to the screen:
public class drawSqaure
{
public int red { get; set; }
public int blue { get; set; }
public int green { get; set; }
}
And here is what the buildUI() loop looks like:
private void buildUI()
{
int index = 0;
int nodeSize = 2;
nodes.Children.Clear();
for (int r = 0; r < totalRow; r++)
{
for (int c = 0; c < totalCol; c++)
{
node n = new node();
n.SetValue(NameProperty, "n_" + c.ToString() + "_" + r.ToString());
n.X = origin.X - (totalCol*(nodeSize*.5)) + (c * nodeSize);
n.Y = origin.Y - 30 + (totalRow*(nodeSize*.5)) - (r * nodeSize);
n.anchor = new Point(n.X, n.Y);
n.vX = 0;
n.vY = 0;
drawSqaure d = tiles[index];
index++;
n.setColor(Color.FromArgb(0xFF, Convert.ToByte(d.red),
Convert.ToByte(d.green), Convert.ToByte(d.blue)));
nodes.Children.Add(n);
}
}
index = 0;
tiles = null;
}
private Int32 convert4Bytes(byte a, byte b, byte c, byte d)
{ return ( (d<<24) | (c<<16) | (b<<8) | (a)); }
private Int32 convert2Bytes(byte a, byte b)
{ return ((b << 8) | (a)); }
The node user control is empty except for a 2x2 rectangle that we can change the fill with through its setColor() function as well as the X and Y public properties that I've gone over many times before.
Finally, the animation engine looks something like this:
void sb_Completed(object sender, EventArgs e)
{
foreach (node n in nodes.Children)
{
n.vX += ((double)r.Next(100) - 50.0)/ 5.0;
n.vY += ((double)r.Next(100) - 50.0) / 5.0;
n.vX *= buzz;
n.vY *= buzz;
n.X += n.vX;
n.Y += n.vY;
n.X += (n.anchor.X - n.X) * .07;
n.Y += (n.anchor.Y - n.Y) * .07;
}
sb.Begin();
}
Of course, I added a couple more bells and whistles in mine, such as controlling the value of the buzz variables above by the position of the slider, but I'll leave you to customize your project anyway you want. The above outlines the essentials required to get it up and running.
Hope you like it. Drop me a note if you do anything cool with this...
Friday January 11, 2008
Connecting to the Wii Control with WPF
Using Brian Peek's Wiimote Library, we'll show you how to get quickly up and running with a Wii Control, WPF and some Infrared emitters to create a multi-point tracking system like we used in Project Maestro.
Read more...
Thursday December 13, 2007
DD#03: Custom Animation Loops
Sorry for the delay in posts, we were offline competing in the Microsoft PhizzPop L.A. challenge all last week. The good news is we won, so we're off to SXSW in March to compete in the Finals!
I'm a big fan of creating your own custom animations and have been pretty vocal about it in both the Flex space and now in Silverlight/WPF. So today we'll look at a basic custom animation process I've been using and explore a little bit the 3D logic in the animation loop.
Here’s what we’re building today:
Instructions: No interaction required. Just sit back and enjoy :)
We'll start off as we normally do, with an empty canvas in our in main Page.XAML to hold the elements we'll be attaching in code:
<Canvas x:Name="nodes" Width="480" Height="480" Canvas.Left="11" Canvas.Top="11" >
<Canvas.Clip>
<RectangleGeometry Rect="0, 0, 480, 480"/>
</Canvas.Clip>
</Canvas>
We'll also have a custom user control, named nodeName.xaml, that looks like this:
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="100"
Height="20"
>
<TextBlock Foreground="#FFFFFF" Width="100" Height="20" Text="silverlight" FontFamily="Arial"
FontSize="9" RenderTransformOrigin="0,0">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scale" ScaleX="1" ScaleY="1"/>
<RotateTransform x:Name="rotate" Angle="-60"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Canvas>
And has a code behind file that looks like this:
namespace SilverlightDirtyDozen
{
public class nodeName : Control
{
private double _FibX;
private double _FibY;
private double _objDepth;
private double _perspective_ratio;
private double _rotation;
public double FibX
{
get { return _FibX; }
set { _FibX = value; }
}
public double FibY
{
get { return _FibY; }
set { _FibY = value; }
}
public double objDepth
{
get { return _objDepth; }
set { _objDepth = value; }
}
public double perspective_ratio
{
get { return _perspective_ratio; }
set { _perspective_ratio = value; }
}
private ScaleTransform st;
private RotateTransform rt;
FrameworkElement lroot;
public double rotation
{
get { return rt.Angle; }
set { rt.Angle = value; }
}
public double scale
{
get { return st.ScaleX; }
set { st.ScaleX = value; st.ScaleY = st.ScaleX; }
}
public nodeName()
{
System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("SilverlightDirtyDozen.nodeName.xaml");
lroot = this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());
st = lroot.FindName("scale") as ScaleTransform;
rt = lroot.FindName("rotate") as RotateTransform;
}
}
}
The most important thing to note is that I'm using two named transforms ('scale' and 'rotate') in the XAML and grabbing a reference to them in the code behind file to manipulate them through code.
Those are the only additional elements we need. Everything else will be handled in the main code behind file of Page.xaml.
I should probably also apologize right now for getting a little math geeky on the actual animation engine since I was bored and wanted to port over a basic ActionScript 1.0 3D engine to C#/Silverlight. So, lets just jump into it...
We'll break this into three parts; the initialization, the setup, and the looper. Here is the initialization stuff:
public partial class dd03 : Canvas
{
double gRatio = 1 / 1.618033989;
double gAngle;
double rad = 50;
double rGrowth = 1.01;
double toRadians = Math.PI / 180;
double depth = 1000;
Point origin = new Point(240, 240);
int totalNames = 50;
int screenDepth = 150;
int z_axis_rotation = 1;
int y_axis_rotation = 0;
int x_axis_rotation = 0;
double convertToRadians = Math.PI / 180;
double rot = 0;
Storyboard looper;
int counter = 0;
public void Page_Loaded(object o, EventArgs e)
{
// Required to initialize variables
InitializeComponent();
looper = new Storyboard();
looper.SetValue<string>(Storyboard.NameProperty, "looper");
this.Resources.Add(looper);
looper.Completed += new EventHandler(looper_Completed);
setupNodes();
}
Again, creating an animation loop is very easy, faking 3D is the messy part. Most of the variables are for creating a 3D environment and placing nodes in a Fibonacci sunflower pattern, not creating a dynamic animation engine. To create a basic engine, you just need to create a Storyboard object (named looper in this code), initialize it, name it, and add it to the page resources. Once we call looper.Begin(), it will play once (at the established framerate, which is why I use a Storyboard instead of a Timer event) and then call it's Completed event Handler. All you gotta do is tell the looper_Completed event handler to looper.Begin() again, and you have yourself an infinite loop that will play at the correct frame rate. We'll talk about kill switches in a subsequent Dirty Dozen, if you want to not leak processor so egregiously or use a custom drag drop animation with drag or elastic..
So before telling our Storyboard object named looper to begin, you'll notice we first setup our elements on our empty node canvas in the setupNodes() function:
private void setupNodes()
{
gAngle = 360 - (360 * gRatio);
for (var x = 0; x < totalNames; x++)
{
nodeName temp = new nodeName();
temp.SetValue<string>(NameProperty, "node_" + x.ToString());
getLoc(temp);
temp.objDepth = -x;
temp.perspective_ratio = screenDepth / (screenDepth - x);
nodes.Children.Add(temp);
}
looper.Begin();
}
private void getLoc(nodeName passObj)
{
rot = rot + gAngle;
rad *= rGrowth;
passObj.FibX = Math.Cos(rot*toRadians)*rad;
passObj.FibY = Math.Sin(rot*toRadians)*rad;
passObj.rotation = rot;
}
Note that these two functions only create the user controls but don't set any rendering values on them, only logical properties we will use to calculate and render later (such as an objDepth, perspective_ratio, FibX, FibY, rotation, etc.) All the rendering changes and placement occur in the looper_completed function, which finally gets started as the last line of setupNodes().
Here is what that function looks like. Remember, it's the Storyboard's completed event handler, so by telling itself to begin again as the last line of code in its function, the function will always loop at the established framerate of your Silverlight movie:
void looper_Completed(object sender, EventArgs e)
{
// this is some fast, ugly code to change the rotations
// after a couple seconds.
if (counter > -1)
{
if (counter > 400)
{
z_axis_rotation = 2;
x_axis_rotation = 1;
counter = -2;
}
counter++;
}
// Here is all the trig for 3D. Never changes between projects...
double sin_x = Math.Sin(x_axis_rotation * convertToRadians);
double cos_x = Math.Cos(x_axis_rotation * convertToRadians);
double sin_y = Math.Sin(y_axis_rotation * convertToRadians);
double cos_y = Math.Cos(y_axis_rotation * convertToRadians);
double sin_z = Math.Sin(z_axis_rotation * convertToRadians);
double cos_z = Math.Cos(z_axis_rotation * convertToRadians);
double left = 0;
double top = 0;
// loop through every node and set their values based on their
// logical position using the trig values from above.
foreach (nodeName n in nodes.Children)
{
// these equations never change between projects either...
double rotatedY = (n.FibY * cos_x) - (n.objDepth * sin_x);
double rotatedDepth = (n.objDepth * cos_x) + (n.FibY * sin_x);
n.FibY = rotatedY;
n.objDepth = rotatedDepth;
double rotatedX = (n.FibX * cos_y) - (n.objDepth * sin_y);
rotatedDepth = (n.objDepth * cos_y) + (n.FibX * sin_y);
n.FibX = rotatedX;
n.objDepth = rotatedDepth;
rotatedX = (n.FibX * cos_z) - (n.FibY * sin_z);
rotatedY = (n.FibY * cos_z) + (n.FibX * sin_z);
// Now we start applying the calculated values with our objects
n.FibX = rotatedX;
n.FibY = rotatedY;
n.perspective_ratio = screenDepth / (screenDepth + n.objDepth);
n.scale = n.perspective_ratio;
n.Opacity = n.perspective_ratio;
n.SetValue<double>(Canvas.ZIndexProperty, n.objDepth);
left = (n.FibX * n.perspective_ratio) + origin.X;
top = (n.FibY * n.perspective_ratio) + origin.Y;
n.SetValue<double>(Canvas.TopProperty, top);
n.SetValue<double>(Canvas.LeftProperty, left);
}
looper.Begin();
}
I'm not going to lie to you. If you haven't played with Trig since High School or been tinkering in Flash, this may be a bit of math to take in, but once you do it a couple times it starts sinking in no problem. I could have just as easily only done this in the loop:
void looper_Completed(object sender, EventArgs e)
{
foreach (nodeName n in nodes.Children)
{
double left = Convert.toDouble(n.getValue(Canvas.LeftProperty)) + 1;
n.setValue<double>(Canvas.LeftProperty, left);
}
looper.Begin();
}
If I just wanted each node to move off the screen to the right, but that experience would have been hilariously boring. The 3D Fibonacci ring is a lot more interesting, so I thought I would share the whole shebang with you.
In summary then, ( after taking all the 3D and Fibonacci math out) to create a custom animation loop:
1. Create a Storyboard variable.
2. In your startup code, initialize it
3. Then name it
4. Add a Completed Event Handler to it (with all your goodie code in there)
5. Add the storyboard to your this.Resources()
6. Call storyboardname.Begin() when you want the engine to start running.
7. To keep the storyboard looping, add another storyboardname.Begin() at
the end of the Completed event handler.
We'll take a little break from animations in the next Dirty Dozen and look at importing and working with custom fonts. Until then, SLapp happy.
Tuesday November 20, 2007
DD#01: Dynamically Attaching Controls in Silverlight
We’ll start with this assumption: You know the basics of XAML, you know how to at least get to the code behind file, and most importantly, you know where to look online for detailed help if you get stuck (‘cause these can go pretty fast ;)
Ready? Here’s what we’re building today:
Instructions: Click on the stage below to add a circle dynamically
Requires Silverlight 1.1
As a quick get your bearings for those of you coming from Flash trying to map up Silverlight to what you already know (Non flashers, ear muff it for a paragraph or so): We’re basically trying to figuring out the equivalent of attaching movieClips dynamically at run time and interacting with them in XAML/C#. Whereas in Flash you’re used to starting off with a single framed timeline and the ability to add ActionScript code to that initial frame, you start off here with a TestPage.xaml file (the visual definition of your stage in markup) and it’s code behind file, TestPage.xaml.cs (All my Sivlerlight 1.1 examples will use C# as the language of choice). Now in Flash, you create a movieClip, export it, and then attach it to the stage with movieClip.attachMovie(). These Flash movieClips come with their own stage and actionscript. In Silverlight, you can use User Controls in a similar way. Like Flash movieClips, Silverlight User Controls comes with their own XAML file (the visuals) and Code Behind file (the script).
So for the Silverlight example above, I created my stage like so:
<Canvas x:Name="parentCanvas"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Page_Loaded"
x:Class="SilverlightDirtyDozen.Page;assembly=ClientBin/SilverlightDirtyDozen.dll"
Width="502"
Height="502"
>
<Canvas.Background>[…]</Canvas.Background>
<Rectangle Width="500" Height="500" Stroke="White" StrokeThickness="10" Canvas.Left="1" Canvas.Top="1">
<Rectangle.Fill>[…]</Rectangle.Fill>
</Rectangle>
<Canvas x:Name="circles" Width="480" Height="480" Canvas.Left="11" Canvas.Top="11" >
<Canvas.Clip>
<RectangleGeometry Rect="0, 0, 480, 480"/>
</Canvas.Clip>
</Canvas>
</Canvas>
The important element is the empty Canvas element toward the bottom named circles. This is where we will add our circle objects when the user clicks on the main stage.
The Rectangle isn’t important as far as what we’re doing here, it’s mainly aesthetic. I also added a clipping region to the empty circles canvas, purely for aesthetic reasons as well. A Clip object is a great, fast way to setup Opacity masks if you want to control render bounds.
Off of this main project in Visual Studio, I add a New Item > Silverlight User Control and name it DropCircle.xaml. I design it like so:
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="1" Height="1" >
<Ellipse x:Name="ellipse" Width="100" Height="100" Canvas.Left="-50" Canvas.Top="-50" Fill="#ccD5FCFC"
Stroke="#FFFFFF" StrokeThickness="2"/>
<Ellipse Width="6" Height="6" Canvas.Left="-3" Canvas.Top="-3" Fill="#FFFFFF"/>
<TextBlock x:Name="display" Width="100" Canvas.Top="6" Canvas.Left="0" Text="0,0" FontFamily="Arial"
FontWeight="Bold" FontSize="11" Foreground="#FFFFFF"/>
</Canvas>
Notice that for demonstration purposes, I left the Width and Height of the Use Control’s canvas to 1px, though the primary Ellipse object has a 100px diameter. I also cheated a little here and offset the Ellipse by setting its Left and Top properties to negative half its diameter. This will keep thing’s centered around the upper left corner of the Canvas. We’ll get to the TextBlock in a moment.
With just this much done, we can already get instances of DropCircle.xaml to added to the TestPage.xaml stage on left mouse button down by adding the following code to TestPage.xaml.cs:
namespace SilverlightDirtyDozen
{
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
// Required to initialize variables
InitializeComponent();
this.MouseLeftButtonDown += new MouseEventHandler(Page_MouseLeftButtonDown);
}
void Page_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
DropCircle dc = new DropCircle();
dc.SetValue(Canvas.TopProperty, e.GetPosition(circles).Y);
dc.SetValue(Canvas.LeftProperty, e.GetPosition(circles).X);
circles.Children.Add(dc);
}
}
}
Since DropCircle and Page share the same namespace and are compiled into the same DLL, I can create new logical instances of DropCircle easily. However, that alone doesn’t visually display the object on the screen. I need to added it to some visual element already in the TestPage (including TestPage itself if I so wanted). In this case, I’m adding each DropCircle to my empty circles canvas I created earlier, so they’re all in one place. The reason for this will become more clear in a subsequent Dirty Dozen lesson.
Using the MouseEventArgs parameter e to get the position on the stage where the mouse clicked is a nice way to dynamically set the TopProperty and LeftProperty of our new object. But what if I also wanted to display that information inside the DropCircle itself? Here is where it gets a little more interesting.
The user control can easily be modified to expose various methods and properties, but the default User Control code needs a little fixing. Here’s how:
FrameworkElement lroot;
TextBlock display;
public DropCircle()
{
System.IO.Stream s = this.GetType().AssemblyGetManifestResourceStream(
"SilverlightDirtyDozen.DropCircle.xaml");
lroot = this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());
display = lroot.FindName("display") as TextBlock;
}
You need to create a FrameworkElement as a self reference inside the user control if you want the class to be able to interacted with its own named XAML elements. Here, I created a FrameworkElement variable called lroot and had it catch a reference to itself at the point of creation. For any important xaml elements inside the UserControl I want it manipulate, I create variables of the appropriate type and create a reference to them by using lroot.FindName(). In this case, the TextBlock I named display in the XAML file is searched for by name off of the User Control and has a reference created for it and stored in a TextBlock variable I created also named display. I can now manipulate it through code.
Since I now have all the references I need, let’s keep adding to DropCircle.xaml.cs to create a function that will accept a string to display in the TextBlock:
public void setDisplay(string text)
{
display.Text = text;
display.SetValue(Canvas.LeftProperty, -display.ActualWidth/2);
}
Here, we have a public function that can accept a text string and will set and center the text inside by using the ActualWidth of the TextBlock once it’s Text property is changed.
Now, the last thing left to do is call this function from TestPage.xaml.cs when we add the DropDots. The final MouseLeftButton down event looks like this:
void Page_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
DropCircle dc = new DropCircle();
dc.SetValue(Canvas.TopProperty, e.GetPosition(circles).Y);
dc.SetValue(Canvas.LeftProperty, e.GetPosition(circles).X);
string location = e.GetPosition(circles).X.ToString() + " , " + e.GetPosition(circles).Y.ToString();
dc.setDisplay(location);
circles.Children.Add(dc);
}
That should do it. When TestPage.xaml is loaded, you can click on it and add a DropCircle dynamically and have it update its display programatically. This first lesson starts nice and easy, but it forms the initial step of many of the other tricks to come in building rich experiences in Silverlight.
But how do we get these dynamic User Controls to move both internally and as a collection? Dirty Dozen #02 will begin to address that with Dynamic Storyboards next.
The Silverlight Dirty Dozen - An Introduction
A couple years ago when I was teaching ActionScript programming, I came up with a series of lessons called the ActionScript Dirty Dozen. The general idea was thus. Most programming books take a very comprehensive, non biased approach to their subject matter (and well they should!). But many designers take a more holistic, intuitive approach to problem solving and pattern recognition. So once the basics are learned, there’s usually a gap of direction in terms of what to do next.
Now coding has the innate disposition toward precision; a cold, quantifiable efficiency. Design, on the other hand, has a this fuzzy, messy, highly subjective element to it, and many times the practitioners of either craft adopt a similar bent. The Dirty Dozen stems from the later, more designer focused inclination and takes a very personal and subjective approach to teaching user experience code. It takes the position: I may have hundreds of tools at my disposal, and those books may be a great reference for hundreds more, but the tips and tricks I used every day, the techniques that get grimy and gritty with use, are these twelve here...
So the next series of blog entries will present my current Dirty Dozen for Silverlight 1.1 projects. I’ll try and present these Dozen tips and tricks in growing order of complexity and often times they build upon each other.
Official Warning: This is very much a work in progress, if for no other reason than Silverlight 1.1 is still Alpha Bits and things can drastically change before the beta. I don’t know what the shelf life is on these, but right now they’re what I’m using all the time. The warning didn’t scare you away? Yeah, me neither. So let’s get cracking.
Friday November 02, 2007
Flash to Silverlight Project 01: Cynergy Matrix with Editor
Ok, so the first and most basic thing I want to know as an interactive designer is how to create something on the screen and power it with code. So what better way to jump into Silverlight than by duplicating a typical ActionScript project and seeing where the metaphors stand and where they fall. The dots are controls (Silverlight version of MovieClips) that are added on the mouseMove and have a dynamic storyboard attached at creation to move them into the position. Opening the grid editor lets you customize your own string of up to 10 characters and stores the data in a collection of bitkeys per character. Why bitkeys? I'm just a sucker for bitshifting and wanted to see how to do it in C#, being still relatively fresh to .NET 3.0.
Unlike my serious Flex and .NET developer friends, I would say my traditional approach to experience coding is more like my approach to design; I start bold and sloppy and refine with iterations. Doing it with functions and math is pretty much the same thing as doing it with pixels and layout if the language is forgiving enough. C# is much more... exacting than ActionScript 1.0 was, so I still have to figure out how to keep reckless creativity compliant with Silverlight's more rigid code environment.
Silverlight Application Below
UPDATED FOR SILVERLIGHT 2.0 BETA
Click to activate object.
Instructions: Move the mouse to spell out messages with the dots you drop. Open the Grid Editor to build your own message. You can customize a message up to 10 characters long. Build your characters from scratch or use the alphabet templates supplied.
Wednesday October 31, 2007
Lost In Translation
There are a lot of tutorials out there for Silverlight, but many of them fall into two camps: Preaching to the choir or treating interactive designers like code phobic pixel pushers. Now, I’m a designer and a developer, as an apparently silent but influential minority of you are. In design mode, I draw the vision and in development mode I make it happen. But personally, I don’t trust my designs to mere markup tags and WYSIWYG editors. I never have, and I don’t think most people like me have. Give me Notepad over Dreamweaver, any day. It’s how I roll. That’s why Flash was such a revolutionary tool. A single environment that actually understood what we wanted; a place to create beautiful things and bring them to life with a powerful and forgiving language.
Some of us designers have been coding our own dynamic interfaces in Flash long before the .NET platform even existed. So why does Silverlight assume that a strong divide between designer and developer must exist and require two different solutions and work environments?
The party line that ‘Designers do their thing over here, and hand it off for developers to do their stuff over there’ doesn’t hold water with me. Not for what Silverlight is trying to do, which is leap frog into a territory that already has Flash as its dominant species.
What it should be focusing on is aggressive innovation, and aggressive innovation is always the result of magicians mixing form and function in a revolutionary way. This is what Flash allowed and this is why they won five years ago. This is no longer what Flex does, because what gets someone to first place is not always the same thing that will keep them there. But I’m afraid Silverlight is copying the compartmentalized but integrated workflow of Flex instead of the innovative Form & Function miracle that was early Flash.
So, as someone who is already used to working in an environment that lets me be both artist and developer at once, if Blend or Designer don’t let me manage at least C# code directly in them, they’re not going to be powerful enough, period. So the other option for me is to work in Visual Studio directly.
Now Orcas is a powerhouse. As a developer, it gives me everything I want, but I happened to have the time, inclination and past Microsoft experience to go up its rather steep learning curve. But it’s definitely programmer focused and a big pill to swallow for designers wanting to experiment with Silverlight.
Which leads me up to this blog. I’ve spent a long time teaching interactive programming to graphic designers, and an even longer time working in Flash. What I want to do with this blog is present Silverlight and build projects approaching them with all the prejudices and assumptions that a seasoned Flash designer fluent in Actionscript 1.0 would have. If anything, it may serve to document our mindset and how we think a little better. At best, it could be a simplified Rosetta Stone for those Interactive Designers looking to translate their Flash based skill set to this new platform.