Hace tiempo me topé con un tema similar a este, aunque con un enfoque diferente; En aquel caso se trataba de que el usuario pudiera crear su propio menú personalizado dentro de una aplicación. Es decir, que además de las opciones propias de la aplicación, el usuario pudiera configurarse un menú con las opciones que más deseara utilizar o tener más «a mano». En este caso, y a partir de este hilo en los foros del Clubdelphi, se ha planteado la posibilidad de que un usuario pueda crearse sus propios «accesos directos» a opciones del menú.
La solución en aquel momento pasó por «volcar» el contenido del menú a otro componente (en ese caso un TreeView, de forma similar a cómo se ve en este ejemplo) y desde ese, generar la estructura del nuevo punto de menú arrastrando elementos.
Para el problema de generar accesos directos, se me antoja que se pueda usar un sistema similar.
(1) «Volcar» el contenido del menú hasta otro componente que nos permita trabajar con los elementos del menú (ya que ni el menú ni los ítems poseen opciones para arrastrar -Drag & Drop-). Este esta caso vamos a utilizar un componente (TListBox) donde almacenaremos los elementos y los apuntadores a los ítems del menú (propiedad Objects).
procedure TFormMain.Button1Click(Sender:TObject);var
i:integer;
str:string;// Recursiva para obtener los subItemsprocedure GetItems(mi:TMenuItem);var
i:Integer;beginfor i :=0to(mi.Count-1)dobegin
Str := mi.Items[i].Caption;
ListBox1.Items.AddObject(Str, mi.Items[i]);// SubItems de este
GetItems(mi.Items[i]);end;end;begin// Recorerr menu principalfor i :=0to(MainMenu1.Items.Count-1)dobegin
Str := MainMenu1.Items[i].Caption;
ListBox1.Items.AddObject(Str, MainMenu1.Items[i]);// SubItems de este
GetItems(MainMenu1.Items[i]);end;end;
procedure TFormMain.Button1Click(Sender: TObject);
var
i:integer;
str:string;
// Recursiva para obtener los subItems
procedure GetItems(mi:TMenuItem);
var
i:Integer;
begin
for i := 0 to (mi.Count - 1) do begin
Str := mi.Items[i].Caption;
ListBox1.Items.AddObject(Str, mi.Items[i]);
// SubItems de este
GetItems(mi.Items[i]);
end;
end;
begin
// Recorerr menu principal
for i := 0 to (MainMenu1.Items.Count - 1) do begin
Str := MainMenu1.Items[i].Caption;
ListBox1.Items.AddObject(Str, MainMenu1.Items[i]);
// SubItems de este
GetItems(MainMenu1.Items[i]);
end;
end;
Con este código poblamos el ListBox con los Caption(Text) de los elementos del menú, y lo que es más importante, los apuntadores a cada elementos que se guardar al utilizar AddObject.
(2) ¿Cómo crear un acceso directo que permita ejecutar una opción de menú? Para ello podemos utilizar un TImage que sobre el cual programaremos el evento OnDblClick/OnClick.
Crear el componente es sencillo, y se puede ver código de ejemplo de cómo hacerlo en estas entradas:
La idea es que cada «acceso directo» posea un apuntador al elemento de menú correspondiente para poder ejecutar el código programado en el OnClick o en la TAction asociada a ese elemento del menú. Lo lógico sería utilizar una propiedad del propio componente (Data, Object,….) que nos permitiera enlazar directamente. No es el caso del TImage, así que en el ejemplo utilizaremos el propio ListBox como el «contenedor» de los apuntadores (como una lista intermedia), aunque como he dicho, la solución ideal, sería que cada «acceso directo» tuviera un puntero «directo» al TMenItem asociado.
El código paras crear el componente y gestionar esa asociación podría ser similar a este:
procedure TFormMain.Button2Click(Sender:TObject);var
img:TImage;
mi:TMenuItem;beginIf ListBox1.ItemIndex=-1thenbegin
MessageDlg('Selecciona un elemento de la llista', mtWarning,[mbOK],0);
Exit;end;// Item del menu
mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]);// Tiene asignado el OnClick?ifAssigned(mi.OnClick)thenbegin// Nadaendelsebegin// Tiene asignada la action?ifAssigned(mi.Action)thenbegin//signado OnExecuteifAssigned(mi.Action.OnExecute)thenbegin// Nadaendelsebegin
MessageDlg('Ese elemento no tiene nada que hacer asignado',
mtWarning,[mbOK],0);
Exit;end;endelsebegin
MessageDlg('Ese elemento no tiene nada que hacer asignado',
mtWarning,[mbOK],0);
Exit;end;end;
Randomize;// Elemento seleccionado
img := TImage.Create(nil);
img.Parent:= Panel1;
img.Height:=32;
img.Width:=32;
img.Left:=Random(panel1.Width- img.Width);
img.Top:=Random(panel1.Height- img.Height);
img.Stretch:=True;
img.Transparent:=True;// El TAG es la posicion en la lista
img.Tag:= ListBox1.ItemIndex;// Item del menu
mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]);// Asignar la imagen
ImageList1.GetBitmap(mi.ImageIndex, img.Picture.Bitmap);// Asignar el evento
img.OnClick:= MyImgClick;end;
procedure TFormMain.Button2Click(Sender: TObject);
var
img:TImage;
mi:TMenuItem;
begin
If ListBox1.ItemIndex = -1 then begin
MessageDlg('Selecciona un elemento de la llista', mtWarning, [mbOK], 0);
Exit;
end;
// Item del menu
mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]);
// Tiene asignado el OnClick?
if Assigned(mi.OnClick) then begin
// Nada
end
else begin
// Tiene asignada la action?
if Assigned(mi.Action) then begin
//signado OnExecute
if Assigned(mi.Action.OnExecute) then begin
// Nada
end
else begin
MessageDlg('Ese elemento no tiene nada que hacer asignado',
mtWarning, [mbOK], 0);
Exit;
end;
end
else begin
MessageDlg('Ese elemento no tiene nada que hacer asignado',
mtWarning, [mbOK], 0);
Exit;
end;
end;
Randomize;
// Elemento seleccionado
img := TImage.Create(nil);
img.Parent := Panel1;
img.Height := 32;
img.Width := 32;
img.Left := Random(panel1.Width - img.Width);
img.Top := Random(panel1.Height - img.Height);
img.Stretch := True;
img.Transparent := True;
// El TAG es la posicion en la lista
img.Tag := ListBox1.ItemIndex;
// Item del menu
mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]);
// Asignar la imagen
ImageList1.GetBitmap(mi.ImageIndex, img.Picture.Bitmap);
// Asignar el evento
img.OnClick := MyImgClick;
end;
Primero se realizan unas comprobaciones para detectar si posee alguna acción asignada (sea OnClick o TAction) y posteriormente se crea el TImage, se configura y se asigna como TAG el ItemIndex del ListBox (que es este caso estamos utilizando como estructura intermedia para guardar el apuntador al TMenuItem).
Finalmente sólo quedar crear el procedimiento MyImgClick, que ejecutará el código asignado al elemento del menú cuando se presione sobre la imagen asociada. Se incluyen comprobaciones similares a las anteriores, por si el elemento no tiene nada asignado y se tiene en cuenta también que haya código en el OnClick del TMenuItem o exista una TAction asociada.
var
i:integer;
str:string;
mi:TMenuItem;begin// Test del senderifnot(sender is TImage)thenbegin
Exit;endelsebegin
i := TImage(Sender).Tag;
Str := ListBox1.Items[i];end;// Acceder a la opción de menú
mi := TMenuItem(ListBox1.Items.Objects[i]);// Asignado código?ifAssigned(mi.OnClick)thenbegin
mi.OnClick(nil);
Exit;endelsebegin// Tiene asignada la action?ifAssigned(mi.Action)thenbegin// Asignado OnExecuteifAssigned(mi.Action.OnExecute)thenbegin
mi.Action.OnExecute(nil);
Exit;end;endend;
MessageDlg('No hay nada asignado a esa opción...', mtInformation,[mbOK],0);end;
var
i:integer;
str:string;
mi:TMenuItem;
begin
// Test del sender
if not (sender is TImage) then begin
Exit;
end
else begin
i := TImage(Sender).Tag;
Str := ListBox1.Items[i];
end;
// Acceder a la opción de menú
mi := TMenuItem(ListBox1.Items.Objects[i]);
// Asignado código?
if Assigned(mi.OnClick) then begin
mi.OnClick(nil);
Exit;
end
else begin
// Tiene asignada la action?
if Assigned(mi.Action) then begin
// Asignado OnExecute
if Assigned(mi.Action.OnExecute) then begin
mi.Action.OnExecute(nil);
Exit;
end;
end
end;
MessageDlg('No hay nada asignado a esa opción...', mtInformation, [mbOK], 0);
end;
Se puede mejorar y «refinar» bastante más, pero creo que la idea queda clara. A partir de aquí cada uno que «añada» lo que quiera. Cualquier sugerencia será bien recibida.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Es una cosa que en multitud de ocasiones hemos necesitado hacer; Ya sea con la configuración del acceso a Base de Datos, con la configuración de propiedades, guardar estado del programa, posición,…
En este caso en concreto (a raiz de este hilo en Clubdelphi) se trata de guardar la configuración de Base de Datos. El contenido importante de la propiedad ConnectionString.
Podemos almacenada TODO el contenido de la propiedad en un sólo elemento o guardar los elemtos relevantes de la conexión (Servidor, usuario, Base de Datos y password); En mi caso he preferido el segundo.
Para ello lo más sencillo es utilizar la clase TIniFile que provee Delphi.
En este ejemplo además se utiliza otra conexión con casi los mismos parámetros introducidos, pero accediendo a la tabla master, para preguntar al servidor SQL por las Bases de Datos disponibles y así mostrarlas al usuario:
En este caso, la conexión es con SQL Server mediante OLEDB. En general, para esta y otras conexiones os recomiendo una web creada específicamente al respecto:
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Hace unos días nos encontramos con el problema (no muy grande ;-D ) de añadir a una aplicación delphi existente la posibilidad de incluir una imagen seleccionada por el usuario. A priori la imagen era un JPG, de la cual se debía crear una miniatura (thumbnail) a unas dimensiones determinadas (180 x 115) y ambas debían subir a un directorio determinado. Ningun problema. Aquí mismo había un par de procedimientos de Domingo Seoane para redimensdionar una imagen.
En concreto modificando un poco el procedimiento Proporcional conseguí lo que necesitaba. Que si la imagen original no era exactamente de las mismas proporciones que la que necesitaba (miniatura) esta rellenara con un color «neutro» (en este caso el blanco) los bordes laterales.
// Esta otra mantiene la relacion entre alto y anchoprocedure Proporcional(Imagen: TGraphic; Ancho, Alto:Integer);var
Bitmap: TBitmap;
Rect:TRect;begin
Bitmap:= TBitmap.Create;try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;/// Calculos para que quede proporcionalif(Ancho/Imagen.Width) <(Alto/Imagen.Height)thenbegin
Alto:=Trunc((Ancho*Imagen.Height)/Imagen.Width);endelsebegin
Ancho:=Trunc((Imagen.Width*Alto)/Imagen.Height);end;// posición nueva// Hay que centarla para que queden márgenes iguales a ambos lados
Rect.Left:=((Bitmap.Width- Ancho)div2);
Rect.Top:=((Bitmap.Height- Alto)div2);
Rect.Right:= Rect.Left+ Ancho;
Rect.Bottom:= Rect.Top+ Alto;// Color neutro para márgenes
Bitmap.Canvas.Brush.Color:= clRed;// copiar
Bitmap.Canvas.FillRect(Bitmap.Canvas.ClipRect);
Bitmap.Canvas.StretchDraw(Rect,Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
// Esta otra mantiene la relacion entre alto y ancho
procedure Proporcional(Imagen: TGraphic; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
Rect:TRect;
begin
Bitmap:= TBitmap.Create;
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
/// Calculos para que quede proporcional
if (Ancho/Imagen.Width) < (Alto/Imagen.Height) then begin
Alto:= Trunc((Ancho*Imagen.Height)/Imagen.Width);
end
else begin
Ancho:= Trunc((Imagen.Width*Alto)/Imagen.Height);
end;
// posición nueva
// Hay que centarla para que queden márgenes iguales a ambos lados
Rect.Left := ((Bitmap.Width - Ancho) div 2);
Rect.Top := ((Bitmap.Height - Alto) div 2);
Rect.Right:= Rect.Left + Ancho;
Rect.Bottom := Rect.Top + Alto;
// Color neutro para márgenes
Bitmap.Canvas.Brush.Color := clRed;
// copiar
Bitmap.Canvas.FillRect(Bitmap.Canvas.ClipRect);
Bitmap.Canvas.StretchDraw(Rect,Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Hice un par de pruebas con imágenes y el resultado no fue exactamente lo que yo esperaba. El procedimiento era correcto, y funcionaba bien, pero las imagenes en minuatura presentaban Aliasing. Y siendo las miniaturas bastante pequeñas el efecto se notaba bastante.
Ampliando un poco la imagen y comparándola con una generada con cualquier programa sencillo de retoque fotográfico se apreciaba bastante la diferencia entre ambas.
Esto es lo que se conoce como aliasing. Se pueden encontrar múltiples definiciones y explicaciones de este problema en Internet (wiki), así que no explicaré aquí de que se trata.
APLICAR ANTIALIASING
La teoría dice que esto se soluciona aplicando algoritmos de altializasing, así me he puesto a hacer unas pruebas a ver qué resultado obtenía.Mi idea es modificar el color de cada uno de los pixels de la imagen teniendo en cuenta en color de los pixels que hay a su alrededor.
Qué pixels seleccionemos para ello y cuantos (distancia) determinará que el resultado sea más o menos satisfactorio, pero también afectará al tiempo de cálculo. Por lo que he leído esto es lo que se conoce como Supersampling/Multisampling.
Un ejemplo de diferentes selecciones de pixels se puede ver en la imagen siguiente:
En cada uno de estos casos se variará el color del pixel central teniendo en cuenta los colores de los pixels que hay a su alrededor.
A partir de aquí me he propuesto hacer algunas pruebas (sencillas) para comprobar si en los resultados se notaban cambios a simple vista.
PRUEBAS DE ALGORITMOS
Para los ejemplos he realizado una imagen sencilla, con varias líneas inclinadas, donde se aprecian bastantes «dientes de sierra» y algunas circunferencias. La imagen inicial es la que se ve en la figura siguiente con un tamaño inicial de 457 x 273 pixels.
La idea es reducir el tamaño de esa imagen hasta la mitad (más o menos) y a una cuarta parte aplicando antes un algoritmo de antialiasing sencillo escogiendo diferentes puntos para modificar el color de los pixels.
Para la reducción de tamaño, he utilizado un procedimiento estandard para reducir el tamaño de imágenes BPL utilizando (StretchDraw), pero en este caso, antes de hacer la reducción he probado a aplicar los algoritmos de AntiAliasing.
// Esta cambia el alto y ancho, estirando la imagen si es necesarioprocedure Redimensionar(Imagen:TBitmap; Ancho, Alto:Integer);var
Bitmap: TBitmap;begin
Bitmap:= TBitmap.Create;// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);// reducirtry
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
// Esta cambia el alto y ancho, estirando la imagen si es necesario
procedure Redimensionar(Imagen:TBitmap; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
begin
Bitmap:= TBitmap.Create;
// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);
// reducir
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Para modificar el color lo que he probado es a sumar los colores de los puntos escogidos al del pixel actual y luego hacer la media para obtener un color resultante; Así por ejemplo, para calcular el nuevo color de un pixel teniendo en cuenta el pixel superior y el inferior de la misma columna, utilizo un código como este:
1
2
3
4
5
6
// R1 es el componente Red del pixel actual y R2 y R3 los del sup. e inferior.
R1:=Round(R1 + R2 + R3 )div3;
G1:=Round(G1 + G2 + R3 )div3;
B1:=Round(B1 + B2 + b3 )div3;// color resultante
Result := RGB(R1,G1,B1);
// R1 es el componente Red del pixel actual y R2 y R3 los del sup. e inferior.
R1:=Round(R1 + R2 + R3 ) div 3;
G1:=Round(G1 + G2 + R3 ) div 3;
B1:=Round(B1 + B2 + b3 ) div 3;
// color resultante
Result := RGB(R1,G1,B1);
Lo que he hecho en las pruebas es aplicar a la imagen, esta mismo procedimiento, pero teniendo en cuenta diferentes selecciones de puntos.
Seleccionando 2 puntos; Superior e inferior.
Seleccionando 4 puntos; Superior, inferior, izquierda y derecha.
Seleccionando 8 puntos. Los 8 puntos que hay alrededor del pixels actual.
Seleccionando 8 puntos y aplicando ponderación al actual. Utilizar los 8 pixels que hay alrededor del actual, pero aplicando más peso (más valor) al pixels actual (a su color) que a los del resto. En mi caso el pisel actual tiene un peso de 4, mientras que el resto queda con un pero 1.
En un primer ejemplo he aplicado los dos primeros (2 y 4 pixels), pensando que no habría grandes cambios y la verdad es qe me ha sorprendido, ya que tomando tan sólo 2 puntos ya se notan algunos cambios y tomando 4 las dioferencias ya son bastante apreciables.
El resultado obtenido por este ejemplo es el siguiente:
La imagen superior es el original (redimensionado tal como lo hace delphi), y las dos inferiores son a las que se les ha aplicado el procedimiento de Antialiasing antes de redimensionarlas. En una escogiendo 2 los pixels laterales y en la otra los 4 pixels que rodean al del cálculo. Superior, inferior, izquierdo y derecho.
Como se puede ver, con dos pixels únicamente, ya hay zonas (1, 3 y 5) donde se aprecian diferencias. Seguramente en estas más que en otras porque la selección de pixels no es homogénea (de ahí que en las líneas horizontales se aprecie más mejora).
Cuando se aplica el algoritmo teniendo en cuenta los 4 pixels de alrededor, se aprecia (2, 3, 4 y 5) ya bastantes diferencias.
En el segundo ejemplo he aplicado los 4 casos comentados antes.
El resultado de este segundo ejemplo es el siguiente:
En este caso entre los dos últimos no se aprecia diferencia visible, pero sí entre escoger 4 puntos y 8 puntos. Ver los puntos marcados como 1 y 3.
Dado que no se aprecian grandes diferencias entre los dos últimos, he integrado en un último ejemplo el redimensionado y el procedimiento de Antialiasing, de forma que este segundo se realice de forma automática.
Aunque el ejemplo que se ha desarrallo aquí y el procedimiento parece que funcionan de manera aceptable, hay que tener en cuenta otros factores a la hora de realizar un algoritmo más completo.
En nuestro caso la distancia de pixel utilizada (muestreo) es una distancia 1; es decir, hemos seleccionado los pixels que hay más cercanos al que vamos a modificar. Podemos seleccionar pixels de distancias mayores (2 y 3); De esta forma el resultado puede ser más correcto, aunque esto también tiene que ver con el porcentaje de reducción del tamaño.
No es lo mismo reducir una imagen a la mitad de su tamaño, que al 10% del tamaño original. Segun el caso el resultado puede ser mejor o peor si seleccionamos pixels a distancias 1,2 y 3 del pixels a calcular.
type
TRGBTripleArray =array[0..32767]of TRGBTriple;
PRGBTripleArray =^TRGBTripleArray;...// Esta cambia el alto y ancho, estirando la imagen si es necesarioprocedure Redimensionar(Imagen:TBitmap; Ancho, Alto:Integer);var
Bitmap: TBitmap;//····························································// Procedimiento de Antialiasing con Distancia=1procedure Antialiasing(bmp1, bmp2:TBitmap);var
r1,g1,b1:Integer;
Y, X, j:integer;
SL1, SL2, SL3: PRGBTripleArray;begin// Tamaño del bitmap destino
bmp2.Height:= bmp1.Height;
bmp2.Width:= bmp1.Width;// SCANLINE
SL1 := bmp1.ScanLine[0];
SL2 := bmp1.ScanLine[1];
SL3 := bmp1.ScanLine[2];// reorrido para todos los pixelsfor Y :=1to(bmp1.Height-2)dobeginfor X :=1to(bmp1.Width-2)dobegin
R1 :=0; G1 :=0; B1 :=0;// los 9 pixels a tener en cuentafor j :=-1to1dobegin// FIla anterior
R1 := R1 + SL1[X+j].rgbtRed+ SL2[X+j].rgbtRed+ SL3[X+j].rgbtRed;
G1 := G1 + SL1[X+j].rgbtGreen+ SL2[X+j].rgbtGreen+ SL3[X+j].rgbtGreen;
B1 := B1 + SL1[X+j].rgbtBlue+ SL2[X+j].rgbtBlue+ SL3[X+j].rgbtBlue;end;// Nuevo color
R1:=Round(R1 div9);
G1:=Round(G1 div9);
B1:=Round(B1 div9);// Asignar el nuevo
bmp2.Canvas.Pixels[X, Y]:= RGB(R1,G1,B1);end;// Siguientes...
SL1 := SL2;
SL2 := SL3;
SL3 := bmp1.ScanLine[Y+1];end;end;//···························································· begin
Bitmap:= TBitmap.Create;// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);// reducirtry
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
type
TRGBTripleArray = array[0..32767] of TRGBTriple;
PRGBTripleArray = ^TRGBTripleArray;
...
// Esta cambia el alto y ancho, estirando la imagen si es necesario
procedure Redimensionar(Imagen:TBitmap; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
//····························································
// Procedimiento de Antialiasing con Distancia=1
procedure Antialiasing(bmp1, bmp2:TBitmap);
var
r1,g1,b1:Integer;
Y, X, j:integer;
SL1, SL2, SL3: PRGBTripleArray;
begin
// Tamaño del bitmap destino
bmp2.Height := bmp1.Height;
bmp2.Width := bmp1.Width;
// SCANLINE
SL1 := bmp1.ScanLine[0];
SL2 := bmp1.ScanLine[1];
SL3 := bmp1.ScanLine[2];
// reorrido para todos los pixels
for Y := 1 to (bmp1.Height - 2) do begin
for X := 1 to (bmp1.Width - 2) do begin
R1 := 0; G1 := 0; B1 := 0;
// los 9 pixels a tener en cuenta
for j := -1 to 1 do begin
// FIla anterior
R1 := R1 + SL1[X+j].rgbtRed + SL2[X+j].rgbtRed + SL3[X+j].rgbtRed;
G1 := G1 + SL1[X+j].rgbtGreen + SL2[X+j].rgbtGreen + SL3[X+j].rgbtGreen;
B1 := B1 + SL1[X+j].rgbtBlue + SL2[X+j].rgbtBlue + SL3[X+j].rgbtBlue;
end;
// Nuevo color
R1:=Round(R1 div 9);
G1:=Round(G1 div 9);
B1:=Round(B1 div 9);
// Asignar el nuevo
bmp2.Canvas.Pixels[X, Y] := RGB(R1,G1,B1);
end;
// Siguientes...
SL1 := SL2;
SL2 := SL3;
SL3 := bmp1.ScanLine[Y+1];
end;
end;
//····························································
begin
Bitmap:= TBitmap.Create;
// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);
// reducir
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Personalmente a veces me sería útil que el formulario que estoy utilizando tuviera algun sistema para detectar cuando se está minimizando el formularo; Y mejor aun que permitiera interactuar con esta acción.
Utilizo a menudo una opción de configuración que llamo: «Minimizar al Tray». Muchas aplicaciones lo utilizan y se trata simplemente de, en lugar de minimizar la aplicación, ocultarla y mostrar un icono junto al reloj en la barra de tareas de Windows.
Para ellos la forma más sencilla que he encontrado es la que explico a continuación. Hay que decir que estoy usando Delphi 5, así que tal vez en alguna versión posterior (que además cuentan con el componnte para el Tray) habrá alguna solución más sencilla.
1
2
3
// Capturar mensajes al formprocedure WMSysCommand(var Msg: TWMSysCommand);message WM_SYSCOMMAND;
// Capturar mensajes al form
procedure WMSysCommand(var Msg: TWMSysCommand);
message WM_SYSCOMMAND;
// Capturar mensajes....
procedure TFormMain.WMSysCommand(var Msg: TWMSysCommand);
begin
// Minimizando?
if (Msg.CmdType = SC_MINIMIZE) then begin
actionOcultar.Execute;
end
else begin
DefaultHandler(Msg);
end;
end;
En mi caso, lo que hago en el procedimiento es llamar al método de ocultar. Importante que en mi caso no deseo que se realice el Minimizar, por eso, la llamada a DefaultHandler está en el else. Si se desea que igualmente se realice esa llamada, esta debe estar fuera del IF.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Ya están actualizados en la nueva web los trucos que tenía en la antigua; Tal vez las fechas y el orden no coincidan del todo, pero creo que es lo menos importante.
Están accesible por el Tag: Trucos o desde el acceso de la página principal donde hay creado un pequeño índice de todos los existentes.
Si detectáis algun error (que seguro los hay) os ruego que me lo comuniquéis.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Utilizando ADO se puede acceder a casi toda la información de la Base de Datos, Tablas y Columnas, pero no a toda. En el caso de las Bases de datos de Access, por ejemplo, la propiedad descripción, que utilizamos para añadir un texto descriptivo a las columnas de las tablas, no es accesible.
Para obtenerla, hace falta acceder directamente a la información del «catálogo» utiliando ADOX. Para poder utilizar ADOX, lo primero que debemos hacer es importar la librería a en Delphi;
Esto se puede hacer desde el menú de: Proyect/Import Type Library.
La librería en concreto es la llamada «Microsoft ADO Ext. for DDL and Security» y proceso paso a paso, podéis verlo es esta página de Zarko Gajic.
Una vez importada la librería, basta con abrir la Base de Datos, acceder a una tabla y a un columna; A partir de ahí ya tenemos todos los datos(propiedades) referentes a esa columna.
El código es sencillo (basta con tener un formulario con un memo (Memo1) y un botón (button1)):
procedure TForm1.Button2Click(Sender:TObject);const
DB_CONNECTION='Provider=Microsoft.Jet.OLEDB.4.0;'+'Data Source=%s';
DATABASENAME ='c:\Archivos de '+'programa\Archivos comunes\Borland Shared\Data'+'\dbdemos.mdb';var
i, j:Integer;
Con:OleVariant;
fCatalog:Catalog;
Column: _Column;
Table:_Table;
Str1, Str2:string;begin// Limpiar la salida
Memo1.Lines.Clear;// Conectar con la Base de Datos
Con := CreateOleObject('ADODB.Connection');// Abrir
Con.Open(Format(DB_CONNECTION,[DATABASENAME]));// protecciontry// Acceder a la Base de Datos
fCatalog := CoCatalog.Create;
fCatalog._Set_ActiveConnection(Con);// Acceder a la tabla de empleados
Table := fCatalog.Tables['employee'];// recorrer las columnasfor i :=0to(Table.Columns.Count-1)dobegin// Acceder a la columna
Column := Table.Columns[i];// Datos de columna
Memo1.Lines.Add(' ');
Memo1.Lines.Add(Format('Columna: %s',[Column.Name]));
Memo1.Lines.Add('---------------------------------');
Memo1.Lines.Add(Format(' Tamaño: %d',[Column.DefinedSize]));
Memo1.Lines.Add(Format(' Precisión: %d',[Column.Precision]));// recorrer las propiedades de la columnafor j :=0to(Column.Properties.Count-1)dobegin// Cada propiedad, Nombre y valor
Str1 := Column.Properties[j].Name;
Str2 := Column.Properties[j].Value;// Saltamos las propiedades Jet...if(Length(Str1) > 0)thenbegin// Saltar las Jetif(Str1[1] <> 'J')thenbegin
Memo1.Lines.Add(Format(' %s: %s',[Str1, Str2]))end;//ifend;//ifend;// forend;// forfinally// Liberar y cerrar
Column :=nil;
Table :=nil;
fCatalog :=nil;
Con.Close;end;end;
procedure TForm1.Button2Click(Sender: TObject);
const
DB_CONNECTION='Provider=Microsoft.Jet.OLEDB.4.0;' +
'Data Source=%s';
DATABASENAME = 'c:\Archivos de ' +
'programa\Archivos comunes\Borland Shared\Data' +
'\dbdemos.mdb';
var
i, j:Integer;
Con:OleVariant;
fCatalog:Catalog;
Column: _Column;
Table:_Table;
Str1, Str2:string;
begin
// Limpiar la salida
Memo1.Lines.Clear;
// Conectar con la Base de Datos
Con := CreateOleObject('ADODB.Connection');
// Abrir
Con.Open(Format(DB_CONNECTION,[DATABASENAME]));
// proteccion
try
// Acceder a la Base de Datos
fCatalog := CoCatalog.Create;
fCatalog._Set_ActiveConnection(Con);
// Acceder a la tabla de empleados
Table := fCatalog.Tables['employee'];
// recorrer las columnas
for i := 0 to (Table.Columns.Count - 1) do begin
// Acceder a la columna
Column := Table.Columns[i];
// Datos de columna
Memo1.Lines.Add(' ');
Memo1.Lines.Add(Format('Columna: %s',[Column.Name]));
Memo1.Lines.Add('---------------------------------');
Memo1.Lines.Add(Format(' Tamaño: %d',[Column.DefinedSize]));
Memo1.Lines.Add(Format(' Precisión: %d',[Column.Precision]));
// recorrer las propiedades de la columna
for j := 0 to (Column.Properties.Count - 1) do begin
// Cada propiedad, Nombre y valor
Str1 := Column.Properties[j].Name;
Str2 := Column.Properties[j].Value;
// Saltamos las propiedades Jet...
if (Length(Str1) > 0) then begin
// Saltar las Jet
if (Str1[1] <> 'J') then begin
Memo1.Lines.Add(Format(' %s: %s',[Str1, Str2]))
end; //if
end; //if
end; // for
end; // for
finally
// Liberar y cerrar
Column := nil;
Table := nil;
fCatalog := nil;
Con.Close;
end;
end;
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Procedimiento que muestra cómo ordenar un TStringGrid a partir de los datos de una columna. El texto está basado en esta página Web donde hay un algoritmo de ordenación. El problema es que sólo es para datos numéricos. En este truco he añadido un par de parámetros para poder definir columnas de otro tipo (u ordenación de otro tipo) y además marcar si que quiere de forma Ascendente o Descendente (que tampoco está en el truco original).
Yo he añadido métodos para ordenar por enteros y Float, aunque ampliando se pueden añadir para alguno más.
// Ordena un TStringGrid.procedure SortStringGrid(var GenStrGrid: TStringGrid;
ThatCol:Integer;
ColData:TGridData=gdString;
SortOrder:TSortOrder=soASC);const
TheSeparator ='@';var
CountItem, I, J, K, ThePosition:integer;
MyList: TStringList;
MyString, TempString:string;
str:string;
vali:Integer;
valf:Double;begin
CountItem := GenStrGrid.RowCount;
MyList := TStringList.Create;
MyList.Sorted:=False;trybeginfor I :=1to(CountItem -1)dobegin
Str := GenStrGrid.Rows[I].Strings[ThatCol];if(ColData = gdInteger)thenbegin
vali :=StrToIntDef(Str,0);
Str :=Format('%*d',[15,vali]);end;if(ColData = gdFloat)thenbegin
valf :=StrToFloat(Str);
Str :=Format('%15.2f',[valf]);end;
MyList.Add(Str + TheSeparator + GenStrGrid.Rows[I].Text);end;
Mylist.Sort;for K :=1to Mylist.Countdobegin
MyString := MyList.Strings[(K -1)];
ThePosition :=Pos(TheSeparator, MyString);
TempString :='';{Eliminate the Text of the column on which we have
sorted the StringGrid}
TempString :=Copy(MyString,(ThePosition +1),Length(MyString));
MyList.Strings[(K -1)]:='';
MyList.Strings[(K -1)]:= TempString;end;if(SortOrder = soASC)thenbeginfor J :=1to(CountItem -1)dobegin
GenStrGrid.Rows[J].Text:= MyList.Strings[(J -1)];end;endelsebeginfor J :=1to(CountItem -1)dobegin
I :=(CountItem - J);
GenStrGrid.Rows[I].Text:= MyList.Strings[(J -1)];end;end;end;finally
MyList.Free;end;end;
// Ordena un TStringGrid.
procedure SortStringGrid(var GenStrGrid: TStringGrid;
ThatCol: Integer;
ColData:TGridData=gdString;
SortOrder:TSortOrder=soASC);
const
TheSeparator = '@';
var
CountItem, I, J, K, ThePosition: integer;
MyList: TStringList;
MyString, TempString: string;
str:string;
vali:Integer;
valf:Double;
begin
CountItem := GenStrGrid.RowCount;
MyList := TStringList.Create;
MyList.Sorted := False;
try
begin
for I := 1 to (CountItem - 1) do begin
Str := GenStrGrid.Rows[I].Strings[ThatCol];
if (ColData = gdInteger) then begin
vali := StrToIntDef(Str, 0);
Str := Format('%*d', [15,vali]);
end;
if (ColData = gdFloat) then begin
valf := StrToFloat(Str);
Str := Format('%15.2f',[valf]);
end;
MyList.Add(Str + TheSeparator + GenStrGrid.Rows[I].Text);
end;
Mylist.Sort;
for K := 1 to Mylist.Count do begin
MyString := MyList.Strings[(K - 1)];
ThePosition := Pos(TheSeparator, MyString);
TempString := '';
{Eliminate the Text of the column on which we have
sorted the StringGrid}
TempString := Copy(MyString, (ThePosition + 1), Length(MyString));
MyList.Strings[(K - 1)] := '';
MyList.Strings[(K - 1)] := TempString;
end;
if (SortOrder = soASC) then begin
for J := 1 to (CountItem - 1) do begin
GenStrGrid.Rows[J].Text := MyList.Strings[(J - 1)];
end;
end
else begin
for J := 1 to (CountItem - 1) do begin
I := (CountItem - J);
GenStrGrid.Rows[I].Text := MyList.Strings[(J - 1)];
end;
end;
end;
finally
MyList.Free;
end;
end;
AÑADO: Me falta una cosa.
Además habrá que definir en la unit los dos tipos que se utilizan para la ordenación:
1
2
3
4
5
Type//: Tipo de Dat de la columna por la que queremos ordenar.
TGridData =(gdString, gdInteger, gdFloat);//: Tipos de ordenación.
TSortOrder =(soASC, soDESC);
Type
//: Tipo de Dat de la columna por la que queremos ordenar.
TGridData = (gdString, gdInteger, gdFloat);
//: Tipos de ordenación.
TSortOrder = (soASC, soDESC);
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Función para buscar ficheros en un directorio de forma recursiva.
Devuelve una lista de nombres de fichero encontrados a partir de la carpeta inicial StartDir, que cumplen el patrón especificado por FileMask.
Mediante recursively se indica si se desea
hacer la busqueda en los subdirectorios.
El resultado se devuelve en FilesList (TStringList), que es la lista que se rellena con los nombres de fichero encontrados.
{: Devuelve una lista de nombres de fichero encontrados a partir
de la carpeta inicial StartDir, que cumplen el patrón especificado
por FileMask.Mediante recursively se indica si se desea hacer la
busqueda en los subdirectorios.
StartDir Carpeta desde la que empezar a buscar.
FileMask Patrón que han de cumplir los ficheros.
Recursively Si hay que continuar la búsqueda en los subdirectorios.
FilesList Lista con los nombres de fichero encontrados.
}procedure FindFiles(StartDir, FileMask:string;
recursively:boolean;var FilesList: TStringList);const
MASK_ALL_FILES ='*.*';
CHAR_POINT ='.';var
SR: TSearchRec;
DirList: TStringList;
IsFound:Boolean;
i:integer;beginif(StartDir[length(StartDir)] <> '\')thenbegin
StartDir := StartDir +'\';end;// Crear la lista de ficheos en el dir. StartDir (no directorios!)
IsFound :=FindFirst(StartDir + FileMask,
faAnyFile - faDirectory, SR)=0;// MIentras encuentrewhile IsFound dobegin
FilesList.Add(StartDir + SR.Name);
IsFound :=FindNext(SR)=0;end;FindClose(SR);// Recursivo?if(recursively)thenbegin// Build a list of subdirectories
DirList := TStringList.Create;// protecciontry
IsFound :=FindFirst(StartDir + MASK_ALL_FILES,
faAnyFile, SR)=0;while IsFound dobeginif((SR.Attrand faDirectory) <> 0)and(SR.Name[1] <> CHAR_POINT)thenbegin
DirList.Add(StartDir + SR.Name);
IsFound :=FindNext(SR)=0;end;// ifend;// whileFindClose(SR);// Scan the list of subdirectoriesfor i :=0to DirList.Count-1dobegin
FindFiles(DirList[i], FileMask, recursively, FilesList);end;finally
DirList.Free;end;end;end;
{: Devuelve una lista de nombres de fichero encontrados a partir
de la carpeta inicial StartDir, que cumplen el patrón especificado
por FileMask.Mediante recursively se indica si se desea hacer la
busqueda en los subdirectorios.
StartDir Carpeta desde la que empezar a buscar.
FileMask Patrón que han de cumplir los ficheros.
Recursively Si hay que continuar la búsqueda en los subdirectorios.
FilesList Lista con los nombres de fichero encontrados.
}
procedure FindFiles(StartDir, FileMask: string;
recursively: boolean; var FilesList: TStringList);
const
MASK_ALL_FILES = '*.*';
CHAR_POINT = '.';
var
SR: TSearchRec;
DirList: TStringList;
IsFound: Boolean;
i: integer;
begin
if (StartDir[length(StartDir)] <> '\') then begin
StartDir := StartDir + '\';
end;
// Crear la lista de ficheos en el dir. StartDir (no directorios!)
IsFound := FindFirst(StartDir + FileMask,
faAnyFile - faDirectory, SR) = 0;
// MIentras encuentre
while IsFound do begin
FilesList.Add(StartDir + SR.Name);
IsFound := FindNext(SR) = 0;
end;
FindClose(SR);
// Recursivo?
if (recursively) then begin
// Build a list of subdirectories
DirList := TStringList.Create;
// proteccion
try
IsFound := FindFirst(StartDir + MASK_ALL_FILES,
faAnyFile, SR) = 0;
while IsFound do begin
if ((SR.Attr and faDirectory) <> 0) and
(SR.Name[1] <> CHAR_POINT) then begin
DirList.Add(StartDir + SR.Name);
IsFound := FindNext(SR) = 0;
end; // if
end; // while
FindClose(SR);
// Scan the list of subdirectories
for i := 0 to DirList.Count - 1 do begin
FindFiles(DirList[i], FileMask, recursively, FilesList);
end;
finally
DirList.Free;
end;
end;
end;
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Muestra como utilizar un BITMAP (en este caso extraído de un TImage) para modificar el cursor activo. Modificando un poco el Tip/Truco es fácil obtener la imagen de disco o desde un recurso.
Se utiliza la API CreateIconIndirect para generar el cursor que posteriormente se activará.
Se puede usar un color de fondo como transparente para obtener un cursos «opaco» o un segundo BITMAP para combinarlo con el primero y así obtener también efectos de transparencia en el cursor generado.
// Crear los bitmaps
BitmapMask := TBitmap.Create;
Bitmap := TBitmap.Create;// protecciontry// Cargar las imágenes
BitmapMask.Assign(Image2.Picture.Bitmap);
Bitmap.Assign(Image1.Picture.Bitmap);// Crear el icono del cursorwith iconInfo dobegin
fIcon :=false;
xHotspot :=(Bitmap.Widthdiv4);
yHotspot :=(Bitmap.Heightdiv3);
hbmMask := BitmapMask.Handle;
hbmColor := Bitmap.Handle;end;// Asignar el icono
Screen.Cursors[1]:= CreateIconIndirect(iconInfo);Self.Cursor:=1;finally// Liberar
BitmapMask.Free;
Bitmap.Free;end;
// Crear los bitmaps
BitmapMask := TBitmap.Create;
Bitmap := TBitmap.Create;
// proteccion
try
// Cargar las imágenes
BitmapMask.Assign(Image2.Picture.Bitmap);
Bitmap.Assign(Image1.Picture.Bitmap);
// Crear el icono del cursor
with iconInfo do begin
fIcon := false;
xHotspot := (Bitmap.Width div 4);
yHotspot := (Bitmap.Height div 3);
hbmMask := BitmapMask.Handle;
hbmColor := Bitmap.Handle;
end;
// Asignar el icono
Screen.Cursors[1] := CreateIconIndirect(iconInfo);
Self.Cursor := 1;
finally
// Liberar
BitmapMask.Free;
Bitmap.Free;
end;
Es ejemplo completo se puede descargar <AQUÍ>. Descargar.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Para cualquier fichero que se encuentra en el sistema de archivos de Windows se almacenan varias fechas. Para acceder a todas ellas puede utilizar el siguiente truco:
// ================================================================// Return the three dates (Created,Modified,Accessed)// of a given filename. Returns FALSE if file cannot// be found or permissions denied. Results are returned// in TdateTime VAR parameters// ================================================================// ================================================================// Devuelve las tres fechas (Creación, modificación y último acceso)// de un fichero que se pasa como parámetro.// Devuelve FALSO si el fichero no se ha podido acceder, sea porque// no existe o porque no se tienen permisos. Las fechas se devuelven// en tres parámetros de ipo DateTime// ================================================================function GetFileTimes(FileName :string;var Created :TDateTime;var Modified :TDateTime;var Accessed :TDateTime):boolean;var
FileHandle :integer;
Retvar :boolean;
FTimeC,FTimeA,FTimeM : TFileTime;
LTime : TFileTime;
STime : TSystemTime;begin// Abrir el fichero
FileHandle :=FileOpen(FileName,fmShareDenyNone);// inicializar
Created :=0.0;
Modified :=0.0;
Accessed :=0.0;// Ha tenido acceso al fichero?if FileHandle < 0then
RetVar :=falseelsebegin// Obtener las fechas
RetVar :=true;
GetFileTime(FileHandle,@FTimeC,@FTimeA,@FTimeM);// CerrarFileClose(FileHandle);// Creado
FileTimeToLocalFileTime(FTimeC,LTime);if FileTimeToSystemTime(LTime,STime)thenbegin
Created :=EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Created := Created +EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);end;// Accedido
FileTimeToLocalFileTime(FTimeA,LTime);if FileTimeToSystemTime(LTime,STime)thenbegin
Accessed :=EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Accessed := Accessed +EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);end;// Modificado
FileTimeToLocalFileTime(FTimeM,LTime);if FileTimeToSystemTime(LTime,STime)thenbegin
Modified :=EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Modified := Modified +EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);end;end;
Result := RetVar;end;
// ================================================================
// Return the three dates (Created,Modified,Accessed)
// of a given filename. Returns FALSE if file cannot
// be found or permissions denied. Results are returned
// in TdateTime VAR parameters
// ================================================================
// ================================================================
// Devuelve las tres fechas (Creación, modificación y último acceso)
// de un fichero que se pasa como parámetro.
// Devuelve FALSO si el fichero no se ha podido acceder, sea porque
// no existe o porque no se tienen permisos. Las fechas se devuelven
// en tres parámetros de ipo DateTime
// ================================================================
function GetFileTimes(FileName : string; var Created : TDateTime;
var Modified : TDateTime; var Accessed : TDateTime) : boolean;
var
FileHandle : integer;
Retvar : boolean;
FTimeC,FTimeA,FTimeM : TFileTime;
LTime : TFileTime;
STime : TSystemTime;
begin
// Abrir el fichero
FileHandle := FileOpen(FileName,fmShareDenyNone);
// inicializar
Created := 0.0;
Modified := 0.0;
Accessed := 0.0;
// Ha tenido acceso al fichero?
if FileHandle < 0 then
RetVar := false
else begin
// Obtener las fechas
RetVar := true;
GetFileTime(FileHandle,@FTimeC,@FTimeA,@FTimeM);
// Cerrar
FileClose(FileHandle);
// Creado
FileTimeToLocalFileTime(FTimeC,LTime);
if FileTimeToSystemTime(LTime,STime) then begin
Created := EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Created := Created + EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);
end;
// Accedido
FileTimeToLocalFileTime(FTimeA,LTime);
if FileTimeToSystemTime(LTime,STime) then begin
Accessed := EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Accessed := Accessed + EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);
end;
// Modificado
FileTimeToLocalFileTime(FTimeM,LTime);
if FileTimeToSystemTime(LTime,STime) then begin
Modified := EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Modified := Modified + EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);
end;
end;
Result := RetVar;
end;
Para llamar a ésta función se puede utilizar un código como éste:
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…