I came across this add-in for Visual Studio the other day that is subtle but added some productivity features to Visual Studio for me.  It’s called Tabs Studio.

NOTE: I’m not getting a complimentary license for this add-in and have already purchased my own license with my own money.  This is an unsolicited opinion.

For me Tabs Studio does two things: organize my open content better and enables me to more quickly close/manage the open tabs.  Take a look at the before after of the same content open in VS:

Visual Studio 2008 tabs normal

In the before I have all the tabs open and can only close them one at a time (i.e., can’t selectively close a tab without first activating it).  Additionally, It is shown in order of opening.  I may have MainPage.xaml somewhere along the project, but not right next to the MainPage.xaml.cs file that I also need.  On a recent project I had about 15 files open at once and hunting to find the related ones was a nuisance when you needed to be fast.  Now look at the after:

Visual Studio 2008 tabs with Tabs Studio

Same amount of tabs open, but the “related” ones are automatically grouped for me, and the bold shows which one is open.  Additionally I can selectively close the code file without first activating the tab (each tab in Tabs Studio has a close button like Firefox tabs implementation).  This is great.

What’s the best part?  It’s all XAML!  The Tabs Studio is a WPF control that you can customize to your liking by putting your own styles in the settings pane using BasedOn styling:

Tabs Studio Settings dialog

Very cool  So far this little add-in is helping me just a tad more productive and it stays out of my way!

One of the new features to Silverlight 3 is the ability to add multi-touch capabilities to your application.  When I posed the question on Twitter, I got some responses of ideas people would use this for.  Honestly most of them could be accomplished with mouse events today and X/Y calculations.  These would be the touch applications that are pretty singular.  But I did get some multi-touch ideas that I think I’ll try to explore.  First though, let’s look at the basics of what Silverlight provides for multi-touch application development.

Hardware

Hopefully I’m stating the obvious here, but your hardware has to support multi-touch.  And I’m not talking about that fake kind.  I’m talking about hardware that announces the WM_TOUCH messages to Windows.  If you (or your customers) aren’t going to be having multi-touch hardware, then writing against this API isn’t going to help!  I’m currently using the HP TouchSmart TX2 laptop running Windows 7.  I find this to be a good machine and fairly cheap-ish with regard to how laptops are these days and with the features it provides.

The Event

The first thing to understand is how to tap into the touch events from the hardware to Silverlight.  Understanding this at the beginning of your application development can be a critical step.  The key reason for this is unlike other input events (i.e., MouseLeftButtonDown, etc.) which can be added to individual UIElements in an application, the touch event is an application-wide event. 

There is one primary event: FrameReported.  This event is what gets fired when the hardware sends the touch events to the runtime.  The Touch class is a static class for the sole reason of this FrameReported API.  To wire it up in your application you can use code like this:

   1: Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);

And now you have to write your event handler.

The Event Handler Arguments

Once the runtime receives a touch message, the FrameReported event fires (and will do so several times…see later here).  The arguments you get that you primarily need to concern yourself in most circumstances are the GetPrimaryTouchPoint and GetTouchPoints.

The primary touch point could be thought of as the first touch message/point that the runtime received in a current sequence.  So if your application is using single gesture touch behaviors, this is likely what you’d use.  Otherwise GetTouchPoints is going to give you all the registered touch points from the hardware reported to the runtime.

For me understanding the Move event is critical.  If you take a look and add the data to my diagnostic app below for Move, you’ll see that even simply touching your finger in one place fires constant Move commands.

What you get in a TouchPoint

Both the primary and the collection of touch points listed above will return the TouchPoint object, which contains valuable information.  Namely it is going to give you Postition, which is a point relative to the offset you provided in the GetTouchPoint call (or absolute if you pass in null).

You also get the Action of the touch.  There are three actions: Down, Move and Up.  It is important to understand the firing sequence here.  Assume a given TouchPoint, it will first report Down, then it will continue to report Move until the touch is removed, at which point Up will occur.  The key piece in the middle is Move.  This action is firing even if you aren’t moving any element.  It is essentially reporting that you have a TouchPoint that is in Down state (i.e., touched).  Move can be helpful if you are needing to move things along with the updated position of the element.

You also get the TouchDevice which contains some helpful information.  Provided via the TouchDevice is an Id value, which is a unique id provided by the operating system for the device which reported the TouchPoint.  Also provided is DirectlyOver which is the topmost UIElement the position is over at the time the touch was produced.

What about my mouse events?

Ah, good point!  In the TouchFrameEventArgs you have a method you can call which is SuspendMousePromotionUntilTouchUp.  You would want to use this if you knew *for sure* that the end user had multi-touch hardware.  This would prevent the mouse event promotion for the given touch point.  This method can only be called if the Action is Down for the TouchPoint.  Once the TouchPoints all report Up, then normal mouse event promotion would resume.

Putting it all together

For these basics, I decided just to create a quick diagnostic application that would show the registering of the TouchPoint elements, as well as identifying the primary touch point.  My application has registered the FrameReported event handler and then I’ve added some logic:

   1: public MainPage()
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum2">   2:</span> {</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum3">   3:</span>     InitializeComponent();</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum4">   4:</span>     Touch.FrameReported += <span style="color: #0000ff">new</span> TouchFrameEventHandler(Touch_FrameReported);</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum5">   5:</span> }</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum6">   6:</span>  </pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum7">   7:</span> <span style="color: #0000ff">void</span> Touch_FrameReported(<span style="color: #0000ff">object</span> sender, TouchFrameEventArgs e)</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum8">   8:</span> {</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum9">   9:</span>     <span style="color: #0000ff">foreach</span> (TouchPoint tp <span style="color: #0000ff">in</span> e.GetTouchPoints(<span style="color: #0000ff">this</span>.Positions))</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum10">  10:</span>     {</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum11">  11:</span>         <span style="color: #0000ff">if</span> (tp.Action == TouchAction.Down)</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum12">  12:</span>         {</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum13">  13:</span>             <span style="color: #008000">// do something</span></pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum14">  14:</span>         }</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum15">  15:</span>     }</pre>
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum16">  16:</span> }</pre>

The end result is that when the user touches that application surface, we add the TouchPoint to an ObservableCollection that is bound to a DataGrid to show the points currently registered and by which device.  When the user removes the touch action, they go away.

Obviously it is hard to demonstrate touch capabilities in a screenshot and it really does it no justice.  I’m going to do my best attempt here to show you a picture-in-picture view of the application running and me interacting with it via Touch.  You’ll need Silverlight to view this demonstration.

Summary

There you have it.  The basics of multi-touch in Silverlight 3.  It’s fairly simple to understand the core mechanics of the API.  What gets tricky is interacting with your application beyond just showing the points :-).  In a future post I’ll show an application that makes use of this multi-touch feature in understanding where the touch occurred in my given application and how you can find the element that was touched (even though it’s an application-wide event).  If you aren’t subscribed, please consider subscribing to my blog for regular updates for Silverlight information.

Feel free to spelunk this diagnostic app code:  MultiTouch_CS.zip (C#) or MultiTouch_VB.zip (Visual Basic).

Hope this helps!

I got enough feedback and suggestions that I figured it would be better just to put the code up on CodePlex rather than package zips on my blog :-).  Here it is: FloatableWindow project.  The latest build I have is up there which incorporates some feedback that I’ve received.

UPDATE: If you like this idea VOTE FOR IT in the Silverlight Toolkit!

Basically the ShowDialog() API operates the same way that ChildWindow.Show() does today.  No changes there, popup is used.  But when you just want some simple MDI type windows, use Show() which will not use Popup but rather add the elements as children to your root visual.  Now the key here is defining that root.  Before you show the window you’d want to do something like this:

   1: FloatableWindow fw = new FloatableWindow();
   2: fw.Width = 200;
   3: fw.Height = 200;
   4: fw.Title = "Foobar";
   5: fw.ParentLayoutRoot = this.LayoutRoot;
   6: fw.VerticalOffset = 200;
   7: fw.HorizontalOffset = 100;
   8: fw.Show();

Notice line #5 where I specify a Panel element to add it to?  That would be required.  An ArgumentNullException is thrown if it is not provided.

Thanks for the great feedback and encouragement on this refactoring.  I hope that putting it on CodePlex provides a better home for evolution and tracking issues (I know there is an animation issue now with non-modal).

I’ve received a few emails about updated code for the Scott Guthrie MIX09 keynote demo referred to as “bouncing plane” Silverlight demo.  A screenshot of this demo is seen here to refresh your memory:

Bouncing plane Silverlight demo

There really isn’t anything ‘new’ about this demo code for SL3, other than being recompiled.  Perhaps the only real change is to accommodate the new requirement that pixel shaders are resources of the project.  You’ll see the Effect1.cs code file where the constructor code for the shaders uses:

   1: pixelShader = new PixelShader();
   2: pixelShader.UriSource = new Uri("/BouncingPlane;component/ShaderBytecode/Ripple.fx.ps", UriKind.Relative);

If you are writing shaders, I’d refer you to my post talking which has some Visual Studio code snippets and item templates so you can say Add New Item…Silverlight Pixel Shader and get the appropriate stub code already there for you!

Here’s the Visual Studio project for Silverlight 3 for the bouncing plane demonstration: BouncingPlane_SL3.zip.

