En Delphi XE, ¿puedo permitir que mi formulario acepte el archivo 'arrastrar y soltar' pero sin tener que manejar los mensajes de Windows?¿Cómo puedo permitir que un formulario acepte la eliminación de archivos sin manejar los mensajes de Windows?
Respuesta
No es necesario para controlar los mensajes para implementar esto. Solo necesita implementar y llamar al RegisterDragDrop
/RevokeDragDrop
. Es realmente muy muy simple. En realidad se puede aplicar IDropTarget
en su código de forma, pero yo prefiero hacerlo en una clase de ayuda que tiene este aspecto:
uses
Winapi.Windows,
Winapi.ActiveX,
Winapi.ShellAPI,
System.StrUtils,
Vcl.Forms;
type
IDragDrop = interface
function DropAllowed(const FileNames: array of string): Boolean;
procedure Drop(const FileNames: array of string);
end;
TDropTarget = class(TObject, IInterface, IDropTarget)
private
// IInterface
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
private
// IDropTarget
FHandle: HWND;
FDragDrop: IDragDrop;
FDropAllowed: Boolean;
procedure GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
procedure SetEffect(var dwEffect: Integer);
function DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; stdcall;
function DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
function DragLeave: HResult; stdcall;
function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
public
constructor Create(AHandle: HWND; const ADragDrop: IDragDrop);
destructor Destroy; override;
end;
{ TDropTarget }
constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
begin
inherited Create;
FHandle := AHandle;
FDragDrop := ADragDrop;
RegisterDragDrop(FHandle, Self)
end;
destructor TDropTarget.Destroy;
begin
RevokeDragDrop(FHandle);
inherited;
end;
function TDropTarget.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then begin
Result := S_OK;
end else begin
Result := E_NOINTERFACE;
end;
end;
function TDropTarget._AddRef: Integer;
begin
Result := -1;
end;
function TDropTarget._Release: Integer;
begin
Result := -1;
end;
procedure TDropTarget.GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
var
i: Integer;
formatetcIn: TFormatEtc;
medium: TStgMedium;
dropHandle: HDROP;
begin
FileNames := nil;
formatetcIn.cfFormat := CF_HDROP;
formatetcIn.ptd := nil;
formatetcIn.dwAspect := DVASPECT_CONTENT;
formatetcIn.lindex := -1;
formatetcIn.tymed := TYMED_HGLOBAL;
if dataObj.GetData(formatetcIn, medium)=S_OK then begin
(* This cast needed because HDROP is incorrectly declared as Longint in ShellAPI.pas. It should be declared as THandle
which is an unsigned integer. Without this fix the routine fails in top-down memory allocation scenarios. *)
dropHandle := HDROP(medium.hGlobal);
SetLength(FileNames, DragQueryFile(dropHandle, $FFFFFFFF, nil, 0));
for i := 0 to high(FileNames) do begin
SetLength(FileNames[i], DragQueryFile(dropHandle, i, nil, 0));
DragQueryFile(dropHandle, i, @FileNames[i][1], Length(FileNames[i])+1);
end;
end;
end;
procedure TDropTarget.SetEffect(var dwEffect: Integer);
begin
if FDropAllowed then begin
dwEffect := DROPEFFECT_COPY;
end else begin
dwEffect := DROPEFFECT_NONE;
end;
end;
function TDropTarget.DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
var
FileNames: TArray<string>;
begin
Result := S_OK;
Try
GetFileNames(dataObj, FileNames);
FDropAllowed := (Length(FileNames)>0) and FDragDrop.DropAllowed(FileNames);
SetEffect(dwEffect);
Except
Result := E_UNEXPECTED;
End;
end;
function TDropTarget.DragLeave: HResult;
begin
Result := S_OK;
end;
function TDropTarget.DragOver(grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
Result := S_OK;
Try
SetEffect(dwEffect);
Except
Result := E_UNEXPECTED;
End;
end;
function TDropTarget.Drop(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
var
FileNames: TArray<string>;
begin
Result := S_OK;
Try
GetFileNames(dataObj, FileNames);
if Length(FileNames)>0 then begin
FDragDrop.Drop(FileNames);
end;
Except
Application.HandleException(Self);
End;
end;
La idea aquí es para envolver la complejidad de las ventanas IDropTarget
en TDropTarget
. Todo lo que necesita hacer es implementar IDragDrop
que es mucho más simple. De todos modos, creo que esto debería ayudarte.
Crea el objeto de destino de caída desde tu control CreateWnd
. Destrúyalo en el método DestroyWnd
. Ese punto es importante porque la recreación de la ventana VCL significa que un control puede tener su manejador de ventana destruido y recreado durante su vida útil.
Tenga en cuenta que el conteo de referencias en TDropTarget
se suprime. Esto se debe a que cuando se llama a RegisterDragDrop
, aumenta el recuento de referencias. Esto crea una referencia circular y este código para suprimir el conteo de referencias se rompe. Esto significa que usaría esta clase a través de una variable de clase en lugar de una variable de interfaz, para evitar fugas.
El uso sería algo como esto:
type
TMainForm = class(TForm, IDragDrop)
....
private
FDropTarget: TDropTarget;
// implement IDragDrop
function DropAllowed(const FileNames: array of string): Boolean;
procedure Drop(const FileNames: array of string);
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
end;
....
procedure TMainForm.CreateWnd;
begin
inherited;
FDropTarget := TDropTarget.Create(WindowHandle, Self);
end;
procedure TMainForm.DestroyWnd;
begin
FreeAndNil(FDropTarget);
inherited;
end;
function TMainForm.DropAllowed(const FileNames: array of string): Boolean;
begin
Result := True;
end;
procedure TMainForm.Drop(const FileNames: array of string);
begin
; // do something with the file names
end;
Aquí estoy usando un formulario como el destino de colocación. Pero podría usar cualquier otro control de ventana de forma similar.
buen ejercicio, gracias –
@gabr Gracias por la edición –
Gracias . Convertí ese código en una unidad, y está funcionando para mí. Simplifiqué la cláusula uses de la siguiente manera. 'interfaz utiliza Winapi.Windows, Winapi.ActiveX; implementación usa Winapi.ShellAPI, Vcl.Forms; ' –
No, a menos que esté a punto de leer algunos descendientes TForm personalizados que ya tienen esta funcionalidad incorporada.
Usted tiene que escribir el código usted mismo, o instalar un producto de terceros como DropMaster, que le permite arrastrar y soltar en versiones mucho más antiguas de Delphi también.
--jeroen
70 dólares por como ~ 30 LoC ?! –
Eso depende totalmente de qué tan rápido escriba 30 líneas de código bien probadas, que funcionen en muchas versiones diferentes de Windows y otras herramientas que se comporten como Windows Explorer. –
Bueno, no sé ... dado que la API anterior (DragXXX) es estable, compatible con cualquier versión de Windows y bastante bien documentada ... probablemente muy rápido. No tengo idea acerca de la herramienta de terceros y sus errores y caprichos, aunque ... –
Si no te gusta el WinAPI puro, entonces puedes usar componentes. Drag and Drop Component Suite es gratis con las fuentes.
Utilicé la solución de David Heffernan como base para mi aplicación de prueba y obtuve 'Operación de puntero inválido' al cerrar la aplicación. La solución para ese problema fue cambiar TDropTarget.Create agregando '_Release;'
constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
begin
inherited Create;
FHandle := AHandle;
FDragDrop := ADragDrop;
RegisterDragDrop(FHandle, Self);
_Release;
end;
Una discusión sobre este problema se puede ver en Embarcadero forum.
Cualquiera que sea el problema en su código, esta no es la solución. Su código presumiblemente obtuvo el recuento de referencias todo mal. Escribo esto por el bien de los futuros lectores para que no tomen esta respuesta al pie de la letra. –
Este es de hecho la solución incorrecta, pero estás en lo cierto de que hay un problema. La última versión de la respuesta resuelve ese problema. –
- 1. ¿Cómo puedo manejar un formulario de Windows datetimepicker ArgumentOutOfRangeException?
- 2. Cómo permitir que <input type = "file"> acepte solo archivos de imagen
- 3. ¿Cómo puedo permitir que php cree archivos con la misma propiedad que los archivos que los crearon?
- 4. Permitir que la función de JavaScript acepte cualquier cantidad de argumentos
- 5. ¿Cómo puedo manejar los mensajes de ventana de un hilo separado?
- 6. Opción de arranque de Windows 7 para permitir que se ignoren los controladores sin firmar
- 7. Permitir a los usuarios actualizar el navegador sin la ventana emergente "Confirmar reenvío de formulario"
- 8. Rails 3 ¿Cómo puedo permitir que los atributos anidados se pasen sin la designación _attributes
- 9. La mejor manera de manejar los mensajes de error
- 10. ¿Cómo puedo hacer que un host virtual acepte varios dominios?
- 11. cómo permitir archivos comenzando con el período y sin extensión en el servidor de Windows 2003?
- 12. Rieles 3 - Manejar mensajes entrantes (Permitir a los usuarios responder a las notificaciones por correo electrónico)
- 13. ¿Cómo puedo permitir que los usuarios carguen archivos mediante copiar/pegar?
- 14. ¿Cómo hago que git acepte cambios de modo sin aceptar todos los cambios de texto?
- 15. Cómo recibir notificaciones de dispositivos Plug & Play sin un formulario de Windows
- 16. ¿Cómo puedo manejar archivos grandes en Ruby?
- 17. Permitir que se configure un servicio de Windows
- 18. ¿Cómo puedo usar la función jQuery $ .ajax para detener o permitir el envío de un formulario?
- 19. Cómo deshacer la eliminación de archivos intermedios
- 20. Opciones para manejar un formulario de datos que cambia frecuentemente
- 21. ¿Cuán robustos son los mensajes de Windows?
- 22. Capturar todos los mensajes de Windows
- 23. ¿Cómo puedo permitir que mi usuario inserte código HTML, sin riesgos? (no sólo los riesgos técnicos)
- 24. ¿Puedo tener un campo de archivo en un formulario de entrada?
- 25. ¿Cómo puedo lograr que IE8 acepte un CSS: antes de la etiqueta?
- 26. Riesgos al permitir que los usuarios carguen archivos HTML/JS
- 27. Crear un middleware expressjs que acepte los parámetros
- 28. Desarrollo web: ¿cómo puedo permitir que un usuario cargue archivos directamente a mi CDN (Cachefly)?
- 29. ¿Cómo creo programáticamente un formulario de Windows?
- 30. ¿Cómo puedo manejar los errores al cargar un iframe?
¿Qué pasa con el manejo de mensajes? Si la técnica de mensaje se adapta a sus necesidades, es * mucho * más fácil que la técnica IDropTarget. –
+1 Tenía la impresión de que WM_DROPFILES no le permitía indicar si la gota sería aceptada o no. De lo contrario, estoy de acuerdo en que es más fácil que IDropTarget. –
Simplemente no me gusta usar winapi cuando puedo evitarlo. Ambos mensajes de técnicas e IDropTarget usan winapi. Estoy impresionado de que Delphi todavía no admite la caída de archivos ... – Astronavigator