Empezando con Windows Phone 7.1 (y XX)– Accediendo al DataSource desde un ListBoxItem


Trabajando en un escenario muy común me he encontrado con un pequeño problema. Veamos cuál es el asunto.

El Problema

El escenario es relativamente simple. Se trata de una página con un ListBox que presenta lugares (Venues) proporcionados por FourSquare.

image

Nada especialmente complicado. Vamos a echarle un vistazo al XAML asociado a la página (bueno, no vamos a mirarlo todo, que es un rollo. Vamos a lo más relevante).

   1: <phone:PhoneApplicationPage x:Class="Lcdad.SquareMatrix.Pages.MainPage"

   2:                             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   3:                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   4:                             xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"

   5:                             xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"

   6:                             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

   7:                             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

   8:                             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

   9:                             xmlns:c4f="clr-namespace:Coding4Fun.Phone.Controls;assembly=Coding4Fun.Phone.Controls"

  10:                             xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

  11:                             mc:Ignorable="d"

  12:                             d:DesignWidth="480"

  13:                             d:DesignHeight="696"

  14:                             FontFamily="{StaticResource PhoneFontFamilyNormal}"

  15:                             FontSize="{StaticResource PhoneFontSizeNormal}"

  16:                             Foreground="{StaticResource PhoneForegroundBrush}"

  17:                             SupportedOrientations="PortraitOrLandscape"

  18:                             Orientation="Portrait"

  19:                             shell:SystemTray.IsVisible="True"

  20:                             DataContext="{Binding Main, Source={StaticResource Locator}}">

  21:  

  22:     <Grid x:Name="LayoutRoot"

  23:           Background="Transparent">

  24:         <Grid.RowDefinitions>

  25:             <RowDefinition Height="Auto" />

  26:             <RowDefinition Height="*" />

  27:         </Grid.RowDefinitions>

  28:  

  29:         <!--TitlePanel contains the name of the application and page title-->

  30:         <Grid x:Name="ContentPanel"

  31:               Grid.Row="1"

  32:               Margin="12,0,12,0">

  33:             

  34:             <ListBox x:Name="ListaLugares" ItemsSource="{Binding Path=Lugares}">

  35:                 <ListBox.ItemTemplate>

  36:                     <DataTemplate>

  37:                         <StackPanel>

  38:                             <Grid x:Name="ItemGrid">

  39:                                 <Grid.ColumnDefinitions>

  40:                                     <ColumnDefinition Width="6" />

  41:                                     <ColumnDefinition Width="*" />

  42:                                 </Grid.ColumnDefinitions>

  43:                                 

  44:                                 <c4f:Tile x:Name="TileSeleccionar" Grid.Column="1" Margin="0,6,6,0" Width="6" />

  45:                                 <TextBlock x:Name="TextoLugar" Grid.Column="3" Text="{Binding Path=name}" />

  46:                             </Grid>

  47:  

  48:                             <toolkit:ContextMenuService.ContextMenu>

  49:                                 <toolkit:ContextMenu x:Name="MenuCheckin">

  50:                                     <toolkit:MenuItem 

  51:                                         x:Name="OpcionCheckin" 

  52:                                         Header="{Binding Source={StaticResource LocationHelper}, 

  53:                                         Path=Recursos.OpcionCheckin}", 

  54:                                         Command=""></toolkit:MenuItem>

  55:                                 </toolkit:ContextMenu>

  56:                             </toolkit:ContextMenuService.ContextMenu>

  57:                         </StackPanel>

  58:                     </DataTemplate>

  59:                 </ListBox.ItemTemplate>

  60:             </ListBox>

  61:         </Grid>

  62:     </Grid>

  63: </phone:PhoneApplicationPage>

Se trata de mostrar una lista de lugares “venues” proporcionados por Four Square, una vez disponemos de los lugares más cercanos a la actual posición, debemos de seleccionar uno para poder hacer check-in sobre el mismo.

