- Chapter 3: Manipulating Files
- The Windows CE TreeView Control
- Creating the PocketExplorer Utility
- Summary
Creating the PocketExplorer Utility
The best way to familiarize yourself with the FileSystem and File controls is to dive in and begin using them for a meaningful purpose. To do that, we're going to write a small utility application called PocketExplorer that's somewhat of a mix between the standard Windows Explorer and Notepad.
PocketExplorer will allow you to navigate through all the folders on your PocketPC as well as view the files within the folders and their attributes. It will also allow you to open and edit any file, though only in text mode.
Setting Up the Project
PocketExplorer uses two formsfrmMain and frmViewand one module, modMain.
Listings 3.1 and 3.2 contain all the heading information for frmMain and frmView, respectively. For clarity, I have included listings only for properties that I modified from their defaults. Figure 3.3 shows what my project looks like.
Listing 3.1 Heading Information from frmMain.ebf
Begin VB.Form frmMain Caption = "PocketExplorer" ClientHeight = 4005 ClientLeft = 60 ClientTop = 840 ClientWidth = 3630 ShowOK = -1 `True Begin FILECTLCtl.FileSystem fsMain Left = 2220 Top = 180 _cx = 2200 _cy = 1400 End Begin MSCETREEVIEWLibCtl.TreeViewCtl tvwFolders Height = 1755 Left = 30 Top = 0 Width = 3555 LabelEdit = 1 LineStyle = 1 PathSeparator = "/" Style = 6 End Begin VBCE.Label lblFiles Height = 255 Left = 2160 Top = 3720 Width = 495 Caption = "Files:" Alignment = 1 End Begin VBCE.CommandButton cmdDelete Height = 315 Left = 1380 Top = 3660 Width = 615 Caption = "Del" End Begin VBCE.CommandButton cmdNew Height = 315 Left = 720 Top = 3660 Width = 615 Caption = "New" End Begin VBCE.ComboBox cboFilter Height = 300 Left = 2700 Top = 3660 Width = 855 Text = "" End Begin VBCE.CommandButton cmdEdit Height = 315 Left = 60 Top = 3660 Width = 615 Caption = "Edit" Enabled = 0 `False End Begin VBCE.ListBox lstFiles Height = 1785 Left = 30 Top = 1800 Width = 3555 End End
NOTE
Listings 3.1 and 3.2 don't show the entire headings, but show all control settings that I've changed from their defaults.
Listing 3.2 Heading Information from frmView.ebf
Begin VB.Form frmView Caption = "FileName" ClientHeight = 4005 ClientLeft = 60 ClientTop = 345 ClientWidth = 3630 ShowOK = -1 `True Begin VBCE.CommandButton cmdSave Height = 375 Left = 2400 Top = 3600 Width = 1035 Caption = "Save" End Begin VBCE.TextBox txtView Height = 3495 Left = 30 Top = 30 Width = 3555 Text = "" MultiLine = -1 `True ScrollBars = 3 End End
Figure 3.3 The final Form layouts for PocketExplorer.
Getting Directory Contents
One primary function of PocketExplorer is to display directory contents, so you need to write a workhorse function that can be called to do so. You want the function to read a directory, and then populate the TreeView with any subfolders in the directory as well as populate the textbox with all the directory files, provided they meet your filter conditions.
To make the code a little easier to reuse in other projects, it's a good idea to make it a function that's entirely independent of this project. That means you should have no direct references to any of the forms' controls, so what you need is a function that will take all this information as input parameters.
Call the ReadDirectory function and put it in the code module modMain.
Public Sub ReadDirectory(Path As String, _ fsFileSystem As FILECTLCtl.FileSystem, _ tvwTreeView As MSCETREEVIEWLibCtl.TreeViewCtl, _ nodParentNode As MSCETREEVIEWLibCtl.Node, _ lstListBox As ListBox, _ strFilter As String)
When giving the parameter types, it's important to provide the libraries they come from (that is, FILECTLClt.FileSystem instead of just FileSystem) because without them, you won't get IntelliSense for the object inside the function.
Next, you populate the controls passed in, and the simplest way to do that is one at a time. Listing 3.3 extracts all the subdirectories from the directory passed in as the Path parameter.
Listing 3.3 Extracting All Subdirectories from a Directory
Dim strDir As String Dim strFile As String On Error Resume Next ` Ensure path is slash terminated If Right(Path, 1) <> "\" Then Path = Path & "\" ` ----- First get all directories ----- ` Priming Read strDir = fsFileSystem.Dir(Path & "*", fsAttrDirectory) ` Iterate through directory Do While strDir <> "" ` add directories to TreeView tvwTreeView.Nodes.Add nodParentNode.Key, tvwChild, strDir, strDir ` if the node already exists, ignore the error If Err.Number = 35602 Then ` ignore duplicate node adds - the add will fail anyway ElseIf Err.Number <> 0 Then MsgBox Err.Description, vbExclamation, "Error adding node!" End If strDir = fsFileSystem.Dir Loop
The code in Listing 3.3 first allows for the incoming path to either be backslash-terminated or not by simply checking for it and adding it if necessary.
Next, it does a priming read with the FileSystem control's Dir() method. The first time you call Dir(), you must specify the path you want to read as well as any filtering options you want enforced. It returns a string of the first item that meets those conditions or an empty string if nothing meeting your conditions was found. In this priming read, you send in your path and "*" and specify fsAttrDirectory, which brings back all directories.
All subsequent calls to Dir() require no parameters and simply fetch the next item matching your criteria from the same directory until no more are found, at which point it returns an empty string. In Listing 3.3, this is handled with a simple Do...While loop.
If you call ReadDirectory with an already populated path, it will try to add duplicate nodes to the TreeView, which will cause an error (specifically, error 35602Key already exists in collection).
You can handle this error by either keeping track of which directories you have already read or by checking to see if the node you are trying to add to already has children, but then you also have to determine if anything had been added, removed, or changed. You also can clear all the node's children and repopulate it, but this seems inefficient, because most often there won't be any differences. I've opted to look specifically for just error 35602 and ignore it. The error prevents duplicates and anything new will be added.
Next, you need to get all the files in the directory and populate the passed-in ListBox. Use the same logic of a priming read with the Dir() function followed by a Do...While Loop. This time in the priming read, you will send in a filter condition and specify fsAttrNormal, which will return files with normal attributes (see Listing 3.4).
Listing 3.4 Extracting a Filtered File List from a Directory
` ----- Next get all files ----- ` Clear ListBox lstListBox.Clear ` Priming Read strFile = fsFileSystem.Dir(Path & strFilter, fsAttrNormal) ` Iterate through directory Do While strFile <> "" `Add Files to ListBox lstListBox.AddItem strFile strFile = fsFileSystem.Dir Loop
Notice that because you clear the ListBox every time, you don't need to trap an error.
Before you can call ReadDirectory, though, you need to get all the information required by the function's parameter list. First up is the path.
The TreeView control works nicely for helping to store the current path with which the user is working. The control enables you to define a path delimiter, so by using a backslash delimiter and setting each node's text to that of a directory, you can almost get the exact string you need just by reading a node's FullPath property. The only problem is that it appends your root node's text to the beginning of the path.
To correct this, you simply need to remove the root node's text. I've done this by using the Replace function, and because you will need to get the path from multiple places in your code, I've wrapped this all in its own small function. Because the code is specific to your TreeView, I've placed the code on frmMain's code page:
Private Function GetPath() As String GetPath = Replace(tvwFolders.SelectedItem.FullPath, "My Device", "") End Function
The next four parameters to ReadDirectory are simple. Just pass in references to controls on frmMain.
The final parameter is the filter, which you can use to display all files by passing in "*.*" or to limit the files displayed by passing a filter. For example, passing in "*.txt" would show only files ending in .txt.
You can get this information from the cboFilter ComboBox on frmMain.
Because the code will call ReadDirectory from multiple places, I've also wrapped a call to it in another simple function and placed it on frmMain's code page:
Private Sub RefreshForm() ` read the selected directory and populate the form ReadDirectory GetPath(), fsMain, tvwFolders, _ tvwFolders.SelectedItem, lstFiles, cboFilter.Text End Sub
Although this isn't absolutely necessary, it's easier for coding because now you can simply call RefreshForm any time you want to read the currently selected directory.
Displaying the Directory Tree
Now that you have created functions to get the contents of any directory, let's display the directory tree in the TreeView control.
Before adding anything to the TreeView control, you must first initialize it by creating and adding the root node. Every node in the TreeView will be a child of this node. A good example of this is the My Computer root node in the standard Windows Explorer TreeView on your desktop machine.
Following this model, call your root node My Device. To add the node to the TreeView, use the Add method, passing in both the Key and Text you want to display. Leave both the Relative and Relationship parameters blank because the root has no siblings or parent. The Add method also returns a reference to the created node, which you can then use to directly call its properties and methods. In the Form_Load event for frmMain, add the following code:
Dim nodRoot As MSCETREEVIEWLibCtl.Node ` Initialize the TreeView tvwFolders.Nodes.Clear Set nodRoot = tvwFolders.Nodes.Add(, , "Root", "My Device")
The reason you are holding a reference to the root node isn't immediately apparent, but you will want to call a couple of methods of the root on startup, so you might as well put the code in now to hold it.
Form_Load is a good time to do any other initialization of the application as well, so put a couple of filter options into the cboFilter ComboBox and set its default as well:
` Initialize the Filter combobox cboFilter.AddItem "*.*" cboFilter.AddItem "*.txt" ` This will fire the ComboBox Change event, ` which will populate the first set of directories cboFilter.Text = "*.*" ` Clean up Set nodRoot = Nothing
This adds a filter for ".txt" files and all files. Feel free to add whatever else you would like.
Setting the default filter to "*.*" causes the Change event for cboFilter to fire, just as the user changing it does. You need to add some code to be sure that these changes refilter the ListBox. Likewise, when the user taps the ComboBox to change it, you also want to handle that event. Add the code in Listing 3.5 to frmMain's code page.
Listing 3.5 Allowing Users to Filter the Files They Want to View
Private Sub cboFilter_Change() ` Refresh the form RefreshForm ` Disable the Edit button cmdEdit.Enabled = False End Sub Private Sub cboFilter_Click() Dim strFilter As String ` The ComboBox Text get set AFTER the Click event, ` so we must get it manually strFilter = cboFilter.List(cboFilter.ListIndex) ` Read the selected directory and populate the form ReadDirectory GetPath(), fsMain, tvwFolders, _ tvwFolders.SelecteItem, lstFiles, strFilter ` Disable the Edit button cmdEdit.Enabled = False End Sub
NOTE
A ComboBox's events in eVB fire in a different order than they do in VB. This is extremely important, and knowing it will save you some headaches trying to debug apparent logic error in the future. The Text property gets set after the Click event fires, so checking the Text property in the Click event handler will actually return the text that the user changed it from, not the new selection. It does, however set the ListIndex property to the new value before the event, so to get the text of the user's selection, you must get the List value at the current ListIndex.
Notice that the code in Listing 3.5 disables the Edit button in both events because the file list is getting repopulated and there will no longer be a file selected. When the user selects a file, you need to re-enable it, so add code to the ListBox's Click event:
Private Sub lstFiles_Click() ` If a file is selected, enable the View button If lstFiles.ListIndex >= 0 Then cmdEdit.Enabled = True Else cmdEdit.Enabled = False End If End Sub
Now the only thing left to do is to populate the node that the user selects by using the RefreshForm method. Handle it in the TreeView's NodeClick event like this:
Private Sub tvwFolders_NodeClick(ByVal Index As Long) ` Repopulate the form RefreshForm ` Disable the View button cmdEdit.Enabled = False End Sub
Finally, a nice feature would be to display all the files and subdirectories in the root directory on startup. The easiest way to do this is to manually select the root node in Form_Load, which will fire the NodeClick event. Then it's just a matter of setting the node's Expanded property to True.
The modified Form_Load event now looks like the code in Listing 3.6.
Listing 3.6 Form_Load Modified to Select and Expand the Root Node
Private Sub Form_Load() Dim nodRoot As MSCETREEVIEWLibCtl.Node ` Initialize the TreeView tvwFolders.Nodes.Clear Set nodRoot = tvwFolders.Nodes.Add(, , "Root", "My Device") ` Select the root node nodRoot.Selected = True ` Initialize the Filter combobox cboFilter.AddItem "*.*" cboFilter.AddItem "*.txt" ` This will fire the ComboBox Change event, ` which will populate the first set of directories cboFilter.Text = "*.*" `Expand the root node nodRoot.Expanded = True ` Clean up Set nodRoot = Nothing End Sub
Running the application at this point gives you pretty good navigation functionality. Figure 3.4 shows PocketExplorer running.
Figure 3.4 PocketExplorer's TreeView and File list in action.
Getting a File's Attributes
The next feature to add to PocketExplorer displays the attributes of a specific file when it is double-clicked in the file list. To add this functionality, use the FileSystem control's Attr(), FileLength(), and FileDateTime() methods.
First, write a function that returns a string representation of any file's attributes. Again, to make it more generic, and therefore more reusable, remove any references to the project's controls and instead pass them in. Listing 3.7 shows the full code for GetFileAttributes().
Listing 3.7 Getting a File's Attributes Given Its Path
Public Function GetFileAttributes(Path As String, _ fsFileSystem As FILECTLCtl.FileSystem) _ As String Dim FileAttr As FileAttrEnum Dim lFileLength As Long Dim dtFileDate As Date Dim strAttributes As String Dim strFileName As String On Error Resume Next ` Get the attributes. A file can have many FileAttr = fsFileSystem.GetAttr(Path) ` Check if it's a system file If FileAttr And fsAttrSystem Then strAttributes = strAttributes & "System File" & vbCrLf End If If FileAttr And fsAttrReadOnly Then strAttributes = strAttributes & "Read-Only" & vbCrLf End If If FileAttr And fsAttrArchive Then strAttributes = strAttributes & "Archive File" & vbCrLf End If ` Get file's date dtFileDate = fsFileSystem.FileDateTime(Path) strAttributes = strAttributes & dtFileDate & vbCrLf ` Get file's length lFileLength = fsFileSystem.FileLen(Path) ` format the size a little nicer ` Since Format() isn't supported, we'll use integer/float division ` as a workaround. If lFileLength < 1024 Then strAttributes = strAttributes & CStr(lFileLength) & " bytes" ElseIf lFileLength < 1048576 Then ` Go out 1 decimal place strAttributes = strAttributes & _ CStr((lFileLength \ 102.4) / 10) & "k bytes" Else ` Go out 2 decimal places strAttributes = strAttributes & _ CStr((lFileLength \ 10485.76) / 100) & "M bytes" End If ` Return the attributes GetFileAttributes = strAttributes End Function
The function is pretty straightforward. First, it determines all the Attr() values for the file, such as Hidden, Read-Only, or System. It then calls FileDateTime to get the file's last modification date and FileLen to get the file's length in bytes.
Because eVB doesn't support the Format() function, and I thought it a bit ugly to show the exact number of bytes for large files, I've implemented a small workaround that formats numbers greater than 1,024 to either kilobytes or megabytes, depending on size.
Figure 3.5 shows the attributes in a MessageBox from the list's DblClick event handler (see Listing 3.8).
Listing 3.8 Displaying a File's Properties on a Double Tap
Private Sub lstFiles_DblClick() Dim strPath As String Dim strAttributes As String Dim strFileName As String ` Make sure the user double-clicked a valid item If lstFiles.ListIndex < 0 Then Exit Sub ` Get the filename strFileName = lstFiles.List(lstFiles.ListIndex) ` Get the file's path strPath = GetPath() & "\" & strFileName ` Show the file's attributes strAttributes = GetFileAttributes(strPath, fsMain) ` Display the info to the user MsgBox strAttributes, vbInformation + vbOKOnly, strFileName End Sub
Figure 3.5 Displaying a file's attributes.
Opening, Editing, and Writing Text Files
The next features you will add to PocketExplorer are similar to those you get on a desktop PC through Notepadthe capability to create, open, and edit text files. The functions you need to implement these features are found in the File control.
Creating the File Object Without Leaking Memory
This is a good opportunity to demonstrate a few of eVB's features. Rather than drop a File control onto frmMain, you can use CreateObject().
As I mentioned in Chapter 2, CreateObject also creates a memory leak. Every time you call it, you lose a chunk of memory, and if you call it often enough, you probably force users to reset their devices.
One way to avoid multiple calls to CreateObject is simply to call it the first time you need the object, and then keep the object around for the life of your application. Rather than create a new object every time you need one, just reuse the original. This increases the overall memory your application requires because you can never release the object, but minimizing the impact of CreateObject's memory leak is more important.
NOTE
This is a trade-off you need to think about for any project that uses CreateObject. If the object is large and rarely used, you may want to destroy and re-create it only when used to conserve memory. Be aware, however, that each time you call CreateObject, you'll lose memory until your application is fully shut down.
Another frustrating aspect of using CreateObject in eVB is that the library and class names exposed to the Object Browser and IntelliSense aren't always the same as the names used in the object's type library. The File control falls into this category. If we look in the Object Browser or use IntelliSense, it seems that we would want to create a FILECTLCtl.File object, but using this as a parameter to CreateObject will result in error 429, ActiveX Component can't create object. This is because the library name isn't actually FILECTLCtl, but just FILECTL. (Notice the omission of the last three letters, Ctl.)
TIP
As a rule of thumb, if you get an error 429 when trying to create an object that ends in Ctl, Lib, or some combination of them, try creating the object without those letters:
CreateObject("FILECTL.File")
Another option for finding the name is to open the library's typelibrary or typelibrary cache. These are usually files with the same name as the DLL, but with a .tlb or .oca extension. In this case, it's MSCEFILE.oca and it can be found in the \Program Files\Microsoft eMbedded Tools\EVB\devctrls directory of your development PC. Opening the file with Notepad displays largely unprintable garbage, but all the library and class names should be readable. Be careful not to modify the file's contents.
CAUTION
.oca and .tlb files are binary files used by the system. Modifying them in any way can render their associated controls unusable.
With all that said, let's create a global File object variable and a function that both creates it and returns a reference to it. This way you can just call this function whenever you need your File control and let the function handle whether it actually needs to call CreateObject(). First, in the General Declarations section of modMain, add the variable declaration:
Private m_FileObject As FILECTLCtl.File
And then add the function shown in Listing 3.9.
Listing 3.9 Minimizing the Impact of the CreateObject Memory Leak
Public Function GetFileObject() As FILECTLCtl.File On Error Resume Next ` If we haven't created the File object yet, do so If IsEmpty(m_FileObject) Then ` Create a File control Set m_FileObject = CreateObject("FILECTL.File") ` Ensure the object was created successfully If Err.Number <> 0 Then MsgBox "Failed to create File object." & vbCrLf & _ "Ensure MSCEFile.dll has been installed and registered.", _ vbCritical, "Error" Exit Function End If End If ` Return our global File object Set GetFileObject = m_FileObject End Function
Deleting a File
Deleting a file is probably the simplest file function, so it is covered first. When the user selects a file in PocketExplorer and taps the Delete button, you call the Kill function of your FileSystem object. As a courtesy, it's always a good idea to give a warning message to ensure that the user really wants to delete the file. Listing 3.10 shows the event handler for the Delete button, and Figure 3.6 shows the Delete dialog.
Listing 3.10 Event Handler for the Delete Button in frmMain.cbf
Private Sub cmdDelete_Click() Dim strPath As String Dim strFileName As String On Error Resume Next ` Make sure a file is selected If lstFiles.ListIndex < 0 Then Exit Sub ` Get the path to the file strFileName = lstFiles.List(lstFiles.ListIndex) strPath = GetPath() & "\" & strFileName ` Make sure the user wants to delete the file If MsgBox("Delete file?: " & vbCrLf & strFileName, _ vbYesNo, "Delete File?") = vbNo Then Exit Sub ` Delete the file fsMain.Kill strPath ` Check for success If Err.Number <> 0 Then MsgBox "Unable to delete file. It may be in use or protected", _ vbExclamation, "Error" End If ` Repopulate the form RefreshForm End Sub
Reading a File's Contents
Reading the contents of a sequential text file is a straightforward operation similar to using the fso in Win32. Essentially, you open the file, loop through the contents until you hit EOF, and then close the file. We'll use the GetFileTextContents function in Listing 3.11 to return the contents of any text file as a String.
Figure 3.6 Asking before you delete a file is a general courtesy.
Listing 3.11 Extracting the Contents of a Text File as a String
Public Function GetFileTextContents(Path As String) As String Dim filFile As FILECTLCtl.File Dim strInput As String On Error Resume Next ` Get our application File object Set filFile = GetFileObject() ` Open the File filFile.Open Path, fsModeInput, fsAccessRead ` Make sure the call to Open was successful If Err.Number <> 0 Then MsgBox "Open Method failed." & vbCrLf & _ "The file could not be read.", _ vbCritical, "Error" End If ` Loop through file, filling our input buffer Do While Not filFile.EOF strInput = strInput & filFile.Input(1) Loop ` Close the file filFile.Close ` Release the File Object Set filFile = Nothing ` Return the text GetFileTextContents = strInput End Function
When the user taps the Edit button, use the function to read the file's contents and fill the multiline TextBox on frmView (see Figure 3.7).
Figure 3.7 Editing a file with PocketExplorer.
Just like Notepad, you don't hold the file open while it's being viewed, so you need to store the filename somewhere so you know where to save any changes the user makes. A simple way would be to just set a global variable in either frmView or modMain, but let's take this opportunity to implement a workaround to the fact that eVB doesn't support user-defined properties.
Just like a property, you need to have a member variable in which to keep the value and implement both a Set and Get method to set or get that member variable's value. In frmView's General Declarations section, add the following member variable:
Private m_strPath As String
And then add the following code:
` Since Properties aren't supported, we'll use a Set/Get ` method pair as a workaround Public Sub SetPath(NewPath As String) ` Store the full path to the current file m_strPath = NewPath End Sub Public Function GetPath() As String ` Retrieve the full path to the current file GetPath = m_strPath End Function
You don't get a single name like you would for an actual property, but these two functions otherwise perform just like a property.
As another courtesy to users, keep track of whether they've saved their changes to the file so you can prompt them before they exit. To do this, keep a dirty flag in frmView, turning it off when they save and on when they make changes. When you first load the file, you need to clear the flag. Add the following to the General Declarations section of frmView:
Private m_bDirty As Boolean
And then add a function to clear the flag:
Public Sub ClearDirty() ` We must clear this flag every time we show the form m_bDirty = False End Sub
While you're at it, you might as well add code that turns the flag back on. It's safe to assume that any stylus tap in the txtView TextBox will be a change to the file, so we'll turn the flag on in that event handler like so:
Private Sub txtView_KeyPress(ByVal KeyAscii As Integer) m_bDirty = True End Sub
Now let's look at the implementation of the event handler for the Edit button (see Listing 3.12). Get the path to the selected file, set your new property to that value, get the file's contents, fill frmView's TextBox, clear the dirty flag, and then lastly, hide frmMain.
Listing 3.12 Opening and Displaying a Text File for User Modification
Private Sub cmdEdit_Click() Dim strPath As String Dim strContents As String Dim strFileName As String ` Build the path to the file strPath = GetPath() strFileName = lstFiles.List(lstFiles.ListIndex) strPath = strPath & "\" & strFileName ` Set frmView's Path "property" frmView.SetPath strPath ` Get the file contents as text strContents = GetFileTextContents(strPath) ` Set frmView's caption frmView.Caption = strFileName ` Show frmView frmView.Show ` Populate the View textbox frmView.txtView.Text = strContents ` Set the form's dirty flag frmView.ClearDirty ` Hide frmMain frmMain.Hide End Sub
Creating a New File
All that's left to do now is to add the capability to create a new file and save the changes the user makes to existing files. Both tasks can be handled with a single function.
Rather than determine what changes a user actually made to a file, it's much simpler to just overwrite the entire file. What you need then is a function that accepts the path to your file and your file's text as parameters (a new file will just have no text passed in). The function then needs to either create the file if it doesn't exist or overwrite it if it does, and write the passed-in text to it.
Again, you can write a generic function that can be reused in other projects (see Listing 3.13). A generic name such as CreateFile also facilitates reuse.
Listing 3.13 Creating and Populating a Text File
Public Sub CreateFile(Path As String, Contents As String) ` This function performs double duty. We use it to create ` new files as well as overwrite old files Dim filFile As FILECTLCtl.File On Error Resume Next ` Get our application File object Set filFile = GetFileObject() ` Open the file, if it doesn't exist, it will be created ` fsModeOutput means overwrite filFile.Open Path, fsModeOutput, fsAccessWrite, fsLockWrite ` Make sure the call to Open was successful If Err.Number <> 0 Then MsgBox "Open Method failed." & vbCrLf & _ "The file could not be created.", _ vbCritical, "Error" End If ` Write our data to the file if it's not empty If Contents <> "" Then filFile.LinePrint Contents End If ` Close the file filFile.Close ` Release the File Object Set filFile = Nothing End Sub
Now you need to add a call to CreateFile from frmMain's New button. Prompt for a new filename by using the InputBox function. If the user doesn't enter a name or taps Cancel, InputBox returns an empty string.
Also add a default .txt extension to the filename if the user didn't provide one (see Listing 3.14).
Listing 3.14 Getting a Filename from the User and Checking for an Extension
Private Sub cmdNew_Click() Dim strFileName As String Dim strPath As String ` Get a filename from the user - omit white spaces strFileName = Trim(InputBox("Filename to create:", "New File")) ` If the return was blank, exit If strFileName = "" Then Exit Sub ` Check for an extension by checking for a period If InStr(strFileName, ".") = 0 Then ` If an extension wasn't provided, default to .txt strFileName = strFileName & ".txt" End If ` Get the currentpath and append our filename strPath = GetPath() & "\" & strFileName ` Create the file CreateFile strPath, "" ` Repopulate the form RefreshForm End Sub
Finally, you need to add a call from frmView's Save button:
Private Sub cmdSave_Click() ` Save the file CreateFile GetPath(), txtView.Text ` Clear the dirty flag m_bDirty = False End Sub
Adding the Final Touches
That pretty much completes PocketExplorer. You do need to add a few finishing touches to make it a bit more user friendly, though.
The PocketPC standard for exiting most forms is to tap the OK button in the upper-right corner of the form's title bar. The OK button has its own event handler to make this simple.
When users finish editing a file and tap OK, you should check to see whether they've saved all their changes and allow them to do so if they haven't. Do this with a simple MsgBox call (see Figure 3.8).
Figure 3.8 The Save Changes dialog.
Listing 3.15 shows frmView's OKClick event handler.
Listing 3.15 Asking to Save Changes Before Closing the Application
Private Sub Form_OKClick() ` check to see if there are unsaved changes If m_bDirty Then ` Prompt to save changes If MsgBox("Save changes before exit?", vbYesNo, "Save?") = vbYes Then ` Save changes CreateFile GetPath(), txtView.Text End If End If frmMain.Show frmView.Hide End Sub
When the user exits the application by tapping OK on frmMain, you want to be sure to clean up after yourself, releasing any objects created. In the case of PocketExplorer, you should release the File control you created. Because the File control is held privately in modMain, you will need an access method to release it:
Public Sub ReleaseFileObject() Set m_FileObject = Nothing End Sub
And finally, call your release method from frmMain's OKClick event handler and end the application:
Private Sub Form_OKClick() ` Clean up our File object ReleaseFileObject ` End app App.End End Sub