View Full Version : How to Drop a file onto an Input box?
dan_moran
11-11-2008, 23:16
I am currently using an InputBox$ to prompt for a filename to process in my script, but I would like to have the ability to drag and drop a file from Explorer onto a dialog/input box as an alternate or primary method of getting the path\filename into the script.
Can anyone help me with the code to get this feature into my script?
Thank you.
ErosOlmi
12-11-2008, 08:53
Hi Dan,
I'm sorry but I've never used drag/drop functionality so I'm not able to say if it is complex or not to develop it, but I will check it as soon as I have some free time and report it here.
In the meantime a possible better solution is to use standard windows file open dialog.
Have a look at \thinbasic\samplescropts\UI\Common\ and you will find some examples on how to use standard dialogs.
For example to ask for a file name you can do something like:
USES "UI"
USES "File"
dim sFile as string
dim sFilter as string
sFilter = "thinBasic Files (*.tBasic, *.tBasicc)|*.tBasic;*.tBasicc|"
sFilter += "Basic Files (*.BAS, *.INC)|*.BAS;*.INC|"
sFilter += "Resource Files (*.RC)|*.RC|"
sFilter += "Help files (*.HLP)|*.HLP|"
sFilter += "Text Files (*.TXT)|*.TXT|"
sFilter += "Word processing (*.RTF)|*.RTF|"
sFilter += "All Files (*.*)|*.*"
sFile = Dialog_OpenFile(0, _
"Open a file", _
DIR_GetCurrent, _
sFilter, _
"tBasic", _
%OFN_FILEMUSTEXIST OR %OFN_HIDEREADONLY or %OFN_ENABLESIZING)
if sFile = "" then
msgbox 0, "No file selected.", %MB_ICONEXCLAMATION, "File confirmation"
else
msgbox 0, "File selected:" & $crlf & sFile, %MB_ICONINFORMATION, "File confirmation"
end if
I will reply about drap/drop asap.
Eros
UPDATED: Fixed issue...
This works 100% with the 1.7.0.0 ThinBasic version. (Ask Eros for a test-drive of that version.)
Drop as many files as you want... This does not "Check" for file-ext. If it can be dropped, it will show in the list!
(Keep in mind, these are API calls. I have a good feeling Eros will make this into a full function for TB.)
The missing API call, will tell you the MOUSE XY position, where the user makes the DROP. (Though, you can use FOCUS to enable/disable DROP processing. If they are not in a drop-control, don't process the call.)
USES "UI"
'###############################################################################
'###############################################################################
' API required for DRAG-DROP
' Also... WS_EX style for the window... Must be set to %WS_EX_ACCEPTFILES
BEGIN CONST
%GetNumOfFiles = &HFFFF
END CONST
Declare Function DragQueryFile Lib "shell32" Alias "DragQueryFileA" ( _
ByVaL wHandle As Long, _
ByVaL NumFiles As Long, _
ByVal NameBuffer As STRING, _
ByVaL BufferLen As Long) As Long
Declare Sub DragFinish Lib "shell32" Alias "DragFinish" ( _
ByVal wHandle As Long)
'###############################################################################
'###############################################################################
FUNCTION TBMain() AS LONG
LOCAL hDlg AS DWORD
DIALOG NEW PIXELS, 0, "thinBasic test button callback", -1, -1, 300, 200, _
%WS_DLGFRAME OR %DS_CENTER OR %WS_CAPTION OR %WS_SYSMENU OR %WS_OVERLAPPEDWINDOW, _
%WS_EX_ACCEPTFILES TO hDlg
DIALOG SET MINSIZE hDlg, 300, 200
DIALOG SHOW modal hDlg, CALL cbDialog
END FUNCTION
CALLBACK FUNCTION cbDialog() AS LONG
'CBHNDL, CBCTL, CBCTLMSG, CBLPARAM, CBWPARAM
SELECT CASE CBMSG
CASE %WM_DROPFILES
LOCAL DropQuantity AS LONG
LOCAL FileNumber AS LONG
LOCAL FilePathLen AS LONG
LOCAL FilePathString AS STRING
LOCAL TextString AS STRING
' CB wParam is the wHandle
' Calls to see how many files are in the drop
' (wHandle, ConstantSwitch, NULL, ZERO) Returns Quantity
DropQuantity = DragQueryFile(CBWPARAM, %GetNumOfFiles, %NULL, 0)
' Loop through each name... Files start at 0
IF DropQuantity > 0 THEN
' There is/are file path/s here, get it/them
FOR FileNumber = 0 TO (DropQuantity-1)
' Get size of FilePath-buffer. (Add one, Buffer has NULL at end.)
' (wHandle, FileNumber {0 to number of files}, NULL, ZERO) Returns {THIS} FilePath buffer length/size
FilePathLen = DragQueryFile(CBWPARAM, FileNumber, %NULL, 0)
' This fills the STRING with the file path read at the buffer size.
DragQueryFile(CBWPARAM, FileNumber, FilePathString, FilePathLen+1)
' Demo-Stack, to show results...
TextString += PEEK$(STRPTR(FilePathString),FilePathLen) & $CRLF
NEXT
END IF
' Display results... (Drop multiple files)
MSGBOX(0, TextString)
' Important: Releases memory used in the DragDrop
DragFinish(CBWPARAM)
CASE %WM_CREATE
CASE %WM_INITDIALOG
CASE %WM_DESTROY
END SELECT
END FUNCTION
ErosOlmi
12-11-2008, 12:47
@Jason:
Thanks a lot, it works perfectly. Your feeling is a perfect feeling ;)
@Dan:
if you want to be able to download thinBasic 1.7.0.0 preview version, please send me a personal message here in forum.
We are very close to final official release but I can send you URL where to download it.
Here the list of changes you will find in it: http://www.thinbasic.org/public/applications/thinbasic/onlyforyoureyes/HTML/index.html?version_1_7_0_0.htm
As you can see it is a huge list of new features.
Ciao
Eros
If you saw what I had to go through, to get it to work! ::)
Here is one with a giant listbox... (It does not remove duplicates, but the default is sorted.)
All commented code removed, and some API and values renamed.
USES "UI"
GLOBAL hWnd AS DWORD
GLOBAL hListBox AS DWORD
BEGIN CONST
%ListBox = 1000
END CONST
BEGIN CONST
%GetNumOfFiles = &HFFFF
END CONST
Declare Function DragQueryFile Lib "shell32" Alias "DragQueryFileA" ( _
ByVaL wParam As Long, _
ByVaL QtyCnt As Long, _
ByVal StrPointer As String, _
ByVaL BuffLen As Long) As Long
Declare Sub DragFinish Lib "shell32" Alias "DragFinish" ( _
ByVal wParam As Long)
FUNCTION TBMain() AS LONG
DIALOG NEW PIXELS, 0, "thinBasic test button callback", -1, -1, 640, 480, _
%WS_DLGFRAME OR %DS_CENTER OR %WS_CAPTION OR %WS_SYSMENU OR %WS_OVERLAPPEDWINDOW, _
%WS_EX_ACCEPTFILES TO hWnd
DIALOG SET MINSIZE hWnd, 300, 200
DIALOG SHOW modal hWnd, CALL hWndCB
END FUNCTION
CALLBACK FUNCTION hWndCB() AS LONG
'CBHNDL, CBCTL, CBCTLMSG, CBLPARAM, CBWPARAM
SELECT CASE CBMSG
CASE %WM_DROPFILES
LOCAL DropQty, FileNum, PathLen AS LONG
LOCAL FilePtr AS STRING
DropQty = DragQueryFile(CBWPARAM, %GetNumOfFiles, %NULL, 0)
IF DropQty > 0 THEN
FOR FileNum = 0 TO (DropQty-1)
PathLen = DragQueryFile(CBWPARAM, FileNum, %NULL, 0)
DragQueryFile(CBWPARAM, FileNum, FilePtr, PathLen+1)
LISTBOX ADD CBHNDL, %ListBox, PEEK$(STRPTR(FilePtr),PathLen)
NEXT
END IF
DragFinish(CBWPARAM)
CASE %WM_CREATE
CASE %WM_INITDIALOG
hListBox = CONTROL ADD LISTBOX, CBHNDL, %ListBox, , 10, 10, 620, 480, 0, %WS_EX_LEFT
CASE %WM_DESTROY
END SELECT
END FUNCTION
ErosOlmi
12-11-2008, 13:42
Jason,
I would just change all hWnd to CBHNDL inside hWndCB callback function. CBHNDL will always have the handle of the dialog where the callback event occurs.
Also avoid hWnd to be global but just local to TBMain
Thanks again for this example.
Ciao
Eros
I would just change all hWnd to CBHNDL inside hWndCB callback function. CBHNDL will always have the handle of the dialog where the callback event occurs.
Change made... handles in callback relevant to the dialog were changed.
Also avoid hWnd to be global but just local to TBMain
I can not place the handles as LOCAL inside MAIN, because the CALLBACK is a FUNCTION, and can not see other FUNCTIONS LOCAL values.
EG, The LISTBOX is created in the CALLBACK, and can not see the HANDLE that is set as...
FUNCTION Main()
LOCAL hListBox AS DWORD
END FUNCTION
Windows handles should all be global, so that every function and sub can access them. (Unless they are a "Dialog Modal" which is a disposable pop-up message, not a real "Window". Not that all Dialog Modal's are disposable... P.S. Calling windows dialogs, and dialogs windows, was the stupidest thing MS ever did. And they topped it off, calling "Windows" windows, and calling controls "Dialogs". Now everything is just an "Object" in .NET LOL.)
I am erie about using CBHNDL, as all processes, not just the window, use that same callback. That is the parent, and each callback, and the parent, get a duplicate call. (EG, CBHNDL is the hWnd of the device at the moment, who is passing through. That can be a control, another window, a sound, or even windows. Each device does not need it's own callback, they just need CASE CBHWND = hWnd or hListBox or hWndChild1 or hWndChild2.)
CBHNDL, is fine up to the point of creation, or where no other "Control/Window" will have a similar event-message.
AnyWho... the sun is rising... time for me to get some sleep.
Actually, I should change the CBHND for the ADD FILES... (But in here, it is not that important. It would be, if there were multiple drop-lists. Here, or on another child-window/dialog/control.)
Turn on the (USES "Console") if you need an instant debugger. That helped me out more than the message-boxes, but I needed both to debug it. (PRINTL "SomeText: [" & MyVariable & "]" ). That helps a lot. (Not talking to you Eros. LOL )
ErosOlmi
12-11-2008, 16:04
Jason,
below the version without any global.
Why do you say window handle must be global? No reasons why.
A handle is just a pointer and if you have it stored somewhere it is ok.
CBHNDL is valid only inside a callback function.
In dialogs callbacks, it will always give the handle of the dialog
In control callbacks, it will always give the parent dialog where the control is child.
So I suppose it is always safe (in case of thinBasic programming language).
When you need to refer to a control, the important is to have the Control ID and the dialog where it is child.
With those two info, you can always get the control handle.
Ciao
Eros
USES "UI"
BEGIN CONST
%ListBox = 1000
END CONST
BEGIN CONST
%GetNumOfFiles = &HFFFF
END CONST
Declare Function DragQueryFile Lib "shell32" Alias "DragQueryFileA" ( _
ByVaL wParam As Long, _
ByVaL QtyCnt As Long, _
ByVal StrPointer As String, _
ByVaL BuffLen As Long) As Long
Declare Sub DragFinish Lib "shell32" Alias "DragFinish" (ByVal wParam As Long)
FUNCTION TBMain() AS LONG
local hWnd AS DWORD
DIALOG NEW PIXELS, 0, "thinBasic test button callback", -1, -1, 640, 480, _
%WS_DLGFRAME OR %DS_CENTER OR %WS_CAPTION OR %WS_SYSMENU OR %WS_OVERLAPPEDWINDOW, _
%WS_EX_ACCEPTFILES TO hWnd
DIALOG SET MINSIZE hWnd, 300, 200
DIALOG SHOW modal hWnd, CALL hWndCB
END FUNCTION
CALLBACK FUNCTION hWndCB() AS LONG
'CBHNDL, CBCTL, CBCTLMSG, CBLPARAM, CBWPARAM
SELECT CASE CBMSG
CASE %WM_DROPFILES
LOCAL DropQty, FileNum, PathLen AS LONG
LOCAL FilePtr AS STRING
DropQty = DragQueryFile(CBWPARAM, %GetNumOfFiles, %NULL, 0)
IF DropQty > 0 THEN
FOR FileNum = 0 TO (DropQty-1)
PathLen = DragQueryFile(CBWPARAM, FileNum, %NULL, 0)
DragQueryFile(CBWPARAM, FileNum, FilePtr, PathLen+1)
LISTBOX ADD CBHNDL, %ListBox, PEEK$(STRPTR(FilePtr),PathLen)
NEXT
END IF
DragFinish(CBWPARAM)
CASE %WM_CREATE
CASE %WM_INITDIALOG
CONTROL ADD LISTBOX, CBHNDL, %ListBox, , 10, 10, 620, 480, 0, %WS_EX_LEFT
control set resize CBHNDL, %ListBox, 1, 1, 1, 1
CASE %WM_DESTROY
END SELECT
END FUNCTION
@ DAN,
Sorry, this is getting a little off topic. But you may find it relevant, if you decide to work with the callback method.
@ EROS,
I use the handles of the controls, to get attributes with API.
hControl_1 = ADD CONTROL
Which is inside the %WM_INITDIALOG of the main callback.
If the (LOCAL hControl AS DWORD), is in the (TBMain()), you get an error. The callback does not see inside the seporate (TBMain()) function, and those variables no longer exist.
EG, inside the main callback, you can not say... (I can not say... LOL, remember, I use one callback for everything.)
SELECT CASE CBHNDL
CASE hWnd
Do something if this is the handle of the main form
CASE hControl_1
Do something if this is the control#1
CASE hControl_2
Do something if this is the control#2
CASE ELSE
This would be any non-specific controls/windows
END SELECT
Without the handle being global, I have no way to tell which call owns this signal coming in. (I know which handle CBHNDL, but what is that? Window 1,2,3,4,5 Button 1,2,3,4,5? Also remembering that dynamic buttons/controls use one %ButtonID value, only the handles are unique, you can not depend on the %ButtonID, which is just a TYPE of button, not an individual button ID. Unless you make an %ID for every possible dynamic form, which is impossible. If you don't know the handle of %ButtonID 4, which is 20 buttons, one handle for each dynamic form.... You can not use that button to fill %Listbox 27, which is handle hListBox 123, which is on another dynamic form, but the form which is intended to get that call.)
%ID is like an ARRAY... It is one unique element created to identify a control type.
%ID(hControl) is the dynamic control value. (But without the () to identify it.)
IF ... = %MyCancelButton AND CBHNDL = hButtonControl(x) THEN
or
CASE %MyCancelButton '(This is every %MyButton on every dynamic window you create with %MyButton on it.)
as opposed to...
CASE %MyDirListbox
and
SELECT CBHNDL
CASE hControl(x) '(Non-Specific handles)
or
CASE hButtonControl(x) '(Specific handles)
or
CASE hButton_1 '(Unique handles)
Just saying that certain things are not possible the other way, and this also reduces CALLBACK flooding/saturation from windows.