Creating a Text Editor with Visual Basic

Introduction

Hello again! Today, I will show you how to make your own text editor in Visual Basic. We have a lot of work to do, so let’s get started.

Our Project

As you probably know, a text editor is a computer program with which you can enter text. You can ultimately save or open the file into or from the program itself.  Basically, what you can expect is the functionality behind opening and saving files, as well as loading a most recently used list of files. These file names appear in your file menu, an aid for quicker access.

Design

Start Visual Studio and create a new Windows Forms project. Once done, design your form to resemble Figure 1:

Design
Figure 1: Our design

Code

Let me start with the Form’s code, and then move to the other objects as we encounter them. Add the following variables to your form:

Private Const APP_NAME As String = "Editor"
Private m_DataDirty As Boolean
Private m_FileName As String
Private WithEvents m_MruList As MruList

App_Name is my application’s name, DataDirty determines if the opened file’s content has changed. Based on this, we will know that we need to save. FileName will be the name of the file that gets used. mruList is a separate class for the Most Recently Used items; I will explain this later.

Add the following code for the New menu item:

'start a new document
Private Sub mnuFileNew_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuFileNew.Click


   'Make sure the current data is safe.
   If Not DataSafe() Then Exit Sub

   Me.rchFile.Text = ""
   m_FileName = Nothing
   Me.Text = APP_NAME & " []"
   m_DataDirty = False

   'no point in saving a blank file
   Me.mnuFileSave.Enabled = False
   Me.mnuFileSaveAs.Enabled = False
End Sub

Here, I reference a function named DataSafe (which we will create now) and set up the Richtextbox and the rest of the screen. Add the DataSafe function now:

'Return true if it is safe to discard the current data.
Private Function DataSafe() As Boolean
   If Not m_DataDirty Then Return True

   Select Case MessageBox.Show("The data has been modified. _
      Do you want to save the changes?", _
      "Save Changes?", MessageBoxButtons.YesNoCancel)
         Case Windows.Forms.DialogResult.Cancel
            'the user is canceling the operation.
            'don't discard the changes.
            Return False
         Case Windows.Forms.DialogResult.No
            'the user wants to discard the changes
            Return True
         Case Windows.Forms.DialogResult.Yes
            'try to save the data
            SaveData(m_FileName)
            'see if the data was saved
            Return (Not m_DataDirty)
   End Select
End Function

Here I just made sure the content of the Rich Textbox has indeed changed, and depending on the displayed messagebox’s result, I either cancel, or save the information into a file. Notice how I keep setting the value of the DataDirty variable…

Now, add the Form_load event:

'Set the Dialog's initial directory.
Private Sub Form1_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
   Dim init_dir As String = Application.StartupPath
   If init_dir.EndsWith("\bin") Then init_dir = _
         init_dir.Substring(0, init_dir.Length - 4)
      dlgOpenFile.InitialDirectory = init_dir
      dlgSaveFile.InitialDirectory = init_dir

      m_MruList = New MruList(APP_NAME, MenuStrip1, 4)
End Sub

Inside Form_Load, you determine your application’s startup path. I made reference to the MruList class here as well, because it needs to load the recent items when loaded. I will explain the class a bit later.

Add the following sub procedure:

'mark the data as modified.
Private Sub rchFile_TextChanged(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles rchFile.TextChanged
   If Not m_DataDirty Then
      Me.Text = APP_NAME & "*[" & FileTitle(m_FileName) & "]"
      m_DataDirty = True
      mnuFileSave.Enabled = True
      mnuFileSaveAs.Enabled = True
   End If
End Sub

This fires when the content of the RichTextBox has changed. It indicates that the file has not yet been saved if you are creating a new file.

Now, add the LoadData and SaveData subs:

'load a data file
Private Sub LoadData(ByVal file_name As String)
   Dim stream_reader As IO.StreamReader
   Try
      'load the file
      stream_reader = New IO.StreamReader(file_name)
      Dim txt As String = stream_reader.ReadToEnd
      rchFile.Text = txt

      'save the file name and title
      m_FileName = file_name
      Me.Text = APP_NAME & " [" & FileTitle(m_FileName) & "]"
      m_DataDirty = False

      'update the MRU list
      m_MruList.Add(m_FileName)
      mnuFileSave.Enabled = False
      mnuFileSaveAs.Enabled = False
   Catch ex As Exception
      MessageBox.Show("Error loading file" & file_name & _
         vbCrLf & ex.Message, "Load Error", _
         MessageBoxButtons.OK, MessageBoxIcon.Error)
      m_MruList.Remove(file_name)
   Finally
      If Not (stream_reader Is Nothing) Then stream_reader.Close()
   End Try
End Sub

'save the file.
Private Sub SaveData(ByVal file_name As String)
   Dim stream_writer As IO.StreamWriter
   Try
      'save the file
      stream_writer = New IO.StreamWriter(file_name)
      stream_writer.Write(Me.rchFile.Text)
      stream_writer.Close()

      'save the file name and title.
      m_FileName = file_name
      Me.Text = APP_NAME & " [" & FileTitle(file_name) & "]"
      m_DataDirty = False

      'Update the MRU List
      m_MruList.Add(m_FileName)
      Me.mnuFileSave.Enabled = False
      Me.mnuFileSaveAs.Enabled = False
   Catch ex As Exception
      MessageBox.Show("Error saving file " & _
         file_name & vbCrLf & ex.Message, _
         "Save Error", MessageBoxButtons.OK, _
         MessageBoxIcon.Error)
   End Try
End Sub

Whether you are saving or opening a file, almost the same happens. I made use of a StreamReader object to read the content of the file into the Richtextbox, and I used a StreamWriter to save the content of the Rich textbox into a file. If it is the first time you’ve worked with files in this way, I suggest you read up on the StreamReader and the StreamWriter objects.

Add the following events:

'open a file selected from the MRU List
Private Sub m_MruList_OpenFile(ByVal file_name As String) _
      Handles m_MruList.OpenFile
   LoadData(file_name)
End Sub
' save the file.
Private Sub mnuFileSave_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles mnuFileSave.Click
   If m_FileName Is Nothing Then
      mnuFileSaveAs_Click(sender, e)
   Else
      SaveData(m_FileName)
   End If
End Sub
'save the file with a new name.
Private Sub mnuFileSaveAs_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles mnuFileSaveAs.Click
   If dlgSaveFile.ShowDialog = Windows.Forms.DialogResult.OK Then
      SaveData(dlgSaveFile.FileName)
   End If
End Sub
'Open a file
Private Sub mnuFileOpen_Click_1(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles mnuFileOpen.Click
   'make sure the current data is safe
   If Not DataSafe() Then Exit Sub
   If dlgOpenFile.ShowDialog = Windows.Forms.DialogResult.OK Then
      LoadData(dlgOpenFile.FileName)
   End If
End Sub

Here you just call the subs we created earlier from the associated menu item.

Add the remaining events for the form:

'close the application
Private Sub mnuFileExit_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles mnuFileExit.Click
   Me.Close()
End Sub
Private Sub Form1_FormClosing(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.FormClosingEventArgs) _
      Handles Me.FormClosing
   e.Cancel = Not DataSafe()
End Sub

This simply exits the application properly.

Add a new class called MruList by clicking Project, Add Class. Add the following variables into it:

Private m_ApplicationName As String
Private m_FileMenu As MenuStrip
Private m_NumEntries As Integer
Private m_FileNames As Collection
Private m_MenuItems As Collection

Add the rest of the class:

Public Event OpenFile(ByVal file_name As String)

Public Sub New(ByVal application_name As String, _
      ByVal file_menu As MenuStrip, _
      ByVal num_entries As Integer)
   m_ApplicationName = application_name
   m_FileMenu = file_menu
   m_NumEntries = num_entries
   m_FileNames = New Collection
   m_MenuItems = New Collection

   'load saved file names from the Registry.
   LoadMruList()

   'Display the Mru list
   DisplayMruList()
End Sub


'Load previously saved file names from the Registry.
Private Sub LoadMruList()
   Dim file_name As String
   For i As Integer = 1 To m_NumEntries
      'get the next file name and tile
      file_name = GetSetting(m_ApplicationName, "MruList", _
         "FileName" & i, "")

      'see if we got anything
      If file_name.Length > 0 Then
         'save this file name
         m_FileNames.add(file_name, file_name)
      End If
   Next i
End Sub
'save the MRU list into the Registry.
Private Sub SaveMruList()
   'remove previous entries
   If GetSetting(m_ApplicationName, "MruList", "FileName1", _
      "").Length > 0 Then
      DeleteSetting(m_ApplicationName, "MruList")
   End If

   'make the new entries
   For i As Integer = 1 To m_FileNames.count
      SaveSetting(m_ApplicationName, "MruList", _
         "FileName" & i, m_FileNames(i).ToString)
   Next i
End Sub
'MRU menu item envent handler
Private Sub MruItem_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs)
   Dim mnu As MenuItem = DirectCast(sender, MenuItem)

   'find the menu item that raised this event
   For i As Integer = 1 To m_FileNames.Count
      'see if this is the item (add 1 for the seperator)
      If m_MenuItems(i + 1) Is mnu Then
         'this is the item. raise the OpenFile
         'event for its file name
         RaiseEvent OpenFile(m_FileNames(i).ToString)
         Exit For
      End If
   Next i
End Sub
'display the MRU list
Private Sub DisplayMruList()
   'remove old menu items from the file menu
   For Each mnu As ToolStripItem In m_MenuItems
      m_FileMenu.Items.Remove(mnu)
   Next mnu
   m_MenuItems = New Collection

   'see if we have any file names
   If m_FileNames.Count > 0 Then
      'make the separator.
      Dim mnu As ToolStripItem
      mnu.Text = "-"
      m_MenuItems.Add(mnu)
      m_FileMenu.Items.Add(mnu)

      'make the other menu items.
      For i As Integer = 1 To m_FileNames.Count
         mnu.Text = "&" & i & " " & _
            FileTitle(m_FileNames(i).ToString)

         ' mnu = New ToolStripItem("&" & i & " " & _
         ' FileTitle(m_FileNames(i).ToString), _
         ' New System.EventHandler(AddressOf MruItem_Click))
         m_MenuItems.Add(mnu)
         m_FileMenu.Items.Add(mnu)
      Next i
   End If

End Sub
'add a file to the MRU list
Public Sub Add(ByVal file_name As String)
   'remove this file from the MRU lis
   'if it is present.
   Dim i As Integer = FileNameIndex(file_name)
   If i > 0 Then m_FileNames.Remove(i)

   'add the item to the begining of the list
   If m_FileNames.Count > 0 Then
      m_FileNames.add(file_name, file_name, _
         m_FileNames.Item(1))
   Else
      m_FileNames.Add(file_name, file_name)
   End If

   'if the list is too long, remove the last item
   If m_FileNames.Count > m_NumEntries Then
       m_FileNames.Remove(m_NumEntries + 1)
   End If

   'display the list
   DisplayMruList()

   'save the updated list
   SaveMruList()
End Sub
'return the index of this file in the list
Private Function FileNameIndex(ByVal file_name As String) _
      As Integer
   For i As Integer = 1 To m_FileNames.Count
      If m_FileNames(i).ToString = file_name Then Return i
   Next i
   Return 0
End Function
'remove a file from the MRU list
Public Sub Remove(ByVal file_name As String)
   'see if the file is present
   Dim i As Integer = FileNameIndex(file_name)
   If i > 0 Then
      'remove the file.
      m_FileNames.Remove(i)

      'display the list
      DisplayMruList()

      'save the updated List
      SaveMruList()
   End If
End Sub

Almost done!

Add a module to your application by clicking Project, Add Module, and enter this function inside:

'Return the file's name without its path
Public Function FileTitle(ByVal file_name As String) As String
   If file_name = Nothing Then Return ""
   Dim pos As Integer = file_name.LastIndexOf("\")
   If pos >= 0 Then Return file_name.Substring(pos + 1)
   Return file_name
End Function

This function simply returns the File name without its full path.

Conclusion

I hope you have enjoyed today’s article. Until next time, cheers!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read