29 March 2012

Attached behaviors for Windows 8 Metro Style XAML

This post was updated substantially at March 31, 2012

Regular readers of my blog know that there are some recurring themes: MVVM, maps and behaviors. I am a big fan of using behaviors ever since I learned how to use this rooting trough the sources of MVVMLight. When I saw the BUILD videos I was elated. I saw a great merger of the best things of Windows and  Windows Phone 7 styles and I knew I was going to get on board too. Five months later, I found myself being an MVP and on the Microsoft Campus of all places, and got a pretty unpleasant surprise: a lady presenting the new Expression Blend version said there would be no behaviors in Windows 8 Metro Style XAML. I was quite disappointed at the time. I still think it’s is quite an omission, but then again, when it’s not your deadline it’s always easy to criticize others.

And then for some reason, this week, I remembered a single line from a presentation by Laurent Bugnion on the 2012 Microsoft Techdays in The Hague. “You can’t use behaviors but you can use attached dependency properties”. It kept reverbing trough my brain for a few moments.

“Use the Force, Luke” ;-)

And the result is this. It’s crude, it’s clumsy, it has no Blend support, but it works, more or less – I have been able to port my DragFlickBehavior to Windows 8 and it bloody works, too. This blog post will be split in two parts: in this part, I will show how to make a behavior in Windows 8 XAML in general, and in a next one I will specifically show the DragFlickBehavior itself.

First of all, the behavior class itself:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;

namespace WinRtBehaviors
{
  public abstract class Behavior : DependencyObject
  {
    private FrameworkElement associatedObject;
    public FrameworkElement AssociatedObject
    {
      get
      {
        return associatedObject;
      }
      set
      {
        if (associatedObject != null)
        {
          OnDetaching();
        }
        associatedObject = value;
        if (associatedObject != null)
        {
          OnAttached();
        }
      }
    }

    protected virtual void OnAttached()
    {
      AssociatedObject.Unloaded += AssociatedObjectUnloaded;
    }

    protected virtual void OnDetaching()
    {
      AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
    }

    void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
    {
      OnDetaching();
    }
  }
}

This is partially ‘borrowed’ from the Windows Phone System.Windows.Interactivity.dll, courtesy of Reflector.  I don’t have a real ‘detached’ event so I’ve decided to call the “OnDetaching’ method when the FrameworkElement is unloaded. Gotta use what’s available, right? The next class, which is the typed version of AttachedBehavior, is also courtesy of Reflector:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;

namespace WinRtBehaviors
{
  public abstract class Behavior<T> : Behavior 
    where T : FrameworkElement
  {
    protected Behavior()
    {
    }

    public T AssociatedObject
    {
      get
      {
        return (T)base.AssociatedObject;
      }
      set
      {
        base.AssociatedObject = value;
      }
    }
  }
}

I’ve closely followed naming conventions as used in Windows Phone and Silverlight, but I took a different root namespace “WinRtBehaviors”. Should the Windows 8 team decide to add behaviors to the API in the future, removing this classes and changing the namespaces should do the trick

Finally there is this pretty crazy piece of code, which is basically a giant Attached Dependency property. This connects the ‘behaviors’ to the FrameworkElements:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.ApplicationModel;

namespace WinRtBehaviors
{
  /// <summary>
  /// Attached dependency property storing 'behaviors'
  /// </summary>
  public static class Interaction
  {
    public static readonly DependencyProperty BehaviorsProperty =
       DependencyProperty.RegisterAttached("Behaviors",
       typeof(ObservableCollection<Behavior>),
       typeof(Interaction),
       new PropertyMetadata(
         DesignMode.DesignModeEnabled ? new ObservableCollection<Behavior>() : null,         
       BehaviorsChanged));


    /// <summary>
    /// Called when Property is retrieved
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static ObservableCollection<Behavior> GetBehaviors(DependencyObject obj)
    {
      var associatedObject = obj as FrameworkElement;
      var behaviors = obj.GetValue(BehaviorsProperty) as ObservableCollection<Behavior>;
      if (behaviors == null)
      {
        behaviors = new ObservableCollection<Behavior>();
        SetBehaviors(obj, behaviors);
      }

      return behaviors;
    }

    /// <summary>
    /// Called when Property is retrieved
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="value"></param>
    public static void SetBehaviors(
       DependencyObject obj,
       ObservableCollection<Behavior> value)
    {
      obj.SetValue(BehaviorsProperty, value);
    }