UPDATE: Hosting a demo of it (minus the video, so you'll get an error if you choose video) here: Bouncing Plane Silverlight Demo.

As we’ve noted before, Visual Studio 2008 doesn’t have multi-targeting support for Silverlight development.  Generally speaking what this means is that if you install the Silverlight 3 tools, you have a Silverlight 3 development environment with VS2008.  True multi-targeting for Silverlight in the IDE will come in Visual Studio 2010 (you can see how that works in this post).

NOTE: Visual Studio 2010 beta 1 (current version available at the time of this writing) does not fully support Silverlight 3 *release version* (also referred to ‘RTM’ or ‘RTW’) development.  There are a few things missing in VS10 beta 1 for full support for SL3 RTW and .NET RIA Services development.  This support will come in later beta builds of VS10 – and no, I have no idea when Visual Studio will be planning on releasing additional beta builds.

But people still want to know how they can build SL2 apps using a single VS2008 machine, no virtual images and without VS2010.  There is a way to do this, but please allow me to set some major caveats.  We must first make sure that what I’m saying here is still that VS2008 does not support multi-targeting IDE development with Silverlight 2 and Silverlight 3.  What I’m also saying is that VS2008 IDE and MSBuild are two different experiences.  MSBuild could care less about an IDE and it just does what it is instructed to do…so let’s instruct it to build Silverlight 2 code, shall we?

Assumptions – please read

Let’s assume this scenario: you are working on SL3 apps so you need the SL3 tools.  Great – install them.  But you also need to either a) support an SL2 application and/or b) for some reason you want to start a new project in Silverlight classic…err, I mean…Silverlight 2.  Okay, let’s proceed with these assumptions and that you already have VS2008 and SL3 dev tools installed.

Step 1: Install the Silverlight 2 SDK

Go grab the Silverlight 2 SDK.  This is different than the Silverlight 2 Tools for Visual Studio.  Don’t install those…you’ll be made at me for no reason if you do.  Again, install the Silverlight 2 SDK.  Once you’ve done that, if you look at your %ProgramFiles%\Microsoft SDKs\Silverlight directory you will see both SDKs installed:

SDK directory with both Silverlight SDKs

Step 2: Create a new project or open your existing Silverlight 2 application

If you are creating a new application and are targeting Silverlight 2 and not taking advantage of all the great new features in Silverlight 3, then create a new Silverlight application.  Obviously (or hopefully so) you cannot choose the Navigation or RIA Services templates…you’ll have to choose the basic Silverlight 2 Application template.  If you are working on an existing SL2 application, open that project.  In this case, VS will convert the project up to an SL3 project.  This is fine for now.

At this point you have your application open in VS and it is basically a SL3 application project.  If you hit F5 at this point, it would build as an SL3 application.

Step 3: Make a copy of the .csproj or .vbproj file

Go to your project’s directory in explorer (fastest way to do this is right-click on the project in Solution Explorer and choose Open Folder in Windows Explorer which is second to the last option by default.  Once in Explorer, make a copy of the .**proj file (either .csproj for C# or .vbproj for Visual Basic).  Name it whatever you want, maybe something like <projectname>-SL2.csproj.

Open that file in notepad or other plain text editor.  You’ll see a line that looks like this (for a default project this is about line 84 – and note the CSharp.Targets will be different if you are using VB):

   1: <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Silverlight\v3.0\Microsoft.Silverlight.CSharp.targets" />

Notice the “v3.0” in the build path?  Alter that only in your copied **proj file to this: (again, noting the difference between CSharp and VB):

   1: <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Silverlight\v2.0\Microsoft.Silverlight.CSharp.targets" />

Basically change the “v3.0” to “v2.0” in that Import node.  This tells the build system to use the SL2 SDK that you installed in Step 1.

Step 4: Modify your build events in VS

At this point, if you still hit F5 you’d be running a SL3 application.  This is still because a) you aren’t opening the altered **proj file we just created in Step 3 and b) you have SL3 tools installed for VS.  What we need to do is instruct Visual Studio to perform an additional build command for your project.

Right-click on the project and select the project properties.  Then from here choose the Build Events tab.  If you really want only a v2 build of your app, then in your post-build event you can enter this (obviously changing to the file name you created in Step 3:

   1: $(MSBuildBinPath)\MSBuild "$(ProjectDir)SilverlightApplication1_2-SL2.csproj"

And when you F5 you’ll end up with a Silverlight 2 XAP.  Go ahead and look at the generated AppManifest.xaml file…it will show you RuntimeVersion=”2.0.31005.0” in the file.

What just happened?

Basically when you F5 in Visual Studio in projects, you are initiating build commands.  Sometimes you’ll see that it just uses csc.exe, but basically these are all build commands in the system.  What we’re doing in Step 4 is telling Visual Studio: When you’re done with that, go ahead and execute this additional build command for me, kthxbye.  Some may look at this and ask if 2 builds are happening.  The answer is YES.  Issuing a build command in VS does its normal process and then we are OVERWRITING THE OUTPUT with our second build.

NOTE: If someone has a better way to tell VS don’t do your normal build but do this instead please post in the comments…I’m not a VS build system pro.

You are definitely overwriting your SL3 compiled bits with the new ones.

Can I build both SL2 and SL3 from the same base?

Sure.  You’ll have to modify the OutputPath setting for your projects so they don’t overwrite each other.  I’m sure if you are asking this question you have a good reason for it as SL2 apps are compatible running under the SL3 runtime without needing to recompile.  I modified my VS IDE app on the Build tab (in the Properties dialog) to put the output in an SL3 folder.  I then modified the OutputPath setting in my file from Step 3 to a folder called SL2.  Now when I build in VS I get both binaries/XAPs created:

post build directories

So I can do what I want with the XAPs now.

So, what’s the catch?  Isn’t this multi-targeting?

Big catches…and NO, this isn’t multi-targeting for VS2008 (at least what we define it for Visual Studio).  Here’s the catches I’ve found:

Your IDE is still a Silverlight 3 IDE environment.  What I mean by this is that VS is doing nothing to prevent you from writing Silverlight 3 code.  Intellisense will be targeted at the SL3 SDK you have installed.  This means if you aren’t paying attention and don’t know what APIs aren’t available in SL2, you can get into trouble VERY fast.  In this event, if you do add code that is SL3 specific your SL2 SDK MSBuild will error out and report back in VS.  Here’s an example of where I added some element-to-element binding in my XAML and VS reports the error for the post-build event (as reported in the Errors output window):

   1: Error    2    The property 'ElementName' does not exist on the type 'Binding' in the XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'.    C:\Users\timheuer\Documents\Visual Studio 2008\Projects\SilverlightApplication66_3\SilverlightApplication1_2\MainPage.xaml    10    61    SilverlightApplication1_2
   2: Error    3    The command "C:\Windows\Microsoft.NET\Framework\v3.5\MSBuild "C:\Users\timheuer\Documents\Visual Studio 2008\Projects\SilverlightApplication66_3\SilverlightApplication1_2\SilverlightApplication1_2-SL2.csproj"" exited with code 1.    SilverlightApplication1_2

My SL3 build will still complete fine, remember the SL2 is a post-build event.

The other thing is copying files to an associated web project.  In my steps above, the XAP that would get copied to the ClientBin directory of the web app was the SL3 version.  Honestly I didn’t take the time to worry about that and I know that you could get more creative with your build command to change that…but I wanted to be clear about the output of my steps outlined here.

Assembly references will be another issue.  Remember, VS is doing nothing to prevent you from doing things SL3-specific.  So the assembly reference list you see will include SL3-specific binaries.  Also when you add references in VS, it alters the **proj file.  So each time this happens you’d have to manually edit your copied file to make sure the references are there and are the correct ones for the SL2 SDK.  This can get messy very fast.

What if I just want to continue doing Silverlight 2 development, but view SL3 sites?

Well, then I need to come over there and talk to you about the aweseomeness of Silverlight 3!  But if you must, let me let you know what is going on here.

Short answer: don’t install the SL3 tools.  But again, you’re crazy right?  Silverlight 3 is awesome!

Longer answer…

The Silverlight 2 Tools installer now installs the Silverlight 3 developer runtime, but still installs the SL2 SDK.  Confusing huh?  Bottom line is that in this configuration you can develop SL2 apps in the IDE (with SL2 Intellisense, assembly references, etc) but still be able to view SL3 sites out on the Internet.  How is this possible?  Because the SL2 tools are using the build commands and VS hooks for SL2 SDK (look at the project file and you’ll see it is like above in Step 3).

If you have SL2 tools already installed but don’t have SL3 yet…you cannot install the end-user SL3 runtime on top of a developer runtime (i.e., you cannot ‘upgrade’ a developer runtime to a non-developer runtime).  So you’ll want to install the SL3 developer runtime on top of your SL2 environment in this situation.

Summary

I do not by any means consider this guidance from the Silverlight team.  This is, in fact, a hack.  It’s unsupported, might not work for your situation and as Scott Hanselman says, it may hurt baby kittens.  This information is merely here to really prove a point that you can use MSBuild to build Silverlight 2 applications with the Silverlight 2 SDK.  That’s really the end output here.  The rest is hackery to get VS to do things with that build.  I firmly recommend you develop using Silverlight 3 anyway!

Use at your own risk.  If there are other MSBuild professionals out there that have better methods than post-build events, please comment – again, I’m not a pro in that area, so this was my first pass at testing this out based on questions I got from the community.

Hope this helps and don’t blame me for any injured baby animals.


This work is licensed under a Creative Commons Attribution By license.