ListPicker a… cafeína!!!

Actualizado a 18/08/2011: este artigo deixa neste momento de ser válido, dado que a Microsoft já disponibilizou a actualização de Agosto de 2011 para o Silverlight for Windows Phone Toolkit, o qual resolve a situação aqui descrita! ;)

De todos os controlos que a equipa do Silverlight lançou no Toolkit para Windows Phone 7, o que utilizo com mais regularidade é mesmo o ListPicker!

No entanto, desde que ele apareceu no Toolkit que sempre me senti frustrado com a fraca performance de scrolling deste, quando aberto no modo FullMode!

A razão para essa fraca performance é devida em grande parte ao seguinte bloco de código do ficheiro “\WindowsPhone7\Microsoft.Phone.Controls.Toolkit\Themes\Generic.xaml”:

<ListBox
    x:Name="FullModeSelector"
    Grid.Row="1"
    ItemTemplate="{TemplateBinding ActualFullModeItemTemplate}"
    FontSize="{TemplateBinding FontSize}"
    Margin="{StaticResource PhoneMargin}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel/> <!-- Ensures all containers will be available during the Loaded event -->
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

Como podemos aqui ver, na propriedade ListBox.ItemContainer foi colocado um StackPanel, substituindo assim o VirtualizingStackPanel que seria utilizado por omissão!

Esta alteração tem como origem um “hack” que foi introduzido para simular o highlight do item seleccionado na lista, mas mantendo o ListBox.SelectedItem = null.

Mas dada a visível perda de performance, o “hack” torna-se um grande problema, e como tal, vamos ter que o remover!!!

A primeira coisa a fazer é mesmo remover toda a alteração da propriedade ListBox.ItemsPanel que vemos acima, de forma a que este volte a utilizar o VirtualizingStackPanel; para além disso, alterei o modo de selecção da ListBox para que o evento ListBox.SelectionChanged ocorra sempre permitindo seleccionar e desseleccionar items:

<ListBox
    x:Name="FullModeSelector"
    Grid.Row="1"
    ItemTemplate="{TemplateBinding ActualFullModeItemTemplate}"
    FontSize="{TemplateBinding FontSize}"
    Margin="{StaticResource PhoneMargin}"
    SelectionMode="Multiple">
</ListBox>

O passo seguinte é remover o “hack” que podemos encontrar no seguinte bloco de código do ficheiro “\WindowsPhone7\Microsoft.Phone.Controls.Toolkit\ListPicker\ListPicker.cs”:

if (null != _fullModeSelectorPart)
{
    // Find the relevant container and make it look selected
    // Note: Selector.SelectedItem is left null so *any* selection will trigger the SelectionChanged event.
    // However, this doesn't highlight the "currently selected" item; the following technique fakes that.
    ContentControl container = _fullModeSelectorPart.ItemContainerGenerator.ContainerFromItem(SelectedItem) as ContentControl;
    if (null == container)
    {
        // Container isn't always available; defer until it is
        // Note: Assumes the container eventually WILL be available (which is why
        // the default Template replaces VirtualizingStackPanel with StackPanel)
        Dispatcher.BeginInvoke(() => HandleFullModeSelectorPartLoaded(sender, e));
    }
    else
    {
        Brush phoneAccentBrush = Application.Current.Resources["PhoneAccentBrush"] as Brush;
        if (null != phoneAccentBrush)
        {
            container.Foreground = phoneAccentBrush;
        }
    }
    // Scroll item into view if possible
    ListBox listBox = _fullModeSelectorPart as ListBox;
    if (null != listBox)
    {
        listBox.ScrollIntoView(SelectedItem);
    }
}

As linhas marcadas em cima deverão ser todas removidas, mas temos ainda que recolocar o funcionamento do ListBox.SelectedItem, e para isso vamos começar a juntar código ao ficheiro, mais propriamente a linha indicada:

if (null != _fullModeSelectorPart)
{
    _fullModeSelectorPart.ItemsSource = Items;
    _fullModeSelectorPart.SelectedItem = SelectedItem;
    _fullModeSelectorPart.SelectionChanged += HandleFullModeSelectorPartSelectionChanged;
    _fullModeSelectorPart.Loaded += HandleFullModeSelectorPartLoaded;
}

Falta apenas alterar o tratamento do evento ListBox.SelectionChanged para processar em modo MultiSelect:

if (null != _fullModeSelectorPart)
{
    object selectedItem = SelectedItem;

    if (e.AddedItems.Count > 0)
        selectedItem = e.AddedItems[0];
    else if (e.RemovedItems.Count > 0)
        selectedItem = e.RemovedItems[0];

    // Commit selected item
    if (SelectedItem != selectedItem)
    {
        SelectedItem = selectedItem;
    }
    else
    {
        // User selected the already-selected item; just switch back to Normal view
        ListPickerMode = ListPickerMode.Normal;
    }
}

E está pronto: passamos a ter um ListPicker devidamente optimizado com o VirtualizingStackPanel!

Para facilitar, os passos acima estão disponíveis neste patch que eu criei especificamente para quem utiliza o TortoiseSVN para obter o código do Silverlight Toolkit directamente do Codeplex!

Posts Relacionados:

  1. Silverlight for Windows Phone Toolkit – Feb 2011
  2. Silverlight for Windows Phone Toolkit – Nov 2010
  3. Currency Converter for Windows Phone 7
  4. GestureService e GestureListener do Silverlight Toolkit
  5. Currency Converter v2 – Now on Caffeine!

3 pensamentos em “ListPicker a… cafeína!!!

  1. Pingback: Jerry Huang International | ListPicker performance tuning

  2. Pingback: Silverlight for Windows Phone Toolkit – Aug 2011 em Nação do Cimbalino

Deixar uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *

*

Pode usar estas etiquetas HTML e atributos: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>