    /// <summary>
    /// Called when the property changes
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private static void BehaviorsChanged(
     object sender,
     DependencyPropertyChangedEventArgs args)
    {
      var associatedObject = sender as FrameworkElement;
      if (associatedObject != null)
      {
        var oldList = args.OldValue as ObservableCollection<Behavior>;
        if (oldList != null)
        {
          foreach (var behavior in oldList)
          {
            behavior.AssociatedObject = null;
          }
        }

        var newList = args.NewValue as ObservableCollection<Behavior>;
        if (newList != null)
        {
          foreach (var behavior in newList)
          {
            behavior.AssociatedObject = sender as FrameworkElement;
          }
          newList.CollectionChanged += (collectionSender, collectionArgs) =>
          {
            switch (collectionArgs.Action)
            {
              case NotifyCollectionChangedAction.Add:
                {
                  foreach (Behavior behavior in collectionArgs.NewItems)
                  {
                    behavior.AssociatedObject = associatedObject;
                  }
                  break;
                }
              case NotifyCollectionChangedAction.Reset:
              case NotifyCollectionChangedAction.Remove:
                {
                  foreach (Behavior behavior in collectionArgs.NewItems)
                  {
                    behavior.AssociatedObject = null;
                  }
                  break;
                }
            }
          };
        }
      }
    }
  }
}

So what do we have here? On top, a pretty standard way of registering an attached dependency property – an ObservableCollection of Behavior. Notice the fact the initial value is null in runtime, but an empty collection in design time. This is because of the next part, the mandatory GetBehaviors method. This is normally ‘just a getter’, but it checks if the collection is null first. And then something interesting happens:

  • If it is null, it creates a new empty collection and initializes the attached dependency property itself with it.
  • That, in turn, fires BehaviorsChanged
  • BehaviorsChanged attaches an internal anonymous method to the ObservableCollectionChanged event of the behavior collection.
  • That anonymous method basically rams the FrameworkElement to which this ObservableCollection is attached in the AssociatedObject property of every new behavior that’s added the list.
  • This will fire the overrideable OnAttached method in the bavhior and boom – your behavior is ready to go.

The SetBehaviors method then is pretty standard. The basic pattern of a behavior is then something like this:

namespace Win8nl.Behaviors
{
  public class DragFlickBehavior : AttachedBehavior<FrameworkElement>
  {
    protected override void OnAttached()
    {
      // Do something
      base.OnAttached();
    }
    protected override void OnDetaching()
    {
      // Do something
      base.OnDetaching();
    }
  }
}

Which, not entirely by accident, looks quite a lot like an behavior looks in Windows Phone or Silverlight. And you call it in XAML like this:

<Page
  x:Class="Catchit8.BlankPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:Catchit8"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:Win8nl_Behaviors="using:Win8nl.Behaviors"
  xmlns:WinRtBehaviors="using:WinRtBehaviors"
  mc:Ignorable="d">

  <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
    <TextBlock HorizontalAlignment="Left" Margin="503,213,0,0" TextWrapping="Wrap" 
   VerticalAlignment="Top" FontSize="18" Text="Drag me">
      <WinRtBehaviors:Interaction.Behaviors>
         <Win8nl_Behaviors:DragFlickBehavior BrakeSpeed ="5"/>
      </WinRtBehaviors:Interaction.Behaviors>
    </TextBlock>
    <Button Content="Drag me too!" HorizontalAlignment="Left" Margin="315,269,0,0" 
   VerticalAlignment="Top" >
      <WinRtBehaviors:Interaction.Behaviors>
          <Win8nl_Behaviors:DragFlickBehavior BrakeSpeed ="5"/>
      </WinRtBehaviors:Interaction.Behaviors>
     </Button>

  </Grid>
</Page>

Like I said: crude, clumsy and no Blend support. There are a few issues with it. The anonymous method doing all the work, is never detached. I wonder how much memory leaks this will produce. But at least I can move forward now porting a lot of stuff I made for Windows Phone to Windows 8. Unfortunately, contrary to what I hoped, data binding to dependency properties of the behavior itself does not seem to work yet (thanks to Filip Skakun for pointing that out in a reaction to that this post) [It does now, see below]. I hope people smarter than me can improve this to a possible better solution. I will soon post a demo solution with the DragFlickBehavior in working condition in it, after I have traced back how I got it working it the first place.

In the mean time, I’ve started an CodePlex project that will be the home of this stuff. I was initially planning of including it in my Win8nl CodePlex library (coming soon) but after careful consideration and advice, I decided to make a separate library. After all, if people would like to go ahead and expand this, adding triggers and whatnot, it would probably interfere with my own ‘hobby’ library and vice versa.

So Metro, meet behaviors, at “WinRtBehaviors”.

