Alinhamento de items dentro de uma ListBox

Ao desenvolver diariamente para Windows Phone por vezes deparo-me com situações em que nem tudo o que parece, realmente o é… esta é uma dessas situações!

Cenário inicial: numa PhoneApplicationPage vazia, adicionamos o seguinte bloco de XAML ao conteúdo da Grid ContentPanel:

<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <TextBlock Text="{Binding}" Style="{StaticResource PhoneTextTitle2Style}" />
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Como podem aqui ver, estamos a criar uma ListBox com um template muito simples (para cada item, apresentar um TextBlock usando o próprio item como texto).

Do lado do código temos uma propriedade Items que tem apenas e só 3 items e que vão servir para alimentar a propriedades ItemsSource da nossa ListBox.

Até aqui tudo bem, ao executar a aplicação o resultado deverá ser este:

Digamos que agora pretendemos alinhar o texto de cada item à direita; normalmente, a seguinte alteração seria suficiente:

<TextBlock Text="{Binding}" HorizontalAlignment="Right" Style="{StaticResource PhoneTextTitle2Style}" />

Ao executar novamente a aplicação, poderemos ver que o resultado se mantém inalterado!

Nesta altura, começamos a testar as propriedades da própria ListBox, ao ponto de fazer algo deste tipo:

<ListBox HorizontalContentAlignment="Right">

E mais uma vez, ao executar poderemos reparar que nada mudou!!!

O que se passa é que cada item da ListBox é instanciado dentro de um ListBoxItem, e este controlo ignora por completo a propriedade HorizontalContentAlignment colocada na própria ListBox.

Para resolver este problema, temos que definir a propriedade ListBox.ItemContainerStyle e ai sim colocar o ListBoxItem.HorizontalAlignment pretendido.

O código corrigido ficará estão da seguinte forma:

<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="HorizontalAlignment" Value="Right"/>
        </Style>
    </ListBox.ItemContainerStyle>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <TextBlock Text="{Binding}" Style="{StaticResource PhoneTextTitle2Style}" />
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

E aqui temos o resultado final:

Pessoalmente, coloco sempre o ListBoxItem.HorizontalAlignment com o valor HorizontalAlignment.Stretch, e depois controlo o alinhamento do item propriamente dito dentro do template; com esta segunda abordagem tenho a garantia de que o item dispõe realmente da largura total da ListBox para apresentar resultados!

Silverlight for Windows Phone Toolkit – Nov 2011

Já se encontra disponível a versão de Novembro do Silverlight for Windows Phone Toolkit!

A lista completa de novidades e correcções é relativamente extensa, e está disponível num artigo do Jeff Wilcox (Senior Software Developer na Silverlight Phone & Devices team).

Como de costume, o toolkit está disponível para download directo do MSI, package através do NuGet, e código fonte no Codeplex!

Nota: pessoalmente já detectei alguns problemas com esta versão, nomeadamente uma situação com o ListPicker que remove todas as transições das páginas após ter sido aberto; este bug já foi aqui reportado, e pretendo brevemente disponibilizar um patch aqui no blog para este problema!

Silverlight for Windows Phone Toolkit – Aug 2011

Foi ontem disponibilizada a actualização de Agosto do Silverlight for Windows Phone Toolkit, sendo que esta versão foi criada com base no SDK do Windows Phone “Mango”, e como tal não pode ser utilizado com a versão anterior!

Para além dos controlos e componentes que já existiam nas versões anteriores, podemos agora contar com os seguintes:

  • LongListSelector foi reconstruido e redesenhado para tirar partido do novo sistema de smooth scroling do “Mango”
  • MultiSelectList que permite a selecção múltipla de items, semelhante à experiência da aplicação de Mail
  • LockablePivot adiciona um modo especial ao controlo Pivot em que apenas o item actual é apresentado
  • ExpanderView é um controlo de items que pode ser utilizado para expandir e colapsar items (como a vista em thread na aplicação de Mail)
  • HubTile permite adicionar Tiles animados e informativos à aplicação, semelhante aos grupos no hub People do “Mango”
  • ContextMenu foi redesenhado, de forma a melhorar a performance e a consistência visual
  • ListPicker permite agora multiplas selecções
  • RecurringDaysPicker permite aos utilizadores escolherem um dia da semana
  • Date & TimeConverters devidamente localizados em 22 linguas, permitem aos programadores apresentarem a data e hora na interface de uma forma relativa (por exemplo, “ontem” ou “há um mês atrás”)
  • Page Transitions foram melhoradas e estão agora muito mais suaves
  • PhoneTextBox é a primeira  versão de um controlo TextBox avançado que permite utilização de icon para acções, marca de água, etc.
  • Todas as mensagens de erro e elementos de interface foram localizados para todas as línguas suportadas, melhorando a experiência para os utilizadores de todo o mundo!

