Etiqueta: c#

  • gsColorearNET

    Nota del 26-oct-2020 20:45:
    Esta página estaba originalmente publicada en elguille.info y la he pasado al blog para poder editarla más fácilmente ya que lo que publico en elGuille.info lo hago con un editor de texto normal y corriente… y ¡es un rollo!
    Por supuesto, esta misma página aparecerá en elGuille.info, así que… no se notará mucho que no está realmente ubicada allí.

     

    Esta es una biblioteca (DLL) compilada para .NET Standard 2.0 y así poder usarla en cualquier plataforma que lo acepte, incluidos los proyectos de .NET Framework 4.6.1 a 4.8 y de .NET Core 2.0 a .NET 5.0, entre otros…
    Si quieres ver dónde se pueden usar las DLL compiladas con .NET Standard 2.0 mira este enlace: .NET Standard

    Últimas actualizaciones

    Nota del 24/Oct/2020 (v1.0.0.14)
    He actualizado tanto el paquete de NuGet como el código en GitHub.
    También la utilidad de gsColorearCodigo para que use esta nueva versión.

    Los cambios principales son para no eliminar las líneas en blanco que haya en el código.
    Esto era porque la función Split original de Visual Basic no elimina las líneas en blanco.
    Además de usar vbCr en lugar de vbCrLf al examinar cada línea y crear nuevas separaciones, ya que al añadir vbCrLf añadía una línea extra en blanco.

    Aunque últimamente se me ha dado el problema que no todos los ficheros tienen el mismo tipo de retorno de carro.
    Por tanto he tenido que comprobar antes de hacer el Split qué tipo de retorno de carro tiene: vbCrLf o vbCr o vbLf.

    Nota del 16/Sep/2020 (v1.0.0.7)
    He actualizado tanto el paquete de NuGet como el código en GitHub.
    También la utilidad de gsColorearCodigo para que use esta nueva versión.

    Los cambios principales son para no eliminar las líneas en blanco que haya en el código.
    Esto era porque la función Split original de Visual Basic no elimina las líneas en blanco.
    Además de usar vbCr en lugar de vbCrLf al examinar cada línea y crear nuevas separaciones, ya que al añadir vbCrLf añadía una línea extra en blanco.

    Nota del 13/Sep/2020 (v1.0.0.6)
    He actualizado tanto el paquete de NuGet como el código en GitHub.
    También la utilidad de gsColorearCodigo para que use esta nueva versión.

    Con fecha del 12 de septiembre de 2020 he creado un repositorio en gitHub para el código de gsColorearNET.

    De ahí puedes descargar el proyecto completo a excepción del fichero de nombre seguro (.snk)

    Este es el enlace: gsColorearNET en gitHub.

    Y este es el enlace a los paquetes de NuGet: gsColorearNET en NuGet.

    Estoy probando la utilidad de colorear en .NET 5.0 Preview 8 y a duras penas ya está operativa… es que el editor de Visual Studio 2019 Preview está aún muy verde para las aplicaciones de Windows Forms para Visual Basic (y para C# tampoco está muy fino).

    El hacerlo con esa versión es para poder depurar el código de la DLL de colorear, ya que en un proyecto de .NET framework no se puede… o yo no sé cómo hacerlo… todo hay que decirlo.

    Cuando tenga tiempo publicaré en el blog las cosillas que recomiendo hacer hasta que mejoren el editor/diseñador de WinForms para Visual Basic.

    NOTA:
    Aún la estoy probando desde NuGet ya que usándola como proyecto compila y funciona bien, pero al publicar el paquete en NuGet (aquí tienes el enlace: gsColorearNET en NuGet), no me encuentra los lenguajes de las palabras clave.
    ¡Esto último ya está solucionado con el paquete 1.0.0.1!
    Smile

    He publicado la utilidad de colorear código (gsColorearCodigo) para instalar con ClickOnce que utiliza el paquete de NuGet 1.0.0.1 y… ¡funciona a la perfección!

    Por ahora no te voy a poner el código fuente, en realidad ha cambiado poco desde lo que publiqué anteayer, y como lo que quiero añadir o corregir aún lo tengo que hacer… pues… habrá que esperar unos días a que lo tenga terminado y así ya la publico de forma, que salvo que haya algunos fallos, no tenga que modificarla en algún tiempo.

    Lo que sí haré es actualizar el paquete de NuGet con las correcciones que le vaya haciendo.

    Sobre el uso de un proyecto DLL de .NET Standard con un proyecto de .NET Framework

    Probando el código de gsColorearCodigo usando el .NET 4.8 en Visual Studio 2019 con el proyecto de gsColorearNET usando .NET Standard 2.0, al producirse un error (o sin que se produzca) y querer depurar en el código de la DLL, el VS no me dejaba…
    Así que… para comprobar porqué fallaba el penúltimo cambio que le hice, tuve que
    abrir un proyecto de .NET 5.0 (Preview 8) usando el Visual Studio Preview, y en ese entorno si pude depurar y averiguar cuál era el fallo… que después resultó ser algo trivial, pero…

    Así qué… ya sabes… si mezclas… no debugues… 😉

    Usar código de Visual Basic en .NET Standard

    Otra de las cosillas que me he encontrado a la hora de poder compilar el código del proyecto gsColorear para .NET Framework (ya hay que ir haciendo las aclaraciones de que no todo es simplemtente .NET, porque tenemos .NET Framework, .NET Core, .NET Standard y… casi ya… también simplemente .NET refiriéndose a .NET Core) es la definición de algunas de las funciones del ensamblado Microsoft.VisualBasic en concreto de la clase Strings que no están definidas en .NET Standard 2.0, tal como:
    Len, Left, Mid, Right, Split, InStr y Trim.

    Para no tener que cambiar todo el código que usaba esas funciones (recuerda que el código de colorear lo hice sobre el año 2001, sí, con Visual Basic 6.0, después en diciembre del 2002 lo pasé a la beta de Visual Studio .NET, la aplicación de aquél entonces era HTMLColorCode, aunque debo tener otra aplicación llamada gsEditor… el tiempo no perdona… por la memoria…) así que… me he creado una clase en la que
    he definido esas funciones, que (seguramente) pueden mejorarse, pero… así las he programado / codificado / como prefieras decirlo.

    Y te pego aquí el código por si te puede ser de ayuda.

    '------------------------------------------------------------------------------
    ' Clase definida en la biblioteca para .NET Standard 2.0            (10/Sep/20)
    ' Basada en gsColorear y gsColorearCore
    '
    ' VBCompat                                                          (10/Sep/20)
    ' Clase con instrucciones para compatibilidad con .NET Standard 2.0
    '
    ' Declaro algunas funciones de Microsoft.VisualBasic.Strings
    ' que no están en .NET Standard 2.0
    '
    ' (c) Guillermo (elGuille) Som, 2020
    '------------------------------------------------------------------------------
    Option Strict On
    Option Infer On
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    'Imports System.Data
    Public Class VBCompat
        ''' <summary>
        ''' Devuelve los caracteres desde la posición (en base 1)
        ''' hasta el final.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <param name="pos"></param>
        Public Shared Function Mid(str As String, pos As Integer) As String
            Return str.Substring(pos - 1)
        End Function
        ''' <summary>
        ''' Devuelve la cadena desde la posición indicada con len caracteres.
        ''' La posición del primer carácter es el 1.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <param name="pos"></param>
        ''' <param name="len"></param>
        ''' <remarks>10/Sep/20/20</remarks>
        Public Shared Function Mid(str As String, pos As Integer, len As Integer) As String
            Return str.Substring(pos - 1, len)
        End Function
        ''' <summary>
        ''' Devuelve el número de caracteres.
        ''' Si es cadena vacía o nulo devuelve 0.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <returns></returns>
        Public Shared Function Len(str As String) As Integer
            If String.IsNullOrEmpty(str) Then Return 0
            Return str.Length
        End Function
        ''' <summary>
        ''' Devuelve los primeros caracteres de la cadena.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <param name="len"></param>
        ''' <returns></returns>
        Public Shared Function Left(str As String, len As Integer) As String
            Return str.Substring(0, If(len > str.Length, str.Length, len))
        End Function
        ''' <summary>
        ''' Devuelve los caracteres indicados desde la derecha.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <param name="len"></param>
        ''' <returns></returns>
        Public Shared Function Right(str As String, len As Integer) As String
            Dim iPos = str.Length - len
            Return str.Substring(iPos, len)
        End Function
        ''' <summary>
        ''' Devuelve la posición (en base 1) de la segunda cadena en la primera
        ''' </summary>
        ''' <param name="str1"></param>
        ''' <param name="str2"></param>
        ''' <returns></returns>
        Public Shared Function InStr(str1 As String, str2 As String) As Integer
            Return str1.IndexOf(str2) + 1
        End Function
        ''' <summary>
        ''' Devuelve la posición (en base 1) de la segunda cadena en la primera 
        ''' empezando en la posición indicada.
        ''' </summary>
        ''' <param name="startPos"></param>
        ''' <param name="str1"></param>
        ''' <param name="str2"></param>
        ''' <returns></returns>
        Public Shared Function InStr(startPos As Integer, str1 As String, str2 As String) As Integer
            Return str1.IndexOf(str2, startPos - 1) + 1
        End Function
        ''' <summary>
        ''' Devuelve una cadena después de haber quitado 
        ''' los espacios delante y detrás.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <returns></returns>
        Public Shared Function Trim(str As String) As String
            Return str.Trim
        End Function
        ''' <summary>
        ''' Divide una cadena en elementos de un array.
        ''' Usando el delimitador indicado.
        ''' </summary>
        ''' <param name="Expression"></param>
        ''' <param name="Delimiter"></param>
        ''' <returns></returns>
        Public Shared Function Split(Expression As String, Optional Delimiter As String = " ") As String()
            Return Expression.Split(Delimiter.ToCharArray, StringSplitOptions.RemoveEmptyEntries)
        End Function
        ''
        '' El código IL de Prueba1 es más corto (y parece que eficiente) que el de Prueba2
        ''
        'Public Shared Function Prueba1(str As String, len As Integer) As String
        '    Return str.Substring(0, If(len > str.Length, str.Length, len))
        'End Function
        'Public Shared Function Prueba2(str As String, len As Integer) As String
        '    If len > str.Length Then
        '        len = str.Length
        '    End If
        '    Return str.Substring(0, len)
        'End Function
    End Class
    

    Y esto es todo por ahora…

    Espero que te sea de utilidad

    Nos vemos.
    Guillermo

     

     
    El código fuente completo

    El código para Visual Basic con el proyecto para Visual Studio 2019 está publicado en gitHub.

     

    Este es el enlace: gsColorearNET en gitHub.

     

    Y este es el enlace a los paquetes de NuGet: gsColorearNET en NuGet.

     

     

  • Truco por si asignas la posición del formulario a una guardada y cambias de tipo de pantalla

    Pues eso… que hace poco mandé al servicio técnico de LG la pantalla externa que conecto a mi ordenador portátil y estoy teniendo problemas cuando abro las aplicaciones que he estado usando con ese monitor, al menos las que tengo programadas para que al cambiar la posición o el tamaño se guardan en un fichero de configuración para cuando cargue de nuevo esa aplicación se muestre donde estuvo la vez anterior.

    Una de las opciones por la que opté fue poner que siempre se mostrasen en el centro de la pantalla, pero con el tamaño indicado en el diseño del formulario.

    La idea que se me ha ocurrido esta mañana es la de comprobar dónde está la posición Left y Top guardadas y si estarían fuera del rango de esos mismos valores de las propiedades de el WorkingArea de PrimaryScreen.

    Los valores de alto y ancho (Height y Width) no los toco, ya que el usuario podrá cambiar esos tamaños a su antojo, ya que ahora sí que podrá ver la ventana de la aplicación.

    Este es el código para Visual Basic con el que hago la comprobación que te he mencionado:

    ' Asignar el tamaño y última posición
    ' Comprobar que esté en la parte visible                    (24/Oct/20)
    Dim l = cfg.GetValue("Ventana", "Left", Me.Left)
    Dim t = cfg.GetValue("Ventana", "Top", Me.Top)
    If Screen.PrimaryScreen.WorkingArea.Left < l Then
        Me.Left = cfg.GetValue("Ventana", "Left", Me.Left)
    Else
        Me.Left = 0
    End If
    If Screen.PrimaryScreen.WorkingArea.Top < t Then
        Me.Top = cfg.GetValue("Ventana", "Top", Me.Top)
    Else
        Me.Top = 0
    End If
    Me.Height = cfg.GetValue("Ventana", "Height", Me.Height)
    Me.Width = cfg.GetValue("Ventana", "Width", Me.Width)
    

    Los valores de la posición y tamaño del formulario los obtengo de un fichero de configuración y solo asigno el valor Left si la posición Left de WorkingArea es menor, ya que cuando está en el monitor externo (al menos en mi caso) el valor de Left suele ser negativo.
    Y con el valor Top hacemos lo mismo, solo asignarlo si no es menor que el valor Top del área de trabajo de la pantalla principal.

     

    Y aquí tienes el mismo código para C#:

    // Asignar el tamaño y última posición
    // Comprobar que esté en la parte visible                    (24/Oct/20)
    var l = cfg.GetValue("Ventana", "Left", this.Left);
    var t = cfg.GetValue("Ventana", "Top", this.Top);
    if (Screen.PrimaryScreen.WorkingArea.Left < l)
        this.Left = cfg.GetValue("Ventana", "Left", this.Left);
    else
        this.Left = 0;
    
    if (Screen.PrimaryScreen.WorkingArea.Top < t)
        this.Top = cfg.GetValue("Ventana", "Top", this.Top);
    else
        this.Top = 0;
    
    this.Height = cfg.GetValue("Ventana", "Height", this.Height);
    this.Width = cfg.GetValue("Ventana", "Width", this.Width);
    

     

    Y esto es todo, espero que te haya sido de utilidad.

     

    Nos vemos.
    Guillermo

  • Moderniza tu aplicación con el efecto grisáceo en el texto de los controles con nota de lo que hay que escribir en VB y C#

    Pues eso… seguramente habrás visto en algunas aplicaciones que en las cajas de texto (o los ComboBox) se muestra un texto en color gris para indicarte lo que puedes escribir en esa casilla.

    Para tener esa funcionalidad en nuestros proyectos no es necesario usar controles especializados ni nada de eso, ya verás que es fácil, y espero que claro, hacerlo mediante unas pocas líneas de código.

    Actualizado y corregido el código (24-oct-2020)
    Ver más abajo el comentario y el nuevo código de QuitarPredeterminado.

     

    En la figura 1 puedes ver el aspecto de esa caja de texto.

    Figura 1. La aplicación en modo ejecución

    En la imagen anterior puedes ver el texto <Escribe tu nombre> en la caja de texto, cuando empieces a escribir, ese texto informativo se quita y solo estará lo que escribas, y si borras todo el texto (dejas vacía la caja de texto) se volverá a mostrar ese texto informativo.

    ¿Dónde controlar si se debe mostrar o no el texto predeterminado?

    En mi caso, yo hago las comprobaciones en tres eventos del (en este caso) TextBox. A saber:

    El evento Enter (cuando toma el foco) ahí se comprueba si debe tener el color grisáceo o el negro del texto.
    Se comprueba si el texto es diferente del predeterminado (el de ayuda o informativo), en ese caso se asigna el color del texto a ControlText (definido en SystemColors).

    El evento Leave (cuando pierde el foco) ahí compruebo si no hay texto, en cuyo caso, asigno el texto informativo y le asigno a la propiedad ForeColor el color GrayText (también de la clase SystemColors).

    El evento TextChange (cuando el texto cambia), hay lo que hay que hacer es más elaborado (menos simple que en los casos anteriores), al menos como yo lo he hecho, que puede que haya un método más fácil y simple de hacerlo. Pero es lo que le da esa vidilla al efecto ;-).

    Yo suelo usar una variable a nivel de formulario llamada inicializando que me sirve para evitar la entrada en cascada del evento TextChange (y otros eventos, pero en este ejemplo es el que se produce cuando el texto cambia). Si el valor de esa variable es True, salimos del método.

    La primera comprobación es si el texto del control está vacío o tiene el texto predeterminado, en cuyo caso lo ponemos en color gris y le asigno el texto informativo.
    Si no se da esa comprobación, el texto ya tiene algo, por ejemplo, si se ha empezado a escribir, y compruebo si el texto que había antes era el texto vacío en cuyo caso le quito el texto predeterminado con idea de que solo se quede lo que el usuario está escribiendo.

    La primera idea que tuve fue quitar directamente el texto predeterminado con un Replace(textoPredeterminado, «»), pero eso solo vale si se está escribiendo al principio o al final del texto (que es lo más común), pero si el usuario se pone a escribir en medio del texto predeterminado, no funcionaría… por tanto, he creado una función en la que quito todo el texto predeterminado del texto que haya, para así dejar solo lo que se esté escribiendo, que será la primera letra que el usuario escriba.

    Esa función yo la tengo como una extensión de la clase String, pero en el código que te mostraré, es simplemente una función llamada QuitarPredeterminado que recibe dos argumentos, el texto a comprobar y el texto a quitar. De esa forma nos servirá para aplicarla a cualquier control y evitar tener que repetir lo mismo en todos los evento TextChange de los controles a los que queramos dar esta funcionalidad.

    Aquí (y en la comprobación anterior) entra en juego la variable inicializando, que es la que evitará que se entre nuevamente mientras modificamos el texto.

    Finalmente asignamos a la variable que contiene el texto anterior, el texto actual para que no se quite nada cuando ya no sea el texto predeterminado, ya que es posible que el usuario escriba en el texto ese mismo texto que damos por predeterminado, el problema es que el usuario solo escriba el texto predeterminado, pero… bueno, se supone que no lo escribirá… 😉

    Y esto es todo lo que hay que hacer… viendo ahora el código para Visual Basic y C# lo entenderás mejor.

     

    Rectificación del código (24-oct-2020)

    Pues resulta que hay que hacer una comprobación extra antes de quitar los caracteres del texto predeterminado, si no, puede pasar lo que me pasó anoche, que al asignar la palabra InitializeComponent a la caja de textos donde está la palabra predeterminada Buscar… pues… se encontró con la a (de Buscar) y la quitó…
    Así que… ahora compruebo primero si están todas las letras del texto predeterminado y de ser así, entonces hago el reemplazo, si no están todas las letras, simplemente se devuelve el texto original y como si nada.

    El código de QuitarPredeterminado que te muestro más abajo ya tiene las modificaciones indicadas.

    El código completo, tanto para VB como para C#, publicado en github lo actualizaré en unos minutos.

    El código de ejemplo para Visual Basic .NET y C#

    Empezaré mostrándote las variables que necesitaremos a nivel de formulario.
    Es decir, la que controla si ya estamos dentro del evento, la que contiene el texto predeterminado y la que contiene el texto anterior de la caja de texto.

    Estas dos últimas serán diferentes para cada control a los que queramos aplicar el efecto. Al menos la del texto anterior, ya que el texto predeterminado puede ser el mismo para varios controles.

    Visual Basic .NET

    Private Const textVacio As String = "<Escribe tu nombre>"
    Private textAnterior As String = textVacio
    Private inicializando As Boolean

    C#

    private const string textVacio = "<Escribe tu nombre>";
    private string textAnterior = textVacio;
    private bool inicializando;
    

     

    Ahora te muestro el código de los eventos Enter y Leave.

    Visual Basic .NET

    Private Sub txtTexto_Enter(sender As Object, e As EventArgs) Handles txtTexto.Enter
        If txtTexto.Text <> textVacio Then
            txtTexto.ForeColor = SystemColors.ControlText
        End If
    End Sub
    
    Private Sub txtTexto_Leave(sender As Object, e As EventArgs) Handles txtTexto.Leave
        If String.IsNullOrEmpty(Me.txtTexto.Text) Then '
            Me.txtTexto.ForeColor = SystemColors.GrayText
            Me.txtTexto.Text = textVacio
        End If
    End Sub
    

     

    C#

    private void txtTexto_Enter(object sender, EventArgs e)
    {
        if (txtTexto.Text != textVacio)
            txtTexto.ForeColor = SystemColors.ControlText;
    }
    
    private void txtTexto_Leave(object sender, EventArgs e)
    {
        if (string.IsNullOrEmpty(this.txtTexto.Text))
        {
            this.txtTexto.ForeColor = SystemColors.GrayText;
            this.txtTexto.Text = textVacio;
        }
    }
    

     

    El código del evento TextChanged en el que uso la función para quitar el texto predeterminado esté en la posición que esté.
    Después te muestro el código de esa función QuitarPredeterminado.

    Visual Basic .NET

    Private Sub txtTexto_TextChanged(sender As Object,
                                     e As EventArgs) Handles txtTexto.TextChanged
        If inicializando Then Return
        If txtTexto.Text = "" OrElse txtTexto.Text = textVacio Then
            txtTexto.ForeColor = SystemColors.GrayText
            inicializando = True
            txtTexto.Text = textVacio
            inicializando = False
        Else
            If textAnterior = textVacio Then
                inicializando = True
                txtTexto.Text = QuitarPredeterminado(txtTexto.Text, textVacio)
                inicializando = False
    
                txtTexto.SelectionStart = txtTexto.Text.Length
            End If
            txtTexto.ForeColor = SystemColors.ControlText
        End If
        textAnterior = txtTexto.Text
    End Sub
    
    

     

    C#

    private void txtTexto_TextChanged(object sender, EventArgs e)
    {
        if (inicializando)
            return;
        if (txtTexto.Text == "" || txtTexto.Text == textVacio)
        {
            txtTexto.ForeColor = SystemColors.GrayText;
            inicializando = true;
            txtTexto.Text = textVacio;
            inicializando = false;
        }
        else
        {
            if (textAnterior == textVacio)
            {
                inicializando = true;
                txtTexto.Text = QuitarPredeterminado(txtTexto.Text, textVacio);
                inicializando = false;
    
                txtTexto.SelectionStart = txtTexto.Text.Length;
            }
            txtTexto.ForeColor = SystemColors.ControlText;
        }
        textAnterior = txtTexto.Text;
    }
    

     

    Fíjate que antes de asignar un texto al TextBox asigno el valor true a inicializando, de esa forma, cuando se cambie el texto (al asignarlo se cambiará), no entrará nuevamente en el evento.

    Y como te comentaba antes, cuando el texto que había anterior es el predeterminado (textVacio), quitamos dicho texto de la propiedad Text de la caja de textos mediante una llamada al método QuitarPredeterminado, al que le pasamos el texto que queremos comprobar (el del control TextBox) y el que queremos quitar.

    Veamos el código de ese método… y verás lo casi retorcido que es… 🙂
    En serio, lo que hago es recorrer cada carácter del texto a quitar (el predeterminado) y quitarlo del texto, de esa forma, esté donde esté ese carácter que queremos quitar lo hará correctamente.

    Aquí tienes el código de la función QuitarPredeterminado.

    Visual Basic .NET

    ''' <summary>
    ''' Quitar de una cadena un texto indicado (que será el predeterminado cuando está vacío).
    ''' Por ejemplo si el texto grisáceo es Buscar... y
    ''' se empezó a escribir en medio del texto (o en cualquier parte)
    ''' BuscarL... se quitará Buscar... y se dejará L.
    ''' Antes de hacer cambios se comprueba si el texto predeterminado está al completo 
    ''' en el texto en el que se hará el cambio.
    ''' </summary>
    ''' <param name="texto">El texto en el que se hará la sustitución.</param>
    ''' <param name="predeterminado">El texto a quitar.</param>
    ''' <returns>Una cadena con el texto predeterminado quitado.</returns>
    ''' <remarks>18/Oct/2020 actualizado 24/Oct/2020</remarks>
    Public Function QuitarPredeterminado(texto As String, predeterminado As String) As String
        Dim cuantos = predeterminado.Length
        Dim k = 0
    
        For i = 0 To predeterminado.Length - 1
            Dim j = texto.IndexOf(predeterminado(i))
            If j = -1 Then Continue For
            k += 1
        Next
        ' si k es distinto de cuantos es que no están todos lo caracteres a quitar
        If k <> cuantos Then
            Return texto
        End If
    
        For i = 0 To predeterminado.Length - 1
            Dim j = texto.IndexOf(predeterminado(i))
            If j = -1 Then Continue For
            If j = 0 Then
                texto = texto.Substring(j + 1)
            Else
                texto = texto.Substring(0, j) & texto.Substring(j + 1)
            End If
        Next
    
        Return texto
    End Function
    

    C#

    /// <summary>
    /// Quitar de una cadena un texto indicado (que será el predeterminado cuando está vacío).
    /// Por ejemplo si el texto grisáceo es Buscar... y
    /// se empezó a escribir en medio del texto (o en cualquier parte)
    /// BuscarL... se quitará Buscar... y se dejará L.
    /// Antes de hacer cambios se comprueba si el texto predeterminado está al completo 
    /// en el texto en el que se hará el cambio.
    /// </summary>
    /// <param name="texto">El texto en el que se hará la sustitución.</param>
    /// <param name="predeterminado">El texto a quitar.</param>
    /// <returns>Una cadena con el texto predeterminado quitado.</returns>
    /// <remarks>18/Oct/2020 actualizado 24/Oct/2020</remarks>
    private string QuitarPredeterminado(string texto, string predeterminado)
    {
        var cuantos = predeterminado.Length;
        var k = 0;
    
        for (var i = 0; i < predeterminado.Length; i++)
        {
            var j = texto.IndexOf(predeterminado[i]);
            if (j == -1)
                continue;
            k += 1;
        }
        // si k es distinto de cuantos es que no están todos lo caracteres a quitar
        if (k != cuantos)
            return texto;
    
        for (var i = 0; i < predeterminado.Length; i++)
        {
            var j = texto.IndexOf(predeterminado[i]);
            if (j == -1)
                continue;
            if (j == 0)
                texto = texto.Substring(j + 1);
            else
                texto = texto.Substring(0, j) + texto.Substring(j + 1);
        }
        return texto;
    }
    

     

    Y esto es todo… yo lo estoy usando de varias formas, por ejemplo en los TextBox (en realidad un ComboBox del tipo ToolStripComboBox) para Buscar y otro para Reemplazar, en el primero muestro de forma predeterminada el texto Buscar… y el de reemplazar el texto Reemplazar….

    La idea la tomé prestada, con estas mejoras que te he mostrado aquí, del proyecto CSharp2VB de Paul1956.

     

    Publicaré Ya está el código completo de ejemplo tanto para Visual Basic como para C# (proyectos para .NET Framework 4.8, aunque sirven igualmente con proyectos para .NET 5.0 RC2) publicado en GitHub: Mostrar-texto-grisaceo.

     

    Espero que te sea de utilidad.

     

    Nos vemos.
    Guillermo