Very much thanks to Geert van Horrik for the suggestions that led to the improvement of my first version.  He is as of now registered as a developer on WinRtBehaviors.

Update 04-04-2012 The WinRtBehaviors library is adapted: a) it no longer introduces memory leaks as described, (thanks to Geert van Horrik), and b) Filip Skakun provided me with code to actually enable data binding.

25 March 2012

Instant language update in a Windows Phone 7 application using MVVM

With more and more Marketplaces being added to the Windows Phone ecosystem, globalization of your app becomes all the more more important. I am happy to see a lot of apps these days support multiple languages. I usually employ a solution described in my article MVVMLight based language selection for Windows Phone 7 – this automatically picks up the language from the phone and defaults to English/US if the default language is not supported by the app. But I also like to give the user the chance to override the automatically selected language. I, for instance, run the phone OS in English/US, but I’d like specific Dutch apps to run in Dutch, thank you. If you follow the globalization example as described by MSDN using my code, that unfortunately requires the app to be restarted after applying the language change. Well, no more!

The sample as provided by MSDN provides a localized string helper that looks more or less like this:

namespace InstantLanguage.Resources
{
  public class LocalizedStrings
  {
    public LocalizedStrings()
    {
    }
  
    private static AppResources localizedResources = new AppResources();

    public AppResources LocalizedResources 
    { 
      get { return localizedResources; } 
    }
  }
}

I employed it since my first localized app, and I usually put it in the same directory as my resource files. You declare it in your App.xaml like this:

<Application.Resources>
    <!-- More resources -->
    <Resources:LocalizedStrings x:Key="LocalizedStrings"/>
</Application.Resources>
Of course you need to declare the namespace for LocalizedResources first, so in the top you will add something like
xmlns:Resources="clr-namespace:InstantLanguage.Resources"

And now you can use it to show localized strings by binding to it like this:

<TextBlock x:Name="ApplicationTitle" 
  Text="{Binding LocalizedResources.AppTitle, Source={StaticResource LocalizedStrings}}" 
  Style="{StaticResource PhoneTextNormalStyle}"/>

But unfortunately, as I said, if you change the language, for instance by calling this code, nothing happens.

Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;

The reason for this is pretty simple: the app has no way of informing the GUI, as LocalizedStrings has no support for INotifyPropertyChanged. But that can be easily fixed, by taking MVVMLight and implement LocalizedStrings as a child class of ViewModelBase:

using System.Windows;

namespace InstantLanguage.Resources
{
  using GalaSoft.MvvmLight;

  public class LocalizedStrings : ViewModelBase
  {
    private static AppResources localizedresources = new AppResources();

    public AppResources LocalizedResources
    {
      get { return localizedresources; }
    }

    public void UpdateLanguage()
    {
      localizedresources = new AppResources();
      RaisePropertyChanged(() => LocalizedResources);
    }

    public static LocalizedStrings LocalizedStringsResource
    {
      get
      {
        return Application.Current.Resources["LocalizedStrings"]
            as LocalizedStrings;
      }
    }
  }
}

and then there is another matter: the app needs to have a way of kicking off the RaisePropertyChanged event on a class used as a StaticResource. That’s what UpdateLanguage and the static LocalizedStringsResource are for. Note that for this to work, you will need to have defined the resource with the key LocalizedStrings:

<Resources:LocalizedStrings x:Key="LocalizedStrings"/>

The keys that need to match are marked in red in both pieces of code. Anyway, after you changed the language, call:

LocalizedStrings.LocalizedStringsResource.UpdateLanguage();

And boom – all your texts coming from the resource file will update instantaneously.

InstantLanuage

Here you can find a sample solution demonstrating this principle using the newest release of my #wp7nl library on codeplex and the technique I mentioned earlier. There’s a simple viewmodel subclassing LanguageSettingsViewModel, and you can see the call to UpdateLanguage directly after the language setting has been changed:

using System.ComponentModel;
using InstantLanguage.Resources;
using Wp7nl.Globalization;

namespace InstantLanguage.ViewModel
{
  /// <summary>
  /// Main view model. By subclassing LanguageSettingsViewModel we get properties 
  /// "SupportedLanguages" and "CurrentLanguage" for free. 
  /// </summary>
  public class MainViewModel : LanguageSettingsViewModel
  {

    public MainViewModel()
    {
      // Note: en-US is added by default
      AddLanguages(new Language 
	    { Description = "Deutsch", Locale = "de-DE" });
      AddLanguages(new Language 
	   { Description = "Nederlands", Locale = "nl-NL" });
      PropertyChanged += MainViewModelPropertyChanged;
    }

