The blog of dlaa.me

Posts from December 2010

"And she'd say, 'Can you see ... what I'm saying?'" [How to: Localize a Windows Phone 7 application that uses the Windows Phone Toolkit into different languages]

While it might be convenient if everybody spoke the same language (or communicated via telepathy), that's not the world we live in. :) Therefore, building applications that can be easily translated to other languages is an important consideration. Fortunately, it's easy - and it's covered in the MSDN article How to: Build a Localized Application for Windows Phone. But what's not covered is how to localize the controls in the Silverlight for Windows Phone Toolkit. As you might expect, it's fairly similar, but I've had a few people ask about this explicitly and I decided to do a quick post on the topic. As long as I was at it, I figured I'd show the entire process from start to finish just to make things a little easier...

 

Prepare the emulator/phone

In order to monitor our progress as the sample application gets localized, it's helpful to work in a non-default language which makes it easy to identify the un-localized parts. I run an English operating system, so I'll use Spanish for this example:

  1. Open the Settings application

  2. Choose region & language

  3. Open the Display language picker

  4. Change to "Español"

  5. Tap the link to restart the emulator/phone:

    Settings

 

Create a new application

We'll start with a brand new application using the default template provided by the Visual Studio 2010 development tools:

  1. From the File menu, choose New, then Project...

  2. Go to the "Silverlight for Windows Phone" section and create a "Windows Phone Application" with the name of your choice

  3. From the Project menu, choose Add New Item...

  4. Select "Resources File" and name it AppResources.resx

  5. Add an entry for Name="Title" and Value="welcome"

  6. Open MainPage.xaml.cs and add the following to the end of the constructor to set the text of the existing PageTitle element:

    PageTitle.Text = AppResources.Title;
  7. Run the application to see the custom title in English:

    New application

 

Localize the application

Now let's localize the sample application so it uses the proper language for the user's settings. In the steps below, we'll add support for Spanish (via the "es" language code), but adding other languages is a simple matter of repeating these steps using the other language's code and translations. It's a little bit of effort, but it's all quite simple:

  1. In the Solution Explorer window, select AppResources.resx by clicking on it

  2. Press Ctrl+C, then Ctrl+V to create a copy of AppResources.resx

  3. Select Copy of AppResources.resx and press F2 to rename it to AppResources.es.resx

  4. Staying in the Solution Explorer window, right-click the project root node and choose Unload Project

  5. Right-click the project node again and choose Edit [ProjectName].csproj

  6. Change the existing SupportedCultures element to be:

    <SupportedCultures>es</SupportedCultures>
  7. Back in the Solution Explorer window, right-click the project node and choose Reload Project

  8. Open AppResources.es.resx and change "welcome" to "bienvenido"

  9. Run the application and verify the custom title now shows up in Spanish (no code changes necessary!):

    Localized application

 

Add the Windows Phone Toolkit project

Now we'll modify the sample application to reference the Windows Phone Toolkit. (You can read more about the Windows Phone Toolkit in my introductory post.) Rather than adding a binary reference to the Toolkit, we'll add a project reference and build the Toolkit code as part of the sample application (for reasons that will become clear soon):

  1. Go to http://silverlight.codeplex.com/ and download the "Silverlight for Windows Phone Toolkit Source & Sample" ZIP file

  2. Unblock the ZIP file (see the notes at the end of this post for instructions)

  3. Right-click the ZIP file and choose Extract All... to extract all files to the directory of your choice

  4. From the File menu in Visual Studio, choose Add, then Existing Project...

  5. Choose the Microsoft.Phone.Controls.Toolkit.csproj file found in the same-named directory of the extracted content

  6. Click on the application project node in Solution Explorer to make it active again

  7. From the Project menu, choose Add Reference...

  8. Switch to the Projects tab and pick the Microsoft.Phone.Controls.Toolkit project

  9. Press F6 and verify the solution builds both projects successfully:

    ------ Build started: Project: Microsoft.Phone.Controls.Toolkit, Configuration: Debug Any CPU ------
    ...
    ------ Build started: Project: LocalizedPhoneApplicationWithToolkit, Configuration: Debug Any CPU ------
    ...
    ========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ==========

 

