The Dirty Bit


When you try to quit a file, any civilized editor will warn you if your file has changed and will ask whether you want to save it. To do this, the editor saves the dirty or clean status in a flag (often called a bit, even when it isn’t). The first time the user alters the file, the editor sets the dirty bit. When the user saves the file, the editor clears the dirty bit. If the user wants to change files or quit, the editor checks the dirty bit and, if it is set, posts a warning.


In the TextBox control, the DataChanged property controls the dirty bit. You won’t discover this in any Visual Basic documentation. All references to the DataChanged property claim that DataChanged is for bound controls. Nevertheless, it also works fine for unbound text boxes. Unfortunately, this “bug”
has been fixed in the RichTextBox control. No matter. The dirty bit of a Rich-Edit or Edit control is actually controlled by the EM_GETMODIFY and the EM_SETMODIFY messages. It’s easy to implement a DirtyBit property by using SendMessage. This design separates testing for unbound data changes from testing for bound changes. Here’s the code for DirtyBit:

Property Get DirtyBit() As Boolean
DirtyBit = SendMessage(txt.hWnd, EM_GETMODIFY, ByVal 0&, ByVal 0&)
End Property

Property Let DirtyBit(ByVal fDirtyBitA As Boolean)
Call SendMessage(txt.hWnd, EM_SETMODIFY, _
ByVal -CLng(fDirtyBitA), ByVal 0&)
StatusEvent
End Property

Edwina uses the DirtyBit property to identify when a file needs to be saved before the user quits or changes files. Edwina also displays the current save state in a panel on the status bar. The bar is updated whenever a PositionChange event occurs. That’s part of why the DirtyBit Property Let calls the StatusEvent procedure, but I’ll get to the details later.


You can also change the status by clicking the status bar. For example, to throw away your latest changes, click the SAV item before you exit; Edwina won’t prompt you to resave. The Click event procedure (in statEdit_PanelClick) toggles the DirtyBit property and then changes the panel to match the state as shown here:

edit.DirtyBit = Not edit.DirtyBit
.Enabled = edit.DirtyBit

The dirty bit is managed at several different levels by different players. Edwina (and any other editors you create with XEditor) will maintain the correct status for the DirtyBit property as long as you load and save files through the Load, Open, Save, and Save As commands that I’ll discuss later. Of course, nothing prevents you from grabbing the contents out of the Text or TextRTF property and saving it in your own way. If you do this, make sure you manage the DirtyBit property yourself.


When a client editor such as Edwina terminates, opens a new file, or takes some other action that might destroy a dirty file, it has two choices. It can test the DirtyBit property itself and take appropriate action; or it can call the DirtyDialog method to test DirtyBit and, if necessary, query the user about the appropriate action to take. DirtyDialog is so named because it tests the dirty bit and also because it is a quick-and-dirty hack based on the MsgBox function. You might want to call a form-based dialog box in a more sophisticated editor, but Dirty-Dialog gives you an easy model of how such a form might work:

Public Function DirtyDialog() As Boolean
Dim s As String
DirtyDialog = True ' Assume success
' Done if no dirty file to save
If Not DirtyBit Then Exit Function
' Prompt for action if dirty file
s = "File not saved: " & FileName & sCrLf & Save now?"
Select Case MsgBox(s, vbYesNoCancel)
Case vbYes
' Save old file
FileSave
Case vbCancel
' User wants to terminate file change
DirtyDialog = False
Case vbNo
' Do nothing if user wants to throw away changes
End Select
End Function

Edwina calls the DirtyDialog method from the mnuFileNew_Click, mnuFile-Open_Click, and Form_QueryUnload event procedures. It doesn’t need to call DirtyDialog from mnuFileExit_Click because QueryUnload will catch that case. Here are two examples, a positive test and a negative test:

Private Sub mnuFileNew_Click()
If edit.DirtyDialog Then edit.FileNew
dropFile.Text = edit.filename
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
If Not edit.DirtyDialog Then Cancel = True
End Sub