    void MainViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      if (e.PropertyName == "CurrentLanguage")
      {
        SetLanguageFromCurrentLocale();
        LocalizedStrings.LocalizedStringsResource.UpdateLanguage();
      }
    }
  }
}

In the end, it’s always pretty simple.

Note that for this globalization and the code to work properly, you will need to take into account the following:

  • You will need to change the “Custom Tool” property for each resource file to “PublicResXFileCodeGenerator” (default is ResXFileCodeGenerator)
  • You will need to have a .resx file for every language you support. For the default language I call mine usually AppResources.resx, for German I then have to define AppResources.de-DE.resx, etc.
  • You will also need to add supported extra languages in the SupportedCultures tag in your main project. You need to do manually, by opening the project file in for instance notepad. Why this can’t be done from Visual Studio – don’t ask me, I’m just the messenger here ;-). To support Dutch and German for instance you change the tag’s contents to:
<SupportedCultures>nl-NL;de-DE</SupportedCultures>

Note: I got the idea from a gentleman I met at the 2012 edition of the Microsoft Techdays in The Hague while while I was manning at the Ask The Expert stand. He showed me it could be done, but did only briefly showed me some of code and certainly not all of it. Once I knew it could be done, I more or less pieced this together in the moments I was not being bombarded by questions from developers dropping by. Unfortunately I don’t remember the gentleman’s name, or else I would have added some credits.

Have fun! I hope this helps you storm the new Marketplaces! ;-)

04 March 2012

MVP Summit 2012–the day after

So here I am, back in the Netherlands, after the MVP Summit 2012, still quite dazed from what hit me. It was my very first summit, and I was not quite sure what to expect. In retro respect the most amazing thing was what might be called ‘super Tuesday’. I spent a whole day with my fellow Windows Phone MVP’s and the product team. Suddenly all the people I only conversed with on twitter or live messenger were in one room: people like Ginny Caugey (I finally now know how to pronounce her last name), Matt Hidinger, Den ‘DennisCode’ Delimarsky (he actually is recognizable from his XBox avatar), Atley Hunter (who I think deserves the nickname ‘Fast Forward’) , Nick ‘ActiveNick’ Landry, Peter Novak, Rudy Huyn, to name just a few – and we where joined by people of the product team, which included Cliff Simpkins, Ben Lower and a few more whose name I omit because I am not even sure if I can mention them without spilling some beans. I feel a bit lame about this, but I like to err on the side of caution. For I must admit that when I got the award and was requested to sign an NDA documents it felt a bit over the top because frankly, I haven’t been seeing very much that required NDA for since October. Up until that remarkable Tuesday. I cannot go into any detail, other than that it was somewhat of a bewildering experience. For obvious reasons Microsoft are keeping their cards very close to the chest about anything related to Windows Phone, and this was quite different. To say the contents were interesting is like calling Mount Everest a ‘pretty steep hill’. After the formal sessions there was also an off-site event, which was at the Lucky Strike Lanes in Bellevue, where we got to know each other in a more informal way. I am still not sure who are the loudest, Canadians or Michiganians, but they give each other a run for the decibels ;-)

Another interesting observation I made during some sessions over the week was related to the recent row on the internet about MVP’s being Microsoft “marketing puppies”. Let me tell you this: behind doors, Microsoft asks for feedback and boy, do they get feedback from their MVP’s. A lot of typing and/or scribbling is being done while this is going on. Debates sometimes get pretty heated, but the setting is always a polite, frank exchange of ideas. Microsoft listens, listens intensely, and not only this occasion. But I feel a lot depends on how you put it in words. Think about what you would like to achieve before you spill your frustrations in a four-letter-words rant on the internet next time.

So what did I take from this occasion?

  1. A lot of solid information. I was told this differs from product group to product group; Windows Phone being pretty new and making aggressive movements forward, inherently is a hot spot where a lot is going on.
  2. Faces, names, and contact cards. It’s unbelievable how much people you meet. Being open to others is a natural things amongst most MVP’s – or else you would not be an MVP in the first place – and conversations start pretty easy, even for people who are bit contact shy like me. And it’s unbelievable how, in this time of online living, things are still different after you have shared a drink or talked a couple of hours into the evening. Apart from the people I mentioned above I met Rene Schulte, Laurent Bugnion, Scott Guthrie (thanks Laurent!), Scott Hanselman (sorry I pretended mixing you up with The Gu ;) ), Micheal Crump (he does exist, and I have pictures to prove it), Morten Nielsen, András Velvárt, Dave Bost, Chris Koening (both Windows Phone 7 DPE, equivalent to our own Matthijs Hoekstra), Davide Zordan (thanks for the Win8 copy!), David 'Wynapse' Campbell and I finally got the see our expertise group contact Tracey Hackney. They now have faces and voices. That still counts.
  3. Too little sleep, a crumpled back from spending too much time in a cramped aircraft chair and if I am not mistaken, a severe cold or a flue in development ;-)

