Windows Phone 7: correct pinch zoom in Silverlight

Pinch zooming is one of those things that look incredibly simple until you actually try to implement them. At that point you realize it hides quite a number of intricacies that make it hard to get it right. If you tried to implement pinch zooming in Silverlight for Windows Phone 7 you probably know what I’m talking about.

What does it means getting it right?

Adrian Tsai already gave an excellent explanation, so I won’t repeat his words. The test is extremely simple: pick two points in the image (for example two eyes) and zoom with your fingers on them. If at the end of the zoom the two points are still under your fingers you got it right –otherwise you got it wrong.

Multitouch Behavior

Laurent Bugnion, Davide Zordan and David Kelly are the men behind  Multitouch Behavior for SL and WPF. It’s an impressive open source project and you should check it out. In addition to pinch-zooming it gives you rotation, inertia, debug mode and much more. It’s extremely easy to work with as you just need a couple of lines of XAML. The only shortcoming is that at the time of writing it seems that there is no way to read the current zoom state, making it difficult to fully support tombstoning. If you don’t need this, go grab Multitouch Behavior and stop reading: it will probably work better and you’ll save some time.

The XAML

This is the XAML we are starting with. Notice that our DIY implementation relies on the Silverlight Toolkit’s InputGesture. If you are not yet using it, please install the toolkit and add a reference to Microsoft.Phone.Controls.Toolkit in your project.

xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
<Image x:Name="ImgZoom"
        Source="sample.jpg"
        Stretch="UniformToFill"
        RenderTransformOrigin="0.5,0.5"> 
    <toolkit:GestureService.GestureListener>
        <toolkit:GestureListener
                PinchStarted="OnPinchStarted"
                PinchDelta="OnPinchDelta"/>
    </toolkit:GestureService.GestureListener>
    <Image.RenderTransform>
        <CompositeTransform
                ScaleX="1" ScaleY="1"
                TranslateX="0" TranslateY="0"/>
    </Image.RenderTransform>
</Image>

The wrong way

I’ve seen this example several times around, I suppose you’ve seen it too somewhere on The Interwebs™:

double initialScale = 1d;

private void OnPinchStarted(object s, PinchStartedGestureEventArgs e)
{
    initialScale = ((CompositeTransform)ImgZoom.RenderTransform).ScaleX;
}

private void OnPinchDelta(object s, PinchGestureEventArgs e)
{
    var transform = (CompositeTransform)ImgZoom.RenderTransform;
    transform.ScaleX = initialScale * e.DistanceRatio;
    transform.ScaleY = transform.ScaleX;
}

Very simple and good looking. I love simple solutions and I bet you do too, but as someone once said “Things should be as simple as possible, but not simpler.” And unfortunately this is simpler than possible (is this even a sentence?). The problem is that the scaling is always centered in the middle of the image, so this solution won’t pass the poke-two-fingers-in-the-eyes test.

The better but still wrong way

The knee-jerk reaction is to move the scaling center between our fingers as we perform the scaling:

double initialScale = 1d;

private void OnPinchStarted(object s, PinchStartedGestureEventArgs e)
{
    initialScale = ((CompositeTransform)ImgZoom.RenderTransform).ScaleX;
}

private void OnPinchDelta(object s, PinchGestureEventArgs e)
{
    var finger1 = e.GetPosition(ImgZoom, 0);
    var finger2 = e.GetPosition(ImgZoom, 1);

    var center = new Point(
        (finger2.X + finger1.X) / 2 / ImgZoom.ActualWidth,
        (finger2.Y + finger1.Y) / 2 / ImgZoom.ActualHeight);

    ImgZoom.RenderTransformOrigin = center;

    var transform = (CompositeTransform)ImgZoom.RenderTransform;
    transform.ScaleX = initialScale * e.DistanceRatio;
    transform.ScaleY = transform.ScaleX;
}

This is better. The first time it actually works well too, but as soon as you pinch the image a second time you realize the image moves around. The reason: the zoom state is the sum of all the zoom operations (each one having its center) and by moving the center every time you are effectively removing information from the previous steps. To solve this problem we could replace the CompositeTransform with a TransformGroup and then add a new ScaleTransform (with a new center) at every PinchStart+PinchDelta event group. This will probably work: every scaling will keep its center and all is well. Except your phone will probably catch fire and explode because of the number of transforms you are stacking up. My team has a name for this kind of solutions, and it isn’t a nice one (fortunately there is no English translation for that).

