Barras de herramientas que se guardan en ficheros INI

Recuperada y explicado en la entrada anterior el funcionamiento de mi implementación de los ficheros de texto tipo INI, ahora voy a comentar lo que motivó esa entrada.


Las aplicaciones típicas de Windows tienen barras de herramientas, barras de menús, y si quieres que tu aplicación sea mínimanente interesante, tienes que implementarlas y permitir su personalización, o al menos guardar entre sesiones las posiciones de las mismas. Personalmente pienso que es una chorrada todo ese tipo de cosas en un programa, pero a veces son necesarias por motivos de espacio u organización.


El .NET Framework 2.0 provee de forma bastante sencilla la facultad de tener elementos acoplables casi de cualquier tipo, y de eso nos vamos a aprovechar. Primero debemos soltar en nuestra ficha un componente llamado ToolStripContainer, y le cambiamos la propiedad Dock al valor Fill, que se puede hacer directamente desde la pestaña que tiene el componente como tareas coumunes. También podemos activar/desactivar en qué lados se va a permitir el acople. El resto es muy sencillo: añadir las barras y los componentes que queramos.


Cuando lancemos nuestra aplicación, los elementos aparecerán allí donde se hayan encajado durante el diseño, y permitirán ser movidos de forma sencilla por todod los laterales de la ficha. Pero cuando cerremos nuestra aplicación los cambios se perderán…


¿Cómo guardar el estado de las posiciones? Una solución sería utilizar el sistema de Config del propio .NET, pero hay que currarse a mano todas las variables, otra utilizar los flujos binarios o xml para guardarla. En este último caso ignoro si se puede salvar toda la estructura de la ficha, pero lo dudo. Además, son elementos secuenciales. En ambos casos no son soluciones generales, sino particulares, con lo que tenemos que repetir en cada aplicación nueva o en cada cambio importante que hagamos.


¿Se puede construir un sistema general, y que encima lo pueda trastear el usuario del programa? La respuesta corta: si, pero con condiciones. La respuesta larga consiste en todo lo que sigue a partir de aquí.


Primero tenemos que echar un vistazo a cómo lo hace el propio Visual Studio, por lo que hay que inspeccionar el interior del método InitializeComponent. El asunto está clarísimo: se crean los distintos elementos, luego se van agregando a la instancia del ToolStripContainer mediante la llamada a Add del array de controles, y posteriormente se colocan en su posición mediante la asignación de la propiedad Location.


Facil, ¿no? Pues no. Fuera de InitializeComponent no funciona. Simplemente coloca las barras de herramientas donde mejor le parece al sistema, ignorando en muchos casos hasta si están unas encima de las otras. Incluso aunque dupliquemos exactamente el mismo bloque de código. Haz lo que digo pero no lo que hago.


La solución más ardua sería, una vez terminado el proyecto, y sin usar el editor visual, cambiar lo que queramos dentro de IntializeComponent, pero entonces nos estaríamos metiendo en camisa de once varas, y no es el tema, así que, como dijo mi tío Saltatrampas, si no puedes contra el enemigo, dale una patada en los bajos.


Una inspección en la ayuda sobre el componente ToolStripContainer nos muestra varias cosas interesantes. La primera de todas consiste en que dicho componente tiene cuatro instancias de ToolStripPanel llamadas TopToolStripPanel, LeftToolStripPanel, RightToolStripPanel y BottomToolStripPanel, que se corresponden a los contenedores de los cuatro laterales de la ficha. La segunda es que cada uno de estos componentes contiene a su vez un array del tipo ToolStripPanelRow llamado Rows y que como su nombre indica es una reperesentación de cuántas tiras hay en cada grupo. Y para finalizar, la propiedad Controls, dependiente de éste último, otro array que contiene, ya por fin, los controles anclados a dicho contenedor.


Vista esta estructura, pensamos que si a la hora de cerrar la ficha podemos generar una representación de la misma, luego a la hora de abrirla seremos capaces de volver a reconstruirla. Dicho y hecho, aquí está nuestra solución.


Guardar el estado es bastante sencillo, para cada uno de los ToolStripContainer recorremos todos los elementos de su propiedad Rows, y dentro de ésta todos los elementos de Controls, guardando finalmente el nombre y el Location de cada uno de ellos:



int iRows,iControls;
for(iRows=0;iRows<ts->Rows->Length;iRows++)
{
  for(iControls=0;iControls<ts->Rows[iRows]->Controls->Length;iControls++)
    ControlToConfig(prefix+«Row»+iRows.ToString()+«Control»+iControls.ToString(),ts->Rows[iRows]->Controls[iControls]);
  iniFile->WriteString(form->Name,prefix+«NumRow»+iRows.ToString()+«Control»,iControls.ToString());
}
iniFile->WriteString(form->Name,prefix+«NumRows»,iRows.ToString());


