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!

