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:
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!