En el código de arriba, prefix representa a una cadena indicando qué parte estamos guardando (Top, Left, Right o Bottom). Un rápido vistazo al fichero INI nos indica que para cada control se guarda su posición y su nombre, y como nombre a la izuqueirda del igual, su prefijo más la fila en la que se encuentra más la palabra «Control» más el número de control (por ejemplo, Top0Control1 se corresponde al control que está en el anclaje superior de la ficha, tira 0 y es el segundo control anclado en esa tira). Con un poco de perspicacia cualquier usuario avanzado es capaz de editar el fichero a mano y cambiar los valores que considere oportunos.


Cargar el estado es algo más complejo, puesto que no debemos crear los controles, sino que estos controles ya están en la ficha, por lo que es necesario buscarlos y reasignarlos.


Hacerlo de esta forma tiene algunas ventajs sobre la opción de crear los controles por programa, puesto que podemos editarlos y modificarlos sin problemas dentro del IDE y no es necesaria acción alguna cuando se arranque la aplicación por primera vez: los controles aparecerán situados en sus posiciones por defecto, y si por alguna causa se nos corrompe el archivo de configuración, con borrarlo tendremos un sistema funcional con los valores por defecto. Está claro que podríamos guardar todos los metadatos necesarios para crear el control en cuestión, pero consideramos que esta es la mejor forma.


Otra ventaja es que este código es general, tan solo requiere la llamada a los métodos ToolbarsToConfig cuando queramos guardar el estado y a ConfigToToolbars cuando queramos recuperarlo, eso sí, hay que hacerlo cuatro veces, una por cada ToolStripPanel que tengamos activo:



void FormMain::LoadToolbars(void)
{
  WindowConfig->ConfigToToolbars(this,»Top»,m_mainToolStripContainer->TopToolStripPanel);
  WindowConfig->ConfigToToolbars(this,»Left»,m_mainToolStripContainer->LeftToolStripPanel);
  WindowConfig->ConfigToToolbars(this,»Right»,m_mainToolStripContainer->RightToolStripPanel);
  WindowConfig->ConfigToToolbars(this,»Bottom»,m_mainToolStripContainer->BottomToolStripPanel);
}
void FormMain::SaveToolbars(void)
{
  WindowConfig->ToolbarsToConfig(this,»Top»,m_mainToolStripContainer->TopToolStripPanel);
  WindowConfig->ToolbarsToConfig(this,»Left»,m_mainToolStripContainer->LeftToolStripPanel);
  WindowConfig->ToolbarsToConfig(this,»Right»,m_mainToolStripContainer->RightToolStripPanel);
  WindowConfig->ToolbarsToConfig(this,»Bottom»,m_mainToolStripContainer->BottomToolStripPanel);
}


Observamos que al método correspondiente se le pasa una referencia a la ficha actual, el prefijo y el ToolStripPanel correspondiente. Pasar un puntero a la ficha no es necesario, puesto que podríamos utilizar el método FindForm del ToolStripPanel.


Estos dos métodos ha de implementarlos el usuario en cada una de sus fichas; el código de la clase WindowConfig (que tiene más cosas aparte de las mencionadas), se puede obtener aquí.


Una de cal y otra de arena. Pese a lo comentado hasta ahora, el sistema no funciona todo lo bien que debiera. De hecho, cuando hay más de una barra de herramientas en una misma tira, sólo se respetan sus respectivas posiciones en la tira número 0; en las demás se colocan siempre como quieren. El autor considera que se trata de uno de tantos bugs de los que el .NET Framework está plagado, puesto que las pruebas que ha realizado han sido bastante exhaustivas.


Entre ellas ha visto cómo al guardar una barra de herramientas, ésta tenía una localización de (300,50) y al ser cargada esa misma barra se negaba a aceptar ese valor, quedando en (300,0), lo ha visto incluso mediante el depurador en tiempo de ejecución, intentando introducir algo diferente a cero y viendo cómo la barra se negaba a aceptarlo. Se negaba tanto estando sin enlazar a ningún contenedor, como estándolo; tan sólo lo aceptaba si estaba enlazada en la tira 0. Y desde luego, una vez en tiempo de ejecución, esa barra ha podido ser movida satisfactoriamente.


Este problema se presenta tanto en el Visual Studio 2005 como en la beta de su SP.

Un comentario sobre “Barras de herramientas que se guardan en ficheros INI”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *