índice de vb.net Utilidad para colorear código de vb y generar el código HTML

Actualizado: 11/Abr/2001 (11/Oct/2004)



Nota del 15/Nov/2002: Sigue este link para ver las últimas modificaciones (en código para VB6)

15/Nov/2002: Sigue este link para ver las últimas actualizaciones

11/Oct/2004: La última revisión a la utilidad de colorear código (versión .NET 1.1)


A continuación te muestro el código completo de la utilidad para colorear el código de vb.Net, (también vale para c# y otros ficheros de Visual Basic), y generar el código HTML para pegar en un fichero HTM.

La utilidad permite seleccionar el fichero a "colorear" y guardar el código generado en un fichero HTML.
También permite generar el código HTML del trozo de código que queramos, (el contenido en ListBox; en el ListBox se puede pegar lo que tengamos copiado en el portapapeles), y una vez "coloreado" el código, se muestra en dicho ListBox, además de pegarlo en el portapapeles.

Estas son capturas de la utilidad en ejecución:


Solapa de colorear el código.


Solapa de las palabras clave y selección del lenguaje a usar.

 

Espero que te pueda ser de utilidad... para mi lo es, ya que me ahorra un montón de trabajo y queda mejor ver el código coloreado que sin colorear.
Sigue este link si quieres ver esta misma utilidad en la versión adaptada a VB6.

 

Te relaciono a continuación las otras cosillas que podrás ver en el código de ejemplo:
Al final de esta página encontrás un link con el código completo.


¡Espero que disfrutes de lo que hay en esta página!

Nos vemos.
Guillermo


Implemetar interfaces en nuestras clases:

Ya sabes que en VB5 y VB6, la única forma de "simular" herencia era usando Implements, en esta nueva versión de Visual Basic, ésta palabra funciona "casi" igual, con la ventaja de que ahora se pueden implementar cualquier interface que exista en cualquier librería (o assembly).

En la colección de cPalabras, he añadido un método para clasificar el contenido de la colección, y aunque he usado el método Sort del ArrayList usado, el código no funcionaba ya que necesitaba implementar la interface IComparable, que tiene el método CompareTo que devuelve un valor según el resultado de comparar dos elementos de dicha colección. Ésta interface no es necesario implementarla en colecciones que contienen tipos básicos, pero en este caso, el tipo de datos almacenado en la colección es cPalabra, por tanto, esa clase debe implementar esa interface.

Veamos parte del código, el resto del código puedes verlo más abajo.


        Public Sub Sort()
            ' Clasificar el contenido de la colección               (30/Mar/01)
            ' Esto necesita que la clase usada en la colección
            ' implemente el interface IComparable, cosa que se hace en cID
            m_col.Sort()
            '

El lugar en el que he implementado esa interface ha sido en la clase cID, (que es heredada por cPalabra), ya que el orden de la clasificación se hace por el ID de cada objeto.


    Public MustInherits Class cID
        ' Implenta la interfaz para hacer comparaciones             (30/Mar/01)
        Implements IComparable
        '
        Public Function CompareTo(ByVal [object] As Object) As Integer Implements System.IComparable.CompareTo
            If Me.ID < CType([object], cID).ID Then
                Return -1
            ElseIf CType([object], cID).ID = Me.ID Then
                Return 0
            ElseIf Me.ID > CType([object], cID).ID Then
                Return 1
            End If
        End Function
        '
        Private sID As String
        Private bYaEstoy As Boolean
        '
        Public Property ID() As String
            Get
                Return sID
            End Get
            Set(ByVal Value As String)
                If Not bYaEstoy Then
                    bYaEstoy = True
                    sID = Value
                End If
            End Set
        End Property
    End Class

Volver arriba


Copiar un array en otro, dejando un elemento libre al principio:

Hay ocasiones en las que necesitamos insertar un elemento en un array.
Existe un método en todos los arrays que es: CopyTo, el cual nos permite indicar el array de destino y a partir de que posición queremos que se copie el contenido del array original.
En este caso, queremos dejar un hueco al principio, por tanto le indicamos que la copia la haga a partir de la segunda posición, (recuerda que todos los arrays en vb.Net empiezan por el índice 0, por tanto la posición 1, indicará que es la segunda posición...

Fíjate que el array que se pasa a la función se hace por referencia, es decir, cualquier cambio que se haga en este método, se reflejará en el array que se usó en la llamada para insertar el nuevo elemento; también se devuelve el array, de esta forma se puede usar esta función de dos formas, usando el valor devuelto y también modificando directamente el array pasado como parámetro. Aunque el efecto "colateral" es que, si se usa el valor devuelto, también se modifica el parámetro... pero... en fin, si se sabe que esto ocurre... tampoco es tan grave... je!


        ' Insertar la cadena indicada en el array
        Public Function InsertAtStart(ByRef StrArray() As String, ByVal sLine As String) As String()
            ' Insertar la cadena indicada al principio del array
            Dim j As Integer
            Dim tmpArray() As String    ' Array temporal
            '
            j = StrArray.Length
            ReDim tmpArray(j + 1)
            StrArray.CopyTo(tmpArray, 1)
            tmpArray(0) = sLine
            ' Copiar el array en el indicado, por si quiere usar como Sub
            StrArray = tmpArray
            ' Devolver el array generado
            Return tmpArray
        End Function

Volver arriba


Copiar un array en otro, dejando un elemento libre al final:

Para insertar un elemento al final del array, haremos lo mismo que hemos visto en el punto anterior, pero en esta ocasión la copia se hace desde el primer elemento (el cero), lo único es que el array de destino debe tener un elemento más, que será el que se usará para asignar el nuevo elemento a insertar.

Al igual que en el procedimiento mostrado anteriormente, esta función devuelve un array con los nuevos valores y también se modifica el array que se pasa como parámetro.


        Public Function InsertAtEnd(ByRef StrArray() As String, ByVal sLine As String) As String()
            ' Insertar la cadena indicada al final del array
            Dim j As Integer
            Dim tmpArray() As String
            '
            j = StrArray.Length
            ReDim tmpArray(j + 1)
            StrArray.CopyTo(tmpArray, 0)
            tmpArray(j) = sLine
            ' Copiar el array en el indicado, por si quiere usar como Sub
            StrArray = tmpArray
            ' Devolver el array generado
            Return tmpArray
        End Function

Volver arriba


Cómo recorrer los elementos de una clase/colección heredada:

La clase cPalabrasFile hereda la clase cPalabras y le añade acceso a datos, tanto para leer las palabras de dicha colección de un fichero, como para guardar dichas palabras en un fichero. Para guardar las palabras, hay que recorrer cada una de las palabras de dicha colección para poder guardarlas, (en este caso sólo se guarda el contenido de la propiedad ID), para ello se recorre de esta forma:
For Each tPalabra In Me

La nueva clase añade dos nuevos métodos: ReadFromFile y SaveToFile, además de sobrecargar el procedimiento New que permite crear un nuevo objeto colección. La nueva colección permite crear la clase vacia o bien indicar el nombre del fichero del que se leerán las palabras y leerlas.
Veamos el código usado:

Para leer el contenido de un fichero y asignarlo a la colección:

        Public Sub ReadFromFile(ByVal FileName As String)
            ' Leer el fichero de palabras
            Dim tPalabra As cPalabra
            Dim sr As System.IO.StreamReader = New System.IO.StreamReader(FileName, System.Text.Encoding.Default)
            Do While Not sr.Peek = -1
                Dim s As String = sr.ReadLine.Trim()
                If s <> "" Then
                    If Me.Exists(s) = False Then
                        tPalabra = New cPalabra()
                        tPalabra.ID = s
                        Me.Add(tPalabra)
                    End If
                End If
            Loop
            sr.Close()
            '
        End Sub

Para guardar el contenido de la colección en un fichero:

        Public Sub SaveToFile(ByVal FileName As String)
            ' Guardar el contenido de mPalabras en el fichero
            Dim tPalabra As cPalabra
            ' Borrarlo si ya existe
            If System.IO.File.Exists(FileName) Then
                System.IO.File.Delete(FileName)
            End If
            ' Guardar en formato ANSI
            Dim sw As New System.IO.StreamWriter(System.IO.File.OpenWrite(FileName), System.Text.Encoding.Default)
            For Each tPalabra In Me
                sw.WriteLine(tPalabra.ID)
            Next
            sw.Close()
        End Sub

Las dos nuevas formas de crear un objeto de este tipo: leyendo el contenido de un fichero y otro normal, como el original de la clase heredada, pero que hay que implementar para que esta clase pueda usar las dos formas de creación.
Lo que no hay que hacer es llamar al método Inicializar de la clase base, ya que esa llamada se hace de forma automáticamente al crear la clase base, lo mismo ocurre al finalizar, las colecciones se destruyen en el método Finalize de cPalabras.

        ' Al crear la instancia de la colección se puede indicar
        ' el fichero de dónde se leerán las palabras.
        Public Overloads Sub New(ByVal FileName As String)
            ' Crear la clase leyendo el contenido de un fichero
            MyBase.New()
            ReadFromFile(FileName)
        End Sub
        '
        Public Overloads Sub New()
            MyBase.New()
        End Sub
        '
        Protected Overrides Sub Finalize()
            ' Las colecciones se destruyen en la clase base (cPalabras)
            MyBase.Finalize()
        End Sub

Volver arriba


El código del proyecto de colorear el código y convertirlo en HTML (HTMCodeColor):

 

Links a los distintos ficheros del código:

 

El código de la aplicación HTMLCodeColor (HTMLCodeColor.zip 22.5KB)
Recuerda que es para la Beta1 de Visual Studio.NET

Volver arriba


Código del formulario:


'------------------------------------------------------------------------------
' Procesar un fichero de código                                     (28/Mar/01)
' y generar el código HTML coloreado para pegar en una página.
'
' Se pueden procesar ficheros de vb.Net y c#
'
' ©Guillermo 'guille' Som, 2001
'------------------------------------------------------------------------------
Option Strict On
Option Compare Text
'

Public Class fHTMColorCode
    Inherits Form

    Private m_lstCode As Guille.Clases.gsListBox
    '
    Private mPalabras As New Guille.Clases.cPalabrasFile()
    Private mFile As New Guille.Clases.cFileToArray()
    '
    Private mPalabrasModificadas As Boolean ' Si se modifican las palabras
    Private sFileTo As String               ' Fichero de destino
    '
    ' Array para almacenar cada línea del fichero de origen
    Private sFileFrom() As String
    '
    ' Variable para procesar cada palabra
    Private LineaCompleta As String
    '
    ' Array para contener los botones de examinar                   (02/Abr/01)
    Private abtnLenguaje As ArrayList
    Private Enum eLenguaje
        esVB '= 0
        esCS '= 1
        esHTM '= 2
    End Enum
    '
    ' Constantes para los tags a añadir
    '
    ' Color verde para los comentarios.
    Const sTagFontGreen As String = "<font color = #008000>"
    ' Color azul de las palabras reservadas.
    Const sTagFontBlue As String = "<font color = #0000FF>"
    ' Color cian para las cadenas de texto, no es estándard, pero queda bien.
    Const sTagFontText As String = "<font color = #408080>"
    Const sEndFontTag As String = "</font>"
    ' Líneas a añadir al código
    Const sTagFontTipo As String = "<pre><font face=" & ControlChars.Quote & "Courier New" & ControlChars.Quote & " size=2>"
    Const sEndFontTipo As String = "</font></pre>" '& ControlChars.CrLf
    ' Constantes de algunos caracteres especiales:                  (31/Mar/01)
    ' Nota: no uso los caracteres que se pondrán, ya que de otra forma,
    '       si este código se convierte, no mostraría el código correcto.
    Const sTagQuote As String = "quot;"     ' comillas dobles
    Const sTagLT As String = "lt;"          ' signo menor que
    Const sTagGT As String = "gt;"          ' signo mayor que
    '
    '
#Region " Windows Form Designer generated code "

    '
    Public Sub New()
        MyBase.New()

        fHTMColorCode() = Me

        'This call is required by the Win Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call
        '
        ' Asignar los botones de examinar al array
        abtnLenguaje = New ArrayList()
        With abtnLenguaje
            .Add(btnExaminarVB())
            .Add(btnExaminarCS())
            .Add(btnExaminarHTM())
        End With
        '
        txtFileFrom.Text = ""
        txtFileTo.Text = ""
        txtWord.Text = ""
        lstWords.Items.Clear()
        '
        ' Estas propiedades hay que asignarlas en el listbox normal
        lstCode.SelectionMode = SelectionMode.MultiExtended
        lstCode.HorizontalScrollbar = True
        ' Asignar el listbox que contendrá el código
        m_lstCode = New Guille.Clases.gsListBox(lstCode())
        m_lstCode.HorizontalScrollbar = True
        m_lstCode.SelectionMode = SelectionMode.MultiExtended
        m_lstCode.Items.Clear()
        '
        chkFP.Checked = CBool(GetSetting("HTMColorCode", "Ficheros", "Cargar al iniciar", "0"))
        Dim FileName As String = GetSetting("HTMColorCode", "Ficheros", "Palabras", "")
        '
        If chkFP.Checked And File.Exists(FileName) Then
            ' Leer el fichero de palabras
            mPalabras.Clear()
            mPalabras.ReadFromFile(FileName)
            ' Añadir al ListBox
            Palabras2List()
        End If
        '
        ' Deshabilitar los valores de HTML
        rbHTM.Enabled = False
        txtHTM.Enabled = False
        btnExaminarHTM.Enabled = False
        '
        rbVB.Checked = CBool(GetSetting("HTMColorCode", "Ficheros", "rbVB", "1"))
        rbCS.Checked = CBool(GetSetting("HTMColorCode", "Ficheros", "rbCS", "0"))
        rbHTM.Checked = CBool(GetSetting("HTMColorCode", "Ficheros", "rbHTM", "0"))
        '
        txtVB.Text = GetSetting("HTMColorCode", "Ficheros", "vb", "vbdotnet.txt")
        txtCS.Text = GetSetting("HTMColorCode", "Ficheros", "cs", "csharp.txt")
        txtHTM.Text = GetSetting("HTMColorCode", "Ficheros", "HTM", "html.txt")
        '
        ' Hacer que se vean los controles de cada Tab
        ' no se porqué, pero algunas veces no se ven los que están
        ' en la solapa de palabras...
        Dim tObj As Control
        tabPage1.Visible = True
        For Each tObj In tabPage1.Controls
            tObj.Visible = True
        Next
        '
        tabPage2.Visible = True
        For Each tObj In tabPage2.Controls
            tObj.Visible = True
        Next
        '
        For Each tObj In groupBox1.Controls
            tObj.Visible = True
        Next
    End Sub
    '
    '
    '
    Private Function CadaPalabra(Optional ByVal sLinea As String = "") As String
        ' Desglosar la línea en palabras
        ' Si se indica el parámetro, se devolverá la primera palabra
        ' si no se indica el parámetro se devolverán las siguientes.
        ' Si no hay más palabras, devuelve una cadena vacia.
        '
        ' Estos signos se considerarán separadores de palabras
        Const sSep As String = " .,;:()<>[]{}'/*" & ControlChars.Quote & ControlChars.Tab
        Dim i, j, k As Integer
        Dim s, c, sID As String
        '
        If sLinea <> "" Then
            LineaCompleta = sLinea
        End If
        If LineaCompleta = "" Then
            Return ""
        End If
        s = LineaCompleta
        ' Desglosar en palabras
        sID = ""
        j = s.Length - 1
        For i = 0 To j
            c = s.Substring(i, 1)
            If c = "" Then
                k = 0
            Else
                k = InStr(sSep, c)
            End If
            If k = 0 And i = j Then
                k = 1
                sID &= c
            End If
            If k > 0 Or i = j Then
                ' Se ha encontrado una palabra
                If i < j Then
                    If i = 0 Then
                        LineaCompleta = s.Substring(i + 1)
                    Else
                        LineaCompleta = s.Substring(i)
                    End If
                Else
                    If LineaCompleta = c Then
                        LineaCompleta = ""
                    ElseIf LineaCompleta <> sID Then
                        LineaCompleta = s.Substring(i)
                    Else
                        LineaCompleta = ""
                    End If
                End If
                If sID = "" Then
                    ' Comprobar si es 
                    ' y cambiarlo por el tag correspondiente
                    If c = "<" Then
                        c = "&" & sTagLT
                    ElseIf c = ">" Then
                        c = "&" & sTagGT
                    End If
                    sID = c
                End If
                Exit For
            Else
                sID &= c
            End If
        Next
        Return sID
    End Function
    '
    Private Sub ProcesarFichero(ByRef saCodigo() As String)
        ' Procesar el contenido del array y convertirlo a HTM
        Dim i, j As Integer
        Dim s, s2, sID As String
        Dim HayComillas As Boolean = False
        Dim HayRem As Boolean = False
        '
        Dim HayMultipleRem As Boolean = False
        Dim k As Integer
        '
        If mPalabras.Count = 0 Then
            MessageBox.Show("¡ATENCION! debes seleccionar el fichero de palabras clave.")
            cmdOpen.Select()
            Exit Sub
        End If
        '
        lblStatus.Text = " Procesando código..."
        lblStatus.Refresh()
        '
        j = saCodigo.Length - 1
        For i = 0 To j
            s = saCodigo(i)
            s2 = Trim(s)
            ' Si es una línea vacia
            If s2 = "" Then
                saCodigo(i) = s
            ElseIf s2.Substring(0, 1) = "'" Then
                ' Si es una línea con comentarios
                saCodigo(i) = sTagFontGreen & s & sEndFontTag
            Else
                ' Tomar la primera palabra
                s2 = s
                sID = CadaPalabra(s2)
                s = ""
                ' Mientras no sea una cadena vacía
                Do While sID <> ""
                    If sID = ControlChars.Quote Then
                        ' Convertirlo en el tag de comillas         (31/Mar/01)
                        sID = "&" & sTagQuote
                        If HayComillas Then
                            If chkTextColor.Checked = True Then
                                s = s & sID & sEndFontTag
                            Else
                                s = s & sID
                            End If
                            HayComillas = False
                        Else
                            HayComillas = True
                            If chkTextColor.Checked = True Then
                                s = s & sTagFontText & sID
                            Else
                                s = s & sID
                            End If
                        End If
                    ElseIf HayComillas = True Or HayRem = True Then
                        ' No interpretar lo que haya entre comillas (31/Mar/01)
                        ' Ni lo que esté en un comentario           (03/Abr/01)
                        s = s & sID
                    ElseIf rbVB.Checked = True And (sID = "'" And HayRem = False) Then
                        s = s & sTagFontGreen & sID
                        HayRem = True
                    ElseIf mPalabras.Exists(sID) And HayComillas = False Then
                        ' Si está en la colección de palabras clave
                        s = s & sTagFontBlue & sID & sEndFontTag
                    Else
                        s = s & sID
                    End If
                    sID = CadaPalabra()
                Loop
                If HayComillas Then
                    If chkTextColor.Checked = True Then
                        s = s & ControlChars.Quote & sEndFontTag
                    Else
                        s = s & ControlChars.Quote
                    End If
                    HayComillas = False
                End If
                '
                ' Si se está procesando un fichero de C (c#, C/C++)
                If rbCS.Checked Then
                    ' Comprobar si hay comentarios de c# o C/C++
                    '
                    ' Comprobar si hay un comentario múltiple
                    k = s.IndexOf("/*")
                    If k > -1 Then
                        HayMultipleRem = True
                        s = s.Substring(0, k) & sTagFontGreen & s.Substring(k)
                    End If
                    ' Comprobar si hay un final de comentario mútiple
                    k = s.IndexOf("*/")
                    If k > -1 Then
                        HayMultipleRem = False
                        s = s.Substring(0, k) & sEndFontTag & s.Substring(k)
                    End If
                    ' Comprobar si hay un comentario de línea completa
                    k = s.IndexOf("//")
                    If k > -1 Then
                        s = s.Substring(0, k) & sTagFontGreen & s.Substring(k) & sEndFontTag
                    End If
                    '
                    If HayMultipleRem Then
                        s = sTagFontGreen & s & sEndFontTag
                    End If
                    '
                End If
                '
                If HayRem Then
                    s = s & sEndFontTag
                    HayRem = False
                End If
                '
                saCodigo(i) = s
            End If
        Next
        ' Añadir los Tags del tipo de letra
        mFile.InsertAtStart(saCodigo, sTagFontTipo)
        mFile.InsertAtEnd(saCodigo, sEndFontTipo)
        '
        lblStatus.Text = " Código convertido."
        lblStatus.Refresh()
    End Sub
    '
    Private Sub cmdCreate_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdCreate.Click
        ' Procesar el fichero
        '
        Try
            ' Comprobar si ya está procesado, para leerlo de nuevo
            If sFileFrom(0).Substring(0, 5) = "<pre>" Then
                sFileFrom = mFile.StringArrayFromFile(txtFileFrom.Text)
            End If
        Catch
            '
        End Try
        ProcesarFichero(sFileFrom)
        ' Guardar el contenido en el fichero indicado (sFileTo)
        mFile.WriteToFile(sFileTo, sFileFrom)
    End Sub
    '
    Private Sub cmdBrowseTo_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdBrowseTo.Click
        ' Seleccionar el fichero de destino
        With SFD()
            .Filter = "Fichero HTML|*.htm;*.html|Todos los ficheros (*.*)|*.*"
            If .ShowDialog = DialogResult.OK Then
                txtFileTo.Text = .FileName
                ' Este será el fichero en el que se guardará
                sFileTo = .FileName
            End If
        End With
    End Sub
    '
    Private Sub cmdBrowseFrom_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdBrowseFrom.Click
        ' Seleccionar el fichero de origen
        With OFD()
            .Filter = "Ficheros .NET|*.vb;*.cs|Visual Basic|*.vb;*.bas;*.frm;*.cls;*.ctl|Ficheros de c# y C/C++|*.cs;*.c;*.h;*.cpp;*.hpp|Ficheros HTML|*.ht*|Todos los ficheros (*.*)|*.*"
            '
            If rbVB.Checked Then
                .FilterIndex = 2
            ElseIf rbCS.Checked Then
                .FilterIndex = 3
            ElseIf rbHTM.Checked Then
                .FilterIndex = 4
            End If
            '
            If .ShowDialog = DialogResult.OK Then
                txtFileFrom.Text = .FileName
                ' Leer el fichero y guardarlo en memoria
                sFileFrom = mFile.StringArrayFromFile(.FileName)
                '
                m_lstCode.Lines = sFileFrom
            End If
        End With
    End Sub
    '
    Private Sub cmdOpen_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdOpen.Click
        ' Leer el fichero con las palabras
        With OFD()
            ' Asignar el fichero según el lenguaje seleccionado
            If rbVB.Checked Then
                .FileName = txtVB.Text
            ElseIf rbCS.Checked Then
                .FileName = txtCS.Text
            ElseIf rbHTM.Checked Then
                .FileName = txtHTM.Text
            End If
            '
            .Filter = "Ficheros de texto (*.txt)|*.txt|Todos los ficheros (*.*)|*.*"
            If .ShowDialog = DialogResult.OK Then
                ' Leer el fichero de palabras
                mPalabras.Clear()
                mPalabras.ReadFromFile(.FileName)
                '
                ' Añadir al ListBox
                Palabras2List()
            End If
        End With
    End Sub
    '
    Private Sub cmdSave_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdSave.Click
        ' Guardar las palabras en el fichero indicado
        With SFD()
            .Filter = "Ficheros de texto (*.txt)|*.txt|Todos los ficheros (*.*)|*.*"
            If .ShowDialog = DialogResult.OK Then
                '
                ' Clasificar las palabras                           (30/Mar/01)
                mPalabras.Sort()
                ' Guardar el contenido de mPalabras en el fichero
                mPalabras.SaveToFile(.FileName)
                '
                mPalabrasModificadas = False
            End If
        End With
    End Sub
    '
    Private Sub lstWords_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs) Handles lstWords.KeyDown
        If e.KeyCode = Keys.Delete Then
            ' Si se pulsa la tecla Del, borrar los elementos seleccionados
            cmdDel_Click(sender, Nothing)
        End If
    End Sub
    '
    Private Sub cmdDel_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdDel.Click
        ' Eliminar los elementos seleccionados del ListBox
        Dim i, j As Integer
        Dim sID As String
        Dim n As Integer = lstWords.SelectedIndex
        '
        ' Recorrer todos los elementos seleccionados
        For i = lstWords.SelectedIndices.Count - 1 To 0 Step -1
            ' Necesitamos el índice
            j = lstWords.SelectedIndices.Item(i)
            ' Obtener el ID de la colección de palabras
            sID = lstWords.Items(j).ToString
            mPalabras.Remove(sID)
            ' borrar este índice del ListBox
            lstWords.Items.RemoveAt(j)
        Next
        mPalabrasModificadas = True
        '
        ' Seleccionar el índice que antes estaba seleccionado
        ' comprobar el error, por si ya no está
        If n > lstWords.Items.Count - 1 Then
            n = lstWords.Items.Count - 1
        End If
        Try
            lstWords.SelectedIndex = n
        Catch
            'lstWords.SelectedIndex = 0
        End Try
    End Sub
    '
    Private Sub cmdAdd_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdAdd.Click
        ' Añadir la palabra al ListBox, si es que no está
        Dim sID As String = Me.txtWord.Text
        If mPalabras.Exists(sID) = False Then
            Dim tPalabra As New Guille.Clases.cPalabra()
            tPalabra.ID = sID
            mPalabras.Add(tPalabra)
            lstWords.Items.Add(sID)
            mPalabrasModificadas = True
        End If
    End Sub
    '
    Private Sub cmdExit_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdExit.Click
        Me.Close()
    End Sub
    '
    Private Sub fHTMColorCode_Closing(ByVal sender As Object, ByVal e As CancelEventArgs) Handles fHTMColorCode.Closing
        ' Cuando se vaya a cerrar, comprobar si hay que guardar las palabras
        If mPalabrasModificadas = True Then
            ' Preguntar si se quiere guardar
            If MessageBox.Show("Se ha modificado la lista de palabras" & Chr(13) & "¿Quieres guardarlas?", "Palabras modificadas", MessageBoxButtons.YesNo) = DialogResult.Yes Then
                cmdSave_Click(sender, Nothing)
            End If
        End If
        ' Guardar el fichero a usar para las palabras
        SaveSetting("HTMColorCode", "Ficheros", "Cargar al iniciar", chkFP.Checked.ToString)
        '
        If rbVB.Checked = True Then
            SaveSetting("HTMColorCode", "Ficheros", "Palabras", txtVB.Text)
        ElseIf rbCS.Checked = True Then
            SaveSetting("HTMColorCode", "Ficheros", "Palabras", txtCS.Text)
        ElseIf rbHTM.Checked = True Then
            SaveSetting("HTMColorCode", "Ficheros", "Palabras", txtHTM.Text)
        End If
        SaveSetting("HTMColorCode", "Ficheros", "rbVB", rbVB.Checked.ToString)
        SaveSetting("HTMColorCode", "Ficheros", "rbCS", rbCS.Checked.ToString)
        SaveSetting("HTMColorCode", "Ficheros", "rbHTM", rbHTM.Checked.ToString)
        '
        SaveSetting("HTMColorCode", "Ficheros", "vb", txtVB.Text)
        SaveSetting("HTMColorCode", "Ficheros", "cs", txtCS.Text)
        SaveSetting("HTMColorCode", "Ficheros", "HTM", txtHTM.Text)
        '
    End Sub
    '
    Private Sub Palabras2List()
        Dim tPalabra As Guille.Clases.cPalabra
        ' Añadir al ListBox
        With lstWords.Items
            .Clear()
            For Each tPalabra In mPalabras
                .Add(tPalabra.ID)
            Next
        End With
        mPalabrasModificadas = False
    End Sub