Menú Acordeón deslizante – WPF


El menú que voy a mostrar es algo diferente al menú acordeón que se encuentra en el Wpf Toolkit, el menú va a tener el siguiente comportamiento:

– Se tienen varios paneles o bordes, los cuales van a tener algún contenido, todos los bordes están dentro de un StackPanel

– El StackPanel tendrá un ancho definido

– El ancho de cada borde depende del total de Controles Border que hay en el stack Panel y del tamaño del StackPanel

– La orientación del StackPanel es Horizontal, por lo tanto es un menú Horizontal, aunque puede ser cambiado a Vertical

– Los bordes van a tener un  estado inicial que es “cerrado” y no se verá todo su contenido

– Cuando el Mouse pase por encima o entre en el contenido de algún borde, este se abrirá, cambiando su ancho a un tamaño definido más grande que el tamaño que tienen cuando esta “cerrado”, los demás bordes se cerraran aún más para ocupar el espacio sobrante, en total la sumatoria del espacio del borde seleccionado abierto y los demás bordes cerrados dará el tamaño del StackPanel.

– Si el Mouse se pasa a otro borde, se cerrará el borde seleccionado anteriormente y se repetirá el mismo efecto con el borde seleccionado.

– Si el Mouse no esta seleccionando ningún borde, estos estarán cerrados, ocupando todo el tamaño del StackPanel.

En acción el Menú se verá así:

El código

Para hacer la animación de los paneles, vamos a usar una animación igual al del Post del Panel Desplegable, pero esta vez la animación se va a hacer por código c# y no en XAML.

Para empezar, donde vayamos a usar el menú, arrastramos o dibujamos un StackPanel y le indicamos que la orientación va a ser Horizontal, también indicamos el ancho:

<StackPanel Orientation="Horizontal" Name="stkPanel" Width="500">

Ahora arrastramos un Borde al StackPanel, al borde le definimos un Color de fondo, un color de borde, el tamaño del grosor del borde, el radio de las esquinas por si queremos un borde redondeado, en fin, le definimos el estilo que queramos, lo importante es definir una altura, el ancho aunque lo definamos va a ser luego cambiado en el código.

Dentro del borde también es importante definir un Grid o un Control funcione como un panel y luego se puedan insertar más controles, en el ejemplo se dejo un borde de la siguiente manera:

<Border Height="200" Width="70" CornerRadius="10" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="2" Background="White">
  <Grid Margin="3">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="100"/>
      <ColumnDefinition Width="120*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <TextBlock Height="14" Text="Panel 1" Grid.Column="0" HorizontalAlignment="Left">
      <TextBlock.RenderTransform>
        <TransformGroup>
          <ScaleTransform/>
          <SkewTransform/>
          <RotateTransform Angle="-90"/>
          <TranslateTransform/>
        </TransformGroup>
      </TextBlock.RenderTransform>
   </TextBlock>
   <TextBlock Text="Titulo 1" Grid.Column="1"></TextBlock>
 </Grid>
</Border>

El mismo borde, lo copiamos y lo pegamos en el StackPanel, la cantidad de veces que lo peguemos es la cantidad de paneles que va a tener el menú.
Ahora en el código c# o code behind, definimos unas variables que tendrán el ancho del StackPanel, el ancho de los bordes en estado “cerrado”, el total de paneles o bordes y el ancho de los demás bordes cuando se ha seleccionado un borde:

Double anchoBordes, anchoPanel;
Int32 contPaneles = 0;
Double anchoOtrosBordes = 0;

En el constructor de la ventana, obtenemos todos los bordes que están en el StackPanel, para hacerlo usamos el siguiente método:

public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
 if (depObj != null)
 {
  for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
  {
   DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
   if (child != null && child is T)
   {
    yield return (T)child;
   }

   foreach (T childOfChild in FindVisualChildren<T>(child))
   {
    yield return childOfChild;
   }
  }
 }
}

Con el método anterior, podemos obtener todos los controles tipo Border que estén dentro del StackPanel, luego que obtenemos cada control, le indicamos a cada uno un evento MouseEnter y MouseLeave, también les definimos el ancho que tendrán en estado “cerrado” y el ancho de los bordes que se van a cerrar aún más cuando se abra un borde:

public MainWindow()
{
 InitializeComponent();

 anchoPanel = stkPanel.Width;
 foreach (Border item in FindVisualChildren<Border>(stkPanel))
 {
  item.MouseEnter += new MouseEventHandler(item_MouseEnter);
  item.MouseLeave += new MouseEventHandler(item_MouseLeave);
  contPaneles++;
 }

foreach (Border item in FindVisualChildren<Border>(stkPanel))
{
 anchoBordes = anchoPanel / contPaneles;
 item.Width = anchoBordes;
}

anchoOtrosBordes = anchoPanel - 300;
anchoOtrosBordes = anchoOtrosBordes / (contPaneles - 1);
}

En el evento MouseEnter, vamos a obtener el borde que se seleccionó, y luego creamos una animación que durará unos 200 milisegundos, en la animación el borde cambiará su propiedad Width hasta 300 o hasta un ancho que definamos.
A los demás bordes, también le vamos a agregar una animación para que disminuyan su ancho, haciendo que la sumatoria de anchos sea igual al ancho del StackPanel:

void item_MouseEnter(object sender, MouseEventArgs e)
{
 Border bordeActual = sender as Border;
 DoubleAnimation daMover = new DoubleAnimation();
 daMover.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 200));
 daMover.To = 300;
 Storyboard sb = new Storyboard();
 sb.Children.Add(daMover);
 Storyboard.SetTarget(daMover, bordeActual);
 Storyboard.SetTargetProperty(daMover, new PropertyPath("Width"));
 bordeActual.BeginStoryboard(sb);
 bordeActual.BorderThickness = new Thickness(3);
 //bordeActual.BorderBrush = Brushes.LightBlue;
 foreach (Border item in FindVisualChildren<Border>(stkPanel))
 {
  if (item != bordeActual)
  {
   DoubleAnimation daMoverOtro = new DoubleAnimation();
   daMoverOtro.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 200));
   daMoverOtro.To = anchoOtrosBordes;
   Storyboard sbOtro = new Storyboard();
   sbOtro.Children.Add(daMoverOtro);
   Storyboard.SetTarget(daMoverOtro, item);
   Storyboard.SetTargetProperty(daMoverOtro, new PropertyPath("Width"));
   item.BeginStoryboard(sbOtro);
  }
 }
}

El evento MouseLeave, lo que hará es que a cada borde, le dará una animación para que disminuyan o aumenten su ancho y queden todos con el ancho del estado “cerrado”, el ancho que se asignó inicialmente:

void item_MouseLeave(object sender, MouseEventArgs e)
{
 foreach (Border item in FindVisualChildren<Border>(stkPanel))
 {
  DoubleAnimation daMover = new DoubleAnimation();
  daMover.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 200));
  daMover.To = anchoBordes;
  Storyboard sb = new Storyboard();
  sb.Children.Add(daMover);
  Storyboard.SetTarget(daMover, item);
  Storyboard.SetTargetProperty(daMover, new PropertyPath("Width"));
  item.BeginStoryboard(sb);
  item.BorderThickness = new Thickness(2);
  //item.BorderBrush = Brushes.Black;
 }
}

Listo!!!, eso es todo, ahora cuando se ejecute la aplicación, y pasen el Mouse por los bordes, estos se abrirán y cerrarán, dando un efecto acordeón deslizable :).

Anuncios

5 pensamientos en “Menú Acordeón deslizante – WPF

  1. Tengo un problema a la hora de ejecutarlo, me manda un error: “El nombre ‘Storyboard’ no existe en el contexto actual” y tambien este otro: “No se puede encontrar el tipo o el nombre de espacio de nombres ‘DoubleAnimation'”, y ya agrege los eventos de los MouseEnter y MouseLeave al StackPanel, no se que mas tenga que hacer. Espero me expliques un poco mas. Gracias.

  2. Hola que tal, Muchisimas gracias Martin por el tutorial, es una gran herramienta me sirvió de mucho.
    Ahora tengo un problema tu utilizas un textblock y funciona perfectamente, yo quiero ponerle boton o cualquier otro tipo de control, pero cuando realizo esta acción de agregar estos controles a la aplicacion lanza una excepcion “inner exception” y no se que es eso o como fixiar that, mi pregunta es, como es que puedo agregar otros controles como button or label dentro el cada grid en el border.

Deseas comentar o sugerir algo?

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s