Add some Windows Phone Toolkit controls

Now it's time to add two of those handy-dandy Windows Phone Toolkit controls (both covered in the aforementioned blog post): DatePicker and ToggleSwitch. We'll follow the usual steps for referencing third-party controls which should be pretty familiar to everyone:

  1. Open MainPage.xaml and switch to XAML view

  2. Add the following to the top of the file along with the other xmlns definitions:

    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
  3. Paste the following inside the empty "ContentPanel" Grid:

    <StackPanel>
        <toolkit:DatePicker/>
        <toolkit:ToggleSwitch/>
    </StackPanel>
  4. Optional: Follow the steps in the notes of my Windows Phone Toolkit introduction to add the DatePicker Application Bar icons to the project

  5. Run the application to see the text of the Toolkit controls in English:

    Toolkit ToggleSwitch Toolkit DatePicker

    Note: The day and month names are already in Spanish because they come from the operating system which knows to use the current language for dates. The Spanish-correct day/month/year formatting (vs. the United States default of month/day/year) is automatically provided by the DatePicker control.

 

Localize the Windows Phone Toolkit controls

Finally, we'll localize the Toolkit controls using the same process we did for the sample application itself:

  1. In Solution Explorer, expand the "Properties" folder for the Toolkit and click Resources.resx

  2. Press Ctrl+C, then Ctrl+V to create a copy of Resources.resx

  3. Select Copy of Resources.resx and press F2 to rename it to Resources.es.resx

  4. Open Resources.es.resx and change "CHOOSE DATE" to "ELIGE UNA FECHA", "cancel" to "cancelar", "done" to "listo", "Off" to "Desactivado", and "On" to "Activado"

  5. Run the application and verify the Toolkit control text is also in Spanish now:

    Localized Toolkit ToggleSwitch Localized Toolkit DatePicker

    Note: If you get the error Unable to copy file "obj\Debug\Microsoft.Phone.Controls.Toolkit.dll" to "..\Bin\Debug\Microsoft.Phone.Controls.Toolkit.dll". The process cannot access the file '..\Bin\Debug\Microsoft.Phone.Controls.Toolkit.dll' because it is being used by another process. when compiling, close all documents in Visual Studio via Window/Close All Documents, then restart Visual Studio itself.

 

At the end of the day, translating an application isn't just a nice thing to do, it's good business! We operate in a global marketplace and that means localized products can have a big advantage over their single-language-only competitors. While good localization can't save a bad application from itself, it can make a good application stand out. So please think about localization in your next marketplace application - your customers will appreciate it!

Gracias. :)

 

Notes:

  • People sometimes ask why the Windows Phone Toolkit (or the Silverlight Toolkit, for that matter) isn't already localized to the same set of languages the host platform supports. While I personally think it should be, doing so would take time and money and those two things can be in rather short supply at times. :) While I hope to see Toolkit localization become official some day, the good news for now is that things have been implemented such that they're easily localizable.

  • I've taken the quick/easy route of assigning the localized text resource directly to PageTitle.Text in the application's constructor, but that is not the recommended technique. Instead, what's generally preferred is what's described in the second half of the "Replacing hard-coded strings with strings in a resource file" section of the MSDN documentation: use a property Binding in XAML to reference the localized resources via a custom object as a StaticResource. It's a little unintuitive at first, but a very reasonable solution in practice. :)

  • After downloading the ZIP file for the Windows Phone Toolkit source code, you should "unblock" it before extracting its contents to avoid warnings from Visual Studio like Security Warning, You should only open projects from a trustworthy source. and The "ValidateXaml" task failed unexpectedly. System.IO.FileLoadException: Could not load file or assembly .... Unblocking is a simple matter of right-clicking on the ZIP file, choosing Properties from the context menu, clicking the Unblock button in the lower, right-hand corner of the resulting dialog, and hitting OK or Apply:

    Unblock button in Properties dialog

No rest for the weary [Free tool and source code to temporarily prevent a computer from entering sleep mode - now available for .NET, 32-bit, and 64-bit!]

It was over a year ago that I wrote and shared the first version of my Insomnia utility. A few months later, I published a new version of Insomnia to satisfy the two most popular feature requests. Here's how I explained Insomnia's motivation at the time:

The default power settings for Windows are set up so a computer will go to sleep after 15 to 30 minutes of inactivity (i.e., no mouse or keyboard input). This is great because a computer that's not being used doesn't need to be running at full power. By letting an idle machine enter sleep mode, the user benefits from a significant reduction in electricity use, heat generation, component wear, etc.. And because sleep mode preserves the state of everything in memory, it's quick to enter, quick to exit, and doesn't affect the user's work-flow. All the same applications continue running, windows stay open and where they were, etc.. So sleep mode is a Good Thing and I'm a fan.

However, sometimes a computer is busy even though someone isn't actively using the mouse and keyboard; common examples include playing a movie, burning a DVD, streaming music, etc.. In these cases, you don't want the machine to go to sleep because you're using it - even though you're not actually using it! So most media players and disc burners tell Windows not to go to sleep while they're running. In fact, there's a dedicated API for exactly this purpose: the SetThreadExecutionState Win32 Function.

But what about those times when the computer is busy doing something and the relevant program doesn't suppress the default sleep behavior? For example, it might be downloading a large file, re-encoding a music collection, backing up the hard drive, or hashing the entire contents of the disk. You don't want the machine to go to sleep for now, but are otherwise happy with the default sleep behavior. Unfortunately, the easiest way I know of to temporarily suppress sleeping is to go to Control Panel, open the Power Options page, change the power plan settings, commit them - and then remember to undo everything once the task is finished. It's not hard; but it's kind of annoying...

 

I've gotten a bunch of positive feedback on Insomnia and heard from a lot of people who use it for exactly the kinds of things I expected. (Thanks, everyone!) But I've also heard from a number of people who use Insomnia for a slightly different purpose: as an override of their machine's current power settings. For one reason or another, these people don't have control over their power configuration (perhaps because their domain enforces the relevant group policy), but still want to prevent their computer from going to sleep. This is usually because they want to connect to their machine remotely (ex: via file sharing or Remote Desktop) and can't do that if the machine is forced to sleep...

Insomnia application

These people tend to put a shortcut to Insomnia in their Startup group (click Start Menu, All Programs, Startup) and set the "Minimize" flag to automatically run Insomnia as an icon in the notification area whenever they log in. Because they leave Insomnia running all the time, their computer stays awake and they're able to use it whenever they need to. I've always thought this is a cool scenario - and felt that maybe a lower-overhead version of Insomnia would be particularly compelling here.

 

I wrote the original Insomnia using the .NET Framework - which meant it was super-easy to write and took practically no time or effort on my part. That's the way (uh-huh, uh-huh!) I like it, because the thing I have the least of is spare time and so anything that makes me more productive is full of win. And in my experience, .NET isn't just more productive, it's dramatically more productive.

That said, everything has a cost, and one of the common downsides of .NET is longer startup time and higher memory use. All the stuff .NET does for you (comprehensive APIs, high-level abstractions, garbage collection, etc.) takes extra time to load and extra memory to keep around. That said, I'll confidently suggest these costs are negligible in the majority of cases and the benefits of .NET (developer productivity, rich feature set, security, etc.) are overwhelmingly in its favor. But Insomnia is a little different than most programs: the application exists only as a wrapper for a single API (SetThreadExecutionState), so the people who run it all the time aren't really benefitting from the power of .NET...

Insomnia minimized to the notification area

I thought it might be fun to re-implement Insomnia in native code (vs. managed code) since it was such a simple program. I've done exactly that - and the result is that Insomnia is now available in three versions: .NET, 32-bit native, and 64-bit native!

 

[Click here to download all three flavors of Insomnia along with the complete source code for managed and native.]

 

Of course, while it was neat to do some Windows API coding for a change, the experience served to reinforce my belief in the fundamental productivity benefits of .NET and the power of the WPF/Silverlight layout system. About half the code in native Insomnia exists for the purpose of layout - which the .NET version accomplishes much more succinctly with XAML. The other half of the code handles basic framework-y stuff like creating a window, configuring it, etc. - more things WPF handles for you or makes quite convenient. And the last half of the code [ ;) ] deals with a variety of little things that are quite simple in the managed world, but require non-trivial effort in native code (ex: copying strings, setting up a complicated function call, etc.).