Simply put: this was definitely worth the trouble.

Now that I’ve had - and survived ;-) - my first Summit, I think I have some advice for next-time-newbies:

  1. Try to get into one of the ‘central’ hotels: Hyatt, Westin, Silver Cloud or Courtyard. The other hotels are hotel-wise just a good but a lot more away from the ‘action’. Microsoft organizes a good shuttle service – but that stops at 9pm and I can assure you most times you are not done at that time. And then you have to get a cab or something.
  2. When you need to be on the campus early in the morning, allow for an hour travelling time. It’s actually more like 20 minutes, but you have to allow for traffic jams or the fact that the bus sometimes needs to take a tour along other hotels. And you do want to be on time. Some people weren’t in time for some special event organized by our Dutch MVP lead and simply missed it. Set an alarm clock, take a strong coffee (or tea) and get outta there.
  3. There’s usually a first-timers event early on the first evening. I found it useful to drop by. There were some Microsoft people and seasoned MVP’s talking with us newbies to get us in the mood. On the practical side, there’s food as well.
  4. Prepare. The Schedule Builder is sometimes confusing and I ran into a problem getting the app on my phone to run, which I only noticed being on-site. Hook up with some seasoned MVP’s up front and ask advice as to which side sessions and/or side parties to go to. I did not, and I missed a few things because of that.
  5. Download the MS Campus Maps app on your Windows Phone. It’s indispensible for getting around and getting directions.
  6. Download My ContacTile  on your Windows Phone – it creates a QR code of your contact info to quickly share via Bing Vision.
  7. Hotel rooms are for storing suitcases, taking a quick shower and some essential sleeping. That’s all. Don’t spend time there. Although most expenses during the Summit are covered I think I paid about €800 to fly to the USA and stuff so I made sure I got the most out of it. I figured I would pay the fatigue price later (like now :-) ).
  8. Bring power. Have a charger with you all the time and/or buy yourself a simple ‘USB juice pack’. It’s also a great way to help out fellow MVP’s who did not bring one, so they can borrow yours. A great conversation starter if any.
  9. When you are in a shuttle bus and get to sit next to a stranger, or at the breakfast room: introduce yourself and talk. Ask after their expertise and what they are working on. I was able to help out two people by showing them a blog post I wrote.
  10. The attendee party is big and spectacular and usually the last occasion to meet everyone. Do go there and have fun.
  11. Especially on the first days you want to be sharp. Keep down the booze and try to get a least some sleep.
  12. Keep your NDA. Watch your use of social media. Don’t discuss things outside your product group or in public places. The hardest part is not to correct incorrect speculations or react with body language ;-). I resorted to “I’d rather talk about something else now” at one point. It made me respect the product group members who have to make this mental juggle all the time very much.

Some special advice to MVP’s from outside the USA:

  1. Make sure you have enough cash on you to be able to tip people. That’s what you do in the USA. Familiarize yourself with the unwritten rules. 10% is ok, 15% if you are really satisfied. Cafeteria and such sometimes don’t expect a tip to be handed over directly but then there is usually a kind of can where you can drop the change. It’s not always obvious to me.
  2. Even more important: make sure you have a credit card. You don’t exist without a credit card in the USA.
  3. There is free Wi-Fi almost everywhere but for whatever deity you care to believe in’s sake, get yourself a $25 AT&T 500mb data plan so you are online all the time so you can find your fellow MVP’s or use online maps and stuff like that. Also, it’s great for keeping contact with the home front. AT&T first tests your phone if it works at all and even configures it before they charge you.
  4. Buses and light trail in Seattle are dirt cheap. Inside the city you pay about $2.25 for a trip from any given place to another and if you get back within a few hours, the return trip is free. The 550 bus from downtown to Bellevue costs $2.50. It partly runs inside the light rail tunnel, that’s why I couldn’t find it at first. There is also the 560 bus from SEA-TAC to Bellevue. Same price.
  5. Visit the Microsoft Store in the Bellevue mall. You won’t believe your eyes. Think hard before you take your credit card along ;-)
  6. Bring an umbrella. Seattle weather is erratic at best of times, but in February you can get everything from sunny weather to snow an back again in one day. People from the Netherlands or the UK just have to pretend they prepare for the weather at home ;-)