home *** CD-ROM | disk | FTP | other *** search
/ PC Plus SuperCD (UK) 2000 March / pcp161b.iso / handson / archive / Issue153 / delphi / copydelp.exe / TreeDir2 / TreeD2.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-03-21  |  16.7 KB  |  486 lines

  1. unit TreeD2;
  2.    { PC Plus sample Delphi 2.0 file manager.
  3.    Author: Huw Collingbourne
  4.  
  5.    ------------IMPORTANT DISCLAIMER----------------
  6.    This application can erase files from your disk!
  7.    It is provided as a sample program and it is not
  8.    guaranteed to be flawless, safe or fit for any
  9.    purpose whatsoever. In short, while I don't
  10.    believe it will damage or corrupt your files,
  11.    I do not guarantee that it won't!
  12.    ------------------------------------------------
  13.  
  14.    Functionality:
  15.    * Displays TreeView directory window
  16.    * Displays ListView of files with appropriate icons
  17.    * Splitter panel lets you resize windows at run-time
  18.    * Double-click to excute selected file
  19.    * Single-click file name to rename file
  20.    * Press the DELETE key to erase selected file
  21.    * Popup menu provides alternatives to keystrokes
  22.    * DriveComboBox lets you log onto a new disk
  23.  
  24.    Limitations:
  25.    * No drag and drop
  26.    * No copy and move functions
  27.    }
  28.  
  29. interface
  30.  
  31. uses
  32.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  33.   StdCtrls, ComCtrls, ExtCtrls, ShellAPI,
  34.   CommCtrl, FileCtrl,
  35.   FMXUtils, Menus, SHLObj;
  36.  
  37. type
  38.   TForm1 = class(TForm)
  39.     StatusBar1: TStatusBar;
  40.     ImageList1: TImageList;
  41.     HeaderPanel: TPanel;
  42.     DriveComboBox1: TDriveComboBox;
  43.     PopupMenu1: TPopupMenu;
  44.     Delete: TMenuItem;
  45.     Rename: TMenuItem;
  46.     MainPanel: TPanel;
  47.     ListView1: TListView;
  48.     TreeView1: TTreeView;
  49.     SplitterPanel: TPanel;
  50.     procedure TreeView1Expanding(Sender: TObject; Node: TTreeNode;
  51.       var AllowExpansion: Boolean);
  52.     procedure FormCreate(Sender: TObject);
  53.     procedure TreeView1Click(Sender: TObject);
  54.     procedure ListView1Click(Sender: TObject);
  55.     procedure ListView1DblClick(Sender: TObject);
  56.     procedure SplitterPanelMouseDown(Sender: TObject; Button: TMouseButton;
  57.       Shift: TShiftState; X, Y: Integer);
  58.     procedure FormResize(Sender: TObject);
  59.     procedure DriveComboBox1Change(Sender: TObject);
  60.     procedure DeleteClick(Sender: TObject);
  61.     procedure RenameClick(Sender: TObject);
  62.     procedure ListView1Edited(Sender: TObject; Item: TListItem;
  63.       var S: string);
  64.     procedure ListView1Change(Sender: TObject; Item: TListItem;
  65.       Change: TItemChange);
  66.   private
  67.     { Private declarations }
  68.   public
  69.     { Public declarations }
  70.     CurrDirName : string;
  71.     CurrFileName : string;
  72.     ItemEdited : boolean;
  73.     function isDirectory( sr : TSearchRec ) : boolean;
  74.     function excludeFromListView( sr : TSearchRec ) : boolean;
  75.     function hasSubDirectories( d : string ) : boolean;
  76.     function fullPath( Node : TTreeNode ) : string;
  77.     procedure addSubDirs( rootDir : string;  Node   : TTreeNode );
  78.     procedure updatePanels;
  79.     procedure updateListView;
  80.   end;
  81.  
  82. const    // default root directory in TreeView
  83.   defaultDir = 'C:';
  84.          // minimum width allowed for TreeView or ListView
  85.   MINWIDTH = 100;
  86.  
  87. var
  88.   Form1: TForm1;
  89.  
  90. implementation
  91.  
  92.  
  93. {$R *.DFM}
  94.  
  95. function DiskInDrive(Drive: Char): Boolean;
  96. { Check to see if a disk is in the drive specified.
  97.   This code is taken from Borland's TechInfo help file }   
  98. var
  99.   ErrorMode: word;
  100. begin
  101.   { make it upper case }
  102.   if Drive in ['a'..'z'] then Dec(Drive, $20);
  103.   { make sure it's a letter }
  104.   if not (Drive in ['A'..'Z']) then
  105.     raise EConvertError.Create('Not a valid drive ID');
  106.   { turn off critical errors }
  107.   ErrorMode := SetErrorMode(SEM_FailCriticalErrors);
  108.   try
  109.     { drive 1 = a, 2 = b, 3 = c, etc. }
  110.     if DiskSize(Ord(Drive) - $40) = -1 then
  111.       Result := False
  112.     else
  113.       Result := True;
  114.   finally
  115.     { restore old error mode }
  116.     SetErrorMode(ErrorMode);
  117.   end;
  118. end;
  119.  
  120.  
  121. function GetFileInfo( fn : string ) : TSHFileInfo;
  122. { Return a TSHFileInfo with information on the file, fn.
  123.   The precise type of information is controlled by the flags
  124.   passed to the SHGetFileInfo function }
  125. var
  126.   FileInfo : TSHFileInfo;
  127. begin
  128.   FillChar(FileInfo, SizeOf(FileInfo), #0);
  129.   SHGetFileInfo(PChar(fn),0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or
  130.                                                          SHGFI_TYPENAME or
  131.                                                          SHGFI_ATTRIBUTES );
  132.   result := FileInfo;
  133. end;
  134.  
  135.  
  136.  
  137. procedure TForm1.updateListView;
  138. { Displays file names from current directory in the ListView.
  139.   This procedure uses data supplied by a TSearchRec and a TSHFileInfo
  140.   structure to retrive and display file information such as the
  141.   file date, size, type and attributes. }
  142. var
  143.    NewItem : TListItem;
  144.    Result : integer;
  145.    SearchRec: TSearchRec;
  146.    TypeName : string;     { file type: e.g 'Text document' or 'Delphi Project'}
  147.    FileInfo : TSHFileInfo;
  148.    ByteSize : integer;    { file size   }
  149.    AttStr   : string;     { string representation of file attributes }
  150.    Attributes : integer;  { integer representation of file attributes }
  151. begin
  152.     ListView1.Items.BeginUpdate; // turn off update for speed
  153.     ListView1.Items.Clear;
  154.     ListView1.Items.EndUpdate;   // turn it on again
  155.     Result := FindFirst(currDirName+'*.*', faAnyFile, SearchRec);
  156.     while Result = 0 do
  157.     begin // filter any unwanted files from display
  158.       if not excludeFromListView( SearchRec ) then
  159.       begin
  160.          // first retrieve file information
  161.         FileInfo :=  GetFileInfo(currDirName + Searchrec.Name );
  162.         TypeName := FileInfo.szTypeName;
  163.         AttStr:='';
  164.         Attributes := SearchRec.Attr;
  165.         if (Attributes and faReadOnly > 0) then AttStr := AttStr + 'R';
  166.         if (Attributes and faHidden  > 0) then AttStr := AttStr + 'H';
  167.         if (Attributes and faSysFile > 0) then AttStr := AttStr + 'S';
  168.         if (Attributes and faArchive > 0) then AttStr := AttStr + 'A';
  169. (* // Note: we could retrieve a set of attributes from FileInfo.
  170.        However, these attributes are both more numerous and different
  171.        from standard file attributes (e.g. they don't include an Archive flag but
  172.        they do have Removable-media ghosted icon flags). I don't feel that
  173.        most of these are not appropriate in this application. You can
  174.        find a list of all these constants in Delphi's SHLOBJ.PAS unit.
  175.        This is an example of how you would use FileInfo attributes:
  176.           Attributes := FileInfo.dwAttributes;
  177.          if (Attributes and SFGAO_READONLY >0) then AttStr := AttStr + 'R';
  178. *)
  179.  
  180.         ByteSize := SearchRec.Size div 1024;
  181.         if ByteSize = 0 then ByteSize := 1;
  182.         // then create a new item in the ListView
  183.         NewItem := ListView1.Items.Add;      // add a file name in lowercase
  184.         NewItem.Caption := LowerCase(SearchRec.Name);
  185.         NewItem.ImageIndex := FileInfo.iIcon; // add an appropriate icon
  186.         // then add subitems in the ListView columns to display file info
  187.         NewItem.SubItems.Add(IntToStr(ByteSize)+'KB');
  188.         NewItem.SubItems.Add(TypeName);
  189.         NewItem.SubItems.Add(AttStr);
  190.         NewItem.SubItems.Add(DateTimeToStr(FileDateToDateTime( SearchRec.Time )));
  191.       end;
  192.       Result := FindNext(SearchRec);
  193.     end;
  194.     FindClose(SearchRec);
  195. end;
  196.  
  197. procedure TForm1.updatePanels;
  198. { display path and number of sub directories in the Statusbar panels }
  199. var
  200.   panel0text : string;
  201. begin
  202.   panel0text := currDirName;
  203.   if TreeView1.Selected.Expanded then panel0text := panel0text +
  204.         ' [ ' + IntToStr( TreeView1.Selected.Count ) + ' subdirectories ]';
  205.   StatusBar1.Panels.Items[0].Text := panel0text;
  206. end;
  207.  
  208. function TForm1.excludeFromListView( sr : TSearchRec ) : boolean;
  209. { Don't display these files in the file list }
  210. begin
  211.     if isDirectory( sr ) then
  212.        excludeFromListView := true
  213.     else if ((sr.Name = '.') or (sr.Name = '..')) then
  214.      excludeFromListView := true
  215.   else excludeFromListView := false;
  216. end;
  217.  
  218. function TForm1.isDirectory( sr : TSearchRec ) : boolean;
  219. { test if current file is a directory }
  220. begin
  221.    isDirectory := ((sr.Attr and faDirectory > 0)
  222.                   and
  223.                   (sr.Name <> '.') and (sr.Name <> '..'));
  224. end;
  225.  
  226. function TForm1.fullPath( Node : TTreeNode ) : string;
  227. { Return a string showing path - e.g. C:\GP\Parent\NodeDir\ }
  228. var
  229.   ANode, FirstNode : TTreeNode;
  230.   path : string;
  231. begin
  232.   ANode := Node;
  233.   path := ANode.Text + '\';
  234.   FirstNode := TreeView1.Items.GetFirstNode;
  235.   while ANode <> FirstNode do
  236.   begin
  237.      ANode := ANode.Parent;
  238.      path := ANode.Text + '\' + path;
  239.   end;
  240.   fullPath := path;
  241. end;
  242.  
  243. function TForm1.hasSubDirectories( d : string ) : boolean;
  244. { test in the directory indicated by the string 'd' contains
  245.  subdirectories. If so, return true }
  246. var
  247.   SearchRec: TSearchRec;
  248.   SResult : integer;
  249.   noSubDirFound : boolean;
  250. begin    // find first file, Result is 0 if successful
  251.     noSubDirFound := true;
  252.     SResult := FindFirst(d + '\*.*', faDirectory, SearchRec);
  253.     while ((SResult = 0) and (noSubDirFound)) do
  254.     begin
  255.       if isDirectory(SearchRec) then noSubDirFound := false;
  256.       SResult := FindNext(SearchRec);
  257.     end;
  258.     FindClose(SearchRec);
  259.     hasSubDirectories := (noSubDirFound = false);
  260. end;
  261.  
  262. procedure TForm1.addSubDirs( rootDir : string;  Node : TTreeNode );
  263. { add directory names as branches of the TreeView . If a directory contains
  264.   any subdirectories, a '+' is placed alongside the directory name in
  265.   the TreeView }
  266. var
  267.   SearchRec: TSearchRec;
  268.   Result : integer;
  269.   NewNode : TTreeNode;
  270. begin    // find first file, Result is 0 if successful
  271.     Result := FindFirst(rootDir + '*.*', faDirectory, SearchRec);
  272.     while Result = 0 do
  273.     begin
  274.       if isDirectory(SearchRec) then
  275.       begin  // if directory is found add it as a child of prev node in TreeView
  276.          NewNode := TreeView1.Items.AddChild(Node, SearchRec.Name);
  277.          NewNode.SelectedIndex := 1;
  278.          if (hasSubDirectories( rootDir + SearchRec.Name ) ) then
  279.             // making HasChildren true adds a '+' to the directory node
  280.             // even if that node hasn't really got any child nodes
  281.             NewNode.HasChildren := true;
  282.       end; // then continue searching }
  283.       Result := FindNext(SearchRec);
  284.     end;
  285.     FindClose(SearchRec);
  286.     Node.AlphaSort;
  287. end;
  288.  
  289. procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
  290.   var AllowExpansion: Boolean);
  291. begin
  292.     if Node.GetFirstChild = nil then
  293.        addSubDirs(fullPath(Node) , Node );
  294.   Node.Selected := true; { if expanding the node, select it }
  295. end;
  296.  
  297. procedure TForm1.FormCreate(Sender: TObject);
  298. var
  299.   ImageListHandle : THandle;
  300.   FileInfo : TSHFileInfo;
  301.  
  302. begin
  303.   // Get system ImageList so that we can retrieve small icons for each file type
  304.   ImageListHandle := SHGetFileInfo('',
  305.                            0,
  306.                            FileInfo,
  307.                            SizeOf(FileInfo),
  308.                            SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  309.   SendMessage(ListView1.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, ImageListHandle);
  310.   //!! The next line causes an OnChange event which will fire up
  311.   //!! DriveComboBox1Change which, in turn, populates the TreeView and ListView
  312.   DriveComboBox1.ItemIndex := 1; // C:\ drive
  313.   // Set Form1 variables to default values
  314.   currFileName := '';
  315.   currDirName := DefaultDir;
  316.   ItemEdited := false;
  317. end;
  318.  
  319. procedure TForm1.TreeView1Click(Sender: TObject);
  320. begin
  321.  // if the same directory node was clicked, do nothing
  322.     if fullpath(TreeView1.Selected) <> currDirName then
  323.     begin
  324.        currDirName := fullpath(TreeView1.Selected);
  325.        updateListView;
  326.        updatePanels;
  327.     end;
  328. end;
  329.  
  330. procedure TForm1.ListView1Click(Sender: TObject);
  331. { if a file is selected then update the currFileName variable }
  332. begin
  333.   if ListView1.Selected <> nil then
  334.      currFileName := ListView1.Selected.Caption
  335.   else
  336.      currFileName := '';
  337.   StatusBar1.Panels.Items[1].Text := currFileName;
  338. end;
  339.  
  340. procedure TForm1.ListView1DblClick(Sender: TObject);
  341. begin
  342.   if ListView1.Selected <> nil then // Check that a file name is Selected
  343.      ExecuteFile(ListView1.Selected.Caption, '',
  344.                  fullPath(TreeView1.Selected), SW_SHOW);
  345. end;
  346.  
  347. procedure TForm1.SplitterPanelMouseDown(Sender: TObject; Button: TMouseButton;
  348.   Shift: TShiftState; X, Y: Integer);
  349. const
  350.   SC_DragMove = $F012; { a magic number }
  351. var
  352.   BottomMargin : integer;
  353. begin
  354.   ListView1.align := alNone; // turn off align during movement
  355.   SplitterPanel.align := alNone;
  356.                        // resize panel so that the top and bottom edges
  357.                        // don't appear if the mouse is moved vertically
  358.   BottomMargin := Form1.ClientHeight - Y;
  359.   SplitterPanel.Top := -BottomMargin;
  360.   SplitterPanel.Height := Form1.ClientHeight * 2;
  361.   Form1.Update;        // update to ensure Memo contents are repainted
  362.   ReleaseCapture;
  363.                        // --- Move the splitter panel ---
  364.   SplitterPanel.perform(WM_SysCommand, SC_DragMove, 0);
  365.                        // make sure Memos aren't resized beneath a minimum width
  366.   if SplitterPanel.Left <= MINWIDTH then
  367.      SplitterPanel.Left := MINWIDTH
  368.   else if (Form1.ClientWidth - (SplitterPanel.Left + SplitterPanel.Width)) <= MINWIDTH then
  369.      SplitterPanel.Left := Form1.ClientWidth - (SplitterPanel.Width+MINWIDTH);
  370.                        // restore default alignments
  371.   TreeView1.Width := SplitterPanel.Left;
  372.   SplitterPanel.align := alLeft;
  373.   ListView1.align := alClient;
  374.  
  375. end;
  376.  
  377. procedure TForm1.FormResize(Sender: TObject);
  378. { Don't allow the user to resize the form below a sensible minimum size }
  379. begin
  380.    if Form1.Width < (MINWIDTH * 2) then
  381.    begin
  382.       Form1.Width := (MINWIDTH * 2);
  383.       TreeView1.Width := MINWIDTH;
  384.  
  385.    end;
  386. end;
  387.  
  388. procedure TForm1.DriveComboBox1Change(Sender: TObject);
  389. { When a new drive has been selected, populate the TreeView with
  390.   the directories on that drive }
  391. var
  392.    Node : TTreeNode;
  393.    currDir : string;
  394. begin
  395.   // When  DriveComboBox1 ItemIndex is set during Form creation,
  396.   // Drive will = ''
  397.   if DriveComboBox1.Drive = '' then
  398.      currDir := defaultDir
  399.   else
  400.      currDir := DriveComboBox1.Drive + ':' ;
  401.  
  402.   if not DiskInDrive( currDir[1] ) then
  403.      ShowMessage( 'No Disk in: ' + currDir )
  404.   else
  405.   begin
  406.     TreeView1.Items.Clear;
  407.     Node := TreeView1.Items.Add(TreeView1.Selected, currDir);
  408.  
  409.     Node.SelectedIndex := 1;
  410.     Node.Selected := true;
  411.     addSubDirs(currDir+'\', Node);
  412.     TreeView1.AlphaSort;
  413.               // Expand(false) means - don't recurse to expand all child items
  414.               // Expand(true) would havethe effect of logging (and expanding)
  415.               // all subdirectories beneath the root!
  416.     TreeView1.Items[0].Expand(false);
  417.     currDirName := fullPath(TreeView1.Selected);
  418.     updateListView;
  419.     updatePanels;
  420.   end;
  421.  
  422. end;
  423.  
  424. procedure TForm1.DeleteClick(Sender: TObject);
  425. { Delete the file whose name is selected }
  426. begin
  427.   if ListView1.Selected <> nil then // Check that a file name is Selected
  428.     if MessageDlg('Delete '+ListView1.Selected.Caption+'?', mtConfirmation,
  429.                   [mbYes, mbNo],0) = mrYes then
  430.                   if DeleteFile(CurrDirName+ListView1.Selected.Caption) then
  431.                   updateListView
  432.                   else ShowMessage( 'Cannot delete this file!');
  433. end;
  434.  
  435. procedure TForm1.RenameClick(Sender: TObject);
  436. begin
  437.   if ListView1.Selected = nil then
  438.      ShowMessage('There is no file name selected!' )
  439.   else
  440.      ListView1.Selected.EditCaption;
  441. end;
  442.  
  443. procedure TForm1.ListView1Edited(Sender: TObject; Item: TListItem;
  444.   var S: string);
  445. begin
  446.    ItemEdited := True; { set a flag to show that item caption has been edited }
  447. end;
  448.  
  449. procedure TForm1.ListView1Change(Sender: TObject; Item: TListItem;
  450.   Change: TItemChange);
  451. { if an item's caption has just been edited (ItemEdited = true) try
  452.   to rename the file. If the Rename operation fails, restore the
  453.   previous caption (curreFileName) and display an error message }
  454. var
  455.    newname : string;
  456.    badname : boolean;  // true if a bad or blank file name is entered
  457. begin
  458.   badname := false;
  459.   if ItemEdited = true then
  460.   begin
  461.     ItemEdited := false;
  462.     if ListView1.Selected = nil then
  463.       ShowMessage ('Nothing is selected' )
  464.     else
  465.     begin  // if ListView1.Selected <> nil
  466.       newname := Item.Caption;
  467.       if newname = '' then
  468.          badname := true
  469.       else
  470.       if RenameFile(currDirName+currFileName, currDirName+newname) then
  471.         currFileName := newname
  472.       else
  473.          badname := true;
  474.       // If File Renaming failed, restore old name
  475.       if badname then
  476.       begin
  477.         ListView1.ItemFocused.Caption := currFileName;
  478.         ShowMessage('Cannot rename ' + currDirName+currFileName +
  479.                    ' to ' + currDirName+newname );
  480.       end; // end if RenameFile fails
  481.     end;  // end if ListView1.Selected <> nil
  482.   end; // end if ItemEdited = true
  483. end;
  484.  
  485. end.
  486.