Please don't get me wrong; I completely agree that native code has its place in life (and there's a certain feeling of power one gets from using it). But I've had enough of that for now and will be happy to return to .NET for my next project. :)

 

Notes:

  • The most common question I get about Insomnia is whether it also disables the screen saver - it does not. When Insomnia is running, the screen saver will continue to kick in and turn off the monitor on its usual schedule. The difference is that the computer itself will not be allowed to enter sleep mode. Once the Insomnia window is closed, the computer can sleep again and will do so on its usual schedule.

  • The second most common question I get is why the Insomnia window stays above other windows. As I explained in the introductory post, this is so Insomnia is always visible when running and people will be less likely to accidentally leave their computers sleep-less by forgetting they've started it. This seems like the right behavior for the original "temporarily prevent sleep mode" scenario; for the "always on" scenario, minimizing Insomnia to the notification area seems to work well for everyone I've talked to.

  • My goal for the native version of Insomnia was to duplicate the functionality of the .NET version as closely as possible - not because I think the .NET version is perfect, but because it works well and it's what people are already familiar with. So while I wasn't obsessive about matching the font face and size exactly, a side-by-side comparison of the two programs shows a very strong correlation. :)

  • The one thing I didn't port from the .NET version was the "/minimize" command-line switch. Originally intended to make it easy for users to start Insomnia minimized, commenter rbirkby reminded me that Windows shortcuts already made that easy enough. It didn't seem necessary to duplicate this somewhat unnecessary feature, so I've omitted it to keep the native version just a bit simpler.

  • To start Insomnia minimized, just create a shortcut to it (right-click+drag+drop its icon somewhere (like the Start Menu / All Programs / Startup folder) then edit the shortcut's settings (right-click it and choose Properties) and choose "Minimized":

    Shortcut to start Insomnia minimized

    Alternatively, the following syntax will do the same thing from a batch file:

    start /MIN Insomnia.exe
  • Because the whole point of this exercise was to reduce Insomnia's run-time footprint, it's interesting to see how things worked out. Here's a table of the Resource Monitor statistics for each flavor as measured by opening and minimizing them all on my 64-bit Windows 7 machine:

    Flavor Commit (KB) Private (KB)
    .NET 66,972 17,940
    64-bit 2,144 1,876
    32-bit 1,652 1,332

    (Note that the .NET version uses somewhat less memory on a 32-bit version of Windows 7.)

  • Not only do the native versions of Insomnia use less memory - they start faster, too! Because they have fewer dependencies, there's simply less stuff to load from disk - and because disk access is (relatively) slow, minimizing it can do a lot to improve startup speeds.

  • Something that made life a little more pleasant for the native version was the SysLink common control - specifically its LM_GETIDEALSIZE message. This message is used to "Retrieve[s] the preferred height of a link for the control's current width." and it enabled me to approximate something kind of like WPF's measure/arrange-based layout system without having to write a bunch of code myself. So while I'd originally thought to use SysLink only for the hyperlink (duh!), I ended up using it for the version text and message as well!

  • As you'd expect, the Visual Studio solution/project for the new, native version of Insomnia uses the Visual Studio 2010 format. However, the solution/project for the original, managed version is still in the 2008 format I originally released it in (of course, opening it in VS 2010 automatically "upgrades" it).

Hash for the holidays [Managed implementation of CRC32 and MD5 algorithms updated; new release of ComputeFileHashes for Silverlight, WPF, and the command-line!]

It feels like a long time since I last wrote about hash functions (though certain curmudgeonly coworkers would say not long enough!), and there were a few loose ends I've been meaning to deal with...

Aside: If my hashing efforts are new to you, more information can be found in my introduction to the ComputeFileHashes command-line tool and the subsequent release of ComputeFileHashes versions for the WPF and Silverlight platforms.

 

When I first needed a managed implementation of the CRC-32 algorithm a while back, I ended up creating one from the reference implementation. Thanks to the strong similarities between C and C#, the algorithm itself required only minimal tweaks and the majority of my effort was packaging it up as a .NET HashAlgorithm. Because HashAlgorithm is the base class of all .NET hash functions, the CRC32 class ends up being trivial to drop into any .NET application that already deals with hashing.

ComputeFileHashesWPF

The Silverlight platform doesn't include an implementation of the MD5 algorithm like "desktop" .NET does, and I soon ended up creating an MD5 implementation from the reference code so I could support that algorithm on Silverlight (and now Windows Phone, too). Again, the C algorithm translated to C# fairly easily - though there's quite a lot more code for MD5 than CRC32 - and the HashAlgorithm base class makes it easy to reuse. Over the next few days, I made a couple of minor revisions to the CRC32 and MD5Managed classes, but have otherwise left things alone. I've used ComputeFileHashes successfully ever since, and things seemed to be in a pretty good state.

 

Then one day kind reader Maurizio contacted me (from Italy!) to report a bug in my CRC32 wrapper: there was a missing variable in a loop that could lead to problems if someone passed a non-0 value as the inputOffset parameter of TransformBlock. Fortunately, this isn't a particularly common scenario - the "primary" overload of ComputeHash doesn't do it, none of my ComputeFileHashes code does it, and most typical scenarios probably won't do it, either. That said, a bug is a bug (is a bug), and I made a note to fix it when I got a chance... And I finally had that chance last week! :)

C:\T>ComputeFileHashesCL.exe ZeroByteFile.txt

C:\T\ZeroByteFile.txt
100.0%
CRC32: 00000000
MD5: D41D8CD98F00B204E9800998ECF8427E
SHA1: DA39A3EE5E6B4B0D3255BFEF95601890AFD80709
SHA256: E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
SHA384: 38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898B95B
SHA512: CF83E1357EEFB8BDF1542850D66D8007D620E4050B5715DC83F4A921D36CE9CE47D0D13C5D85F2B0FF8318D2877EEC2F63B931BD47417A81A538327AF927DA3E
RIPEMD160: 9C1185A5C5E9FC54612808977EE8F548B2258D31

 

And as long as I was already messing with the ComputeFileHashes code (which meant recompiling for each platform, re-packaging, uploading, etc.), there were a few other things I decided to take care of at the same time. And it's just as well - in the process of doing so, I discovered (and fixed) a seemingly obscure bug in MD5Managed (which I suspect has never been hit in real life). Along the way, I added the complete suite of .NET HashAlgorithms to each tool so you'll automatically get the results of every supported algorithm when you hash a file!

 

ComputeFileHashes has been a fun project and a nice demonstration of how .NET lets you run the same code across a wide variety of environments and platforms. And the comprehensive automated test framework I added this time around makes me feel better about the correctness of these two HashAlgorithms. I deal with hashes regularly and have found all flavors of ComputeFileHashes to be handy tools to have around - especially the Silverlight version which brings simple, lightweight, install-free hashing to nearly every machine in the world. :)

 

