A Folder Selector With Initial Folder Setting
by
, 22-May-2011 at 02:16 AM (15970 Views)
This week two developers asked me about a folderbrowser/selector. Over the years several solutions have been created (fully) using the Windows API. But as far as I know they all do not have the ability to set the focus on a certain folder shown in the tree. And that is what the developers wanted to have. I said it is "simple" to make a folder selector yourself. This blog tells you how you can create one yourself using Visual DataFlex code and a bit Windows API code.
The Dialog
I think it is good if our folder selector dialog would popup on command which means you should create a ModalDialog component. The dialog should contain a tree with folders and buttons to accept the folder and close the dialog. Very useful would be a toolbar with buttons to create a new folder or delete one. So start designing a dialog. For this go to Create New, Dialog / Lookup and select Modal Dialog. Enter a name like oFolderSelectorDialog and see a ModalDialog object created with two buttons. From the class palette drag a cCJToolBar (Menus and Toolbars) and a treeview (Base Controls) to the dialog. Name the treeview object oFoldersTreeView. If you like you can give the tool-bar already two buttons but later the full source code for the tool-bar is published in this blog and that source code includes buttons and code. Position the treeview at 18,5 and align the buttons created by the template with the right hand side of the treeview. Set the peAnchors property of the treeview to anAll so that the folders list nicely resizes if the user resizes the dialog. Make the dialog look like:
Read the Folder Structure
Now all the visual controls are in place the coding starts. No-one want to re-invent the wheel and you can borrow some code from the ImageGallery demo application in the Graphics Library. If you do not have this library yet you can use the link to download and install. Copy the package FileDateTime.Pkg from the functions sub-folder. From the code copy the oImageList object to the dialog and do not forget to copy the bitmaps named in the source code to the bitmaps folder of your workspace. Copy the property settings for TreeLinesState and ImageListObject. Then copy the Enum_List and the methods LocateTreeItem, LocateItem, LocateRootItem, LocateFolder, OnItemExpanding, OnItemCollapsed, ReadFolderData and OnCreateTree to your treeview. Remove the following code from the OnCreateTree method as the code is special to the ImageGallery example:
The code used the Windows API to retrieve your pictures folder and place this in the top of the folders list.Code:Get AddTreeItem (WindowsFolderPath (CSIDL_MYPICTURES, True)) 0 C_FOLDERITEM C_FOLDERBMP C_FOLDERBMP to hItem Set ItemChildCount hItem to 1 If (SysConf (SYSCONF_OS_MAJOR_REV) >= 6) Begin Get AddTreeItem (WindowsFolderPath (CSIDL_COMMON_PICTURES, True)) 0 C_FOLDERITEM C_FOLDERBMP C_FOLDERBMP to hItem Set ItemChildCount hItem to 1 End
Also replace the following code:
with:Code:Get ReadString of ghoApplication "Preferences" "CurrentFolder" "" to sCurrentFolder If (sCurrentFolder <> "") Begin Send LocateFolder sCurrentFolder End
The above code makes it possible to set the initial folder of the folder selector to the folder we desire.Code:Get psInitialFolder to sInitialFolder If (sInitialFolder <> "") Begin Send LocateFolder sInitialFolder End
Select the Folder and Return
The dialog needs to have a method to show itself and wait for user input. For this change the oOk_Btn into a folder selection button:
and add a new function named SelectFolder that initializes and opens the dialog. Make the function like:Code:Object oSelectButton is a Button Set Label to "&Select" Set Location to 200 153 Set peAnchors to anBottomRight Procedure OnClick Handle hItem String sFolderPath Get CurrentTreeItem of oFoldersTreeView to hItem Get ItemFullPath of oFoldersTreeView hItem to sFolderPath Set psSelectedFolder to sFolderPath Send Close_Panel End_Procedure End_Object
The above code also requires you add a property to store the selected folder name into. Add this definition to the dialog using the following code:Code:Function SelectFolder Returns String String sSelectedFolder // Set to empty to avoid the function returns the selected folder // of a previous operation Set psSelectedFolder to "" Send Popup_Modal Get psSelectedFolder to sSelectedFolder Function_Return sSelectedFolder End_Function
The meta-data tag above the property definition makes the property available for our coding but hidden for code-sense and for visual designers. The property is really a kind of hidden one.Code:{ Visibility = Private } Property String psSelectedFolder
Because the oOk_Btn created by the template is no longer there replace the On_Key statement with:
The Tool-BarCode:On_Key Key_Alt+Key_S Send KeyAction of oSelectButton
The tool-bar on top of the view - as mentioned - needs to have a button to create a new folder and a button to remove a folder. The icons for the buttons are already available in your rich Visual DataFlex development environment. The coding of the tool-bar can be:
The property settings of the tool-bar make it not re-locatable (pbGripper), stretched to the full width (peStretched) and not closeable (pbCloseable) by the user. The tool-bar buttons show both the icon as well as the caption. For the IsEnabled function of the 2nd button we need to add the following code to the treeview.Code:Object oFolderSelectorCommandBarSystem is a cCJCommandBarSystem Object oToolbar is a cCJToolbar Set pbCustomizable to False Set pbCloseable to False Set peStretched to stStretch Set pbGripper to False Set pbEnableDocking to False Object oNewFolderMenuItem is a cCJMenuItem Set peControlStyle to xtpButtonIconAndCaption Set psImage to "New16.bmp" Set psCaption to "New" Set psDescription to "New Folder" Procedure OnExecute Variant vCommandBarControl Send CreateFolder of oFoldersTreeView End_Procedure End_Object Object oDeleteFolderMenuItem is a cCJMenuItem Set psCaption to "Delete" Set psImage to "ActionDelete.ico" Set peControlStyle to xtpButtonIconAndCaption Set psDescription to "Delete Folder" Set pbActiveUpdate to True Procedure OnExecute Variant vCommandBarControl Send DeleteFolder of oFoldersTreeView End_Procedure Function IsEnabled Returns Boolean Boolean bEnabled Get MapsCurrentNodeToExistingFolder of oFoldersTreeView to bEnabled Function_Return bEnabled End_Function End_Object End_Object End_Object
The method uses a function called PathIsDirectory which you can find in the code of this whitepaper that I wrote back in 2003. The other functions (FileDateTime for example) can be found in the Graphics Demo application.Code:Function MapsCurrentNodeToExistingFolder Returns Boolean Handle hItem String sItemFullPath Boolean bExist Integer iItemData Get CurrentTreeItem to hItem Get ItemData hItem to iItemData If (iItemData <= -1) Begin // Only accept folders for delete items that were created by the CreateFolder method Get ChildItem hItem to hChildItem If (hChildItem = 0) Begin Get ItemFullPath hItem to sItemFullPath Get PathIsDirectory sItemFullPath to bExist End End Function_Return bExist End_Function
NewFolder and DeleteFolder
Our dialog won't compile without the methods CreateFolder and DeleteFolder. Start with DeleteFolder as this is the easiest one. For a succesfull delete the method needs to get the path of the current tree item and after asking the user to confirmation the delete operation attempt to delete the folder with the Visual DataFlex command Remove_Directory.
The code is really meant to delete a folder that is wrongly created by the CreateFolder method and not to delete other folders. Therefore it test if the folder was just added by CreateFolder via the tree-item's ItemData property. If the value of that property is equal to -1 delete can be attempted, else not.Code:Procedure DeleteFolder Handle hItem String sCurrentFolder Integer iItemData iAnswer Get CurrentTreeItem to hItem Get ItemData hItem to iItemData If (iItemData <= -1) Begin // Only delete items that were created by the CreateFolder method Get ItemFullPath hItem to sCurrentFolder Move (YesNo_Box ("Do You Want To Delete '" - sCurrentFolder - "'?", "Confirm", MB_DEFBUTTON2)) to iAnswer If (iAnswer = MBR_Yes) Begin Remove_Directory sCurrentFolder Send DoDeleteItem hItem End End End_Procedure
The CreateFolder is a bit more complex. For the real folder creation you can make use of the Make_Directory command. Before Make_Directory command is executed the user can change the name. The default name for the new folder will be "New Folder". If there is already a folder with that name the code need to suggest an alternative name. Add the following code to the treeview:
Now the new folder name is constructed it will be added to the treeview and the item will be opened for editing. To be able to edit the text you need to set the TreeEditLabelsState property of the treeview to true.The item will be marked via the ItemData member as created by the CreateFolder method. Add the following code to complete the CreateFolder method:Code:Procedure CreateFolder Handle hItem hNewItem String sCurrentFolder sNewFolder sNewSubFolderName Boolean bExist Integer iCount Get CurrentTreeItem to hItem Get ItemFullPath hItem to sCurrentFolder If (Right (sCurrentFolder, 1) <> "\") Begin Move (sCurrentFolder - "\") to sCurrentFolder End Move "New Folder" to sNewSubFolderName Move (sCurrentFolder - sNewSubFolderName) to sNewFolder Get PathIsDirectory sNewFolder to bExist While (bExist) Increment iCount Move ("New Folder (" - String (iCount) - ")") to sNewSubFolderName Move (sCurrentFolder - sNewSubFolderName) to sNewFolder Get PathIsDirectory sNewFolder to bExist Loop
As you can see there is no Make_Directory yet. This is done in the treeview event that is fired when the user is ready with editing the folder name, OnEndLabelEdit. For this add the event to the treeview and paste the following code inside the method.Code:Set ItemChildCount hItem to 1 Get AddTreeItem sNewSubFolderName hItem -1 C_FOLDERBMP C_FOLDERBMP to hNewItem Send DoExpandItem hItem Set CurrentTreeItem to hNewItem Send Windows_Message TVM_EDITLABEL 0 hNewItem End_Procedure
As you can see the code again tests the ItemData member on -1. Then it finds the last folder separator character (backslash) and appends the new folder name to the path. Finally to avoid a user starts editing a label with a double click or F2 augment the OnBeginLabelEdit event with:Code:Integer iItemData iPos String sItemFullPath sNewFolder Get ItemData hItem to iItemData If (iItemData = -1) Begin Get ItemFullPath hItem to sItemFullPath If (not (bWasCanceled)) Begin Move (RightPos ("\", sItemFullPath)) to iPos Move (Left (sItemFullPath, iPos) - sNewLabel) to sNewFolder End Else Begin Move sItemFullPath to sNewFolder End Set ItemData hItem to -2 Make_Directory sNewFolder End
Floating MenuCode:Integer iItemData Get ItemData hItem to iItemData If (iItemData <> -1) Begin Move True to bCancel End
To really complete the component add a context (floating) menu to the treeview object with the same features as the tool-bar. For this drag a cCJContextMenu from the class palette to the treeview and add two items: New Folder and Delete Folder. Use the same pictures used for the tool-bar buttons and copy/paste the OnExecute and IsEnabled event code into the objects of the context menu. Activate the context menu via the treeview OnItemRClick event.
Accessing the Folder SelectorCode:Procedure OnItemRClick Handle hItem Send Popup of oFoldersContextMenu End_Procedure
To select a folder you call the SelectFolder method and before this you can set the initial folder property. An example for calling is:
Code:Set psInitialFolder of oFolderSelectorDialog to "C:\Projects\VDF\16.0\Visual Report Writer Integration Examples 1.5" Get SelectFolder of oFolderSelectorDialog to sFolder
If you followed the steps above you now have a nice in Visual DataFlex written folder selector dialog. The source code is written while using Visual DataFlex 2010/16.1.