Social Icons

jueves, 21 de abril de 2011

Creando un Expander en Silverlight para Windows Phone

Hace unos días estaba migrando una aplicación a WP7 y me di cuenta de que el control Expander no estaba... Me instalé entonces el Silverlight for Windows Phone Toolkit pero me di cuenta de que tampoco había Expander allí.

No se me ocurría ninguna manera nueva de representar la información sin expanders así que tuve que crear una versión simplificada para la ocasión. El control se compone de un Grid, un StackPanel y un TextBlock.

Está muy limitado y no se supone que con esto se vaya a poder sustituir al Expander de toda la vida, pero quizás pueda ayudar a alguien por ahí.

El código XAP es:
<Grid x:Name="LayoutRoot">
  <Border VerticalAlignment="Top" BorderBrush="#FF929EB0" BorderThickness="1" CornerRadius="2" Background="#FFE9E9E9" Margin="5,2">
            <Grid d:LayoutOverrides="Width">
    <Grid.RowDefinitions>
     <RowDefinition Height="Auto" MinHeight="20"/>
     <RowDefinition Height="Auto" MinHeight="5"/>
    </Grid.RowDefinitions>
    <TextBlock x:Name="Header" TextWrapping="Wrap" d:LayoutOverrides="Width" Foreground="#FF656565" Margin="15,2,0,0" VerticalAlignment="Top"/>
    <StackPanel x:Name="ChildrenPanel" VerticalAlignment="Top" d:LayoutOverrides="Width" Margin="0,2,0,0" Grid.Row="1"/>
   </Grid>
  </Border>
 </Grid>
El truco está en añadir un TextBlock en la cabecera del expander y suscribirse al evento MouseLeftButtonUp del TextBlock para controlar el plegado y desplegado. También he añadido una propiedad Folded para manejar el estado del expander desde afuera.

Si el expander está plegado añado los elementos al StackPanel, si está desplegado los quito. Suena muy simple, pero no era capaz de hacerlo funcionar. El StackPanel se quedaba del mismo tamaño, pero vacío, sin hijos... feísimo.

Después de pasar un rato intentando encoger el StackPanel a manija cambiando el parámetro Heigh encontré el problema.

Estaba intentando borrar todos los elementos del StackPanel a la vez con Children.Clear y eso estaba volviéndolo loco. La solución era borrar los elementos dentro de Children de uno en uno.

El código para la clase Expander es algo así:
public partial class Expander : UserControl
 {
        bool _Folded;
        public bool Folded
        {
            get { return _Folded; }
            set
            {
                _Folded = !value;
                ExpanderSwitch();
            }
        }
        List ChildrenList;


  public Expander()
  {
   // Required to initialize variables
   InitializeComponent();

            ChildrenList = new List();

            Header.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(UserActivity_MouseLeftButtonUp);
  }

        void UserActivity_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            ExpanderSwitch();
        }

        private void ExpanderSwitch()
        {
            if (Folded)
            {
                foreach (var child in ChildrenList)
                    ChildrenPanel.Children.Add(child);
            }
            else
            {
                if (ChildrenList.Count == 0)
                    foreach (var item in ChildrenPanel.Children)
                        ChildrenList.Add(item);

                while (ChildrenPanel.Children.Count > 0)
                    ChildrenPanel.Children.RemoveAt(0);
            }

            _Folded = !_Folded;
        }
 }
Y para usarlo solo tienes que añadir el control:
<Grid x:Name="LayoutRoot">
        <this:Expander x:Name="UserNews" />
    </Grid>
Y en el code behind puedes hacer algo como esto:
UserNews.Header.Text = GetHeader(News[0]);

            foreach (FrameworkNews news in News)
                UserNews.ChildrenPanel.Children.Add(new NewsDetail(news.Link, GetDetail(news), news.Where));

            UserNews.Folded = true;
Si tienes tiempo puedes añadir imágenes y animaciones al expander, y también un mejor "control del despliegue"... Yo no puedo dedicarle más tiempo, aunque me encantaría.... Una pena.

No hay comentarios: