Windows Phone 7 LongListSelector Sample with Accents and Culture support

Recently I’ve made a few posts about how to support different cultures and accents when using the Windows Phone 7 Long List Selector from the Windows Phone Toolkit. So rather than try and explain each bit I decided to finally publish the code showing how the MSDN sample can be altered to support both accented groupings and cultures;

https://wp7llssample.codeplex.com/

The sample is provided as-is, the code is mostly from MSDN with a few tweaks. You should not accept this as production ready code, please carry out your own tests.

Accented grouping

Capture

Japanese grouping

JapaneseGrouping

Japanese Jump List

JapaneseJumpList

Windows Phone Jump List groupings

In a recent post about displaying a longlistselector in wp7 I eluded to code that would need to be correctly localised. Whilst I haven’t had the time to produce the code, I thought this may prove useful in the meantime (Note: the cultures are from wp8 and may change in future releases);
(Edit) – made it a bit easier to consume, shows phonetic support and corrected issue with the a poor cut that chopped the last character

{“sq-AL”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“ar-SA”,”ابتثجحخدذرزسشصضطظعغفقكلمنهوي…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“Az-Latn-AZ”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“be-BY”,”абвгѓґдђеёєжзѕиїјкќлљмнњопрстћуўфхцччџшщъыьэюя…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“bg-BG”,”абвгѓґдђеёєжзѕиїјкќлљмнњопрстћуўфхцччџшщъыьэюя…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“ca-ES”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“zh-CN”,”#ABCDEFGHIJKLMNOPQRSTUVWXYZ…”}, \\ SupportsPhonetics False
{“zh-TW”,”ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩ…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“hr-HR”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“cs-CZ”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“da-DK”,”#abcdefghijklmnopqrstuvwxyzæøå…”}, \\ SupportsPhonetics False
{“nl-NL”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“en-US”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“en-GB”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“et-EE”,”#abcdefghijklmnopqrsšzžtuvwõäöũxy…”}, \\ SupportsPhonetics False
{“fil-PH”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“fi-FI”,”#abcdefghijklmnopqrstuvwxyzåäö…”}, \\ SupportsPhonetics False
{“fr-FR”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“de-DE”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“el-GR”,”αβγδεζηθικλμνξοπρστυφχψω…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“he-IL”,”אבגדהוזחטיכלמנסעפצקרשת…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“hi-IN”,”अआइईउऊऋएऐऑओऔकखगघचछजझटठडढणतथदधनपफबभमयरलवशषसह…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“hu-HU”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“id-ID”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“it-IT”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“ja-JP”,”アカサタナハマヤラワ…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics True
{“kk-KZ”,”абвгѓґдђеёєжзѕиїјкќлљмнњопрстћуўфхцччџшщъыьэюя…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“ko-KR”,”ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎ#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“lv-LV”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“lt-LT”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“mk-MK”,”абвгѓґдђеёєжзѕиїјкќлљмнњопрстћуўфхцччџшщъыьэюя…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“ms-MY”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“nb-NO”,”#abcdefghijklmnopqrstuvwxyzæøå…”}, \\ SupportsPhonetics False
{“fa-IR”,”ابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“pl-PL”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“pt-BR”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“pt-PT”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“ro-RO”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“ru-RU”,”абвгдеёжзийклмнопрстуфхцчшщъыьэюя…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“sr-Latn-CS”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“sk-SK”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“es-ES”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“es-MX”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“sv-SE”,”#abcdefghijklmnopqrstuvwxyzåäö…”}, \\ SupportsPhonetics False
{“th-TH”,”กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“tr-TR”,”#abcçdefgğhıijklmnoöprsştuüvyz…”}, \\ SupportsPhonetics False
{“uk-UA”,”абвгѓґдђеёєжзѕиїјкќлљмнњопрстћуўфхцччџшщъыьэюя…#abcdefghijklmnopqrstuvwxyz”}, \\ SupportsPhonetics False
{“uz-Latn-UZ”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False
{“vi-VN”,”#abcdefghijklmnopqrstuvwxyz…”}, \\ SupportsPhonetics False

How to use wp7 LongListSelector with Blend Sample Data

In my previous post, How to use Blend sample data with a LongListSelector, I described a a step by step approach to allow Blend sample data to be used with the Wp8 LongListSelector. It makes development a lot easier. However, recently I’ve seen people struggling with the Windows Phone 7 toolikit version and so I thought I’d create this little amendment to support wp7.

  1. Follow the wp8 guide from the link above, it won’t compile but don’t worry we’ll correct it
  2. Replace the magic alpha key class with the following (it has some room for improvement, I’ll get to that later);
     
    
    using System.Collections.Generic;
    using System.Globalization;
    
    namespace LLSSample
    {
        public class AlphaKeyGroup<T> : List<T>
        {
            static string SortedLocalGrouping = "#abcdefghijklmnopqrstuvwxyz";
            /// <summary>
            /// The delegate that is used to get the key information.
            /// </summary>
            /// <param name="item">An object of type T</param>
            /// <returns>The key value to use for this object</returns>
            public delegate string GetKeyDelegate(T item);
    
            /// <summary>
            /// The Key of this group.
            /// </summary>
            public string Key { get;  set; }
    
            
            /// <summary>
            /// Public constructor.
            /// </summary>
            /// <param name="key">The key for this group.</param>
            public AlphaKeyGroup(string key)
            {
                Key = key;
            }
    
            public override string ToString()
            {
                return Key;
            }
            /// <summary>
            /// Create a list of AlphaGroup<T> with keys set by a SortedLocaleGrouping.
            /// </summary>
            /// <param name="slg">The </param>
            /// <returns>Theitems source for a LongListSelector</returns>
            private static List<AlphaKeyGroup<T>> CreateGroups(string nameList)
            {
                List<AlphaKeyGroup<T>> list = new List<AlphaKeyGroup<T>>();
    
                foreach (char key in nameList.ToCharArray() )
                {
                    list.Add(new AlphaKeyGroup<T>((key.ToString())));
                }
    
                return list;
            }
    
            /// <summary>
            /// Create a list of AlphaGroup<T> with keys set by a SortedLocaleGrouping.
            /// </summary>
            /// <param name="items">The items to place in the groups.</param>
            /// <param name="ci">The CultureInfo to group and sort by.</param>
            /// <param name="getKey">A delegate to get the key from an item.</param>
            /// <param name="sort">Will sort the data if true.</param>
            /// <returns>An items source for a LongListSelector</returns>
            public static List<AlphaKeyGroup<T>> CreateGroups(IEnumerable<T> items, CultureInfo ci, GetKeyDelegate getKey, bool sort)
            {
    
                List<AlphaKeyGroup<T>> list = CreateGroups(SortedLocalGrouping);
    
                foreach (T item in items)
                {
                    int index = 0;
                    
                    {
                        string label = getKey(item);
                        index = SortedLocalGrouping.IndexOf(label[0].ToString().ToLower());
                    }
                    if (index >= 0 && index < list.Count)
                    {
                        list[index].Add(item);
                    }
                }
    
                if (sort)
                {
                    foreach (AlphaKeyGroup<T> group in list)
                    {
                        group.Sort((c0, c1) => { return ci.CompareInfo.Compare(getKey(c0), getKey(c1)); });
                    }
                }
    
                return list;
            }
    
        }
    }
    
  3. That’s enough, you can carry on following your favourite wp7 LLS tutorial. However, I’ve included the XAML from my sample too;
     
        <phone:PhoneApplicationPage.Resources>
            <DataTemplate x:Key="AddrBookItemTemplate">
                <StackPanel VerticalAlignment="Top">
                    <TextBlock FontWeight="Bold"  Text="{Binding FirstName}" />
                    <TextBlock Text="{Binding LastName}" />
                    <TextBlock Text="{Binding Address}" />
                    <TextBlock Text="{Binding Phone}" />
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="AddrBookGroupHeaderTemplate">
                <Border Background="Transparent" Padding="5">
                    <Border Background="{StaticResource PhoneAccentBrush}" BorderBrush="{StaticResource PhoneAccentBrush}" BorderThickness="2" Width="62" 
             Height="62" Margin="0,0,18,0" HorizontalAlignment="Left">
                        <TextBlock Text="{Binding Key}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="48" Padding="6" 
                FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
                    </Border>
                </Border>
            </DataTemplate>
            <DataTemplate x:Key="GroupItemsTemplateWp7">
                <Border Background="{StaticResource PhoneAccentBrush}" Margin="{StaticResource PhoneTouchTargetOverhang}" Padding="{StaticResource PhoneTouchTargetOverhang}">
                    <TextBlock Text="{Binding Key}" Style="{StaticResource PhoneTextLargeStyle}"/>
                </Border>
            </DataTemplate>
            <ItemsPanelTemplate x:Key="GroupItemsPanelTemplate">
                        <toolkit:WrapPanel Orientation="Horizontal"/>   
            </ItemsPanelTemplate>
        </phone:PhoneApplicationPage.Resources>
    
     
    <toolkit:LongListSelector
                      x:Name="AddrBook"
                      IsFlatList="False"
                      DisplayAllGroups="True"
                      DataContext="{Binding Source={StaticResource LongListSelectorData}}"
                      ItemsSource="{Binding DataSource}"
                      Background="Transparent"
                      GroupHeaderTemplate="{StaticResource AddrBookGroupHeaderTemplate}"
                      ItemTemplate="{StaticResource AddrBookItemTemplate}" GroupItemTemplate="{StaticResource GroupItemsTemplateWp7}" GroupItemsPanel="{StaticResource GroupItemsPanelTemplate}"
                      />
    
    
  4. Enjoy design time editing in wp7 too 🙂

Ah, about the differences. The original alpha key class uses some nice localisation tricks that provides the jump list and group headers to be correctly grouped depending upon the users culture. This version is stuck with a hard-coded English character set. I’ve left the skeleton of the code the same as the wp8 version so you can implement your own localisation routines. If I have time I’ll publish that a later date. Hopefully there should be enough there to help you get on.

Discovering what Data Item the control in your template is bound to

One issue that raises its head every now and again is how to add controls to a List’s Data Template in such a way that you can understand what data item the control is associated with when the control raises an event. E.g. I want to know what data item I should remove from a list when the user de-selects a checkbox;

<DataTemplate x:Key="MyDataClassItemTemplate">
    <StackPanel>
        <CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" 
                    Content="{Binding DisplayName}" 
                    Unchecked="CheckBox_Unchecked"
                    />
    </StackPanel>
</DataTemplate>

As you can see the checkbox has an Unchecked event but when that fires you will only be told what specific instance of the checkbox raised the event, not what the associated data item is. Previously I have worked-around this issue using the Visual Tree but I’ve always hated that solution. So I’m now using the following code that relies on the fact that my control has some form of data binding;

private MyDataClass MyDataClassFromCheckBox(object sender)
{
    FrameworkElement fe = sender as FrameworkElement;
    BindingExpression be = fe.GetBindingExpression(CheckBox.IsCheckedProperty);
    MyDataClass selectedMyDataClass = be.DataItem as MyDataClass;
    return selectedMyDataClass;
}

There, a much nicer solution. Note the ‘IsCheckedProperty’, this would need to be changed depending upon the property you are using in the binding – it is easy to pass that in as a dependancy property argument. Another quick point, previously I’ve had a similar problem using a Button in a template. However, the button only had a fixed caption so there was no binding expression to use. One simple trick here is to create a fixed property in your view model and bind to that, e.g. myViewModel.ButtonCaption { get {return “My Button Caption” }}. Yes it’s a slight overhead but you may prefer that to using the visual tree, I do.

LongListSelector nullexception gotcha

Just had a little puzzler with my Windows Phone 7 application. My application uses the LongListSelector which is bound to a view model. However, if I launch a second page that will add extra data to the model and return back the LongListSelector kept throwing a nullexception error. It would appear the the LongSelector does not like receiving an OnPropertyChanged whilst on a page that is not currently showing. To workaround the problem I’ve had to expose a ‘Refresh’ method on my view model that the page invokes when it redisplays. I dare say it’s something I’ve not understood correctly about the page life cycle, so I thought I’d share it to a) help someone else with the same problem b) hope someone helps me with a better solution

Repost of Determine Light or Dark Theme with WP7 RTM

This is not my content but a repost from http://mobileworld.appamundi.com/blogs/peterfoot/archive/2010/09/17/determine-light-or-dark-theme-with-wp7-rtm.aspx , I just wanted to ensure I didn’t lose it

In the previous Beta release you had to compare the RGB values of the background brush to determine if the theme was Light (White background) or Dark (Black background). Now there is an additional resource you can query:-

Visibility v = (Visibility)Resources["PhoneLightThemeVisibility"];
 
if (v == System.Windows.Visibility.Visible)
{
    ImageBrush ib = new ImageBrush();
    Uri u = new Uri("PanoramaBack2.png", UriKind.Relative);
    ib.ImageSource = new System.Windows.Media.Imaging.BitmapImage(u);
    PanoramaControl.Background = ib;
}

Any time you use a background image you can check the PhoneLightThemeVisibility resource and switch between different versions of the image to provide the best experience with either Black or White text.

Wp7 Application Bar, update binding helper

As you may have read I’ve been having ‘fun’ with the Application Bar. Following the really useful post by Laurent Bugnion, I’ve written the following helper that allows you to quickly manage your editable bindable controls. It’s not as simple as I hoped, I’d prefer to be able to get the helper to search for bindable properties but I’ve yet to find a nice way to achieve this – as always I welcome suggestions. Anyway hope this helps;

using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Media;
using System.Collections.Generic;
using System.Windows.Data;

namespace pauliom.Helpers
{
    public class BindingHelper
    {
        private BindingHelper(){}

        private IEnumerable<BindingExpression> bindables;
        public BindingHelper(DependencyObject parent, Type type, DependencyProperty property)
        {
            bindables = GetChildsRecursive(parent).OfType<FrameworkElement>().
                Where(c => c.GetType() == type && c.GetBindingExpression(property) != null).
                Select(c => c.GetBindingExpression(property));
        }

        public void UpdateAllBindings()
        {
            if (bindables != null)
            {
                foreach (BindingExpression binding in bindables)
                {
                    binding.UpdateSource();
                }
            }
        }

        // code from 'tucod'
        IEnumerable<DependencyObject> GetChildsRecursive(DependencyObject root)
        {
            List<DependencyObject> elts = new List<DependencyObject>();
            elts.Add(root);
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
                elts.AddRange(GetChildsRecursive(VisualTreeHelper.GetChild(root, i)));

            return elts;
        }
    }
}

So to use the code you create helper per type of control and property you want to keep updated;

private BindingHelper bindingForGridTextBoxes;
void Page_Loaded(object sender, RoutedEventArgs e)
{
      this.bindingForGridTextBoxes = 
                new BindingHelper(LayoutRoot, typeof(TextBox), TextBox.TextProperty);
      this.DataContext = viewModel;
}

then whereever you need to ensure the bindings are up-to-date, e.g. when one of the cheeky non-framework Application Bar buttons are clicked;

this.bindingForGridTextBoxes.UpdateAllBindings();

wp7 Keyboard helper as a behavior

Thanks to a number of comments regarding the problems I had with the ApplicationBar and the keyboard I’ve decided to apply some of the ideas to my previous post (thanks everyone for the comments) about a keyboard helper to create a self contained keyboard tab helper behavior (mouthful). I’ve posted the full code and binaries, Wp7KeyboardSource.zip
but essentially all you now have to do is;

<Grid x:Name=ContentGrid Grid.Row=1>

  <i:Interaction.Behaviors>

    <keyboard:KeyboardTabHelperBehavior />

  </i:Interaction.Behaviors>

  <StackPanel Orientation=Vertical>

    <CheckBox IsTabStop=True TabIndex=4 Content=Some check/>

    <TextBox TabIndex=1 Text=start here/>

    <TextBox IsTabStop=True Text=Won’t tab here cause not set tabindex/>

    <TextBox IsEnabled=False TabIndex=2 Text=not here cause disabled/>

    <TextBox TabIndex=3 Text=next stop here/>

  </StackPanel>

</Grid>

 …and that’s it, no code behind, no adding of event handlers. Just set up your tab index and stops and it will take care of the rest.

Wp7 vs keyboard vs ApplicationBar vs binding

As you may have read in my previous posts I’m currently trying to iron out some nagging issues with Windows Phone 7 keyboard (SIP). Another problem I’ve hit is when using it in combination with binding and the ApplicationBar. My scenario is straight forward, I have a TextBox for the user’s name which, via binding, changes a view model’s representation of the name (and vice versa). I then have what amounts to a save button in the Application Bar (application bar icon button). When I press the save button I want; a) the keyboard to disappear b) the view model updated with the current text entered for the user name. Sounds simple, and yet it doesn’t work. Making the keyboard disappear is easy enough, I’ve extended the Keyboard helper class to have a HideKeyboard method – which simply does page.Focus(). So what’s the problem? When the save button is pressed it;

  1. calls HideKeyboard which sets the focus to the page
  2. which in turn causes the text box to lose focus and fire the OnPropertyChanged for data binding
  3. calls my SaveName service.

Again seems simple, but the problem is the timing of the events. What actually happens is the execution order is 1->3->2. Why? Well the binding events are only executed once control returns from the function that caused the focus to change. Therefore my service is trying to save the name before the name has been updated, argh! The simplest solution I can think of (please say if you have a better method) is this hack;

void Default_Loaded(object sender, RoutedEventArgs e)
{

    this.keyboardHelper = new KeyboardHelper(this, LayoutRoot);
    this.keyboardHelper.AddKeyUpHander(typeof(TextBox), TabControls_KeyUp);
    this.DataContext = viewModel;
    this.viewModel.PropertyChanged +=
        new PropertyChangedEventHandler(viewModel_PropertyChanged);
}


void saveButton_Click(object sender, EventArgs e)
{
    // hide the keyboard
    this.keyboardHelper.HideKeyboard();
    // [Edit] If you edit one control and then tab to the next this won't work
    // because the 1st control fires the property change but we've not yet pressed saved
    // so I now have to push the values into the view model, i.e. lose some of the clean adv of binding
    this.viewModel.Name = TextBoxName.Text;

    // [Edit 2] Please see comments; 'SS' suggested queing the save via the dispatcher, which is much nicer
    // I'm not sure if you can guarantee the binding will occur first but it's much nicer
    //  Dispatcher.BeginInvoke(() => viewModel.VerifyProfile());
    // remember that we actually want to verify this change
    this.doesWantToVerify = true;
}

private bool doesWantToVerify;
void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    // some view model property has changed but 
    // did we really want to verify as well?
    if (doesWantToVerify)
    {
        doesWantToVerify = false;
        viewModel.VerifyProfile();
    }
}