A class to manage your MRU list

Nutster 0 Tallied Votes 633 Views Share

Here is a class I wrote to manage the MRU in a VB.Net application am writing. I did not find a build-in class in Visual Studio or on-line that did not look like a pain to use, so I made my own. While I have some 30 years of programming experience, I am pretty new to the .Net languages, so it is quite likely that I missed something that was already available in the .Net libraries, as this is a fairly common functionality, especially with the kinds of applications I write.

Instructions for use are in the REM statements at the beginning of the class. In addition to an instance of the class, you will need to define a Collections.Specialized.StringCollection to store the actual information. This can then be stored in My.Settings or serialized into a file to be stored.

You also need to define a menu item (passed to New), typically under File, to be the anchor for the list. All the menu items will be on a child menu of this menu item.

The class has a sub-class that it uses for throwing exceptions: MRUException. It keeps track of multiple types of errors that each define a different Message for each type of error. Most of these indicate that the functions were called with invalid arguments (like passing Nothing for the StringCollection, etc.).

Any suggestions for improvements will be welcome. If there are any questions, please comment here and I will try to answer them. I hope others on DaniWeb find this class helpful.

Public Class clsMRU
    REM A class to manage the MRU (most recently used) list for a menu.  The records are stored in a StringCollection, which could be stored in My.Settings.
    REM This class is specifically designed for storing file paths, but it could be used for other types of entries as well.

    REM Usage:
    REM   Add at the top of the form code (inside the class definition, before the first function) which will have the MRU:  
    REM     Public WithEvents mruList As clsMRU
    REM
    REM   In Form_Load:
    REM     mruList = New clsMRU(Me, mnuMRU, stcEntries)    ' mnuMRU is the MenuItem that will have the MRU list as a descendant.  stcEntires is a System.Collections.Specialized.StringCollection that will be used to store the MRU entries.
    REM     mruList.MaxEntries = 6             ' This must be set.  Default is actually no entries.  Can be 1 to 36.
    REM     mruList.LoadEntries
    REM     <Optional properties to set before calling LoadEntries>
    REM         mruList.ShowPath = True/False  ' Show the full path on the menu OR show just the filename without the path (default).
    REM
    REM   When you add an entry to the list (such as after opening a file), call:
    REM     mruList.AddFile(strFilename, [Reload])
    REM     where: strFilename is the full path to the file to be stored in the MRU list.
    REM     and: Reload is a boolean to say whether the MRU menu list should be reloaded after adding the file to the list.  If Reload is not specified, the menu will be reloaded.
    REM     Reload is intended to be used to speed up adding multiple entries at once.  By setting Reload to false on all calls, expect the last one, the function just adds the entries to the list, but only updates the menu on the last call.
    REM
    REM   If you want to remove an entry from the list, call:
    REM     mruList.RemoveFile(strFilename, [Reload])
    REM     where: strFilename is the full path to the file to be removed from the list.
    REM     and: Reload is a boolean to say whether the MRU menu list should be reloaded after removing the file from the list.  If Reload is not specified, the menu is reloaded.
    REM
    REM   Create event handler to handle items being clicked.
    REM     Private Sub mruItem_Clicked(strFullPath as String) Handles mruList.Item_Clicked
    REM     
    REM     End Sub
    REM
    REM   In Form_Closed: (optional, depending on how the StringCollection is defined)
    REM     SaveEntries() - really just reorganizes and cleans up the list, so unused entries are removed from the StringCollection.
    REM     save stcEntries in persistant storage, like My.Settings or serialize to a file.
    REM
    REM   If there is a problem in a call, an MRUException is thrown.  This class has an internal type that defines exactly what type of error occured.
    REM     The Message property is customized for each sub-type of exception.

    Public Class MRUException
        Inherits Exception

        Public Enum MRUExceptionType
            REM No extra parameters
            NullForm                ' The form has not been defined.
            NullMenu                ' The anchoring menu item has not been defined.
            NullCollection          ' The StringCollection being used to store the MRU has not been defined.
            EmptyPath               ' The path given to AddFile is blank.
            UninitializedMaxEntries ' LoadEntries was called before MaxEntries was set.
            REM One integer parameter
            MaxEntriesOutOfRange    ' The given value to MaxEntries is too large.
        End Enum

        Private metType As MRUExceptionType
        Private intInvalidValue As Integer
        Private strBadPath As String

        Public Sub New(ByVal metReason As MRUExceptionType)
            metType = metReason
            intInvalidValue = 0
        End Sub

        Public Sub New(ByVal metReason As MRUExceptionType, ByVal intValue As Integer)
            metType = metReason
            intInvalidValue = intValue
            strBadPath = ""
        End Sub

        Public Overrides ReadOnly Property Message() As String
            Get
                Dim strMessage As New System.Text.StringBuilder

                Select Case metType
                    Case MRUExceptionType.EmptyPath
                        strMessage.Append("An empty path was passed to a function that required a full path.")
                    Case MRUExceptionType.UninitializedMaxEntries
                        strMessage.Append("MaxEntries data member has not been set prior to an operation that relies on this value.")
                    Case MRUExceptionType.MaxEntriesOutOfRange
                        strMessage.Append("MaxEntries can only be in the range of 1-36.  The value assigned (")
                        strMessage.Append(intInvalidValue)
                        strMessage.Append(") was outside this range.")
                    Case MRUExceptionType.NullForm
                        strMessage.Append("The form the MRU list is to appear on must be defined and allocated.")
                    Case MRUExceptionType.NullMenu
                        strMessage.Append("The menu item to anchor the MRU list to must be defined and allocated.")
                    Case MRUExceptionType.NullCollection
                        strMessage.Append("The StringCollection to store the entries must already be defined and allocated.")
                    Case Else
                        REM Not sure how we got here, but just in case.
                        strMessage.Append(MyBase.Message())
                End Select
                Return strMessage.ToString
            End Get
        End Property
    End Class

    Private bolShowPath As Boolean
    Private intMaxEntries As Integer    ' Actual allowable range is 1-36 (1-9, a-z)
    Private stcEntryCollection As System.Collections.Specialized.StringCollection
    Private mnuAnchor As ToolStripMenuItem   ' All the MRU entries will be children of this Anchor.

    Public Sub New(ByRef ParentForm As Form, ByRef Anchor As ToolStripMenuItem, ByRef Entries As System.Collections.Specialized.StringCollection)
        intMaxEntries = -1    ' MaxEntries will set this later.
        If Anchor Is Nothing Then
            Throw New MRUException(MRUException.MRUExceptionType.NullMenu)
        Else
            mnuAnchor = Anchor
        End If
        If Entries Is Nothing Then
            Throw New MRUException(MRUException.MRUExceptionType.NullCollection)
        Else
            stcEntryCollection = Entries
        End If
    End Sub

    Public Property MaxEntries() As Integer
        REM Sets or returns the maximum number of entries in the MRU list.
        Set(ByVal intNewMax As Integer)
            If intNewMax > 0 AndAlso intNewMax <= 36 Then
                intMaxEntries = intNewMax
            Else
                Throw New MRUException(MRUException.MRUExceptionType.MaxEntriesOutOfRange)
            End If
        End Set
        Get
            If intMaxEntries > 0 Then
                Return intMaxEntries
            Else
                Throw New MRUException(MRUException.MRUExceptionType.UninitializedMaxEntries)
            End If
        End Get
    End Property

    Public Property ShowPath() As Boolean
        REM Sets or returns whether the full path should be shown (true) or just the filename without the path (false, default)
        Set(ByVal bolValue As Boolean)
            bolShowPath = bolValue
        End Set
        Get
            Return bolShowPath
        End Get
    End Property

    Public Sub LoadEntries()
        REM Create the menu items based on the the filenames stored in the StringCollection
        Dim intPos As Integer, intLocation As Integer
        Dim chrTmp As Char
        Dim strSequence As String, strTmp As String

        If intMaxEntries < 0 Then
            Throw New MRUException(MRUException.MRUExceptionType.UninitializedMaxEntries)
        ElseIf mnuAnchor Is Nothing Then
            Throw New MRUException(MRUException.MRUExceptionType.NullMenu)
        ElseIf stcEntryCollection Is Nothing Then
            Throw New MRUException(MRUException.MRUExceptionType.NullCollection)
        End If
        If stcEntryCollection.Count = 0 Then
            strSequence = ""
        Else
            strSequence = Left(stcEntryCollection(0), intMaxEntries)
        End If
        mnuAnchor.DropDownItems.Clear()
        intPos = 1
        While intPos <= Len(strSequence)
            chrTmp = CChar(Mid(strSequence, intPos, 1))
            intLocation = Char2Index(chrTmp)
            REM Find the string for this position.
            If intLocation < 0 Then
                REM The character does not map to a position.  Kill the character.
                strSequence = Replace(strSequence, chrTmp, "")
            ElseIf intLocation > stcEntryCollection.Count Then
                REM The location is beyond the end of the of the string collection.  Kill this character from the list.
                strSequence = Replace(strSequence, chrTmp, "")
            ElseIf stcEntryCollection(intLocation) = "" Then
                REM The location is empty.  Kill this character from the list.
                strSequence = Replace(strSequence, chrTmp, "")
            Else
                If bolShowPath Then
                    strTmp = CStr(intPos) & " - " & stcEntryCollection(intLocation)
                Else
                    strTmp = CStr(intPos) & " - " & BaseName(stcEntryCollection(intLocation))
                End If
                If intPos < 10 Then strTmp = "&" & strTmp
                With mnuAnchor.DropDownItems.Add(strTmp, Nothing, New System.EventHandler(AddressOf Me.ItemClickHandler))
                    .Tag = stcEntryCollection(intLocation)
                End With
                intPos = intPos + 1 ' Move to the next character.  Not needed with the other branches because the string is getting shortened.
            End If
        End While
        If stcEntryCollection.Count < 1 Then
            stcEntryCollection.Add("")
        End If
        stcEntryCollection(0) = Left(strSequence, intMaxEntries)
    End Sub

    Public Sub AddFile(ByVal strFilename As String, Optional ByVal bolReload As Boolean = True)
        REM Add a given file to beginning of the MRU list.
        Dim intLocation As Integer
        Dim chrTmp As Char
        Dim strSequence As String

        strFilename = strFilename.Trim
        If strFilename = "" Then
            Throw New MRUException(MRUException.MRUExceptionType.EmptyPath)
        ElseIf stcEntryCollection Is Nothing Then
            Throw New MRUException(MRUException.MRUExceptionType.NullCollection)
        ElseIf intMaxEntries < 0 Then
            Throw New MRUException(MRUException.MRUExceptionType.UninitializedMaxEntries)
        End If
        If stcEntryCollection.Count = 0 Then
            strSequence = ""
            intLocation = -1
        Else
            strSequence = stcEntryCollection(0)
            intLocation = stcEntryCollection.IndexOf(strFilename)
        End If
        If intLocation < 0 Then
            REM Not in the list already.
            REM Find a location for it in the collection.
            For intLocation = 1 To stcEntryCollection.Count - 1
                chrTmp = Index2Char(intLocation)
                If strSequence.Contains(chrTmp) Then
                    REM Found this one.  Now is it blank?
                    If stcEntryCollection(Char2Index(chrTmp)) = "" Then
                        REM Found a place to put it.
                        strSequence = chrTmp & Replace(strSequence, chrTmp, "")
                        stcEntryCollection(intLocation) = strFilename
                    End If
                Else
                    REM Store it here.
                    strSequence = chrTmp & strSequence
                    stcEntryCollection(intLocation) = strFilename
                    Exit For
                End If
            Next
            If intLocation < stcEntryCollection.Count Then
                REM Already dealt with.
            ElseIf strSequence.Length < intMaxEntries Then
                REM Is there room to add it at the end?
                strSequence = Index2Char(stcEntryCollection.Count) & strSequence
                stcEntryCollection.Add(strFilename)
            Else
                REM Replace the last entry with this one and adjust the sequence.
                chrTmp = CChar(Mid(strSequence, intMaxEntries, 1))
                stcEntryCollection(Char2Index(chrTmp)) = strFilename
                strSequence = chrTmp & strSequence
            End If
        Else
            REM Found in the list at intLocation.
            REM Map the character for that location.
            chrTmp = Index2Char(intLocation)
            REM Move the character to the beginning of the sequence string.
            strSequence = chrTmp & Replace(strSequence, chrTmp, "")
        End If
        REM Write it back into the Collection
        stcEntryCollection(0) = Left(strSequence, intMaxEntries)
        If bolReload Then
            LoadEntries()
        End If
    End Sub

    Public Function RemoveFile(ByVal strFilename As String, Optional ByVal bolReload As Boolean = True) As Boolean
        REM Return True if the filename is removed from the MRU list.  Returns False if the filename was not found (usually not considered an error).
        Dim intLocation As Integer
        Dim chrTmp As Char

        For intLocation = 1 To stcEntryCollection.Count - 1
            If stcEntryCollection(intLocation) = strFilename Then
                chrTmp = Index2Char(intLocation)
                stcEntryCollection(0).Replace(chrTmp, "")
                If bolReload Then
                    LoadEntries()
                End If
                Return True
            End If
        Next
        Return False
    End Function

    Public Sub SaveEntries()
        REM Reorganize the StringCollection to remove unused entries in preparation for storage.
        Dim intPos As Integer, intIndex As Integer
        Dim chrTmp As Char
        Dim strSequence As String, strTmp As String

        strSequence = Left(stcEntryCollection(0), My.Settings.MRU_Size)
        intPos = 1
        Do While intPos <= strSequence.Length
            chrTmp = CChar(Mid(strSequence, intPos, 1))
            intIndex = Char2Index(chrTmp)
            If intPos < intIndex Then   ' If intPos = intIndex, no swap needed.  If intPos > intIndex, 
                REM Swap the filename to this location.
                strTmp = stcEntryCollection(intPos)
                stcEntryCollection(intPos) = stcEntryCollection(intIndex)
                stcEntryCollection(intIndex) = strTmp
                REM Swap the index characters.
                Mid(strSequence, intPos, 1) = Index2Char(intPos)
                strSequence.Replace(Index2Char(intPos), chrTmp)
            ElseIf intPos > intIndex Then
                REM This has already been dealt with.  Remove it.
                strSequence = Left(strSequence, intPos - 1) & Mid(strSequence, intPos + 1)
            End If
        Loop
        stcEntryCollection(0) = strSequence
        REM Trim off any extra strings from the collection
        Do While stcEntryCollection.Count > My.Settings.MRU_Size + 1
            stcEntryCollection.RemoveAt(stcEntryCollection.Count - 1)
        Loop
    End Sub

    Public Event Item_Clicked(ByVal strFullPath As String)

    Private Sub ItemClickHandler(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim tmiTmp As ToolStripMenuItem

        If TypeOf sender Is ToolStripMenuItem Then
            tmiTmp = CType(sender, ToolStripMenuItem)
            RaiseEvent Item_Clicked(CStr(tmiTmp.Tag))
        End If
    End Sub

    Private Shared Function Index2Char(ByVal intLocation As Integer) As Char
        REM Used for managing the internal indexing of the entries.
        Dim chrSequence As Char

        If intLocation < 1 Then
            chrSequence = ChrW(0)
        ElseIf intLocation < 11 Then
            chrSequence = ChrW(intLocation + 47)
        ElseIf intLocation < 37 Then
            chrSequence = ChrW(intLocation + 86)
        Else
            chrSequence = ChrW(0)
        End If
        Return chrSequence
    End Function

    Private Shared Function Char2Index(ByVal chrSequence As Char) As Integer
        REM Used for managing the internal indexing of the entries.
        Dim intLocation As Integer

        If chrSequence >= "0"c AndAlso chrSequence <= "9"c Then
            intLocation = AscW(chrSequence) - 47  ' "0" to maps to item 1.
        ElseIf chrSequence >= "a"c AndAlso chrSequence <= "z"c Then
            intLocation = AscW(chrSequence) - 86  ' "a" maps to 11, so 97-11 = 86
        Else
            intLocation = -1
        End If
        Return intLocation
    End Function
End Class
Gilu 0 Newbie Poster

A working example would've been nice ... I don't get it to work :(
mid and left needs visualbasic :(

Nutster 58 Newbie Poster

I have learned a lot about VB.Net in the last year and I see a few places where I can improve this, so I will edit and repost it, with a frmMain to exercise it.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.