Prometido está ainda o lançamento de uma versão compatível com o Windows Phone “pré-Mango”, que poderá ser publicada em alguns dias!

Podem ainda ler o artigo oficial no blog da equipa do Windows Phone e mais alguma informação no blog do Jeff Wilcox, Senior Software Developer na Silverlight Phone & Devices team!

Nota: com esta nova versão e com a reconstrução do controlo ListPicker, deixa de ser necessária a alteração que aqui coloquei anteriormente para resolver os problemas de scrolling com listas de muitos items!

Cuidado com o FrameworkElement.Language!!

Ontem dei com um post do MVP Paulo Morgado nos fóruns do App Hub com uma questão muito curiosa!

Se todas as definições do emulador estão com “Português (Portugal)” (tanto interface como definições regionais), como é que ao utilizar um IValueConverter num qualquer Binding da interface, o parâmetro “culture” apresenta uma CultureInfo de “en-US”?…

Para investigar este caso, criei uma pequena aplicação, com 2 TextBlocks para apresentar os valores do Thread.CurrentThread.CurrentCulture e Thread.CurrentThread.CurrentUICulture respectivamente, e um terceiro TextBlock que utiliza um IValueConverter que devolve o culture.Name por ele utilizado, e o resultado foi no mínimo… surpreendente!

Esta é a classe CultureDebugValueConverter que implementa o IValueConverter:

public class CultureDebugValueConverter : IValueConverter
{
    public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return culture.Name;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Reparem que em vez de efectuar uma qualquer conversão, ela apenas retorna o “culture.Name”

O ContentPanel principal da MainPage.xaml está definido da seguinte forma:

<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <TextBlock Style="{StaticResource PhoneTextSubtleStyle}">CurrentCulture</TextBlock>
    <TextBlock Text="{Binding CurrentCulture}" Style="{StaticResource PhoneTextTitle2Style}" />
    <TextBlock Style="{StaticResource PhoneTextSubtleStyle}">CurrentUICulture</TextBlock>
    <TextBlock Text="{Binding CurrentUICulture}" Style="{StaticResource PhoneTextTitle2Style}" />
    <TextBlock Style="{StaticResource PhoneTextSubtleStyle}">Converter Culture</TextBlock>
    <TextBlock Text="{Binding Converter={StaticResource CultureDebugValueConverter}}" Style="{StaticResource PhoneTextTitle2Style}" />
</StackPanel>

Note-se que o último TextBlock apenas tem o Binding.Converter a apontar para um StaticResource do tipo CultureDebugValueConverter.

No MainPage.xaml.cs coloquei as restantes propriedades para os outros Bindings:

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();

        this.DataContext = this;
    }

    public string CurrentCulture
    {
        get
        {
            return Thread.CurrentThread.CurrentCulture.Name;
        }
    }

    public string CurrentUICulture
    {
        get
        {
            return Thread.CurrentThread.CurrentUICulture.Name;
        }
    }
}

Depois, lancei o emulador, mudei todas as definições para Português, e este foi o resultado ao executar a aplicação:

Ora se o sistema operativo, o CurrentCulture, e o CurrentUICulture estão todos “pt-PT”, de onde vem aquele “en-US”?

A razão não me parece nada trivial, e para a perceber, temos que entender primeiro de onde vem o valor do parâmetro “culture” no IValueConverter.Convert; na documentação pode-se ler o seguinte:

The culture is determined in the following order:

  • The converter looks for the ConverterCulture property on the Binding object.
  • If the ConverterCulture value is null, the value of the Language property is used.

Ora como eu não defini o Binding.ConverterCulture, quer isso então dizer que ele vai utilizar o FrameworkElement.Language, que por sua vez diz o seguinte na documentação:

The default is an XmlLanguage object that has its IetfLanguageTag value set to the string “en-US”

E heis que encontramos finalmente o “culpado”!!!

A solução passa assim por corrigir a propriedade Page.Language, no XAML ou directamente no código; assim, o código corrigido é algo deste tipo:

public MainPage()
{
		Language = System.Windows.Markup.XmlLanguage.GetLanguage(Thread.CurrentThread.CurrentUICulture.Name);

		InitializeComponent();

		this.DataContext = this;
}

Na terceira linha pode-se ver que estou a inicializar a propriedade Language da página actual com o XmlLanguage que posso obter utilizando por base o CurrentUICulture.Name.

Uma nota final: para ter o efeito esperado, esta correcção tem de ser efectuada antes de ser invocado o InitializeComponent() da própria página!

E aqui está o resultado final:

Francamente não sei o porquê deste comportamento, mas é um factor muito importante a ter em conta quando estivermos a pensar em Globalization nas nossas aplicações Windows Phone!

Se quiserem experimentar, podem fazer o download do código que utilizei neste artigo!

Camera and Sensors API @ WP7 “Mango” Dev Hub

No próximo dia 29 de Junho decorre no Auditório Microsoft no Tagus Park (Lisboa) o Windows Phone 7 “Mango” Developer Hub, aquele será o primeiro evento da Microsoft Portugal totalmente dedicado à nova versão do Windows Phone!

O programa conta com um variado leque de oradores, no qual estarei presente para fechar o evento com uma sessão sobre as novas API’s de interface com a Câmara e os mais variados sensores dos dispositivos Windows Phone (como por exemplo o acelerómetro, a bússola digital, ou o giroscópio – quando disponível).

Aqui fica o programa completo:

  • 09:30 – 10:30 – Mango is coming… But I’m a developer, why should I care?
    Nuno Silva, Microsoft
  • 10:30 – 11:30 – What’s new for Silverlight and XNA in Mango?
    Virgílio Raposo, Comunidade NetPonto
  • 11:30 – 12:00 – (Coffee-break)
  • 12:00 – 13:00 – Data Access Features in Mango
    Caio Proiete, Ciclo Formação, Comunidade NetPonto
  • 13:00 – 14:30 – (Almoço Livre)
  • 14:30 – 16:00 – Multitasking Deep Dive
    Nuno Silva, Microsoft
  • 16:00 – 16:30 – (Coffee-break)
  • 16:30 – 17:30 – Enhanced Push Notifications and Live Tiles
    Nuno Godinho, itech4all, Comunidade AzurePT
  • 17:30 – 18:30 - Camera and Sensors API
    Pedro Lamas, Devscope, Comunidade Pocketpt.net

O evento é gratuito e as inscrições limitadas, pelo que se aconselha ao registo quanto antes! :)

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!

3º Seminário TI&PRO PocketPT

Caminhamos a passos largos para o 3º Seminário TI&PRO PocketPT, um evento sobre Windows Phone direccionado especialmente para os profissionais da área da mobilidade!

Iremos ter oradores de grande relevância no panorama nacional da mobilidade, bem como os oradores do PocketPT e da Microsoft Portugal que iram sem dúvida enriquecer o evento e os seus participantes em termos de conteúdos.

O evento realiza-se no próximo dia 28 de Maio (sábado) e será no Auditório da Microsoft Portugal no Tagus Park, sendo de inscrição gratuita para todos os interessados!

Desmistificando o “Copy & Paste”

A última versão do Windows Phone 7 (com nome de código “NoDo”) apresentou uma nova funcionalidade largamente pedida por todos: capacidade para copiar e colar texto (copy & paste)!

A sua utilização é muito simples: em qualquer caixa de texto (e no Internet Explorer), basta carregar numa palavra para que apareça de imediato os selectores de início/fim de selecção, modificar a selecção arrastando os selectores conforme o pretendido, e carregar no pequeno icon de cópia que aparece junto ao texto.

Depois é ir à caixa de texto onde pretendemos colar o texto, e carregar no botão que aparece logo acima do teclado (uma dica: podem fazer multiplas colagens simplesmente deslizando o dedo na área do botão de “colar” para que ele torne a aparecer).

Seguindo a documentação disponibilizada pela Microsoft, quem desenvolve aplicações para o Windows Phone 7 tem apenas que colocar na interface controlos TextBox para que automaticamente o sistema operativo disponibilize esta funcionalidade nesses controlos!

Pelo menos para já, não é possível programaticamente aceder à funcionalidade de copy & paste, logo esta apenas pode ser invocada explicitamente por acção do próprio utilizador.

Mas aqui surge uma questão que tenho visto ser feita vezes sem conta: como fazer copy & paste em controlos TextBlock? Bem, na verdade, tal não é possível, mas podemos no limite fazer com que uma TextBox seja apresentada e se comporte como um TextBlock!!

Para isso, podem usar o seguinte Style nas vossas aplicações:

<Style x:Key="ReadOnlyTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}" />
    <Setter Property="IsReadOnly" Value="True" />
    <Setter Property="TextWrapping" Value="Wrap" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Grid>
                    <ContentPresenter x:Name="ContentElement" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Assim, o passo seguinte será substituir os controlos TextBlock por TextBox e aplicar o estilo ReadOnlyTextBox para que estas passem visualmente a ser como os TextBlock, mas com a funcionalidade de copy & paste sempre presente! :)

Silverlight for Windows Phone Toolkit – Feb 2011

Foi publicada mais uma actualização ao Silverlight for Windows Phone Toolkit, e de entre os vários melhoramentos e correcções, apresenta ainda as seguintes novidades:

  • TiltEffect
  • PerformanceProgressBar

Apesar de não serem necessariamente “novos controlos” (dado que já andam pela Internet à muito tempo), estes passam agora também a figurar dentro do Toolkit, dado o enorme número de pedidos feitos pela comunidade para tal.

Para além disso, as samples foram actualizadas para agora estarem disponíveis em C# e VB.NET!