The right way

It is clear by now that simply setting a scale factor and moving the center won’t take us far. As we are real DIYourselfers we will do it with a combination of scaling and translation. In the already mentioned article, Adrian Tsai uses this technique in XNA and we will apply the same concept in Silverlight. If an image is worth a million worth, a line of code is probably worth even more, so I’ll let the c# do the talking.

// these two fully define the zoom state:
private double TotalImageScale = 1d;
private Point ImagePosition = new Point(0, 0);

private Point _oldFinger1;
private Point _oldFinger2;
private double _oldScaleFactor;

private void OnPinchStarted(object s, PinchStartedGestureEventArgs e)
{
    _oldFinger1 = e.GetPosition(ImgZoom, 0);
    _oldFinger2 = e.GetPosition(ImgZoom, 1);
    _oldScaleFactor = 1;
}

private void OnPinchDelta(object s, PinchGestureEventArgs e)
{
    var scaleFactor = e.DistanceRatio / _oldScaleFactor;

    var currentFinger1 = e.GetPosition(ImgZoom, 0);
    var currentFinger2 = e.GetPosition(ImgZoom, 1);

    var translationDelta = GetTranslationDelta(
        currentFinger1,
        currentFinger2,
        _oldFinger1,
        _oldFinger2,
        ImagePosition,
        scaleFactor);

    _oldFinger1 = currentFinger1;
    _oldFinger2 = currentFinger2;
    _oldScaleFactor = e.DistanceRatio;

    UpdateImage(scaleFactor, translationDelta);
}

private void UpdateImage(double scaleFactor, Point delta)
{
    TotalImageScale *= scaleFactor;
    ImagePosition = new Point(ImagePosition.X + delta.X, ImagePosition.Y + delta.Y);

    var transform = (CompositeTransform)ImgZoom.RenderTransform;
    transform.ScaleX = TotalImageScale;
    transform.ScaleY = TotalImageScale;
    transform.TranslateX = ImagePosition.X;
    transform.TranslateY = ImagePosition.Y;
}

private Point GetTranslationDelta(
    Point currentFinger1, Point currentFinger2,
    Point oldFinger1, Point oldFinger2,
    Point currentPosition, double scaleFactor)
{
    var newPos1 = new Point(
        currentFinger1.X + (currentPosition.X - oldFinger1.X) * scaleFactor,
        currentFinger1.Y + (currentPosition.Y - oldFinger1.Y) * scaleFactor);

    var newPos2 = new Point(
        currentFinger2.X + (currentPosition.X - oldFinger2.X) * scaleFactor,
        currentFinger2.Y + (currentPosition.Y - oldFinger2.Y) * scaleFactor);

    var newPos = new Point(
        (newPos1.X + newPos2.X) / 2,
        (newPos1.Y + newPos2.Y) / 2);

    return new Point(
        newPos.X - currentPosition.X,
        newPos.Y - currentPosition.Y);
}

Also note that in the XAML we must set the RenderTransformOrigin to 0,0.
This finally passes the fingers-in-the-eyes test! Now we can add some bells and whistles like handling dragging, blocking the zoom-out when the image is at full screen, and avoiding that the image is dragged outside the visible area. For those extra details please see the sample solution at the end of the article.

What about MVVM?

You are using MVVM-light for your WP7 app, aren’t you? We all agree my code is ugly and not very MVVM friendly, I’ll make no excuses. However it’s all strictly UI code, so it doesn’t feel so bad to have it in the code behind. What you will probably do is wire TotalImageScale and ImagePosition to your ViewModel. Those two values fully define the state of the zoom, so if you save and reload them in your ViewModel you will be good to go.

Download

Here is the full sample project so that you can play with the code within the comfort of your Visual Studio (my daughter is in the picture, please treat her with respect :-) ).
Feel free to use the code in your project. As always, any kind of feedback is deeply appreciated!

WP7 icons quick and undirty

An unexpectedly time consuming part of Windows Phone 7 development are icons. Developers often don’t put much care into icons, and they are wrong. Your app is listed in the marketplace with an icon and most users just skip the crappy ones. If you make a bad icon most users won’t even read what the application is about, let alone download and install it.

That said, as a developer with some occasional design inspirations I found Expression Blend to be the perfect tool to generate WP7 graphics. The simple, minimalist style of WP7 icons just fits well with Blend and XAML in general.  Pro designers will probably be better off with specific graphic tools, but to me it’s just easier and faster to “program” my icons in Blend. I’ve had some decent results to support this approach but of course YMMV (the smile below is a placeholder and should be judged as such :-) ).