[Click here to download the complete source code for the command-line, Silverlight, and WPF implementations of ComputeFileHashes along with the new test project.]

Click here or on the image below to run ComputeFileHashes in your browser with Silverlight 4:

ComputeFileHashesSL

Note: Bookmark the link above for easy access to hashing anytime, anywhere, on any machine!

 

Here are the major changes since last time:

  • CRC32 bug fix: HashCore was not adding the ibStart offset in its for loop. This is the issue Maurizio reported and would affect all scenarios where a non-0 value was passed for ibStart.

  • MD5Managed bug fix: MD5Update was not adding the inputIndex offset in its call to MD5Transform. This is the obscure issue found by the new test framework - in certain fairly specific circumstances (mostly around odd offsets and buffer sizes), the incorrect offset could result in an invalid hash result.

  • All supported algorithms are run on each file. Initially, only CRC32, MD5, and SHA1 were supported because they were the most common at the time and because I didn't want to waste CPU cycles on obscure algorithms that were hardly ever used. But since then, some of the "obscure" algorithms have become more common (ex: SHA256) and multi-core CPUs have become much more widespread. Because the ComputeFileHashes tools are already multi-processor friendly and because quad-processor CPUs are now commonplace, I've decided not to limit them due to CPU cycles. Most files hash instantaneously anyway, so the additional algorithms won't slow things down there; for longer files where CPU might start to dominate over disk access, the additional overhead shouldn't be that big of a deal. With this release, the bias is for convenience, and I'm optimistic that's the right tradeoff most of the time. :)

    Here's what each tool supports:

    • ComputeFileHashesCL, ComputeFileHashesWPF: CRC32, MD5, SHA1, SHA256, SHA384, SHA512, RIPEMD160
    • ComputeFileHashesSL: CRC32, MD5, SHA1, SHA256

    (Note: The Silverlight platform doesn't provide SHA384/SHA512/RIPEMD160. (And I haven't done my own implementation. (Yet...)))

  • There's a comprehensive set of automated tests for CRC32 and MD5Managed. I'd done some basic testing of this code in the past, but hadn't covered the edge cases - and a few bugs slipped by because of that. So I wanted to create a thorough automated test suite this time around and do what I could to cover all the bases.

    • The automated tests have a small library of different inputs and known-good hashes and process it in lots of different ways: all at once, in all different chunk sizes, with and without Initialize, etc.. If any of these techniques generates the wrong hash, the test suite reports the failure.
    • The new tests are thorough enough to yield 100% code coverage on both the CRC32 and MD5Managed implementations.
    • In addition to testing my CRC32 and MD5Managed classes, the automated tests also test the .NET MD5CryptoServiceProvider class - not because I expect to find errors in it, but so I can ensure both MD5 implementations behave the same in each scenario.
    • Consequently (and as a result of the more thorough coverage) CRC32 and MD5Managed now behave the same as the .NET implementations for invalid scenarios, too - all the way to exception-level compatibility for misuse of the API!
    • The only difference is for CryptographicUnexpectedOperationException which can't be constructed on Silverlight - its base class CryptographicException is thrown instead in cases where the hash value is retrieved before the process has been finalized.
  • The Visual Studio solution and project files have been upgraded to the Visual Studio 2010 format. This makes developing ComputeFileHashes in Visual Studio 2010 easy and enables the use of its new and improved feature set.

  • The CRC32/MD5Managed classes and the three ComputeFileHashes programs are now code analysis-clean for the complete Visual Studio 2010 rule set. Additional code analysis rules were introduced with VS 2010 and they reported some new violations in the code. These have all been fixed (or suppressed where appropriate).

  • The namespace of the CRC32 and MD5Managed classes has been changed to "Delay". This change brings these classes inline with the rest of the sample code I publish and makes their generality a bit clearer.

  • ComputeFileHashesCL remains a .NET 2.0 application for maximum versatility. By targeting .NET 2.0, ComputeFileHashesCL runs nearly everywhere .NET does.

  • ComputeFileHashesWPF is now a .NET 4 application for compactness and ease of distribution. ComputeFileHashesWPF used to have a dependency on the WPFToolkit for its DataGrid control. Because that control is part of the .NET 4 Framework, the new ComputeFileHashesWPF no longer depends on any non-Framework assemblies and can be distributed as a single file.

  • ComputeFileHashesSL is now a Silverlight 4 application to make use of new features in that platform. Most notably, ComputeFileHashesSL uses Silverlight's drag+drop support to enable the handy scenario of dragging a file directly from Windows Explorer and dropping it onto the ComputeFileHashesSL window to hash it (just like ComputeFileHashesWPF already supported). Additionally, I'm making use of my SetterValueBindingHelper class to use Bindings in a Setter and create the same ToolTip experience that ComputeFileHashesWPF already had: hovering over a hash failure shows the reason for the failure (typically because the file was locked by another process). Consequently, the "Details" column is no longer necessary in ComputeFileHashesSL and has been removed.

  • The ClickOnce flavor of ComputeFileHashesWPF is no longer supported. With ComputeFileHashesSL's functionality getting closer to that of ComputeFileHashesWPF and the elimination of the WPFToolkit.dll dependency from ComputeFileHashesWPF, the need for a ClickOnce install seems minimal and has been removed.

  • The version number has been updated to 2010-11-30.