Cambiando el primer día de la semana en el control DatePicker de "Applications = Code + Markup"

Nota. A efectos de poder seguir adecuadamente el contenido de este artículo, los ejemplos de código fuente del libro que se menciona se encuentran en este enlace para C# y en este enlace para VB.


 


Un interesante control de selección de fecha


Hace unos cuantos meses leí el estupendo libro «Applications = Code + Markup» de Charles Petzold, una obra totalmente recomendable para todos aquellos que quieran iniciarse y profundizar en el nuevo paradigma de aplicaciones de escritorio que representa Windows Presentation Foundation (WPF).


Al llegar al capítulo 25 encontré un ejemplo muy interesante, en el cual se desarrollaba un control de selección de fecha denominado DatePicker, que al ser ejecutado presenta el siguiente aspecto.



 


Este control cumple su cometido a la perfección, aunque debemos destacar un inconveniente que el propio Petzold menciona en su obra, más concretamente en la página 696, y que consiste en que el primer día de la semana que se muestra es el domingo, lo cual representa un problema si lo utilizamos en países donde el primer día de la semana es lunes, tal y como ocurre en España.


Continúa Petzold indicando en su libro, que una posible solución a este problema pasaría por acceder, desde el codebehind de la página XAML, a la propiedad FirstDayOfWeek de la clase DateTimeFormatInfo, para asignar manualmente el valor correcto. Sin embargo, si accedemos a dicha propiedad, veremos que ya tiene establecido el lunes como primer día de la semana -aunque lo muestre en inglés-, por lo que parece ser que la raíz del problema no se encuentra en esta propiedad.



 


Observando el código XAML del control DatePicker, vemos que para mostrar las columnas que contienen como título los nombres abreviados de los días de la semana utiliza un control StatusBar, cuyo contenido rellena asignando a su propiedad ItemsSource un enlace a datos que apunta a la propiedad AbbreviatedDayNames del objeto DateTimeFormatInfo, situado en DateTimeFormatInfo.CurrentInfo, como podemos apreciar en el siguiente bloque de código.


<!– =============================================
DatePicker.xaml (c) 2006 by Charles Petzold
============================================= –>
<UserControl xmlns=»http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x=»http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:g=»clr-namespace:System.Globalization;assembly=mscorlib»
x:Class=»Petzold.CreateDatePicker.DatePicker»>
<!–….–>
<!– StatusBar with UniformGrid for days of the week. –>
<StatusBar Grid.Row=»1″
ItemsSource=»{Binding Source={x:Static
g:DateTimeFormatInfo.CurrentInfo},
Path=AbbreviatedDayNames}»
>
<!–….–>

 


Si en tiempo de ejecución observamos los elementos de la colección contenida en la propiedad AbbreviatedDayNames, comprobaremos que el primer elemento es «dom», siendo esta la causa de que el control visualice los nombres de los días en un orden que no se ajusta al utilizado en nuestro calendario.



 


Retocando la colección AbbreviatedDayNames


Para solventar este inconveniente podemos recurrir al empleo de un pequeño truco dentro del constructor del control DatePicker, consistente en obtener el objeto CultureInfo de la hebra de ejecución en curso de la aplicación, y pasar a una variable el contenido de la propiedad AbbreviatedDayNames. A continuación añadimos un nuevo elemento al final de esta colección, en el que depositamos el mismo valor existente dentro del primer elemento, eliminando por último el primer elemento, con lo que ya tenemos la colección que utilizará el StatusBar rellena de los valores en el orden que necesitamos.


//——————————————-
// DatePicker.cs (c) 2006 by Charles Petzold
//——————————————-
//….
// código añadido —————
using System.Threading;
using System.Collections.Generic;
// ———————————-

