I can setup a drag and drop simple example as outlined in the following code
(excerpted from http://www.chami.com/tips/delphi/111196D.html)
But if I use an embedded form (a form contained in another form I am unable to drop a file on an embedded form: the embedded form does not act as a drop target
unit dropfile;
interface
uses
Windows, Messages, SysUtils, Classes,
Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
//>>>
// declare our DROPFILES message handler
procedure AcceptFiles( var msg : TMessage );
message WM_DROPFILES;
//<<<
end;
var
Form1: TForm1;
implementation
uses
//>>>
//
// this unit contains certain
// functions that we'll be using
//
ShellAPI;
//<<<
{$R *.DFM}
//>>>
procedure TForm1.AcceptFiles( var msg : TMessage );
const
cnMaxFileNameLen = 255;
var
i,
nCount : integer;
acFileName : array [0..cnMaxFileNameLen] of char;
begin
// find out how many files we're accepting
nCount := DragQueryFile( msg.WParam,
$FFFFFFFF,
acFileName,
cnMaxFileNameLen );
// query Windows one at a time for the file name
for i := 0 to nCount-1 do
begin
DragQueryFile( msg.WParam, i,
acFileName, cnMaxFileNameLen );
// do your thing with the acFileName
MessageBox( Handle, acFileName, '', MB_OK );
end;
// let Windows know that you're done
DragFinish( msg.WParam );
end;
//<<<
procedure TForm1.FormCreate(Sender: TObject);
begin
//>>>
//
// tell Windows that you're
// accepting drag and drop files
//
DragAcceptFiles( Handle, True );
//<<<
end;
end.
You are calling DragAcceptFiles() in the Form's OnCreate event. That event is called only one time during a Form's lifetime. But the Form's window may be recreated multiple times during the Form's lifetime. And that is certainly the case when embedding a Form inside another window. The Form's window gets recreated, but you are not calling DragAcceptFiles() on the newly recreated Form window. That is why your WM_DROPFILES message handler stops working.
To account for window recreation, you need to override the Form's virtual CreateWnd() and call DragAcceptFiles() from there instead.
unit dropfile;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
private
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
public
// declare our DROPFILES message handler
procedure AcceptFiles(var msg: TMessage); message WM_DROPFILES;
end;
var
Form1: TForm1;
implementation
uses
ShellAPI;
{$R *.DFM}
procedure TForm1.AcceptFiles(var msg: TMessage);
var
i, nCount: Integer;
acFileName: array [0..MAX_PATH-1] of Char;
begin
// find out how many files we're accepting
nCount := DragQueryFile(msg.WParam, $FFFFFFFF, nil, 0);
// query Windows one at a time for the file name
for i := 0 to nCount-1 do
begin
DragQueryFile(msg.WParam, i, acFileName, MAX_PATH);
// do your thing with the acFileName
MessageBox(Handle, acFileName, '', MB_OK);
end;
// let Windows know that you're done
DragFinish(msg.WParam);
end;
procedure TForm1.CreateWnd;
begin
inherited;
// tell Windows that you're
// accepting drag and drop files
DragAcceptFiles(Handle, True);
end;
procedure TForm1.DestroyWnd;
begin
// tell Windows that you're no
// longer accepting drag and drop files
DragAcceptFiles(Handle, False);
inherited;
end;
end.
Related
I want to build a menu form that acts similar to ribbon keytips - you can
press and hold Alt, then press and release e. g. d, then release Alt to trigger an action or
press and release Alt, then press and release d to trigger the same action
I took inspiration at Hidden Main Menu in a delphi program automatically shown using Alt key and came up with the following demo:
unit Unit1;
interface
uses
Windows,
Messages,
SysUtils,
Variants,
Classes,
Graphics,
Controls,
Forms,
Dialogs,
ImgList,
StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
strict private
FShowKeyTips: Boolean;
procedure UpdateKeyTipState(AShowKeyTips: Boolean);
procedure WMExitMenuLoop(var Message: TMessage); message WM_EXITMENULOOP;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
ShellAPI,
Menus;
{ TForm1 }
constructor TForm1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Label1.Caption := 'Dummy';
end;
destructor TForm1.Destroy;
begin
inherited Destroy;
end;
procedure TForm1.WMExitMenuLoop(var Message: TMessage);
begin
UpdateKeyTipState(False);
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
const
MAPVK_VK_TO_CHAR = 2;
// Adapted from dxBar.pas:
function IsTextCharForKeyTip(AKey: Word): Boolean;
var
ARes: UINT;
begin
ARes := MapVirtualKey(AKey, MAPVK_VK_TO_CHAR);
Result := ((ARes and $FFFF0000) = 0) and (Char(ARes) <> ' ') and (Char(ARes) in [#32..#255]);
end;
var
hk: string;
CheckKeyTips: Boolean;
begin
if (Key = VK_MENU) or (Key = VK_F10) then
begin
UpdateKeyTipState(True);
Exit;
end;
if FShowKeyTips then
CheckKeyTips := True
else
CheckKeyTips := Shift = [ssAlt];
if CheckKeyTips and IsTextCharForKeyTip(Key) then
begin
hk := Char(Key); // TODO: Handle analogouos to TdxBarItemLink.IsAccel?
if SameText(hk, 'd') then
begin
Caption := Caption + '+';
Key := 0;
Exit;
end;
end;
end;
procedure TForm1.UpdateKeyTipState(AShowKeyTips: Boolean);
begin
if FShowKeyTips = AShowKeyTips then
Exit;
FShowKeyTips := AShowKeyTips;
if AShowKeyTips then
Label1.Caption := 'Dummy (d)'
else
Label1.Caption := 'Dummy';
end;
end.
(Create a standard VCL app, add Label1 to Form1 and replace the contents of Unit1.pas with the above.)
The first bullet point works (adds a + to the form caption), however I can't make the second one work. I can't find where the d gets handled. I tried WM_(SYS)KEYDOWN, CM_DIALOGCHAR and more to no avail.
Any ideas?
As documented the Alt key, when pressed and released alone, "toggles in and out of menu bar mode". This is true even if your form does not have a window menu, the system menu is sufficient for the system to put the window into a modal menu loop. In this mode a non-accelerator will generate a WM_MENUCHAR message:
Sent when a menu is active and the user presses a key that does not
correspond to any mnemonic or accelerator key.
This is the message that you're looking for, read the character from the User field. And you don't have to track the Alt key, since the window being in a modal menu loop means the Alt key has been pressed once. Otherwise a key down message is generated instead of a menu character message.
Note that if your form does not have a system menu (in BorderIcons uncheck biSystemMenu) and a window menu, a regular WM_KEYDOWN will be sent which you're already handling.
I am using PNGImage library in my project, which entire GUI is made up of .png images, which i loaded to TImages at run-time. For some purposes i have to dynamically create plenty of components groups that are similar to each other. Every group consists of some TImages and have a button that lets user proceed to another page with more details about clicked item.
The code i am using:
procedure TMain_Frame.selection_click(Sender: TObject);
var id: string;
begin
id := StringReplace(TLabel(sender).Name, 'label_item_select_', '', [rfReplaceAll]);
hide_created_components; // It does Free all components
show_details(id);
end; // (1)
Access violation error occurs at (1). The odd thing is that it happenes completly random: error may happen at the very first click or may not happen for 10 clicks. If no error occured, F8 leads me inside PNGImage library where some stuff is done. However when error occurs, F7/8 immediately throws it without doing what it has to. This problem happenes only when i go from dynamicaly created objects to static.
CPU window shows that error occured at this ASM code:
movzx ecx, [edi]
ecx value is 755A2E09, edi is 00000000
Is it correct to .Free all dynamically created components? Or should be .Destroy used instead? And why does PNGImage goes inside itself on procedure end;?
Demo:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, pngimage, ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Image1: TImage;
procedure selection_click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure create_label;
var Button: TLabel;
begin
Button := TLabel.Create(Form1);
with Button do
begin
Name := 'dynamic_label_1';
Parent := Form1;
Autosize := false;
Left := 100;
Top := 100;
Width := 150;
Height := 20;
Caption := 'Dynamic Label: Click Me';
BringToFront;
Cursor := crHandPoint;
end;
Button.OnClick := Form1.selection_click;
end;
procedure hide_dyn_label(L: TLabel; mode: boolean);
begin
if mode then
begin
L.Free;
Form1.Image1.Picture.LoadFromFile(PAnsiChar('button_close.png'));
Form1.Image1.Visible := true;
end
else
create_label;
end;
procedure TForm1.selection_click(Sender: TObject);
var id: string;
begin
id := StringReplace(TLabel(Sender).Name, 'dynamic_label_', '', [rfReplaceAll]);
Form1.Button1.Visible := true;
hide_dyn_label(Form1.FindComponent('dynamic_label_1') as TLabel, true);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
create_label;
Form1.Image1.Visible := false;
Form1.Button1.Visible := false;
end;
end.
You are freeing the TLabel while still in its OnClick event handler, Selection_Click which calls hide_dyn_label() which calls L.Free. You can't do that. Use some kind of delayed destruction, f.ex. with a boolean variable FreeDynLabels which you can check in Application.OnIdle. Or post a custom message to the form.
This question already has answers here:
Cross-application drag-and-drop in Delphi
(2 answers)
Closed 8 years ago.
I am trying to drag and drop a video file (like .avi) from desktop But ı can not take it to the my program.But when ı try to drag and drop inside my program it works fine.For ex: I have an edittext and a listbox inside my pro and ı can move text that inside edittext to listbox.I could not get what is the difference ??
I take the video using openDialog.But ı wanna change it with drag and drop.
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
MediaPlayer1.DeviceType:=dtAutoSelect;
MediaPlayer1.FileName := OpenDialog1.FileName;
Label1.Caption := ExtractFileExt(MediaPlayer1.FileName);
MediaPlayer1.Open;
MediaPlayer1.Display:=Self;
MediaPlayer1.DisplayRect := Rect(panel1.Left,panel1.Top,panel1.Width,panel1.Height);
panel1.Visible:=false;
MediaPlayer1.Play;
end;
end;
Here is a simple demo how to drag&drop files from Windows Explorer into a ListBox (for Delphi XE):
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
protected
procedure WMDropFiles(var Msg: TMessage); message WM_DROPFILES;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses ShellAPI;
procedure TForm1.FormCreate(Sender: TObject);
begin
DragAcceptFiles(Handle, True);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
DragAcceptFiles(Handle, False);
end;
procedure TForm1.WMDropFiles(var Msg: TMessage);
var
hDrop: THandle;
FileCount: Integer;
NameLen: Integer;
I: Integer;
S: string;
begin
hDrop:= Msg.wParam;
FileCount:= DragQueryFile (hDrop , $FFFFFFFF, nil, 0);
for I:= 0 to FileCount - 1 do begin
NameLen:= DragQueryFile(hDrop, I, nil, 0) + 1;
SetLength(S, NameLen);
DragQueryFile(hDrop, I, Pointer(S), NameLen);
Listbox1.Items.Add (S);
end;
DragFinish(hDrop);
end;
end.
You can also use DropMaster from Raize software.
You can catch the WM_DROPFILES message.
First, set that your form will "accept" files from dragging in the FormCreate procedure:
DragAcceptFiles(Self.Handle, True);
After, declare the procedure in the desired form class:
procedure WMDropFiles(var Msg: TMessage); message WM_DROPFILES;
Finally, fill the procedure body as follows:
procedure TForm1.WMDropFiles(var Msg: TMessage);
begin
// do your job with the help of DragQueryFile function
DragFinish(Msg.WParam);
end
Alternatively, check out "The Drag and Drop Component Suite for Delphi" by Anders Melander. It works as-is with 32-bit and with some tweaking can be made to work with 64-bit as well (read the blog - it has been upgraded by 3rd parties).
as an extension of my previous post forms an a pagecontrol at runtime
I need a solution how to pass a buttonclick event back to the parent pagecontrol.
Do I have a assign a click function as a property and assign a new click function to all my forms for all the buttons i have placed :-( .... much work , any better solution
MyMainForm = CLass( )
....
aPagecontrol : TPageControl;
aTabForm_1 : TTabForm_1 ; // in the real case I use an dynamic array
aTabForm_2 : TTabForm_2 ;
aTabForm_3 : TTabForm_3 ;
....
UserData : TUserdata ; // lot of user data ....
function MyMainForm.CreateTabAndForm: TTabForm_1;
var
tabSheet : TTabSheet;
begin
//Create a new tab sheet
tabSheet := TTabSheet.Create(PageControl1) ;
tabSheet.PageControl := PageControl1;
//create a form
Result := TTabForm_1.Create(tabSheet) ;
Result.Parent := tabSheet;
Result.Align := alClient;
Result.BorderStyle := bsNone;
Result.Visible := true;
tabSheet.Caption := Result.Caption;
//activate the sheet
PageControl1.ActiveSheet := tabSheet;
end;
// program code , now failing :
aTabForm_1 := CreateTabAndForm;
aTabForm_1.onclick := MyButtonOnclick; // here AV happens !!
....
end;
the definition of the form
//
TTabForm_1 = class(TForm)
...
property clickButton1 : TClickfunction .......
end;
Solution #1 -> pass all the data to TTabForm_1 using properties
Solution #2 -> pass Button Click event to Mainform
target : readable code - good design
One way to expose events raised inside a form or control is like this :
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FButton1Clicked : TNotifyEvent; //Create a private TNotifyEvent field
public
// ...and expose it as a property
property OnButton1Click : TNotifyEvent read FButton1Clicked
write FButton1Clicked;
end;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
// Execute the method if it has been assigned when Button1 is clicked.
if Assigned(FButton1Clicked) then FButton1Clicked(Sender);
end;
end.
Which you would consume like :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Unit2;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
FForm2 : TForm2;
procedure Form2ButtonClick(sender : TObject);//Create a TNotifyEvent handler
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FForm2 := TForm2.Create(self);
//Assign a method to your custom event property
FForm2.OnButton1Click := Form2ButtonClick;
FForm2.Show;
end;
procedure TForm1.Form2ButtonClick(sender: TObject);
begin
// Do Something...
end;
end.
Of course, you don't have to use a TNotifyEvent, you can create any custom event, with parameters, that you like. For example
type
TFooEvent = procedure(ANumber : double; Sender : TObject) of object;
Which you could then use to send data with the click event :
if Assigned(FButton1Clicked) then FButton1Clicked(1.23, Button1);
I've tried to accept files that are dragged and dropped to a Form from the File Explorer but it doesn't work. My WM_DROPFILES handler is never called. I'm running Windows 8 if that makes any difference.
Here's a simple example of what I do (I just have a TMemo on the form):
type
TForm1 = class(TForm)
Memo1: TMemo;
private
{ Private declarations }
procedure WMDROPFILES(var msg : TWMDropFiles) ; message WM_DROPFILES;
procedure CreateWnd; override;
procedure DestroyWnd; override;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.CreateWnd;
begin
inherited;
DragAcceptFiles(Handle, True);
end;
procedure TForm1.DestroyWnd;
begin
inherited;
DragAcceptFiles(Handle, false);
end;
procedure TForm1.WMDROPFILES(var msg: TWMDropFiles);
var
i, fileCount: integer;
fileName: array[0..MAX_PATH] of char;
begin
fileCount:=DragQueryFile(msg.Drop, $FFFFFFFF, fileName, MAX_PATH);
for i := 0 to fileCount - 1 do
begin
DragQueryFile(msg.Drop, i, fileName, MAX_PATH);
Memo1.Lines.Add(fileName);
end;
DragFinish(msg.Drop);
end;
Most likely you are running your application elevated. Probably because you are running Delphi elevated. In Vista and later, low privilege processes cannot send messages to higher privilege processes. This MSDN blog explains more.
If you are running your Delphi IDE elevated, I urge you to stop doing so. There's very seldom a need to do so for standard desktop application development.
As Remy points out, your DestroyWnd is incorrect. You are destroying the window handle before calling DragAcceptFiles. Simply reverse the order. Personally I'd use WindowHandle in both CreateWnd and DestroyWnd. The Handle property creates the window handle if it is not assigned and so masks such errors.
procedure TForm1.CreateWnd;
begin
inherited;
DragAcceptFiles(WindowHandle, True);
end;
procedure TForm1.DestroyWnd;
begin
DragAcceptFiles(WindowHandle, false);
inherited;
end;