|
Bien, todo cocinado, ¿es acaso este el fin del sufrimiento?.
Pues, no. Como verás tengo muchas más cartas debajo de la manga,
entre ellas hacer un menú MRU. ¿Qué significa eso? Bueno, simplemente
es la abreviatura de Most Recently Used o Los
Usados Más Recientemente. Por supuesto, esta frase está haciendo
referencia a los documentos abiertos/guardados más recientemente.
Esto puede resultar un agregado bastante útil para un editor de
textos, sobre todo si es MDI porque eso quiere decir que el usuario
está permanentemente abriendo y cerrando documentos, o más aún,
modificando varios a la vez.
EL MRU, entonces, es una característica muy común
de la programación en Windows - una sección del menú archivo que
lista los archivos abiertos/guardados recientemente. Al hacer clic
en cualquiera de estos se abre el archivo correspondiente permitiéndole
al usuario seguir con el trabajo que había dejado.
Como era de esperar esta es la parte más complicada
de todo el artículo, por eso va al final. Ahora bien, ¿por qué complicada?.
Bueno, porque debemos utilizar un TStringList y un archivo INI.
Aparentemente, sin ser ningún experto en el tema,
podemos decir que vamos a necesitar hacer 3 cosas:
1) mostrar la lista de los MRU en el menú al iniciarse
la aplicación.
2) ir actualizando la lista a medida que se abren/guardan documentos.
3) al hacer clic en alguno de los MRU listados abrirlo.
Uno imagina que para mostrar los MRU todo ocurre
en tiempo de ejecución. De hecho eso es casi cierto. Digo casi,
porque los captions de los items sí los determinamos en tiempo de
ejecución, pero antes en tiempo de diseño debemos agregar items
en blanco en la posición donde irán los MRU. En cierta forma estamos
"reservando el espacio" y en tiempo de ejecución determinamos
los caption. Pero, ¿no me van a quedar los espacios en blanco en
caso de que no haya MRU o haya 2 en vez de 4?. No, Delphi se encarga
automaticamente de que los items con caption vacio no se muestren,
lo cuál es lógico, ya que no hay nada que mostrar.
Antes de empezar determinemos cuantos MRU vamos a
mostrar. Una cantidad aceptable sería 4, pero pueden ser más. Sin
embargo, no es recomendable sobrepasar los 9 por 2 razones: los
menús se alargan mucho y no hay forma de crear accesos directos
con & (por ejemplo, &1, &2 ... &8, &9, &10
se repite el &1).
En cuanto a la posición de los MRU dentro del menú,
esta puede variar según los gustos, pero tampoco puede hacerlo demasiado.
Básicamente hay 2 maneras de mostrar los MRU en una aplicación.
Colocarlos, limitados por 2 separadores uno al comienzo y otro al
final, justo antes del último item del menú archivo, generalmente
Salir. La segunda opción es crear un sub-menú llamado Archivos Recientes
en el menú Archivo y colocar los MRU allí.
Entonces, crearemos 4 items en blanco en el menú
Archivo que estarán limitados por 2 separadores. A cada uno de ellos
los nombramos mniMRU1, mniMRU2, mniMRU3, mniMRU4 y modificamos su
propiedad Tag a 0, 1, 2, 3 respectivamente. Una vez que hayas terminado,
haz lo mismo en el menú de la ventana hija utilizando los mismos
nombres y propiedades.
El objeto TStringList almacenará la lista de MRUs
durante la ejecución del programa, mientras que entre sesión y sesión
esa información se guardará en un archivo INI.
El mejor momento para cargar en el objeto TStringList
(MRUList) todos los MRUs almacenados en el archivo INI será en el
evento OnCreate de la ventana madre y el mejor momento para guardar
en el archivo INI todos los MRUs almacenados en el objeto TStringList
será en el evento OnDestroy de la ventana madre. Veamos, entonces
cómo debemos implementar estos eventos.
|
procedure TfrmMain.FormCreate(Sender:
TObject);
var IniFile: TIniFile;
Index: integer;
begin
MRUList := TStringList.Create;
IniFile := TIniFile.Create('anotador.ini');
try {escribimos el
contenido del INI en el stringlist}
for Index := 0 to 3
do
MRUList.Add(IniFile.ReadString('MRU',IntToStr(Index),
''));
finally
IniFile.Free;
end;
end;
procedure TfrmMain.FormDestroy(Sender:
TObject);
var IniFile: TIniFile;
Index: integer;
begin
IniFile := TIniFile.Create('anotador.ini');
try {escribimos el
contenido del stringlist en el INI}
for Index := 0 to 3
do
IniFile.WriteString('MRU',
IntToStr(Index), MRUList[Index]);
finally
IniFile.Free;
end;
end;
|
Nota: para que
Delphi pueda hacer uso de los archivos INI debes incluir el unit
IniFiles en tu lista uses.
Bien, ya hemos creado nuestra lista y hemos logrado
que se cargue/guarde al abrir/cerrar la aplicación. Pero también
debe ser actualizada periodicamente en la medida que se abran/guarden
nuevos documentos. Para mantener la lista al día debemos crear un
procedure que se encargue del trabajo sucio y luego referenciarlo
cada vez que se abran/guarden nuevos docs. Veamos, veamos...
|
procedure TfrmMain.ActualizarMRU(const
Nombre: string);
var i: integer;
begin
{revisar que no este repetido
en la lista}
for i := MRUList.Count - 1 downto 0
do
if Nombre = MRUList[i] then
MRUList.Delete(i);
{revisar que no este repetido
en la lista}
for i := 0 to (MRUList.Count - 1) do
if Nombre = MRUList[i] then
MRUList.Delete(i);
{nos aseguramos de que la
lista tenga 3 elementos}
while MRUList.Count > 3 do
MRUList.Delete(MRUList.Count - 1);
while MRUList.Count < 3 do
MRUList.Add('');
{insertar el nuevo doc en
primer lugar}
MRUList.Insert(0, Nombre);
end;
|
Nota: los documentos
más recientes van al comienzo de la lista.
Como ves, este procedimiento primero verifica que
el documento a agregar no se encuentre ya listado, si lo está lo
borra, luego nos aseguramos de que la lista tenga 3 elementos agregando
en caso de que falten y quitando en caso de que sobren; por último
inserta el nuevo documento en la primera posición de la lista.
Ok, pero ActualizarMRU sólo actualiza el objeto MRUList
pero no la lista que se visualiza en el menú. Para ello debemos
crear otro procedimiento llamado VisualizarMRU que ponga el menú
al día con respecto a las modificaciones que sufre el objeto MRUList.
Veamos su implementación.
|
procedure TfrmMain.VisualizarMRU;
begin
mniMRU1.Caption := '&1 ' + MRUList[0];
mniMRU1.Visible := (MRUList[0] <> '');
mniMRU2.Caption := '&2 ' + MRUList[1];
mniMRU2.Visible := (MRUList[1] <> '');
mniMRU3.Caption := '&3 ' + MRUList[2];
mniMRU3.Visible := (MRUList[2] <> '');
mniMRU4.Caption := '&4 ' + MRUList[3];
mniMRU4.Visible := (MRUList[3] <> '');
end;
|
Nota: (MRUList[x]
<> '') es una expresión booleana que devuelve True en caso
de ser cierta o False en caso de no serlo.
Ahora bien, todo esto que estuvimos haciendo lo hicimos
en la ventana madre, pero ¿es que la ventana hija no hay que actualizarla?
Por supuesto que sí, asi que debes agregar este mismo procedimiento
(VisualizarMRU) en las sección public de la ventana hija. No es
necesario cambiarle los nombres ya que son los mismos para ambos
menús, pero sí es necesario cambiar a MRUList[x] por frmMain.MRUList[x].
Esta es la razón por la que MRUList fue declarado public en la ventana
madre.
Como ya explicamos ActualizarMRU y VisualizarMRU
deben ser llamados al crear/abrir documentos, ya que de acuerdo
a los estándares, los nuevos documentos no pueden ser agregados
hasta que no se les asigne un nombre y sean guardados. De la misma
forma, los documentos que se cierran no pueden ser agregados si
no fueron guardados anteriormente. Bien, veamos cómo debemos llamar
a ActualizarMRU y VisualizarMRU.
|
procedure TfrmMain.mniArAbrirClick(Sender:
TObject);
begin
if OpenDialog.Execute then
CrearVentanaMDIHija(OpenDialog.FileName);
TfrmChild(ActiveMDIChild).DocNuevo := False;
ActualizarMRU(OpenDialog.FileName);
TfrmChild(ActiveMDIChild).VisualizarMRU;
end;
|
y el procedimiento para guardar declarado en frmChild
queda así:
|
procedure TfrmChild.Guardar(Nombre:
string);
begin
reMain.Lines.SaveToFile(Nombre);
reMain.Modified := False;
DocNuevo := False;
frmMain.ActualizarMRU(Nombre);
VisualizarMRU;
end;
|
Como vemos, aunque los procedimientos son bastante
directos y fáciles de entender, deben ser llamados en el momento
indicado para evitar cualquier tipo de errores. Hasta el momento
venimos llamando a frmChild.VisualizarMRU, es decir que venimos
actualizando al menú que se muestra siempre que hay documentos abiertos,
pero como todos sabemos no siempre los hay. También es necesario,
entonces, llamar a frmMain.VisualizarMRU cada vez que se inicia
la aplicación y cuando se cierra la última ventana MDI hija, es
decir, en los casos en los que no hay documentos abiertos y por
lo tanto se va a mostrar el menú de frmMain.
|
procedure TfrmMain.FormCreate(Sender:
TObject);
var IniFile: TIniFile;
Index: integer;
begin
MRUList := TStringList.Create;
IniFile := TIniFile.Create('anotador.ini');
try {escribimos el
contenido del INI en el stringlist}
for Index := 0 to 3
do
MRUList.Add(IniFile.ReadString('MRU',IntToStr(Index),
''));
finally
IniFile.Free;
end;
VisualizarMRU;
end;
procedure TfrmChild.FormClose(Sender:
TObject; var Action: TCloseAction);
begin
Action := caFree;
{si este es el último MDI
child abierto ...}
if frmMain.MDIChildCount = 1 then
frmMain.VisualizarMRU;
end;
|
Finalmente, debemos llamar a VisualizarMRU siempre
que el foco pase de una ventana hija a otra para actualizar el listado
de MRUs en el menú de las ventanas MDI hijas. De lo contrario, cada
ventana hija visualizará los MRUs de acuerdo a su última actualización,
y por lo tanto, no necesariamente el listado actual.
|
procedure TfrmChild.FormActivate(Sender:
TObject);
begin
VisualizarMRU;
frmMain.DeterminarToolbar(tlbMain);
end;
|
Para concluir con esta parte, entonces, queda que
ActualizarMRU, el procedimiento utilizado para actualizar el objeto
MRUList, se define sólo en frmMain, mientras que VisualizarMRU debemos
definirlo en frmMain y también en frmChild. Los llamados a ActualizarMRU
sólo se dan en frmMain.Abrir y frmChild.Guardar; mientras que los
llamados a frmMain.VisualizarMRU
Bien, por fin llegamos al fin. Jej!, después de tanto
escribir como que cansa, pero seguiremos hasta el final. Nos falta
sólo permitir que cuando el usuario haga clic sobre el archivo listado
en el menú éste se abra. Para ello creamos un nuevo procedimiento
public llamado AbrirMRU. Veamos cómo hacerlo.
|
procedure TfrmMain.AbrirMRU(Sender:
TObject);
var Index: integer;
begin
Index := TMenuItem(Sender).Tag;
if MRUList[Index] <> '' then
begin
CrearVentanaMDIHija(MRUList[Index]);
TfrmChild(ActiveMDIChild).DocNuevo := False;
TfrmChild(ActiveMDIChild).VisualizarMRU;
end;
end;
|
Ahora, todo lo que nos queda por hacer es llamar
a este procedimiento desde el evento OnClick de los items del menú
de frmMain y frmChild.
|
procedure TfrmMain.MRUClick(Sender:
TObject);
begin
AbrirMRU(Sender);
end;
procedure TfrmChild.MRUClick(Sender:
TObject);
begin
frmMain.AbrirMRU(Sender);
end;
|
Le asignamos el procedimiento MRUClick al evento
OnClick de todos los items de los menús y listo. Salió con fritas.
Terminamos. Una pavada esto de los MDI, ¿eh?
Nota: Para que
sea posible asignar MRUClick al evento OnClick de los items de los
menús en tiempo de diseño es necesario declarar MRUClick en la sección
donde se dclaran los demás eventos (no en private ni en public)
del form correspondiente.
|