Home > Articles

0672322773

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 forms—frmMain and frmView—and 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 35602—Key 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 Notepad—the 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

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020