namespace Petzold.CreateDatePicker
{
public partial class DatePicker
{
//….
// Constructor.
public DatePicker()
{
//….

// código añadido ————————————-
CultureInfo oCultureInfoActual = (CultureInfo)Thread.CurrentThread.CurrentCulture;
List<string> lstDiasAbreviados = new List<string>(oCultureInfoActual.DateTimeFormat.AbbreviatedDayNames);
lstDiasAbreviados.Add(lstDiasAbreviados[0]);
lstDiasAbreviados.RemoveAt(0);
// —————————————————-

//….


 


Empleando el depurador, podemos apreciar el resultado de las mencionadas operaciones, como muestra la siguiente imagen.



 


Para que estos cambios sobre la colección de nombres de días de la semana que acabamos de realizar sean efectivos, tenemos que asignar la variable que contiene la colección de nuevo a la propiedad AbbreviatedDayNames del objeto CultureInfo.DateTimeFormat, pero en este punto vamos a encontrarnos con una desagradable sorpresa ya que al realizar dicha operación mediante la siguiente línea de código.


oCultureInfoActual.DateTimeFormat.AbbreviatedDayNames = lstDiasAbreviados.ToArray();

 


Se producirá una excepción que nos indica que el objeto al que intentamos asignar el valor es de sólo lectura.


 


 


La forma de solucionar este problema pasa por crear una copia del objeto CultureInfo obtenido de la hebra de ejecución -utilizando su método Clone-, lo que nos permitirá realizar la modificación que precisamos y devolverlo de nuevo a la hebra actual de ejecución, como vemos en el siguiente bloque de código. Nótese igualmente, que para conseguir que este código funcione eficazmente, debe estar situado antes de la llamada al método InitializeComponent; dicho método es el encargado de hacer que el compilador de código de marcado procese el código XAML de la página, por lo que si en el momento de invocar a InitializeComponent la propiedad AbbreviatedDayNames no tiene establecida nuestra modificación, la página no se percatará de la misma.


CultureInfo oCultureInfoActual = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone();
List<string> lstDiasAbreviados = new List<string>(oCultureInfoActual.DateTimeFormat.AbbreviatedDayNames);
lstDiasAbreviados.Add(lstDiasAbreviados[0]);
lstDiasAbreviados.RemoveAt(0);
oCultureInfoActual.DateTimeFormat.AbbreviatedDayNames = lstDiasAbreviados.ToArray();
Thread.CurrentThread.CurrentCulture = oCultureInfoActual;

InitializeComponent();


 


Al volver a ejecutar nuestro proyecto, el control mostrará los nombres de los días de la semana en el orden deseado.



 


Reubicando los días del mes


Pero ahora nos encontraremos con un nuevo inconveniente: si bien hemos cambiado las posiciones de los nombres de los días, no hemos hecho lo propio con los números, los cuales ahora no se corresponden con las columnas del día de la semana, es decir, la primera columna de números pertenece todavía al domingo, la segunda al lunes y así sucesivamente.


El origen de este comportamiento se debe al control UniformGrid utilizado para visualizar los números de los días del mes. Dicho control se utiliza como plantilla de elementos dentro de otro control ListBox, como vemos en el siguiente código.


<!– =============================================
DatePicker.xaml (c) 2006 by Charles Petzold
============================================= –>
<!–….–>
<ListBox Name=»lstboxMonth»
SelectionChanged=»ListBoxOnSelectionChanged»>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Name=»unigridMonth»
Columns=»7″ Rows=»6″
IsItemsHost=»True»
Background=»{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}» />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem>dummy item</ListBoxItem>
</ListBox>
<!–….–>

 


El control UniformGrid dispone de una propiedad llamada FirstColumn, que como su nombre indica, nos permite especificar la columna a partir de la cual se van a comenzar a rellenar los valores del control. Dicha propiedad es utilizada en este ejemplo del libro en dos lugares: durante la carga del control -evento Loaded- y cada vez que se produzca un cambio en la fecha seleccionada -propiedad de dependencia Date-, momento en el que se producirá una llamada al método DateChangedCallback y desde este a OnDateChanged, que es donde se establece la columna para el UniformGrid. En ambos casos se toma el número de día de la semana que corresponde al primer día del mes utilizado por el control, asignando este valor a la propiedad UniformGrid.FirstColumn.


Para sincronizar correctamente los números del día del mes con las columnas de los días de la semana, lo que debemos hacer es restar uno a la operación anteriormente mencionada, de modo que el código quedaría de la siguiente manera.


//——————————————-
// DatePicker.cs (c) 2006 by Charles Petzold
//——————————————-
//….
public partial class DatePicker
{
//….
// Handler for window Loaded event.
void DatePickerOnLoaded(object sender, RoutedEventArgs args)
{
//….
// código modificado ———————
unigridMonth.FirstColumn =
(int)(new DateTime(dt.Year, dt.Month, 1).DayOfWeek) – 1;
// ———————————————-
}
}

// OnDateChanged changes the visuals to match the new value of Date.
protected virtual void OnDateChanged(DateTime? dtOldValue,
DateTime? dtNewValue)
{
//….
// Set the first day of the month.
if (unigridMonth != null)
// código modificado ——————
unigridMonth.FirstColumn =
(int)(new DateTime(dtNew.Year,
dtNew.Month, 1).DayOfWeek) – 1;
// ———————————————-
//….
}
}


 


Al ejecutar de nuevo la aplicación comprobaremos cómo ahora los días del mes sí que coinciden adecuadamente con los de la semana.



 


Conclusión


El modelo de desarrollo de interfaces de usuario con WPF, basado en una clara separación entre la parte de presentación y la correspondiente a la lógica de funcionamiento, permite que podamos realizar modificaciones, como en este caso sobre la lógica, sin necesidad de alterar en absoluto el código XAML de presentación, lo que supone en muchos casos una ventaja en los procesos de mantenimiento de dichas interfaces.


Espero que os resulte interesante.


Un saludo.

6 Comentarios

  1. anonymous

    «Hace unos cuantos meses leí el estupendo libro «Applications = Code + Markup» de Charles Petzold»

    A mi juicio de estupendo no tiene nada, sirve como referencia para buscar algunos temas o ejemplos que otros libros no tocan, pero es un tocho que es lo menos didáctico que he encontrado en 5-6 libros de WPF. Desagraciadamente es más una transcripción de sus libros de programación con Win32 a WPF que un libro con filosofía WPF.

    Recomiendo WPF Unleashed de Adam Nathan sin dudarlo.

  2. tio_luiso

    A Pablo Alarcón:

    Te concederé casos que no es el mejor primer libro sobre WPF. Y definitivamente no es el mejor libro para empezar. Pero para mí es el mejor libro. Es el más completo.

    Pero después de todo, ¿que iba a contar alguien de la horda? XD (Chiste de un viciado del WoW a otro)

  3. anonymous

    Jajaja, joder Luis 🙂

    No se es más completo por tener más páginas, y en un libro espero que o me resuma y me haga aprender más rápido que la documentación de la MSDN o que me enseñe cosas que no aparecen documentadas o que me aporte algo de la experiencia del escritor. No creo que sea el caso en ese libro, pero para gustos los colores ( que el de Unleashed tiene, viene impreso a color 😉 )

  4. lmblanco

    Muchas gracias a todos por vuestra opinión e interés en el post. No es mi intención crear de nuevo polémica acerca de si el libro de Petzold es mejor o peor que el de Nathan, que ya se ha discutido bastante el tema 8-), baste como muestra estos dos enlaces:

    http://www.codinghorror.com/blog/archives/000846.html

    http://www.ericsink.com/entries/Petzold_Nathan.html

    Desde mi punto de vista el libro de Petzold es muy bueno, pero es que el de Nathan es también lo es, y no sabría por cual decidirme si tuviera que elegir entre ambos, ya que de ambos se pueden aprender cosas interesantes 😉

    De nuevo os agradezco el interés en el post y espero que os haya resultado de interés 8-D

    Un saludo.
    Luismi

  5. anonymous

    ¡Excelente Luismi!

    Y en cuanto a las polémicas con los libros, yo tampoco entro, Pero ¿por que no tener ambos?. (y no me digáis que por el precio, que puede ser digital, y no digo más…).

    Petzold hace una análisis genial de la parte no-XAML del asunto. Nathan cubre el resto, sobradamente. Y el manual del Grupo Weboo que publicó en «Cuadernos Técnicos de dotNetMania» es un estupendo resumen didáctico para iniciarse en castellano.

    Y, luego,lo de siempre, a pegarse con el código, y buscar bien…

    Un abrazo
    Marino

  6. lmblanco

    Hola Marino

    Muchas gracias por tu opinión y creo que esa es la idea, si podemos aprovechar lo mejor que cada una de estas obras aporta ¿por qué rechazar una u otra?.

    Un abrazo
    Luismi

Responder a Cancelar respuesta

Tema creado por Anders Norén