wp7buddy3

The main issue in creating the graphics with Blend is that you spend a lot of time cropping pictures to the correct size. That’s why I built myself a raw tool that is now decent enough to share with the world. It’s really raw, but it does the job. In fact it’s nothing more than a Blend/VS solution with correctly sized canvas and the ability to export all the images in one shot. The code is horrible and all, but it saved me a lot of time.

wp7buddy1

Pixel-perfect

The Windows Phone 7 marketplace requires you to create several icons in different sizes. Don’t take this as an unnecessary hassle, it is in fact an opportunity: it means you can create a pixel-perfect image for every size. Do not create an image and just resize it to each size. There are good reasons against this:

1. The tile image is not a simple icon. It will be shown on the main phone page and includes at least the application name. That’s why your image must have an offset to take this into account. My solution overlays the system settings icon, so that you can check if your logo is correctly centered. If your icons are full-width you can ignore this.

wp7buddy2

2. You can (and should) use a different detail level for every size. A good looking 173×173 icon may look like an undefined mass of blurry pixels when resized to 62×62. Just keep the general theme and image consistent.

3. Straight lines will become anti-aliased and look blurry when you resize them (in XAML when you use a viewbox). It’s simple: the width of a line when stretched could become a non-integer value (for ex. 3.5 pixels) and will look blurry. If you have a different image for every size you have full control and can make one-pixel changes to avoid this effect. Look at this example: it may not look obvious but on a close look you’ll see that the left picture is not as well defined as the right one. On the phone, the difference is even more obvious.

wp7buddy4

Download

Usage is simple: open the solution in Blend 4 or Visual Studio 2010 (it’s a WPF application), delete the placeholder smile and put your graphics in its place. Run the application and hit the export button to save the images. Tip: use resources for colors, shapes, etc. so that you can change them in one shot.
Enough said: download WP7IconBuddy and use it at your own risk. I’d love to hear some feedback.

Simple Error Reporting on WP7 RE-REDUX

If you haven’t read Rudi Grobler’s Simple Error Reporting on WP7 and Simple Error Reporting on WP7 Redux, please go read them now. Basically it’s about reporting errors at runtime from a Windows Phone 7 application. I always incorporate this kind of functionality in my applications (not only on Windows Phone) and it has proven to be very useful in these years.

While Rudi’s implementation uses the EmailComposeTask to send an error report, I do it in a slightly different and by submitting the report as http POST to a web page that then sends the email. This way the process is entirely silent and nothing is shown to the user (except the prompt). My reasoning is that the less steps she has to perform, the more likely she is to send the report.

In order to use this method you’ll need some server, but nothing fancy is required. I suspect you already have something adequate in place (for example the one hosting your blog).

Client Side

This is the code I put on the phone:

public void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
    if (MessageBoxResult.OK != MessageBox.Show("We encountered a problem and need to close. Would you like to tell us?",
                                "Oh no!",
                                MessageBoxButton.OKCancel))
    {
        return;
    }

    try
    {
        var subj = e.ExceptionObject.Message;
        var body = new StringBuilder("Stacktrace:");
        body.AppendLine(e.ExceptionObject.StackTrace);

        var wc = new WebClient();
        wc.Headers[HttpRequestHeader.UserAgent] = "Mozilla/5.0";
        wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
        wc.UploadStringCompleted += (s, er) => MessageBox.Show("Thanks for your report!");
            
        wc.UploadStringAsync(
            new Uri("http://your-host.com/bug-report.php"),
            "POST",
            string.Format("subject={0}&amp;amp;amp;amp;amp;body={1}",
                HttpUtility.UrlEncode(subj),
                HttpUtility.UrlEncode(body.ToString())));
    }
    catch (WebException)
    {
        MessageBox.Show("The report could not be sent (no network available?).");
    }
}

Don’t forget to replace the messages with something better. You can add whatever you like to the report, just keep in mind that if you query DeviceExtendedProperties to get the peak memory usage, your app will need the Identity Capability (which looks a bit scary in the marketplace in my opinion).

Server Side

On the server side you can do pretty much anything: you could insert the report in a database or fill in a ticket in your bug tracker. I keep it simple and send an email (please set up a dedicated address if you do this). My server is rather fast but only supports PHP, so that’s what I use:

<?php
	if (isset($_POST["subject"]) &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; isset($_POST["body"]))
	{
		mail("youremail@your-host.com", "wp7 app bug report: ".$_POST["subject"], $_POST["body"]);
		echo "ok.";
	}
	else
	{
		echo "missing parameters.";
	}
?>

Please notice that this is not the most secure practice in the world as someone could sniff the traffic generated by your app and exploit this page to flood your mailbox. A simple solution is to limit this page to send a maximum of say 10 emails/hour. Anyways if you are getting a much larger number of legit hits, chances are that the exception is always the same one (or your app is really buggy and you deserve to be flooded :-P).

Is this method better than using the EmailComposeTask? I don’t know, but it’s just another option at your disposal.

Magura Marta installation tip

Sorry, no tech this time, I’m talking mountain bike brakes. This is more a note to myself, but as I’ve not found this advice anywhere I’ll share it.

Shortening the lines on Marta brakes (SL in my case) is very easy, but there is one small gotcha you should be aware of.

The system is simple: the olive is pressed in place by the threaded nut and seals the connection to the lever. This is what you want to achieve (sorry for the really crappy drawing):

marta

The problem is that it’s easy to end up in the second situation, i.e. the olive not being completely over the hose.

If you install the hose that way, as soon as you pull the lever the connection  will leak oil (and suck in air). It’s not a big issue but you’ll have to correct the situation with a new insert/olive and bleed the system.

The good news is that it’s very simple to avoid this. Just keep the hose pressed all the way towards the lever with your hand while you thread the nut the first time. This forces the olive to slide all the way over the hose.

Random tips: 1) keep some margin when shortening the hose just in case you’ll have to press in another insert. 2) Make sure to have an extra insert and olive before starting, it sucks to be stuck in the middle of the process without parts.

My experience with hydraulic disc brakes is still limited, so please forgive me if all this was obvious.

Windows Phone 7: internet tethering with Swisscom (now!)

While internet to USB tethering is not officially supported by Windows Phone 7 until the promised Q1 2011 update, it seems that devices and OS already support it. Probably Microsoft has not yet reached an agreement with some carriers and their idiotic pricing plans, so the feature is just hidden.

The good news is that if you own a Samsung or an LG you can already enable it without waiting for the update: just follow these direction. The only problem is that those settings apply to Orange France.

Being Swiss and having a Swisscom subscription (thankfully they allow tethering without any restriction) I had to play a bit but in the end I found out that these settings work fine with Swisscom:

Modem configuration: +cgdcont=1,”IP”,”gprs.swisscom.ch”
Call number: *99#***1# (instead of *99#)

Enjoy!

An error occurred while accessing IsolatedStorage.

While developing a Silverlight Windows Phone 7 app I ran into this problem: when the application closes, an internal  IsolatedStorageException is raised:

An error occurred while accessing IsolatedStorage.
   at System.IO.IsolatedStorage.IsolatedStorageSecurityState.EnsureState()
   at System.IO.IsolatedStorage.IsolatedStorageFile.get_AvailableFreeSpace()
   at System.IO.IsolatedStorage.IsolatedStorageSettings.Save()
   at System.IO.IsolatedStorage.IsolatedStorageSettings.TrySave()
   at System.IO.IsolatedStorage.IsolatedStorageSettings.SaveAllSettings()
   at MS.Internal.FrameworkCallbacks.ShutdownAllPeers()

IsolatedStorageSettings seemed to be the cause of the problem. Very weird because I wasn’t even using it! Just adding this line in my App class would cause the problem:

private System.IO.IsolatedStorage.IsolatedStorageSettings appSettings = System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings;

Now I created a new, empty, project (Windows Phone Application) and inserted that line. Weird: no problems.
I thought there was something wrong in my solution, so I made a backup copy and started removing stuff. At some point my solution was identical to the default one (except for the IsolatedStorageSettings line and the project’s GUID). Not “almost identical”, but completely, literally identical, i.e. WinMerge found no difference except the two mentioned. And of course the default solution worked while mine gave the IsolatedStorageException on shutdown.

I honestly haven’t understood the cause of the problem, but at least I’ve found a solution: change the project GUID:

  • close Visual Studio
  • open your solution’s .sln file with notepad
  • replace a couple of numbers in the Project(“{<your GUID here>}”)
  • open the .csproj with notepad
  • apply the same change to the GUID (in the first line after the comment and in all the Build Configurations)
  • Re-open your solution with Visual Studio and the problem is gone.

I suspect the problem has something to do with the emulator but I haven’t had a chance to try a real device yet. I hope I saved you an evening of head scratching.

Windows Phone 7 Marketplace subscription for Swiss individuals

Lately I’ve been busy coding stuff for Windows Phone 7 –really fun! If you are a Swiss individual (i.e. you are not developing your apps for a company) then joining the Marketplace is not very straightforward. If you reside in another non-U.S. country you may as well read this post, I suppose you will only have to make minor changes to the procedure.

Sascha Corti of Microsoft Switzerland has provided a great walkthrough, but I whish to add a couple of things, in particular re. obtaining your notarized passport copy.

Sign up

First, you can sign-up to the App Hub on create.msdn.com. You need a credit card to pay the CHF 129 annual fee. Be careful: the publisher name is final and cannot be changed later. So think about it for a minute and if you are not sure wait until you’ve decided what to display as your apps’ publisher.

Payee Details

When you are signed-up and have clicked on all the confirmation links you receive, you have to log into your account and fill in your bank account data under my account/payee details. If you only have a Post account, no problem, use the Post’s IBAN calculator –they also provide the BIC and correct address.

At this point you are ready to submit your applications to the marketplace, but you may want to continue reading. In fact as you probably already know, you will receive 70% of your app’s total sales, the other 30% being kept by Microsoft. The problem is that as a non-U.S. resident, another 30% will be removed from your part –which is a rather big hit IMO! Keep in mind that you will have to pay Swiss taxes on the income, so in the end not much will be left in your hands.

That’s why you have to provide some documentation proving that you are indeed paying taxes on your country –so that the U.S. won’t charge you that infamous additional 30%. In practice, it means asking an ITIN number to the U.S. International Revenue Service and giving it to Microsoft.

Notarized Copy

To ask an ITIN number you need a certified true copy of your passport. The IRS is rather strict on this aspect and won’t accept notarized copies from entities not in their list. This excludes the Swiss Post and probably anything else except the U.S. Embassy.

So -unless you live in Bern or Zurich and can take an appointment- you will have to send the following:

  • your passport
  • a copy of your passport
  • the filled-in W7 form (more on that later)
  • the Microsoft printed letter (more on that later)
  • a CHF 1.00 stamped envelope with your address

to this address:

U.S. Embassy
Consular Section
P.O. Box
3001 Bern

and at the same time, pay CHF 50.- (plus the 18.- postal fee) to

American Consular Services
Sulgeneckstrasse 19
3007 Bern

Yiikes! This will probably be the most expensive photocopy of your life (also considering that you made the copy itself :-) ), but they don’t have a post/bank account so you cannot avoid the 18.- fee.

W7 Form

In some days you will receive your notarized passport copy (and hopefully your passport) back. You are now ready to forward the W7 form. If you sent it to the embassy, they will probably have checked it for obvious mistakes, but I have not yet received mine back so I cannot tell at this time.

Anyways the W7 form can be downloaded here. Fill it in as explained in Sascha Corti’s presentation. In particular make sure that you check point a. and h., and enter “Exception 1 (d) – Royalty Income”  at point h., plus “12” as treaty article number.

Now log into your Marketplace account, go to the payee details page, scroll down until you find “Click here to download the Microsoft letter”, download the letter, print it and hand-write the date and your full name. Make sure the letter includes Todd Biggs signature at the end. A previous version did not include the signature and applications were rejected because of it.

Now send W7 and the letter to

International Revenue Service
ITIN Operations
P.O. Box 149342
Austin, TX 78714-9342
United States of America

After n weeks they will hopefully assign you an ITIN number.

W-8BEN form

With your ITIN in hand, you can finally fill in the W-8BEN form, print it, sign it and send it to

Microsoft
’Windows Marketplace for Mobile’
One Microsoft Way
Redmond WA 98052
United States of America

Once they receive and process it you should be free of the 30% tax and receive your full 70% royalties. As far as I understand it this is not retro-active, i.e. the taxes you previously paid won’t be reimbursed.

That’s all

This should be all (I never said it was simple ;-) ). It takes some effort, time and money (about CHF 80.- one-time plus the annual 129.- fee), but if you are doing some interesting apps it may be worth it as you will hopefully earn it back soon.

 

UPDATE:
Getting your ITIN number from the IRS can take a long time. It took me more than 3 months since I sent the W7 form. Send it with signed mail for piece of mind, and be patient, they will eventually answer.

