Drag and drop - przeciąganie plików
michalkopacz
Mechanizm drag and drop (z ang. przeciągnij i upuść) to bardzo wygodny sposób przenoszenia obiektów w systemie Windows. Obsługa drag and drop polega na chwyceniu obiektu kursorem myszy, a następnie na przeciągnięciu go w pożądane miejsce i upuszczeniu.
1 Interfejs aplikacji
2 Jak to działa?
3 Włączamy obsługę drag and drop
4 Komunikat WM_DROPFILES
5 Obsługa komunikatów przez Delphi
6 Uzyskiwanie informacji o plikach
7 Złóżmy to w całość
Biblioteka VCL wspiera metodę drag and drop w obrębie danej aplikacji, definiując mechanizm drag and drop pomiędzy kontrolkami. Ten sposób obsługi mechanizmu drag and drop jest dokładnie opisany w pomocy Delphi, dlatego też zajmiemy się mniej znanym sposobem obsługi drag and drop. W tym celu sięgniemy do WinAPI i napiszemy program umożliwiający przeciąganie plików do okna aplikacji.
Interfejs aplikacji
Istniej wiele programów pozwalających użytkownikowi otworzyć plik poprzez przeciągniecie go do okna aplikacji. My również dodamy taką opcję do naszego programu, dzięki czemu użytkownik będzie mógł przeciągnąć plik tekstowy, a my wczytamy go do komponentu TMemo
. Tworzymy zatem nowy projekt, wybierając z menu File|New|VCL Forms Application ? Delphi for Win32. Dodajemy na formularz komponent TMemo
, zmieniamy jego nazwę na DragMemo, właściwośćAlign
na alClient oraz kasujemy zawartość właściwości Lines
.
Zmieniamy także właściwości Name
i Caption
formularza odpowiednio na DragForm i Drag and Drop Notebook. Na koniec zapisujemy projekt pod nazwą DragNotebook.bdsproj, a moduł jako UnitDrag.pas.
Jak to działa?
Istnieją dwa sposoby na dodanie obsługi mechanizmu drag nad drop do naszej aplikacji. Pierwszy polega na wykorzystaniu funkcji WinApi i obsłużeniu komunikatu WM_DROPFILES
. Druga metoda związana jest z obiektem COM, który udostępnia odpowiednie interfejsy. My skorzystamy z pierwszego, łatwiejszego sposobu.
Należy zauważyć, że obydwa sposoby umożliwiają jedynie przeciąganie plików. W dodatku nasz program może akceptować upuszczanie plików z Eksploratora Windows, jednak my nie możemy przenosić plików do innej aplikacji. Pomimo tego ograniczenia, użycie drag and drop do przeciągania plików do aplikacji jest bardzo użyteczne.
Przeciąganie plików metodą drag and drop umożliwiają nam, zdefiniowane w bibliotece shell32.dll, funkcje DragQueryFile
i DragQueryPoint
, procedury DragAcceptFiles
i DragFinish
oraz komunikat WM_DROPFILES
. W Delphi komunikat ten jest zdefiniowany w module Messages
, a funkcje API są zadeklarowane w module ShellApi
. Dodaj zatem moduł ShellApi
do listy uses
(moduł Messages
już tam się znajduje).
Włączamy obsługę drag and drop
Mechanizm drag and drop jest domyślnie wyłączony w aplikacji. Aby go włączyć użyjemy procedury DragAcceptFiles
. W zdarzeniu OnCreate
formularza umieszczamy taki kod:
DragAcceptFiles(Self.Handle, True);
Procedura DragAcceptFiles
przyjmuje dwa parametry. Pierwszy parametr to uchwyt okna, dla którego włączamy lub wyłączamy akceptacje drag and drop. Wartość Self.Handle
odpowiada uchwytowi formularza. Drugim parametrem jest wartość logiczna informująca o włączeniu (True
) lub wyłączeniu (False
) akceptacji mechanizmu drag and drop przez okno, którego uchwyt przypisaliśmy do pierwszego parametru.
Komunikat WM_DROPFILES
Kiedy pliki są upuszczane na formularzu, Windows wysyła do aplikacji komunikat WM_ DROPFILES
. Komunikat ten, poprzez parametr Drop, dostarcza nam uchwyt do struktury przechowującej informację o zrzucanych plikach. Napiszemy teraz szkielet procedury obsługującej komunikat WM_ DROPFILES
, a następnie dodamy do niej kod, który umieści zawartość pliku w komponencie TMemo
.
Dodajemy najpierw do sekcji publicznej formularza deklaracje metody WMDropFiles
:
procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
Następnie w sekcji implementation wpisujemy poniższy kod:
procedure TDragForm.WMDropFiles(var Msg: TWMDropFiles);
var
hDrop: THandle;
FileCount: Integer;
FileNameLength: Integer;
FileName: String;
begin
hDrop:= Msg.Drop;
// tu wpisujemy kod przetwarzający strukturę z nazwami plików
end;
Do zmiennej hDrop typu THandle
przypisujemy zawartość parametru Drop
komunikatu. Tworzymy także trzy pomocnicze zmienne FileCount, FileName i FileNameLength, które wykorzystamy do przechowywania informacji o liczbie plików, poszcze-ólnych nazwach (dokładnie ścieżek dostępu wraz z nazwą) i długości tej nazwy. Jak zatem uzyskać ilość i nazwy przeciąganych plików? Do wykonania tych dwóch zadań służy funkcja DragQueryFile
. Przyjrzyjmy się jej dokładniej.
Obsługa komunikatów przez Delphi
Komunikat WM_DROPFILES
możemy obsłużyć w naszej aplikacji na dwa sposoby. Pierwszy sposób polega na wykorzystaniu metody obsługującej komunikat (ang. message handler) WM_DROPFILES
, co zostało pokazane w poprzednim podpunkcie. Możemy również skorzystać ze zdarzenia OnMessage
klasy TApplication
przypisując mu procedurę obsługi w kodzie programu (alternatywnie oprogramować zdarzenie OnMessage
komponentu TApplicationEvents
).
Do konstruktora formularza wpisujemy kod:
procedure TDragForm.FormCreate(Sender: TObject);
begin
Application.OnMessage:=ApplicationMessage;
//włączamy akceptacje drag and drop
DragAcceptFiles(Self.Handle, True);
end;
Napiszmy teraz metodę ApplicationMessage
. Powinna ona pełnić jedynie funkcję dystrybutora
rozdzielającego obsługę przychodzących zdarzeń pomiędzy metodami w zależności od numeru komunikatu:
procedure TDragForm.ApplicationMessage(var Msg : TMsg; var Handled : Boolean);
begin
case (Msg.message) of
WM_DROPFILES: WMDropFiles(Msg);
end;
end;
Oczywiście w tym przypadku zastosowanie polecenia switch/case jest przesadzone, swobodnie wystarczyłby
prosty warunek sprawdzający, czy nadchodzący komunikat jest tym, który nas interesuje, ale taka forma w
naturalny sposób umożliwia obsługę kolejnych komunikatów.
Pozostało nam napisanie medoty WMDropFiles
. W sekcji implementation
wpisujemy poniższy kod:
procedure TDragForm.WMDropFiles(var Msg: TMsg);
var
hDrop: THandle;
FileCount: Integer;
FileNameLength: Integer;
FileName: String;
begin
hDrop:= Msg.wParam;
// tu wpisujemy kod przetwarzający strukturę z nazwami plików
end;
Przedstawianie alternatywnych sposobów obsługi komunikatów w artykule o drag and drop może wydawać się czynnością nie na miejscu. Opisałam jednak to drugie podejście aby ułatwić początkującym zrozumienie innych artykułów na ten temat, których autorzy skorzystali ze zdarzenia OnMessage
. Natomiast w dalszej części artykułu będziemy korzystać z pierwszego sposobu obsługi komunikatów przez Delphi.
Uzyskiwanie informacji o plikach
Funkcja DragQueryFile
ma następującą postać:
DragQueryFile(Drop: THandle; FileIndex: Integer; FileName: PChar; cb: Integer): Integer;
Drop
przyjmuje uchwyt do struktury plików, czyli wartość parametru Drop
komunikatu WM_DROPFILES
. Do parametru FileIndex
przypisujemy indeks pliku do którego chcemy się odwołać. Trzeci parametr FileName
wskazuje na bufor, do którego zostaje przypisana nazwa pliku o indeksie zawartym w parametrze FileIndex
. Ostatni parametr, cb,
zawiera rozmiar, w znakach, bufora przypisanego do parametru FileName
. W zależności od wartości przekazanych parametrów funkcja zwraca jedną z podanych trzech wartości:
1. liczbę opuszczonych plików
Aby uzyskać tę informacje należy przypisać do parametrów następujące wartości:
*FileIndex: $FFFFFFFF;
*FileName: nil;
*cb: 0;
Poniższy kod przypisuje do zmiennej FileCount ilość plików:
FileCount:= DragQueryFile(hDrop, $FFFFFFFF, nil, 0);
2. rozmiar bufora, który przechowuje nazwę (wraz z pełną ścieżką) pliku wynaczonego przez wartość parametru FileIndex
Aby uzyskać tę informacje należy przypisać do parametrów następujące wartości:
*FileIndex: indeks pliku, dla którego zostanie zwrócony rozmiar bufora;
*FileName: nil;
*cb: 0;
Zobaczmy fragment kodu, który zwraca rozmiar bufora, podając ilość jego znaków:
FileNameLength:=DragQueryFile(hDrop, 0, nil, 0);
3. nazwę pliku wraz ze ścieżką dostępu
Aby uzyskać tę informacje należy przypisać do parametrów następujące wartości:
*FileIndex: indeks pliku, dla którego zostanie zwrócona nazwa pliku;
*FileName: bufor do którego przypiszemy nazwę pliku ;
*cb: rozmiar bufora;
Aby uzyskać nazwę pliku należy wpisać poniższy kod:
SetLength(FileName, FileNameLength);
DragQueryFile(hDrop, 0, PChar(FileName), FileNameLength + 1);
W powyższym fragmencie parametrowi FileIndex
przypisaliśmy wartość 0, czyli indeks pierwszego pliku. Z tej wersji funkcji korzystamy wtedy, kiedy utworzyliśmy zmienną FileName typu String
. Długi łańcuch zawiera tzw. zerowy ogranicznik, który jest wymagany przez funkcje WinApi. Możesz również spotkać się z przykładami, w których zmienna FileName zadeklarowana jest jako tablica typu Char
. W takiej sytuacji nie musisz uzyskiwać rozmiaru bufora poprzez tą wersje funkcji DragQueryDrop
, która to umożliwia, wystarczy, że skorzystasz z funkcji sizeof
, lub wpiszesz dowolnie dużą liczbę (tak aby zmieściła się ilość znaków parametru FileName
), jednak ten sposób wydaje się mało elegancki i może spowodować błąd.
Jeżeli zmienna FileName była typu tablicą typu Char
, wpisujemy poniższy kod:
//Najpierw tworzymy tablice
//znaków
var
FileName: array[0..MAX_PATH] of Char;
//między begin i end
DragQueryFile(hDrop, 0, PChar(FileName),sizeof(FileName));
//albo
DragQueryFile(hDrop, 0, PChar(FileName), 500);
Jeżeli chcesz odczytać nazwy wszystkich plików musisz utworzyć pętle, która może wyglądać np tak:
var
I: Integer;
...
for i:= 0 to FileCount ? 1 do
begin
FileNameLength:= DragQueryFile(hDrop, i, nil, 0);
SetLength(FileName, FileNameLength);
DragQueryFile(hDrop, i, PChar(FileName), FileNameLength + 1);
//teraz możemy skorzystać ze
//zmiennej FileName
end;
Złóżmy to w całość
Najwyższy czas wykorzystać zdobyte wiadomości i dokończyć tworzenie naszego notatnika. Poniżej znajduje się kod modułu UnitDrag.pas, który został dla ułatwienia analizy, opatrzony komentarzami.
unit UnitDrag;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ShellAPI;
type
TDragForm = class(TForm)
DragMemo: TMemo;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
//deklaracja medoty obsługującej komunikat WM_DROPFILES
procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
end;
var
DragForm: TDragForm;
implementation
{$R *.dfm}
procedure TDragForm.WMDropFiles(var Msg: TWMDropFiles);
var
hDrop: THandle; //uchwyt do struktury plików
FileCount: Integer; //ilość opuszczanych plików
FileNameLength: Integer; //długość nazwy pliku
FileName: String; //nazwa pliku
begin
//przypisujemy zawartość parametru Drop komunikatu
hDrop:= Msg.Drop;
//ilość opuszczanych plików
//blok try...finally powoduje, że uchwyt zostanie zawsze zniszczony
try
//odczytujemy ilość opuszczanych plików
FileCount:= DragQueryFile(hDrop, $FFFFFFFF, nil, 0);
//możemy maksymalni otworzyć tylko jeden plik
if FileCount > 1 then
ShowMessage('Można otworzyć tylko jeden plik!' )
else
begin
//ustalamy długość nazwy pliku
FileNameLength:= DragQueryFile(hDrop, 0, nil, 0);
//SetLength przypisuje do zmiennej FileName rozmiar bufora
//(ilość znaków nazwy plików) i znak #0 (ogranicznik zerowy)
SetLength(FileName, FileNameLength);
//odczytujemy nazwę pliku
DragQueryFile(hDrop, 0, PChar(FileName), FileNameLength + 1);
//badamy, czy plik jest plikiem tekstowym
if ExtractFileExt(FileName) <> '.txt' then
ShowMessage('Dany plik nie jest plikiem tekstowym!')
else
//wczytujemy do kontrolki zawartośc pliku
DragMemo.Lines.LoadFromFile(FileName);
end;
finally
//niszczymy uchwyt
DragFinish(hDrop);
end;
end;
procedure TDragForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//wyłączamy akceptacje drag and drop
DragAcceptFiles(Self.Handle, False);
end;
procedure TDragForm.FormCreate(Sender: TObject);
begin
//włączamy akceptacje drag and drop
DragAcceptFiles(Self.Handle, True);
end;
end.
Dziwne, twoja funkcja działa mi pięknie na TPanel użyłem jej aby pobierać ścieżkę do upuszczonego pliku niestety co dziwne jeśli program na Win7 jest odpalony z prawami admina to nie działa, upuszczanie plików nie daj żadnej reakcji.