Para ello he incluido un control “ContextMenu” incluido en el paquete SilverlightToolkitWP (lo he bajado mediante NuGet). El mecanismo es indiferente, el quid de la cuestión está en las siguientes líneas.

   1: <ListBox x:Name="ListaLugares" ItemsSource="{Binding Path=Lugares}">

   2:     <ListBox.ItemTemplate>

   3:         <DataTemplate>

   4:             <StackPanel>

   5:                 <Grid x:Name="ItemGrid">

   6:                     <Grid.ColumnDefinitions>

   7:                         <ColumnDefinition Width="6" />

   8:                         <ColumnDefinition Width="auto" />

   9:                         <ColumnDefinition Width="*" />

  10:                     </Grid.ColumnDefinitions>

  11:                     

  12:                     <c4f:Tile x:Name="TileSeleccionar" Grid.Column="1" Margin="0,6,6,0" Width="6" />

  13:                     <TextBlock x:Name="TextoLugar" Grid.Column="3" Text="{Binding Path=name}" FontSize="{StaticResource PhoneFontSizeLarge}"/>

  14:                 </Grid>

  15:  

  16:                 <toolkit:ContextMenuService.ContextMenu>

  17:                     <toolkit:ContextMenu x:Name="MenuCheckin">

  18:                         <toolkit:MenuItem 

  19:                             x:Name="OpcionCheckin" 

  20:                             Header="{Binding Source={StaticResource LocationHelper}, 

  21:                             Path=Recursos.OpcionCheckin}"

  22:                             Command={}></toolkit:MenuItem>

  23:                     </toolkit:ContextMenu>

  24:                 </toolkit:ContextMenuService.ContextMenu>

  25:             </StackPanel>

  26:         </DataTemplate>

  27:     </ListBox.ItemTemplate>

  28: </ListBox>

Como podéis ver, el comando asociado al elemento del menú contextual no tiene asociado ninguna propiedad en la vista-modelo. La razón es la siguiente: La página tiene asociada la Vista-Modelo como contexto de datos (accede a ella mediante la propiedad Main del ViewModelLocator).

El ListBox tiene asociado, a su vez, una colección de elementos como contexto de datos. Esta colección es una propiedad de la Vista-Modelo llamada Lugares.

El problema es que queremos acceder a un comando de la VistaModelo desde un elemento ubicado en el interior del ListBox (dentro del DataTemplate) y no lo conseguimos. Pensando en el tema, cometí uno de los errores que últimamente me veo cometiendo con demasiada frecuencia: me voy a buscar la solución antes de pensarla yo mismo. Pude ver un post que os indico aquí para vuestra referencia y que resulta interesante por la idea que explora. Se trata de un Proxy para el DataSource. De forma resumida: creamos un objeto que hereda de FrameworkElement y que expone el DataSource del control padre, de esta forma podemos acceder al DataSource de la página a través del DataSource del ListBox.

La Solución

Sin embargo, voy a proponer una solución más sencilla (la verdad es que mucho más sencilla).

Haciendo uso del MVVM Light disponemos de una clase encargada de proporcionar acceso a cada una de las vistas. ¿No es así?

Efectivamente, el ViewModelLocator.

Bueno, pues ya está. Simplemente, cambiamos el binding para acceder a la VistaModelo de la siguiente manera.

   1: <toolkit:ContextMenuService.ContextMenu>

   2:     <toolkit:ContextMenu x:Name="MenuCheckin">

   3:         <toolkit:MenuItem 

   4:             x:Name="OpcionCheckin" 

   5:             Header="{Binding Source={StaticResource LocationHelper}, 

   6:             Path=Recursos.OpcionCheckin}" 

   7:             Command="{Binding Source={StaticResource Locator}, Path=Main.RealizarCheckin}">

   8:         </toolkit:MenuItem>

   9:     </toolkit:ContextMenu>

  10: </toolkit:ContextMenuService.ContextMenu>

Quizá no sea muy limpio, pero desde luego es mucho más sencillo.

Anuncios

Responder

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