Best of Swiss Silverlight 2010

During this year’s Shape 2010 conference in Zurich-Oerlikon, Microsoft Switzerland announced the winners of the Best of Swiss Silverlight 2010 Award in collaboration with the Best of Swiss Web Association, simsa and Netzwoche.

best_of_sw_silver_2010

Incredibly my application Trails of Switzerland won the Bronze award. I was completely taken by surprise (not to mention super excited) because I didn’t really expect anything when I started the project. In fact it was just a “weekend project” to try a couple of things. When I saw the award application form I thought it could be worth a try so I polished a bit the front-end and added a couple of cool gadgets.

I was familiar with the competition’s application procedure also because I already did it a few times before for my company (that won this year’s .NET Award by the way).

boss_bronze_2010

My application leverages Silverlight’s DeepZoom component to show a full topographic map of Switzerland. The base image is a huge 19 Gigapixels (~3 Gigabytes) JPEG, but movement and zooming is wonderfully smooth.

The tricky part was mapping the GPS data to the map and then keeping content synchronized with the DeepZoom zooming and panning.

screenshot-1

Currently Trails of Switzerland is in closed-beta at http://maps.frenk.com and will probably never go live (except if someone wants to buy it from me) for the simple reason that copyrights on the maps are incredibly expensive and I cannot afford to buy them “just for fun”.
To be honest I must say that the Swisstopo maps are of incredible precision and quality, but still are way too expensive for a no-profit application.

The good part is that Trails of Switzerland could probably be ported to Windows Phone 7 (with a major restyling of course) as DeepZoom seems to work very smoothly there too.

I’d like to congratulate the other contest winners (Coresystems AG/Misapor, Extrafilm AG, VASP Datatecture AG/ETHZ, Portia AG/Immostreet AG): your applications were really mind-blowing!

Shape

The conference was very interesting as well. In particular Bob Muglia should have taken notes from Ronnie’s talk on HTML5/Silverlight. If you have watched the PDC2010 keynote (and the twitter/blog-storm that followed) you know what I’m talking about.

As always Laurent’s Bugnion’s talks were interesting, but also many others were worth the trip (in particular from a Windows Phone 7 and design/UX standpoint).

Moral: you never can tell

Moral of the story is you never know where a weekend project is going to bring you. It seems that someone else also agrees on this. This is one of the things I love in this field.

Thanks

Thanks to Microsoft Switzerland and most of all thanks to my wife for the continuous support and for understanding when I forget stuff/don’t listen because I’m thinking about code (i.e. most of the time) :-)

Team Foundation Server upgrade 2005->2010

Very smooth upgrade. Except:

  • Dell boot cd for our PowerEdge server does not support windows 2008
  • Dell does not support our RAID/SCSI controller for windows 2008 (no drivers)
  • Team Foundation Server does not run on windows 2003 R2 64-bit
  • Windows 2003 msdn DVD is not bootable. But there is an identical one (minor version +1) that is bootable.
  • SQL server 2008 R2 msdn DVD contains by mistake the evaluation version without key (thus starts in evaluation mode). Fortunately the SP1 DVD is correctly pre-pidded.
  • Thunderstorms caused three power failures (we have no UPS).

All in all it took 4 complete days, a hundred reboots and a lot of cookies. Not a funny experience. Maybe that’s why they pay sysadmins to do this kind of stuff.

However I must say that all the issues were not directly caused by TFS and the TFS 2010 setup itself is impressively smooth and straightforward. A huge step ahead compared to 2005, congrats to the TFS team!

Silverlight: web service calls fail when OOB

Situation: your Silverlight 3 application is calling a web service and while it works fine in the browser, it miserably fails in out of the browser mode (OOB).
Don’t panic. It seems that you cannot call a webservice (in App.xaml.cs) before having assigned the application’s root visual. As mentioned, this is not a problem when the application runs inside the browser.

So, for example, this will fail (timeout):

private void Application_Startup(object sender, StartupEventArgs e)
{
    myService.MyMethodCompleted((s, e) => this.RootVisual = new MyPage());
    myService.MyMethodAsync();
}

while this will work fine:

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = new MyPage();
    myService.MyMethodAsync();
}

On a side note, remember that RootVisual can only be set once. I haven’t checked what happens with Silverlight 4, but if you are seeing weird behaviours it may be worth it to check this before setting everything on fire.

I hope I saved you long hours sniffing http traffic, checking for crossdomain issues, etc… :-)

Happy coding!