Archivo del Autor: elGuille

Ya están los foros en modo SOLO-LECTURA

Pues eso… ya es 1 de febrero de 2019 y como te comenté hace unos días, los foros (no el blog ni el sitio) ya está en modo SOLO-LECTURA, es decir, se podrán seguir viendo los mensajes que haya, pero no se podrán hacer nuevas consultas, ni responder a las existentes, ni tampoco editarlas. aunque sí se podrán marcar los mensajes como que responden a la pregunta o consulta realizada, pero si quieres que te diga la verdad, dudo que muchos entren nuevamente en los foros para marcar las respuestas… si no lo hicieron en su día… no lo van a hacer después de algún tiempo… Winking smile

Desde el 1 de febrero de 2019 los foros del Guille son de solo lectura

Si eres moderador de mis foros o estás en la categoría de MVP (antes debes estar registrado en los foros) podrás seguir modificando, respondiendo o escribiendo nuevos mensajes.

Los usuarios «normales» si pulsan en el botón de responder, modificar o nuevo, los mandará a la página de aviso de que está cerrado.

Por cierto, si quieres colaborar comprobando las respuestas a los mensajes para ver si responden a la pregunta, coméntalo en este mismo post y así los foros tendrán un valor añadido. Gracias.

Y esto es todo por ahora…. en marzo nuevos cambios… espero…

Nos vemos.
Guillermo

Quitar avisos de Windows 10 al instalar con ClickOnce

Pues eso… que el otro día estaba probando en mi equipo con Windows 10 la instalación con ClickOnce de una aplicación y era un rollo la de preguntas que te decía, que si te está protegiendo, que no confía en la aplicación, y cosas así (ver figura 1).

Figura 1. Aviso de Windows 10 al instalar con ClickOnce desde un sitio Web
Figura 1. Aviso de Windows 10 al instalar con ClickOnce desde un sitio Web

La solución es simple.

Pero debes hacerlo en el equipo en el que se va a instalar la aplicación.

Panel control>opciones internet>Seguridad>Sitios de confianza>sitios (agregar la dirección web)

Abre el Panel de Control, (si no lo tienes a mano abre Inicio y escribe «panel» y te lo mostrará), selecciona Opciones de Internet (si no ves las opciones de internet, pulsa en Redes e Internet) se mostrará una ventana como la de la figura 2.

Figura 2. Propiedades de Internet.
Figura 2. Propiedades de Internet.

Pulsa en la pestaña Seguridad, a continuación en Sitios de confianza y en el botón Sitios. Te mostrará una ventana nueva con los Sitios de confianza (figura 3).

Figura 3. Sitios de confianza.
Figura 3. Sitios de confianza.

Si tu sitio no es https quita la marca de abajo a la derecha donde dice «Requerir comprobación del servidor (https:) para todos los sitios de esta zona«.

Escribe el nombre de tu sitio en «Agregar este sitio web a la zona de:» (sin el http:// ni https://) y pulsa en el botón Agregar.

Cierra todas las ventanas y ya no tendrás más avisos como el de la figura 1 (y otros parecidos) cuando se vuelva a instalar (o actualizar) algo desde el sitio que has indicado.

Y ya está… Smile

Espero que te sea de utilidad.

Nos vemos.
Guillermo

En breve cerraré los foros del Guille

Pues eso.,.. me refiero a la página: http://foros.elguille.info/ no al blog, ni al sitio ni la página de Facebook Winking smile

Ya han pasado unos 15 años desde que los puse en marcha (hubo antes otras versiones de los foros, pero me refiero al que está actualmente ¿activo?) y no hay prácticamente movimiento, ni por mi parte… así que… si no cambio de opinión… el 31 de enero de este año 2019 será el último día que se podrá postear en los foros de elGuille.info… y a partir del 1 de febrero de 2019 serán de solo lectura (o solo para consultar y ver los hilos), porque quitarlo no lo voy a quitar, ya que sé que hay respuestas válidas, y muchas… Si acaso quitaré visibilidad a los hilos que no han obtenido respuesta, más que nada para que no aparezcan en los buscadores, que no es plan que alguien busque algo y le aparezca solo la pregunta sin respuesta (que suele ocurrir en muchos foros, jeje).

Los foros de elGuille.info pasarán a solo lectura el 1 de febrero de 2019
Los foros de elGuille.info pasarán a solo lectura el 1 de febrero de 2019

Pues eso… si quieres dar tu opinión, puedes darla en este hilo.

Gracias.

Nos vemos.
Guillermo

DoEvents para aplicaciones WPF

Pues eso… que en las aplicaciones de Windows Presentation Foundation (WPF) no podemos usar el equivalente a DoEvents de Windows Forms, simplemente porque no existe esa funcionalidad para WPF, pero no te preocupes porque aquí te explico cómo crear un método DoEvents listo para usar en las aplicaciones de WPF, por supuesto con el código del VB y C#.

Yo utilizo DoEvents en las ocasiones que quiero refrescar la pantalla (formulario o ventana) de la aplicación, por ejemplo cuando se está haciendo un proceso largo para que no se quede congelada la aplicación.

Y anoche me ocurrió eso mientras ejecutaba una aplicación (de WPF) que me fabriqué para copiar el contenido de una lista de canciones (PlayList tipo m3u) en una carpeta. Y como el disco usado para guardar los MP3 era un disco externo, pues… aparte de que eran muchas canciones (354), pues… parecía que la aplicación fallaba, ya que no mostraba la canción que estaba copiando y… pues eso… que hasta yo pensé que me había equivocado escribiendo el código… Angel

Así que… sabiendo que DoEvents no está definido en WPF y después de probar con que tampoco hay Refresh en los controles de WPF, probé con Thread.Sleep que no solucionó el problema (y no era plan de crear un temporizador), así que… ¡a buscar en Internet!

Y dio resultado la búsqueda, concretamente en esta página:
Implement Application.DoEvents in WPF, ¿el problema? ninguno, salvo que, como suele ocurrir por los interneses, todo está con ejemplos para C#, así que… a convertir el código encontrado.

Este es el código de esa página para simular el DoEvents (en C#):

public static void DoEvents()
    {
        Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,
                new EmptyDelegate(delegate{}));
    }

En otra parte del código debes tener la definición de EmptyDelegate:

private delegate void EmptyDelegate();

No me sonó demasiado a chino mandarín ya que algo parecido usé hace muuuuuuchos años para llamar a otro control desde el método de evento de un temporizador:
24- Acceder a un control desde un evento de un timer, pero ya ni me acordaba Eye rolling smile

Y esta es la versión para Visual Basic .NET:

' Adaptado de:
' http://www.java2s.com/Tutorial/CSharp/0470__Windows-Presentation-Foundation/
'   ImplementApplicationDoEventsinWPF.htm

Private Delegate Sub EmptyDelegate()

Private Sub DoEvents()
    Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,
                                        New EmptyDelegate(Sub()
                                                          End Sub))
End Sub

Te tienes que fiar de mi palabra, pero sin el DoEvents, al pulsar en el botón Copiar la aplicación parece como si se hubiese colgado… hasta pasado un buen rato no termina… (Figura 1)

Figura 1. Sin el DoEvents, después de pulsar en Copiar la aplicación se queda congelada
Figura 1. Sin el DoEvents, después de pulsar en Copiar la aplicación se queda congelada

Sin embargo, poniendo el DoEvents, se va mostrando el progreso de copia. Y de eso se trata… que se vea lo que está haciendo Winking smile

Pues… ¡ya está! esto es todo…

Nos vemos.
Guillermo

Si quieres que tu aplicación se muestre en el monitor externo dile que se centre en la pantalla (CenterScreen)

Pues eso… que estuve un tiempo buscando soluciones para que se mostrasen mis aplicaciones en el monitor secundario (que es el que uso como principal) y resulta que la solución es más simple que todo eso… sí, solo con indicarle que se centre en la pantalla (CenterScreen) es suficiente Winking smile

Y esto vale tanto para aplicaciones de Windows Forms como para las de WPF (Windows Presentation Foundation).

En WPF lo haces con este código en el diseñador de la ventana (Window):

WindowStartupLocation = "CenterScreen"

En WinForms asigna a la propiedad StartPosition del formulario de inicio el valor CenterScreen.

Y ya está… ya no tengo más que contarte Winking smile

Nos vemos.
Guillermo

Indicar el Encoding al guardar el contenido de un RichTextBox de WPF

Pues eso… que el otro día te puse un ejemplo de Abrir y guardar archivos usando RichTextBox para WPF y anoche haciendo pruebas con vocales acentuadas, me di cuenta que el formato XAML (DataFormats.Xaml) las tildes se las pasaba por el forro… así que… buscando en la red de redes vi un ejemplo que evita eso… o casi, al menos te permite tener la opción de poder hacerlo.

El problema está (o estaba) en que en el ejemplo de donde saqué el código para guardarlo utiliza esto para crear el Stream de salida: Using fStream As New FileStream(_fileName, FileMode.Create) y usando FileStream no se puede indicar la codificación. O yo no sé cómo hacerlo, que todo hay que decirlo Winking smile

Al abrir el Stream se hace la llamada al método Save del rango (TextRange) y se indica el formato con el que se guardará: range.Save(fStream, formato, True). range.Save precisa de un Stream, pero el ofrecido por StreamWriter, que es el que yo suelo usar para guardar indicando la codificación, no le sirve.

El truco está en guardar primero el contenido del RichTextBox en la memoria (usando MemoryStream) y después pasar ese flujo de caracteres al disco por medio de StreamWriter.

El código final quedaría de la siguiente forma:

''' <summary>
''' Adaptado del ejemplo de la documentación de Microsoft
''' https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
'''     richtextbox-overview
''' </summary>
Private Function SaveRtfFormat(_fileName As String,
                               richTB As RichTextBox,
                               formato As String) As Boolean
    Dim range As TextRange
    Dim guardado As Boolean = False

    range = New TextRange(richTB.Document.ContentStart,
                          richTB.Document.ContentEnd)

    ' Para guardar con el formato que queramos
    ' Adaptado de:
    ' https://social.msdn.microsoft.com/Forums/vstudio/en-US/
    '   a9ef25ef-fada-4cbd-a341-f9eb22fb2f48/
    '   how-to-save-a-rich-text-into-a-sql-server-database-in-a-wpf-application?forum=wpf
    Using stream As New MemoryStream
        Try
            range.Save(stream, formato, True)
            Dim buffer = Encoding.UTF8.GetString(stream.ToArray())
            Using sw As New StreamWriter(_fileName, False, Encoding.Default)
                sw.Write(buffer)
            End Using

            guardado = True
        Catch ex As Exception
            MessageBox.Show("Error el formato no es válido" & vbCrLf &
                            ex.Message,
                            $"Guardar {formato}",
                            MessageBoxButton.OK,
                            MessageBoxImage.Asterisk)
        End Try
    End Using
    'Using fStream As New FileStream(_fileName, FileMode.Create)
    '    Try
    '        range.Save(fStream, formato, True)
    '        guardado = True
    '    Catch ex As Exception
    '        MessageBox.Show("Error el formato no es válido" & vbCrLf &
    '                        ex.Message,
    '                        $"Guardar {formato}",
    '                        MessageBoxButton.OK,
    '                        MessageBoxImage.Asterisk)
    '    End Try
    '    fStream.Close()
    'End Using

    Return guardado
End Function

Al final de la función tienes (comentado) el código anterior.

/// <summary>
///  Adaptado del ejemplo de la documentación de Microsoft
///  https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
///     richtextbox-overview
///  </summary>
private bool SaveRtfFormat(string _fileName, 
                           RichTextBox richTB, 
                           string formato)
{
    TextRange range;
    bool guardado = false;

    range = new TextRange(richTB.Document.ContentStart, 
                          richTB.Document.ContentEnd);

    // Para guardar con el formato que queramos
    // Adaptado de:
    // https://social.msdn.microsoft.com/Forums/vstudio/en-US/
    // a9ef25ef-fada-4cbd-a341-f9eb22fb2f48/
    // how-to-save-a-rich-text-into-a-sql-server-database-in-a-wpf-application?forum=wpf
    using (MemoryStream stream = new MemoryStream())
    {
        try
        {
            range.Save(stream, formato, true);
            var buffer = Encoding.UTF8.GetString(stream.ToArray());
            using (StreamWriter sw = new StreamWriter(_fileName, 
                                                      false, 
                                                      Encoding.Default))
            {
                sw.Write(buffer);
            }

            guardado = true;
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error el formato no es válido\r\n" + 
                            ex.Message, 
                            $"Guardar {formato}", 
                            MessageBoxButton.OK, 
                            MessageBoxImage.Asterisk);
        }
    }

    //    using (FileStream fStream = new FileStream(_fileName, 
    //                                               FileMode.Create))
    //    {
    //        try
    //        {
    //            range.Save(fStream, formato);
    //            guardado = true;
    //        }
    //        catch (Exception ex)
    //        {
    //            MessageBox.Show("Error el formato no es válido\r\n" + 
    //                ex.Message, $"Guardar {formato}", 
    //                MessageBoxButton.OK, MessageBoxImage.Asterisk);
    //        }

    //        fStream.Close();
    //    }

    return guardado;
}

Lo curioso del caso es que si lo guardaba como Rtf (DataFormats.Rtf) las vocales acentuadas se guardaban bien… Pero de esta forma, todo se guarda bien, al menos los tres formatos que he probado: Rtf, Xaml y Text.

Y es que en las pruebas que estaba haciendo usaba el código de ejemplo de la utilidad Compilar y ejecutar y tengo en varios sitios escrita la palabra versión y al guardarlo y después volver a abrirlo con formato Xaml, se mostraba como en la figura 1.

Figura 1. Los caracteres raros de la o con tilde ó
Figura 1. Los caracteres raros de la o con tilde ó

Al principio ni me fijé, pero cuando la ristra esa de caracteres raros se hizo más larga, ya que si me fijé Surprised smile

¡Como para no darme cuenta!

Y ya está… ahora modificaré la entrada anterior o pondré una aclaración para que vengas aquí Winking smile (para que se vea que el Guille también se equivoca jajaja)

Espero que te sea de utilidad. Esa es la idea.

Nos vemos.
Guillermo

Abrir y guardar archivos usando RichTextBox para WPF

Pues eso… hoy te voy a explicar cómo implementar las opciones de abrir y guardar archivos usando un control RichTextBox para WPF.

Actualizado (o nota del 15/Ene/19)
En realidad actualizado no está, salvo este comentario del martes 15 de enero.

Donde está la actualización es en el post que he publicado hoy: Indicar el Encoding al guardar el contenido de un RichTextBox de WPF, y es que resulta que con el código tal como te lo muestro aquí, si decides usar el formato Xaml (ya sabes: lo guarda como párrafos, etc.) y tu texto tiene tildes (vocales acentuadas), pues… resulta que no lo hace bien.

Así que… te recomiendo que veas el post de hoy y si te has descargado el zip con los proyectos, modifiques el código. Gracias

Como ya vimos en el post anterior (Leer el contenido como cadena y asignar un valor nuevo) usaremos tres opciones de formatos admitidos por RichTextBox de WPF. ¿Los recuerdas? Vale, te los resumo de nuevo:

  • Rtf el contenido debe estar en formato RTF.
  • Xaml el contenido debe estar en formato Xaml pero el que se puede poner en un RichTextBox o FlowDocument, cuando lo guardas lo pone dentro de un elemento Section. Es decir, no vale cualquier código Xaml y menos el que define un objeto Window.
  • Text Formato de texto plano, sin ningún tipo de formato.

Contenido de la ventana principal (MainWindow)

En la aplicación he puesto un control RichTextBox (de eso se trata este ejemplo, ¿no?) y además de una etiqueta para mostrar la información del archivo activo, también hay un menú con un elemento (Archivo) con las opciones de Abrir, Guardar como y Salir.

En esos tres submenús he puesto imágenes, éstas están descargadas de las que pone Visual Studio 2017 a nuestra disposición, realmente las que utiliza el propio Visual Studio 2017. El enlace para descargar todas esas imágenes (son un montón, lo que yo te diga) es este: Biblioteca de imágenes de Visual Studio. En ese enlace te explica qué tipos de imágenes contiene y más cosillas, aparte, claro del enlace para descargarlas.

Este es el código XAMl de la ventana principal (recuerda que es el mismo diseño para Visual Basic que para C#, lo único que cambia es el espacio de nombres usado en cada proyecto, más abajo te pongo el ZIP con el código completo para Visual Basic y para C# en una solución de Visual Studio 2017.

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Wpf_Abrir_y_guardar_en_RichTextBox_vb"
        mc:Ignorable="d"
        Title="Abrir y guardar archivos en un RichTextBox (VB)" 
        WindowStartupLocation="CenterScreen"
        ResizeMode="CanResizeWithGrip" 
        WindowStyle="ThreeDBorderWindow"
        Loaded="Window_Loaded" Closing="Window_Closing"
        Height="450" Width="800"
        Icon="Images/RichTextBox_16x.png">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="Auto" MinWidth="100" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" MinHeight="24"/>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition Height="Auto" MinHeight="24" />
        </Grid.RowDefinitions>
        <Menu Grid.Row="0" Grid.Column="0" 
              Grid.ColumnSpan="2" Background="AliceBlue">
            <MenuItem Header="_Archivo" ToolTip="Abrir, Guardar, Salir">
                <MenuItem x:Name="mnuAbrir" Header="_Abrir..."
                                  Click="MnuAbrir_Click">
                    <MenuItem.Icon>
                        <Image Source="Images\OpenFile_16x.png" />
                    </MenuItem.Icon>
                </MenuItem>
                <MenuItem x:Name="mnuGuardar" Header="_Guardar cómo..."
                                  Click="MnuGuardar_Click">
                    <MenuItem.Icon>
                        <Image Source="Images\Save_16x.png" />
                    </MenuItem.Icon>
                </MenuItem>
                <Separator />
                <MenuItem x:Name="mnuSalir" Header="_Salir" Click="MnuSalir_Click">
                    <MenuItem.Icon>
                        <Image Source="Images\Close_16x.png" />
                    </MenuItem.Icon>
                </MenuItem>
            </MenuItem>
        </Menu>
        <RichTextBox x:Name="rtb" 
                     BorderThickness="2"
                     AcceptsTab="True" AcceptsReturn="True"
                     Grid.Column="0" Grid.Row="1" 
                     Grid.ColumnSpan="2" Grid.RowSpan="2"
                     TextChanged="Rtb_TextChanged">
            <FlowDocument />
        </RichTextBox>
        <Label x:Name="lblStatus" Content="Información..."
               Padding="4,2,4,2"
               Grid.Column="0" Grid.Row="3" 
               Grid.ColumnSpan="2"
               Background="{DynamicResource {x:Static SystemColors.InfoBrushKey}}" />
    </Grid>
</Window>

Las definiciones de las filas y columnas del Grid nos dan espacio (no demasiado ancho) para la primera fila (la de los menús) y la última (la de la etiqueta de estado).

La columna esa que tengo con MinWidth a 100 era para poder poner el típico botón Salir, pero como ya lo tengo en el menú de Archivo, ¿pa qué ponerlo? pero ya que estaba… la he dejado Smile

Las imágenes usadas como recurso están en una carpeta llamada Images y aparte de las mostradas en los menús, hay otra para usarla como icono de la ventana: RichTextBox_16x.png.

Nota:
Fíjate que en la definición de la ventana (Window) se asigna a la propiedad Icon, pero en realidad no es un icono. Te lo digo por si lo quieres usar como icono de la aplicación. En ese caso tendrás que crear un icono nuevo, añadir una nueva imagen (o tipo de imagen) de 16×16 con 24 bits, copiar la imagen con un programa, por ejemplo el Paint que se incluye con Windows y después pegarla en ese icono creado en Visual Studio.

Vale, te lo explico paso a paso.

Crear un icono con Visual Studio a partir de una imagen

Lo he puesto como post separado para no cargar más de la cuenta este: Crear un icono con Visual Studio a partir de una imagen.

Fíjate que en el código XAML no he indicado la visibilidad de las barras de desplazamiento del control RichTextBox, por tanto no se muestran (tampoco está incluido en un control ScrollViewer). Si quieres que se muestren tanto la horizontal como la vertical, tendrás que indicarlo expresamente.

HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"

Si le asignas un valor Auto se mostrarán según sea necesario (al menos la vertical, ya que la horizontal siempre se mostrará). Si quieres que siempre sean visible las dos, en vez de Auto indica el valor Visible.

El código para abrir un archivo y asignar el contenido en el RichTextBox

A continuación te muestro las funciones (tanto para VB.NET como para C#) del método usado para abrir un archivo y asignarlo al contenido del control RichTextBox.

Nota:
El código mostrado en el método LoadRtfFormat lo he convertido a partir de un ejemplo mostrado en la documentación en línea de Visual Studio.

''' <summary>
''' Adaptado del ejemplo de la documentación de Microsoft
''' https://docs.microsoft.com/es-es/dotnet/framework/wpf/
'''     controls/richtextbox-overview
''' </summary>
Private Function LoadRtfFormat(_fileName As String,
                               richTB As RichTextBox,
                               formato As String) As Boolean
    Dim abierto = False

    If File.Exists(_fileName) Then
        Dim range = New TextRange(richTB.Document.ContentStart,
                                  richTB.Document.ContentEnd)
        Using sr As New StreamReader(_fileName, Encoding.Default, True)
            Try
                ' leer el contenido para admitir tildes, etc.   (09/Ene/19)
                Dim texto = sr.ReadToEnd
                Dim stream = New MemoryStream(Encoding.UTF8.GetBytes(texto))

                range.Load(stream, formato)
                abierto = True
            Catch ex As Exception
                MessageBox.Show("Error el formato no es válido" & vbCrLf &
                                ex.Message,
                                $"Abrir {formato}",
                                MessageBoxButton.OK,
                                MessageBoxImage.Asterisk)
            End Try
        End Using
    End If

    Return abierto
End Function

Private Function LoadRtf(ByVal _fileName As String,
                         richTB As RichTextBox) As Boolean
    Return LoadRtfFormat(_fileName, richTB, DataFormats.Rtf)
End Function

' Al cargar como Xaml da error el Visual Studio
' ya que lo trata como una clase
' y esto en realidad es para abrir con el XAML generado,
'   con los elementos Paragraph, Bold, Run, etc.
Private Function LoadRtfXaml(ByVal _fileName As String,
                             richTB As RichTextBox) As Boolean
    Return LoadRtfFormat(_fileName, richTB, DataFormats.Xaml)
End Function

''' <summary>
''' El formato texto abrirlo directamente
''' Aunque funciona igual que llamando a LoadRtfFormat
''' </summary>
Private Function LoadRtfText(ByVal _fileName As String,
                             richTB As RichTextBox) As Boolean
    'Return LoadRtfFormat(_fileName, richTB, DataFormats.Text)

    Dim abierto = False

    If File.Exists(_fileName) Then
        Dim range = New TextRange(richTB.Document.ContentStart,
                                  richTB.Document.ContentEnd)
        Using sr As New StreamReader(_fileName, Encoding.Default, True)
            Try
                ' leer el contenido para admitir tildes, etc.   (09/Ene/19)
                Dim texto = sr.ReadToEnd

                Dim textRange = New TextRange(richTB.Document.ContentStart,
                                              richTB.Document.ContentEnd)
                textRange.Text = texto

                abierto = True
            Catch ex As Exception
                MessageBox.Show("Error el formato no es válido" & vbCrLf &
                                ex.Message,
                                "Abrir Text",
                                MessageBoxButton.OK,
                                MessageBoxImage.Asterisk)
            End Try
        End Using
    End If

    Return abierto
End Function
/// <summary>
/// Adaptado del ejemplo de la documentación de Microsoft
/// https://docs.microsoft.com/es-es/dotnet/framework/wpf/
///     controls/richtextbox-overview
/// </summary>
private bool LoadRtfFormat(string _fileName, RichTextBox richTB, string formato)
{
    var abierto = false;

    if (File.Exists(_fileName))
    {
        var range = new TextRange(richTB.Document.ContentStart, 
                                  richTB.Document.ContentEnd);

        using (StreamReader sr = new StreamReader(_fileName, 
                                                  Encoding.Default, 
                                                  true))
        {
            try
            {
                // leer el contenido para admitir tildes, etc.   (09/Ene/19)
                var texto = sr.ReadToEnd();
                var stream = new MemoryStream(Encoding.UTF8.GetBytes(texto));

                range.Load(stream, formato);
                abierto = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error el formato no es válido\r\n"  + 
                                ex.Message, $"Abrir {formato}", 
                                MessageBoxButton.OK, 
                                MessageBoxImage.Asterisk);
            }
        }
    }

    return abierto;
}

private bool LoadRtf(string _fileName, RichTextBox richTB)
{
    return LoadRtfFormat(_fileName, richTB, DataFormats.Rtf);
}

// Al cargar como Xaml da error el Visual Studio
// ya que lo trata como una clase
// y esto en realidad es para abrir con el XAML generado,
// con los elementos Paragraph, Bold, Run, etc.
private bool LoadRtfXaml(string _fileName, RichTextBox richTB)
{
    return LoadRtfFormat(_fileName, richTB, DataFormats.Xaml);
}

/// <summary>
/// El formato texto abrirlo directamente
/// Aunque funciona igual que llamando a LoadRtfFormat
/// </summary>
private bool LoadRtfText(string _fileName, RichTextBox richTB)
{
    var abierto = false;

    if (File.Exists(_fileName))
    {
        var range = new TextRange(richTB.Document.ContentStart, 
                                  richTB.Document.ContentEnd);

        using (StreamReader sr = new StreamReader(_fileName, 
                                                  Encoding.Default, 
                                                  true))
        {
            try
            {
                // leer el contenido para admitir tildes, etc.   (09/Ene/19)
                var texto = sr.ReadToEnd();

                var textRange = new TextRange(richTB.Document.ContentStart, 
                                              richTB.Document.ContentEnd);
                textRange.Text = texto;

                abierto = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error el formato no es válido\r\n" + 
                                ex.Message, 
                                "Abrir Text", 
                                MessageBoxButton.OK, 
                                MessageBoxImage.Asterisk);
            }
        }
    }

    return abierto;
}

Nota:
El código del método LoadRtfText se podría haber reducido haciendo una llamada al método LoadRtfFormat e indicando como último argumento DataFormats.Text. Pero… ese también vale, por si quieres verlo de la forma tradicional.

Te recuerdo que el tipo XAML no es un archivo de diseño normal XAML / WPF si no el formato XAML del contenido del RichTextBox.

Para llamar a ese método lo haremos desde el método de evento MnuAbrir_Click que es el que usará la aplicación cuando el usuario pulse en el menú Abrir.

Veamos el código para VB y C# y te explico un par de detalles.

Private Sub MnuAbrir_Click(sender As Object, e As RoutedEventArgs)
    If rtbModificado Then
        If MessageBox.Show("El texto está modificado," & vbCrLf &
                           "¿seguro que quieres abir?",
                           "Texto Modificado",
                           MessageBoxButton.YesNo,
                           MessageBoxImage.Question) = MessageBoxResult.No Then
            Exit Sub
        End If
    End If
    Dim oFD As New OpenFileDialog
    oFD.Filter = "Formato RTF|*.rtf|" &
                 "Formato Xaml del contenido RTF|*.xaml|" &
                 "Código C# y VB|*.cs;*.vb|" &
                 "Texto (*.txt)|*.txt|Todos (*.*)|*.*"

    oFD.Title = "Selecciona el archivo"
    oFD.InitialDirectory = My.Computer.FileSystem.SpecialDirectories.MyDocument
    oFD.FileName = ""
    If oFD.ShowDialog() Then
        Dim ext = System.IO.Path.GetExtension(oFD.FileName).ToLower()
        Select Case ext
            Case ".rtf"
                LoadRtf(oFD.FileName, rtb)
            Case ".xaml"
                LoadRtfXaml(oFD.FileName, rtb)
            Case Else
                LoadRtfText(oFD.FileName, rtb)
        End Select

        rtbModificado = False
        lblStatus.Content = $"Texto cargado de: {oFD.FileName}"
    End If
End Sub
private void MnuAbrir_Click(object sender, RoutedEventArgs e)
{
    if (rtbModificado)
    {
        if (MessageBox.Show("El texto está modificado,\r\n" + 
                            "¿seguro que quieres abir?", 
                            "Texto Modificado", 
                            MessageBoxButton.YesNo, 
                            MessageBoxImage.Question) == MessageBoxResult.No)
            return;
    }
    OpenFileDialog oFD = new OpenFileDialog();
    oFD.Filter = "Formato RTF|*.rtf|" + "Formato Xaml del contenido RTF|*.xaml|" + 
                 "Código C# y VB|*.cs;*.vb|" + "Texto (*.txt)|*.txt|Todos (*.*)|*.*";

    oFD.Title = "Selecciona el archivo";
    // oFD.InitialDirectory = My.Computer.FileSystem.SpecialDirectories.MyDocuments;
    oFD.InitialDirectory = Environment.GetFolderPath(
                                        Environment.SpecialFolder.MyDocuments);
    oFD.FileName = "";
    if (oFD.ShowDialog() == true)
    {
        var ext = System.IO.Path.GetExtension(oFD.FileName).ToLower();
        switch (ext)
        {
            case ".rtf":
                {
                    LoadRtf(oFD.FileName, rtb);
                    break;
                }

            case ".xaml":
                {
                    LoadRtfXaml(oFD.FileName, rtb);
                    break;
                }

            default:
                {
                    LoadRtfText(oFD.FileName, rtb);
                    break;
                }
        }

        rtbModificado = false;
        lblStatus.Content = $"Texto cargado de: {oFD.FileName}";
    }
}

En este método se comprueba si el texto está modificado, de ser así, da la oportunidad para guardarlo antes de abrir el nuevo.

El método OpenFileDialog está definido en el espacio de nombres Microsoft.Win32, si prefieres el clásico de Windows Forms, tendrás que agregar una referencia al proyecto a esa biblioteca de WinForms.
Para los de Visual Basic no hay gran diferencia, salvo que el método ShowDialog devuelve True o False según se haya aceptado o cancelado. En el caso de C# hay que comprobarlo con el signo de igualdad, ya que el valor devuelto por el método ShowDialog es de tipo bool?.

En cuanto al directorio de inicio (InitialDirectory) con Visual Basic he utilizado el objeto My, para C# tengo una clase que simula algunas de las características de My, concretamente My.Properties, My.Application.Info y My.Computer.FileSystem.SpelciaDirectories, pero he preferido usar aquí la llamada directa a las clases de .NET (que es lo que supongo que harán las definiciones correspondientes de Visual Basic). Concretamente la llamada al método Environment.GetFolderPath al que le pasamos el valor MyDocuments de la enumeración Environment.SpecialFolder y devuelve el valor como una cadena, que es lo que necesitamos aquí.

El tipo de archivo lo dará la extensión del mismo y eso hago, una comprobación según la extensión, diferenciando los tipos Rtf y Xaml del resto, que los considero como texto (Text).

Finalmente mostramos en la etiqueta de información el nombre del archivo e indicamos que el texto no se ha modificado.

El código para guardar en un archivo el contenido del RichTextBox

Ahora le toca la parte de guardar. El método principal lo he llamado SaveRtfFormat que también está en el ejemplo sobre RichTextBox que te he indicado antes.

Veamos el código para Visual Basic y C#.

''' <summary>
''' Adaptado del ejemplo de la documentación de Microsoft
''' https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
'''     richtextbox-overview
''' </summary>
Private Function SaveRtfFormat(_fileName As String,
                               richTB As RichTextBox,
                               formato As String) As Boolean
    Dim range As TextRange
    Dim guardado As Boolean = False

    range = New TextRange(richTB.Document.ContentStart, richTB.Document.ContentEnd)
    Using fStream As New FileStream(_fileName, FileMode.Create)
        Try
            range.Save(fStream, formato)
            guardado = True
        Catch ex As Exception
            MessageBox.Show("Error el formato no es válido" & vbCrLf &
                            ex.Message,
                            $"Guardar {formato}",
                            MessageBoxButton.OK,
                            MessageBoxImage.Asterisk)
        End Try

        fStream.Close()
    End Using

    Return guardado
End Function

Private Function SaveRtf(ByVal _fileName As String,
                         richTB As RichTextBox) As Boolean
    Return SaveRtfFormat(_fileName, richTB, DataFormats.Rtf)
End Function
Private Function SaveRtfXaml(ByVal _fileName As String,
                             richTB As RichTextBox) As Boolean
    Return SaveRtfFormat(_fileName, richTB, DataFormats.Xaml)
End Function

''' <summary>
''' El formato Text lo guardo como texto normal
''' </summary>
Private Function SaveRtfText(ByVal _fileName As String,
                        richTB As RichTextBox) As Boolean
    'Return SaveRtfFormat(_fileName, richTB, DataFormats.Text)

    Dim guardado = False

    Try
        Dim texto = getRtbText(richTB)
        Using sw As New StreamWriter(_fileName, False, Encoding.Default)
            sw.WriteLine(texto)
        End Using

        guardado = True
    Catch ex As Exception
        MessageBox.Show("Error al guardar:" & vbCrLf &
                        ex.Message,
                        "Guardar Text",
                        MessageBoxButton.OK,
                        MessageBoxImage.Asterisk)
    End Try

    Return guardado
End Function
/// <summary>
/// Adaptado del ejemplo de la documentación de Microsoft
/// https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
///     richtextbox-overview
/// </summary>
private bool SaveRtfFormat(string _fileName, RichTextBox richTB, string formato)
{
    TextRange range;
    bool guardado = false;

    range = new TextRange(richTB.Document.ContentStart, 
                          richTB.Document.ContentEnd);

    using (FileStream fStream = new FileStream(_fileName, 
                                               FileMode.Create))
    {
        try
        {
            range.Save(fStream, formato);
            guardado = true;
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error el formato no es válido\r\n"+ 
                            ex.Message, 
                            $"Guardar {formato}", 
                            MessageBoxButton.OK, 
                            MessageBoxImage.Asterisk);
        }

        fStream.Close();
    }

    return guardado;
}

private bool SaveRtf(string _fileName, RichTextBox richTB)
{
    return SaveRtfFormat(_fileName, richTB, DataFormats.Rtf);
}
private bool SaveRtfXaml(string _fileName, RichTextBox richTB)
{
    return SaveRtfFormat(_fileName, richTB, DataFormats.Xaml);
}

/// <summary>
/// El formato Text lo guardo como texto normal
/// </summary>
private bool SaveRtfText(string _fileName, RichTextBox richTB)
{
    var guardado = false;

    try
    {
        var texto = getRtbText(richTB);
        using (StreamWriter sw = new StreamWriter(_fileName, 
                                                  false, 
                                                  Encoding.Default))
        {
            sw.WriteLine(texto);
        }

        guardado = true;
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error al guardar:\r\n" + 
                        ex.Message, 
                        "Guardar Text", 
                        MessageBoxButton.OK, 
                        MessageBoxImage.Asterisk);
    }

    return guardado;
}

/// <summary>
/// Extrae el texto de un RichTextBox y lo devuelve como una cadena.
/// De un ejemplo en C# de:
/// https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
///     how-to-extract-the-text-content-from-a-richtextbox
/// </summary>
private string getRtbText(RichTextBox rtb)
{
    var textRange = new TextRange(rtb.Document.ContentStart,
                                  rtb.Document.ContentEnd);
    return textRange.Text;
}

Como en el método SaveRtfText lo hago directamente, llamo al método getRtbText que ya vimos en el post anterior.

Estos métodos los llamaremos desde el método de evento relacionado con el evento Click del menú Guardar como…, tal como te muestro a continuación.

Private Sub MnuGuardar_Click(sender As Object, e As RoutedEventArgs)
    Dim oFD As New SaveFileDialog
    oFD.Filter = "Formato RTF|*.rtf|" &
                 "Formato Xaml del contenido RTF|*.xaml|" &
                 "Código C# y VB|*.cs;*.vb|" &
                 "Texto (*.txt)|*.txt|Todos (*.*)|*.*"

    oFD.Title = "Selecciona el archivo para guardar el contenido RTF"
    oFD.InitialDirectory = My.Computer.FileSystem.SpecialDirectories.MyDocuments
    oFD.FileName = ""
    If oFD.ShowDialog() Then
        Dim ext = System.IO.Path.GetExtension(oFD.FileName).ToLower()
        Select Case ext
            Case ".rtf"
                SaveRtf(oFD.FileName, rtb)
            Case ".xaml"
                SaveRtfXaml(oFD.FileName, rtb)
            Case Else
                SaveRtfText(oFD.FileName, rtb)
        End Select

        rtbModificado = False
        lblStatus.Content = $"Texto guardado como: {oFD.FileName}"
    End If

End Sub
private void MnuGuardar_Click(object sender, RoutedEventArgs e)
{
    SaveFileDialog oFD = new SaveFileDialog();
    oFD.Filter = "Formato RTF|*.rtf|" + "Formato Xaml del contenido RTF|*.xaml|" + 
                 "Código C# y VB|*.cs;*.vb|" + "Texto (*.txt)|*.txt|Todos (*.*)|*.*";

    oFD.Title = "Selecciona el archivo para guardar el contenido RTF";
    // oFD.InitialDirectory = My.Computer.FileSystem.SpecialDirectories.MyDocuments;
    oFD.InitialDirectory = Environment.GetFolderPath(
                                        Environment.SpecialFolder.MyDocuments);
    oFD.FileName = "";
    if (oFD.ShowDialog() == true)
    {
        var ext = System.IO.Path.GetExtension(oFD.FileName).ToLower();
        switch (ext)
        {
            case ".rtf":
                {
                    SaveRtf(oFD.FileName, rtb);
                    break;
                }

            case ".xaml":
                {
                    SaveRtfXaml(oFD.FileName, rtb);
                    break;
                }

            default:
                {
                    SaveRtfText(oFD.FileName, rtb);
                    break;
                }
        }

        rtbModificado = false;
        lblStatus.Content = $"Texto guardado como: {oFD.FileName}";
    }
}

Aquí nada especial que contar, salvo que en vez de usar la clase OpenFileDialog usamos SaveFileDialog, el resto, es lo mismo que te expliqué antes.

Y este es el código principal de la aplicación, a falta de la comprobación al cerrar el formulario, perdón ventana, principal que se comprueba si el código se ha modificado y el evento TextChanged del control RichTextBox.

Private Sub Rtb_TextChanged(sender As Object, e As TextChangedEventArgs)
    If inicializando Then Return

    rtbModificado = True
End Sub



Private Sub Window_Closing(sender As Object, e As CancelEventArgs)
    ' Comprobar si están modificados
    If rtbModificado Then
        If MessageBox.Show("El texto está modificado," & vbCrLf &
                           "¿Quieres guardarlo?",
                           "Texto Modificado",
                           MessageBoxButton.YesNo,
                           MessageBoxImage.Question) = MessageBoxResult.Yes Then
            rtb.Focus()
            MnuGuardar_Click(mnuGuardar, Nothing)
        End If
    End If
End Sub
private void Rtb_TextChanged(object sender, TextChangedEventArgs e)
{
    if (inicializando)
        return;

    rtbModificado = true;
}

private void Window_Closing(object sender, CancelEventArgs e)
{
    // Comprobar si est\f2án modificados
    if (rtbModificado)
    {
        if (MessageBox.Show("El texto está modificado,\r\n" + 
                            "¿Quieres guardarlo?", 
                            "Texto Modificado", 
                            MessageBoxButton.YesNo, 
                            MessageBoxImage.Question) == MessageBoxResult.Yes)
        {
            rtb.Focus();
            MnuGuardar_Click(mnuGuardar, null);
        }
    }
}

Ah, sí, una cosilla que he puesto en el evento de carga de la ventana (evento Window_Loaded) en el que asigno un ancho grande al documento interno del RichTextBox con idea de que no se muestren las líneas cortadas (ver figuras 1 y 2).

Figura 1. Si no asignamos un valor grande a Document.PageWidth, las líneas largas se mostrarán partidas
Figura 1. Si no asignamos un valor grande a Document.PageWidth, las líneas largas se mostrarán partidas
Figura 2. Al asignar un valor alto a Document.PageWidth las líneas no se cortan
Figura 2. Al asignar un valor alto a Document.PageWidth las líneas no se cortan

Nota:
El archivo mostrado en las figuras 1 y 2 es uno en formato RTF coloreado por mi aplicación gsColorear.

Lo que hago es asignar un valor 2000 a la propiedad PageWidth del objeto Document del RichTextBox (si crees que tendrás líneas de más de 2000, pues ponle un valor mayor).

Ese ejemplo (o el truco para hacer eso) lo saqué (creo) de un foro de StackOverflow:
C#/WPF: Disable Text-Wrap of RichTextBox.

Private Sub Window_Loaded(sender As Object,
                          e As RoutedEventArgs)
    ' Para que no corte las líneas
    rtb.Document.PageWidth = 2000

    lblStatus.Content = My.Application.Info.Copyright & " - " &
                        My.Application.Info.Description

    inicializando = False

    rtb.Focus()

End Sub
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // Para mostrar la info del Copyright y Description
    FileVersionInfo fvi;
    System.Reflection.Assembly ensamblado;
    ensamblado = System.Reflection.Assembly.GetExecutingAssembly();
    fvi = FileVersionInfo.GetVersionInfo(ensamblado.Location);

    // Para que no corte las líneas
    rtb.Document.PageWidth = 2000;

    //lblStatus.Content = My.Application.Info.Copyright + " - " +
    //                    My.Application.Info.Description;

    lblStatus.Content = fvi.LegalCopyright + " - " +
                        fvi.Comments;

    inicializando = false;

    rtb.Focus();
}

Nota:
Esto ocurre (que se corten las líneas) porque el control RichTextBox no tiene una propiedad TextWrapping como ocurre con el control TextBox.

Eso mismo se puede hacer asignando el valor directamente en la definición del objeto FlowDocument del RichTextBox:

<FlowDocument PageWidth="2000" />

Resaltar en el código para C# que como no tiene el objeto My de Visual Basic, para acceder a la información del Copyright y Description uso llamadas directas a la información ofrecida por la clase FileVersionInfo a partir del ensamblado actual.

Pues ya está, esto es todo… para la próxima ocasión veremos cómo cambiar algunas propiedades del contenido del RichTextBox, como por ejemplo el tipo y tamaño de la letra y algunas cosillas más, ya que si pruebas a abrir un archivo de texto plano, pues… como que no se ve muy bien, en la figura 3 tienes un ejemplo de cómo se vería un archivo de C# (sin colorear).

Figura 3. Así se vería el contenido de un archivo abierto como texto
Figura 3. Así se vería el contenido de un archivo abierto como texto

Nos vemos.
Guillermo

Aquí tienes el código completo para poder usarla (una vez descomprimido) con la solución para Visual Studio 2017. Están los dos proyectos, el de Visual Basic y el de C# (como de costumbre).

El zip: Wpf_Abrir_guardar_RichTextBox_20190112_2355.zip (34.7 KB)

MD5 Checksum: 461518743ED0913B642C788342774057

Crear un icono con Visual Studio a partir de una imagen

Pues eso… esto en realidad lo empecé a incluir en el post que estoy escribiendo sobre cómo implementar las opciones de abrir y guardar archivos usando un control RichTextBox para WPF, opero se iba a hacer demasiado largo, así que… he decidido publicarlo como post separado, ya que creo que puede servir para otra gente que no quieran saber nada sobre WPF ni RichTextBox 😉

Yo estoy usando Visual Studio 2017, pero creo que servirá con las versiones anteriores.

Lo que te voy a explicar es cómo crear un icono a partir de una imagen incluida como recurso en el proyecto, concretamente en una carpeta llamada Images. Como Visual Basic y C# no tienen exactamente las mismas opciones a la hora de añadir un nuevo icono ni a la hora de seleccionar el icono del proyecto, te pongo capturas diferenciadas para cada uno de esos lenguajes. El resto de acciones son comunes a los dos lenguajes.

Veamos paso a paso cómo crear un icono a partir de una imagen

Lo primero es seleccionar la imagen que queremos como un icono (figura1), con el botón secundario del ratón y elegimos la opción Abrir con… eso nos muestra otra ventana (figura 2) seleccionamos Paint y pulsamos en Aceptar.

Figura 1. Abrir la imagen a usar como icono
Figura 1. Abrir la imagen a usar como icono
Figura 2. Abrir con Paint
Figura 2. Abrir con Paint

La imagen se abre en Paint, allí indicamos Seleccionar todo y después le damos a copiar y ya tenemos la imagen en el portapapeles. ¡No la dejes escapar! Winking smile

Volvemos a Visual Studio y agregamos al proyecto un nuevo archivo de tipo icono (figura 3): Menú contextual sobre el proyecto>Agregar>Nuevo elemento…

Figura 3. Añadir un nuevo archivo de tipo icono
Figura 3. Añadir un nuevo archivo de tipo icono

Nota:
En C# te mostrará una lista con todo lo que puedes añadir, entre los cuales se incluye Archivo de icono.
En Visual Basic, te mostrará la lista de Elementos comunes, tendrás que selecciona la rama General y ahí estará el Archivo de icono.

Habrá un montón de tipos de imágenes con varias resoluciones, pero la que nos interesa es la de 16×16 de 24 bits, que no está, así que… tendrás que añadirla. Pulsa con el botón secundario del ratón sobre una de las imágenes que hay para que muestre el menú contextual y selecciona Nuevo tipo de imagen… y tendrás un cuando de diálogo como el mostrado en la figura 4.

Figura 4. Añadimos una imagen de 16x16 de 24 bits
Figura 4. Añadimos una imagen de 16×16 de 24 bits

Nota:
Lo mismo piensas que la de 32 bits nos puede valer, ¡pero no! ya que en ese tipo de imágenes, Visual Studio no permite la edición.

En la barra de herramientas de edición de imágenes selecciona Herramienta Seleccionar rectángulo (figura 5) (para que se seleccione la imagen y poder pegar en ella) y dale a pegar y te quedará algo como lo mostrado en la imagen 6. El icono ese de 256 es uno de los que estaban ya al crear el icono.

Figura 5. Seleccionamos Herramienta Seleccionar rectángulo
Figura 5. Seleccionamos Herramienta Seleccionar rectángulo
Figura 6. Aspecto al pegar la imagen copiada en la memoria en el icono
Figura 6. Aspecto al pegar la imagen copiada en la memoria en el icono

Todos los iconos que hay, salvo el de 16×16 de 24 bits, los tenemos que eliminar. Para ello, selecciona el icono a eliminar, pulsa con el botón secundario del ratón y del menú desplegable que te muestra, elige Eliminar tipo de imagen (figura 7). Eso lo tienes que hacer con todas las imágenes, salvo la que nos interesa.

Figura 7. Eliminar los tipos de imágenes que no nos interesan
Figura 7. Eliminar los tipos de imágenes que no nos interesan

Una vez que hemos quitado las imágenes que nos sobran, guárdalo.

El siguiente paso es abrir las propiedades del proyecto y seleccionar el icono que hemos guardado. En las propiedades de Aplicación selecciona el icono. En Visual Basic será Icono (figura 8) y en C# será donde indica Icono y manifiesto (figura 9). En ambos casos, el icono que acabamos guardado estará en la lista de iconos disponibles.

Figura 8. Seleccionar el icono en el proyecto de Visual Basic
Figura 8. Seleccionar el icono en el proyecto de Visual Basic
Figura 9. Seleccionar el icono en el proyecto de C#
Figura 9. Seleccionar el icono en el proyecto de C#

¡Y ya está!

Espero que te sea de utilidad.

Nota:
La imagen usada para el icono es una basada en una imagen PNG que se incluye con los recursos de Visual Studio 2017, concretamente RichTextBox_16x.png.

Si quieres las imágenes de recursos de Visual Studio 2017, pulsa en el siguiente enlace: Biblioteca de imágenes de Visual Studio.

Nos vemos.
Guillermo

El contenido de un RichTextBox de WPF, leer como cadena y asignar uno nuevo

Pues eso… sigo con las cosillas que estoy aprendiendo a hacer al meterme con esto de programar RichTextBox en WPF (Xaml) y ahora le toca el turno a leer el contenido de un RichTextBox y devolverlo como cadena y a lo contrario: teniendo una cadena, asignarla a un RichTextBox, básicamente ese texto a asignar será en formato de texto enriquecido (Rtf).

El código que te voy a mostrar está adaptado de los que me he encontrado buscando en la web, adaptado porque están en C# y los he convertido a Visual Basic y porque les he añadido otras comprobaciones para que funcione como yo quiero Smile

Devolver como cadena el texto de un RichTextBox (WPF)

Este código que lo he puesto en un método llamado getRtbText al que se pasa como argumento el control RichTextBox del que queremos extraer el contenido lo he adaptado de un ejemplo de la documentación en línea de Microsoft: Cómo: Extraer el contenido de texto de un control RichTextBox y básicamente lo que hace es definir un rango con el contenido completo del RichTextBox y devolverlo, muy simple, pero debes saber que existe una cosa llamada TextRange In love

Por eso existen los foros, blogs y demás, para que nos iluminen con esas cosas que desconocemos.

Aquí tienes el código para Visual Basic y C# tal como lo estoy usando en la aplicación de ejemplo para manipular y sincronizar el contenido de dos RichTextBox de WPF.

''' <summary>
''' Extrae el texto de un RichTextBox y lo devuelve como una cadena.
''' De un ejemplo en C# de:
''' https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
'''     how-to-extract-the-text-content-from-a-richtextbox
''' </summary>
Private Function getRtbText(ByVal rtb As RichTextBox) As String
    Dim textRange = New TextRange(rtb.Document.ContentStart,
                                  rtb.Document.ContentEnd)
    Return textRange.Text
End Function
/// <summary>
///  Extrae el texto de un RichTextBox y lo devuelve como una cadena.
///  De un ejemplo en C# de:
///  https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
///      how-to-extract-the-text-content-from-a-richtextbox
///  </summary>
private string getRtbText(RichTextBox rtb)
{
    var textRange = new TextRange(rtb.Document.ContentStart, 
                                  rtb.Document.ContentEnd);
    return textRange.Text;
}

Según la documentación, TextRange es una selección de contenido entre dos posiciones indicadas por TextPointers, y entre otras cosas, tiene una propiedad (Text) que devuelve el texto que hay entre esas dos posiciones.

Simple, ¿verdad? Pues eso… Pero yo no tenía ni idea de que existía TextRange Eye rolling smile.

Nota:
El valor obtenido con esa función es el texto plano, es decir, sin formato, del contenido del control RichTextBox.

Sigamos…

Asignar el contenido de una cadena a un RichTextBox (WPF)

Y esta es la segunda cosa que te quiero explicar hoy. Teniendo una cadena de texto enriquecido (con código interno de RTF o de otros formatos) asignarla a un RichTextBox.

Hay un montón de formatos aceptados por el RichTextBox, los que están en la enumeración DataFormats, que son tanto de imágenes como de texto, pero aquí solo veremos tres:

  • Rtf el contenido a asignar debe estar en formato RTF.
  • Xaml el contenido a asignar debe estar en formato Xaml pero el que se puede poner en un RichTextBox o FlowDocument, en mis pruebas me ha admitido solo lo que está dentro de Section (ver el código de ejemplo). Es decir, no vale cualquier código Xaml.
  • Text Formato de texto plano, sin ningún tipo de formato.

La función que se encarga de asignar el contenido del RichTextBox se llama setRtbText que recibe como argumentos el control RichTextBox al que queremos asignar el texto, el texto a asignar y como último argumento el formato, que debe ser uno de los indicados en la enumeración DataFormats o el hard-code correspondiente, por ejemplo, para Rtf es Rich Text Format, para Xaml es Xaml y para Text es Text.

Nota:
En el enlace de DataFormats en la documentación en línea, te indica los hard-codes que puedes usar.

Te muestro el código de la función setRtbText y verás que el último parámetro es opcional y he puesto por defecto el valor de DataFormats.Rtf.

''' <summary>
''' Asigna el texto al RichTextBox
''' Adaptado de un código de C# de:
''' https://stackoverflow.com/questions/1367256/
'''     set-rtf-text-into-wpf-richtextbox-control
''' </summary>
Private Sub setRtbText(rtb As RichTextBox,
                       text As String,
                       Optional formato As String = "Rich Text Format")
    ' Antes usaba ASCII                                         (08/Ene/19)
    '   ASCIIEncoding.Default.GetBytes(text)
    Dim stream = New MemoryStream(Encoding.UTF8.GetBytes(text))

    ' Borrar el contenido del RichTextBox
    ' antes de añadir el nuevo contenido
    ' ya que me ha pasado que mezclaba el texto nuevo con 
    ' el que ya tenía
    rtb.Document.Blocks.Clear()

    ' Selection.Load(stream, DataFormats.Rtf)
    rtb.Selection.Load(stream, formato)

End Sub
/// <summary>
///  Asigna el texto al RichTextBox
///  Adaptado de un código de C# de:
///  https://stackoverflow.com/questions/1367256/
///     set-rtf-text-into-wpf-richtextbox-control
///  </summary>
private void setRtbText(RichTextBox rtb, string text,
                        string formato = "Rich Text Format")
{
    // Antes usaba ASCII                                         (08/Ene/19)
    var stream = new MemoryStream(Encoding.UTF8.GetBytes(text));

    // Borrar el contenido del RichTextBox
    // antes de añadir el nuevo contenido
    // ya que me ha pasado que mezclaba el texto nuevo con 
    // el que ya tenía
    rtb.Document.Blocks.Clear();

    rtb.Selection.Load(stream, formato);
}

Para llamar a este método podemos hacerlo indicando en el tercer argumento un valor de la enumeración DataFormats o la cadena correspondiente a su valor (hard-code string) y en este caso, como el tercer parámetro es opcional, si no lo indicamos usará el Rtf o la cadena correspondiente Rich Text Format.

Escucho voces… a ver, a ver… Call me Valeee… que quieres un ejemplo completo de cómo usar todo esto… valeeee ¡a sus órdenes! Flirt male

Un ejemplo práctico para usar todo lo explicado

Y con un par de extras, ya que para que te resulte más fácil probar los tres formatos indicados, incluyo código de ejemplo RTF y XAML (el de texto no tiene mucho misterio).

Ese código o texto de ejemplo está hard-code-ado (ya que estamos con el palabro ese), es decir, como en el código lo asigno como cadena, las cadenas que contengan deben tener dobles comillas dobles para que el editor usando (en mi caso el de Visual Studio) no lo interprete de forma no-texto.

El código Xaml con el diseño de la aplicación (ventana principal)

Este código vale tanto para Visual Basic como para C#, lo único que hay que indicar es el nombre del espacio de nombres (namespace) de la aplicación que hayas creado para probarlo.

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

         <!-- Aquí tienes que indicar el espacio de nombres 
             (y quitar este comentario o te dará error) -->
        xmlns:local="clr-namespace:Wpf_Leer_y_asignar_contenido_de_RichTextBox_vb"

        mc:Ignorable="d"
        Title="Leer y asignar contenido de RichTextBox (VB)" 
        WindowStartupLocation="CenterScreen"
        Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" MaxWidth="100"/>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition Width="Auto" MaxWidth="250" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" MaxHeight="30"/>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition Height="Auto" MaxHeight="30"/>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Label Content="El texto: " 
               Grid.Column="0" Grid.ColumnSpan="1" 
               FontWeight="Bold"
               HorizontalAlignment="Left" />
        <StackPanel Orientation="Horizontal"
                    Grid.Column="1" Grid.ColumnSpan="2"
                    HorizontalAlignment="Right">
            <Label Content="Pulsa para pegar texto en formatos diferentes"/>
            <Button x:Name="btnRtf" Click="BtnRtf_Click"
                    Content="Ejemplo RTF" Margin="4" />
            <Button x:Name="btnXaml" Click="BtnXaml_Click"
                    Content="Ejemplo Xaml" Margin="4" />
            <Button x:Name="btnText" Click="BtnText_Click"
                    Content="Ejemplo Text" Margin="4" />
        </StackPanel>
        <TextBox x:Name="txt" 
                 BorderThickness="2" BorderBrush="Blue"
                 AcceptsReturn="True"
                 AcceptsTab="True"
                 TextWrapping="Wrap"
                 VerticalScrollBarVisibility="Visible"
                 Grid.Column="0" Grid.Row="1" 
                 Grid.ColumnSpan="3" Grid.RowSpan="2" />
        <StackPanel Orientation="Vertical" Margin="4"
                    Grid.Row="1" Grid.Column="3" Grid.RowSpan="2">
            <Button x:Name="btnLeer" Click="BtnLeer_Click"
                    Grid.Column="3" 
                    Content=" Leer del RichTextBox " />
            <Separator Height="20" />
            <Button x:Name="btnAsignar" Click="BtnAsignar_Click"
                    Grid.Column="3" Grid.Row="2" 
                    Content=" Asignar al RichTextBox " />
            <Label Content="Opciones de asignar:" />
            <RadioButton x:Name="optRtf" Content="Formato RTF" IsChecked="True" />
            <RadioButton x:Name="optText" Content="Formato Text" />
            <RadioButton x:Name="optXaml" Content="Formato Xaml" />
        </StackPanel>
        <Label Grid.Row="3" Grid.ColumnSpan="3" 
               Content="El RichTextBox: "
               FontWeight="Bold"
               HorizontalAlignment="Left" />
        <RichTextBox x:Name="rtb" 
                     VerticalScrollBarVisibility="Visible"
                     BorderThickness="4" BorderBrush="Green"
                     AcceptsTab="True" AcceptsReturn="True"
                     Grid.Column="0" Grid.Row="4" 
                     Grid.ColumnSpan="5" Grid.RowSpan="3">
            <FlowDocument />
        </RichTextBox>
    </Grid>
</Window>

El aspecto en tiempo de diseño de este código es el mostrado en la siguiente figura:

Figura 1. La ventana de la aplicación en tiempo de diseño
Figura 1. La ventana de la aplicación en tiempo de diseño.

Como puedes comprobar, tenemos un control RichTextBox en la parte inferior (con borde verde), un TextBox en la parte superior (con borde azul), tres botones (arriba del todo) para pegar texto de ejemplo en cada uno de los tres formatos creo que son los más comunes (para manejar textos) y en la parte de la derecha otros dos botones, uno para leer el contenido del texto y recuperarlo como una cadena normal (string) que se mostrará en el TextBox y otro botón con tres opciones del formato que vamos a asignar.

A destacar (sobre el código XAML del diseño) es el uso de Grid con columnas y filas para indicar dónde irá cada control y dos StackPanel para agrupar los botones y las opciones y etiquetas.

El StackPanel de los tres botones tienen la opción Orientation = Horizontal para que se coloquen horizontalmente (apilados de izquierda a derecha) y el otro con Orientation = Vertical para que se apilen de arriba a abajo.

El código completo tanto para Visual Basic como para C#.

En ese código están asignadas las cadenas de los tres ejemplos a usar según se pulse en uno de los tres botones de ejemplo.

Visual Basic:

'------------------------------------------------------------------------------
' Leer y asignar texto a un RichTextBox                             (10/Ene/19)
' Para el artículo:
' El contenido de un RichTextBox de WPF, leer como cadena y asignar uno nuevo
'
' (c) Guillermo (elGuille) Som, 2019
'------------------------------------------------------------------------------
Option Strict On
Option Infer On

Imports System

Imports System.IO
Imports System.Text

Class MainWindow
    ''' <summary>
    ''' Extrae el texto de un RichTextBox y lo devuelve como una cadena.
    ''' De un ejemplo en C# de:
    ''' https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
    '''     how-to-extract-the-text-content-from-a-richtextbox
    ''' </summary>
    Private Function getRtbText(ByVal rtb As RichTextBox) As String
        Dim textRange = New TextRange(rtb.Document.ContentStart,
                                  rtb.Document.ContentEnd)
        Return textRange.Text
    End Function

    ''' <summary>
    ''' Asigna el texto al RichTextBox
    ''' Adaptado de un código de C# de:
    ''' https://stackoverflow.com/questions/1367256/
    '''     set-rtf-text-into-wpf-richtextbox-control
    ''' </summary>
    Private Sub setRtbText(rtb As RichTextBox,
                           text As String,
                           Optional formato As String = "Rich Text Format")
        ' Antes usaba ASCII                                         (08/Ene/19)
        '   ASCIIEncoding.Default.GetBytes(text)
        Dim stream = New MemoryStream(Encoding.UTF8.GetBytes(text))

        ' Borrar el contenido del RichTextBox
        ' antes de añadir el nuevo contenido
        ' ya que me ha pasado que mezclaba el texto nuevo con 
        ' el que ya tenía
        rtb.Document.Blocks.Clear()

        ' Selection.Load(stream, DataFormats.Rtf)
        rtb.Selection.Load(stream, formato)

    End Sub

    Private Sub BtnLeer_Click(sender As Object, e As RoutedEventArgs)
        ' Lee el contenido del RichTextBox y lo asigna a la caja de texto
        Dim s = getRtbText(rtb)
        txt.Text = s
    End Sub

    Private Sub BtnAsignar_Click(sender As Object, e As RoutedEventArgs)
        If optRtf.IsChecked Then
            setRtbText(rtb, txt.Text, DataFormats.Rtf)
        ElseIf optXaml.IsChecked Then
            setRtbText(rtb, txt.Text, DataFormats.Xaml)
        ElseIf optText.IsChecked Then
            setRtbText(rtb, txt.Text, DataFormats.Text)
        End If
    End Sub

    Private Sub BtnRtf_Click(sender As Object, e As RoutedEventArgs)
        txt.Text =
        {\colortbl ;\red255\green0\blue0;\red0\green176\blue80;}
\ 
Hola Mundo del texto enriquecido.\


        Esto es un \b fichero\b0  RTF.\

}"
        optRtf.IsChecked = True
    End Sub

    Private Sub BtnXaml_Click(sender As Object, e As RoutedEventArgs)
        txt.Text =
"<Section xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""> 
    <Paragraph>
      <Run>Paragraph 1</Run>
    </Paragraph>

  <Paragraph>
    Before the LineBreak in Paragraph.
    <LineBreak />
    After the LineBreak in Paragraph.
    <LineBreak/><LineBreak/>
    After two LineBreaks in Paragraph.
  </Paragraph>
  <Paragraph>
    <Span Background=""Red"" Foreground=""White""><Bold>Fondo rojo</Bold></Span>
    <LineBreak/>
    Texto normal, <Bold>en negrita</Bold>, <Italic>en itálica</Italic>
    más texto normal... <Span Foreground=""Red""><Bold>¡Atención!</Bold></Span>
  </Paragraph>
<Paragraph FontFamily=""Consolas"" 
           FontSize=""24"" FontWeight=""Bold""
           Foreground=""Green"">Párrafo con 
<Span Foreground=""Blue"">varios</Span> atributos de 
<Span Foreground=""FireBrick"">fuente</Span>.
</Paragraph>
<Paragraph FontSize=""28"" FontFamily=""Palatino Linotype""
  Typography.NumeralStyle=""OldStyle""
  Typography.Fraction=""Stacked""
  Typography.Variants=""Inferior"">
Ahora va de números... <LineBreak/>
  <Run>
    0123456789 10 11 12 13
  </Run>
  <LineBreak/><LineBreak/>
  <Run>
    1/2 2/3 3/4
  </Run>
</Paragraph>
</Section>
"
        optXaml.IsChecked = True
    End Sub

    Private Sub BtnText_Click(sender As Object, e As RoutedEventArgs)
        txt.Text =
"Érase una vez un texto normal, que está en el TextBox para pasarlo al 
RichTextBox, (si pulsas en el botón de asignar."
        optText.IsChecked = True
    End Sub
End Class

C#:

// ------------------------------------------------------------------------------
// Leer y asignar texto a un RichTextBox                             (10/Ene/19)
// Para el artículo:
// El contenido de un RichTextBox de WPF, leer como cadena y asignar uno nuevo
// 
// (c) Guillermo (elGuille) Som, 2019
// ------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Wpf_Leer_y_asignar_contenido_de_RichTextBox_cs
{
    /// <summary>
    /// Lógica de interacción para MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        ///  Extrae el texto de un RichTextBox y lo devuelve como una cadena.
        ///  De un ejemplo en C# de:
        ///  https://docs.microsoft.com/es-es/dotnet/framework/wpf/controls/
        ///      how-to-extract-the-text-content-from-a-richtextbox
        ///  </summary>
        private string getRtbText(RichTextBox rtb)
        {
            var textRange = new TextRange(rtb.Document.ContentStart,
                                          rtb.Document.ContentEnd);
            return textRange.Text;
        }

        /// <summary>
        ///  Asigna el texto al RichTextBox
        ///  Adaptado de un código de C# de:
        ///  https://stackoverflow.com/questions/1367256/
        ///     set-rtf-text-into-wpf-richtextbox-control
        ///  </summary>
        private void setRtbText(RichTextBox rtb, string text,
                                string formato = "Rich Text Format")
        {
            // Antes usaba ASCII                                         (08/Ene/19)
            var stream = new MemoryStream(Encoding.UTF8.GetBytes(text));

            // Borrar el contenido del RichTextBox
            // antes de añadir el nuevo contenido
            // ya que me ha pasado que mezclaba el texto nuevo con 
            // el que ya tenía
            rtb.Document.Blocks.Clear();

            rtb.Selection.Load(stream, formato);
        }

        private void BtnLeer_Click(object sender, RoutedEventArgs e)
        {
            // Lee el contenido del RichTextBox y lo asigna a la caja de texto
            var s = getRtbText(rtb);
            txt.Text = s;
        }

        private void BtnAsignar_Click(object sender, RoutedEventArgs e)
        {
            if (optRtf.IsChecked == true)
                setRtbText(rtb, txt.Text, DataFormats.Rtf);
            else if (optXaml.IsChecked == true)
                setRtbText(rtb, txt.Text, DataFormats.Xaml);
            else if (optText.IsChecked == true)
                setRtbText(rtb, txt.Text, DataFormats.Text);
        }

        private void BtnRtf_Click(object sender, RoutedEventArgs e)
        {
        {\colortbl ;\red255\green0\blue0;\red0\green176\blue80;}
\ 
Hola Mundo del texto enriquecido.\


        Esto es un \b fichero\b0  RTF.\

}";
            optRtf.IsChecked = true;
        }

        private void BtnXaml_Click(object sender, RoutedEventArgs e)
        {
            txt.Text = @"<Section xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""> 
    <Paragraph>
      <Run>Paragraph 1</Run>
    </Paragraph>

  <Paragraph>
    Before the LineBreak in Paragraph.
    <LineBreak />
    After the LineBreak in Paragraph.
    <LineBreak/><LineBreak/>
    After two LineBreaks in Paragraph.
  </Paragraph>
  <Paragraph>
    <Span Background=""Red"" Foreground=""White""><Bold>Fondo rojo</Bold></Span>
    <LineBreak/>
    Texto normal, <Bold>en negrita</Bold>, <Italic>en itálica</Italic>
    más texto normal... <Span Foreground=""Red""><Bold>¡Atención!</Bold></Span>
  </Paragraph>
<Paragraph FontFamily=""Consolas"" 
           FontSize=""24"" FontWeight=""Bold""
           Foreground=""Green"">Párrafo con 
<Span Foreground=""Blue"">varios</Span> atributos de 
<Span Foreground=""FireBrick"">fuente</Span>.
</Paragraph>
<Paragraph FontSize=""28"" FontFamily=""Palatino Linotype""
  Typography.NumeralStyle=""OldStyle""
  Typography.Fraction=""Stacked""
  Typography.Variants=""Inferior"">
Ahora va de números... <LineBreak/>
  <Run>
    0123456789 10 11 12 13
  </Run>
  <LineBreak/><LineBreak/>
  <Run>
    1/2 2/3 3/4
  </Run>
</Paragraph>
</Section>
";
            optXaml.IsChecked = true;
        }

        private void BtnText_Click(object sender, RoutedEventArgs e)
        {
            txt.Text = @"Érase una vez un texto normal, que está en el TextBox para pasarlo al 
RichTextBox, (si pulsas en el botón de asignar.";
            optText.IsChecked = true;
        }

    }
}

El código de ejemplo para Xaml interno

A resaltar el texto a asignar para el formato XAML, para que veas que no es el código XAML de diseño de WPF, por ejemplo que te he mostrado antes, si no, el usado internamente por el control.

Ese ejemplo lo podrías poner dentro de la definición de FlowDocument y quedaría de esta forma:

<Section xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <Paragraph>
        <Run>Paragraph 1</Run>
    </Paragraph>
    <Paragraph>
        Before the LineBreak in Paragraph.
        <LineBreak />
        After the LineBreak in Paragraph.
        <LineBreak/>
        <LineBreak/>
        After two LineBreaks in Paragraph.
    </Paragraph>
    <Paragraph>
        <Span Background="Red" Foreground="White">
            <Bold>Fondo rojo</Bold>
        </Span>
        <LineBreak/>
        Texto normal,
        <Bold>en negrita</Bold> ,
        <Italic>en itálica</Italic>
        más texto normal...
        <Span Foreground="Red">
            <Bold>¡Atención!</Bold>
        </Span>
    </Paragraph>
    <Paragraph FontFamily="Consolas" 
               FontSize="24" FontWeight="Bold"
               Foreground="Green">Párrafo con
        <Span Foreground="Blue">varios</Span> atributos de
        <Span Foreground="FireBrick">fuente</Span> .
    </Paragraph>
    <Paragraph FontSize="28" FontFamily="Palatino Linotype"
               Typography.NumeralStyle="OldStyle"
               Typography.Fraction="Stacked"
               Typography.Variants="Inferior">
        Ahora va de números...
        <LineBreak/>
        <Run>
            0123456789 10 11 12 13
        </Run>
        <LineBreak/>
        <LineBreak/>
        <Run>
            1/2 2/3 3/4
        </Run>
    </Paragraph>
</Section>

Fíjate en las asignaciones del párrafo último, para los números, que tiene definiciones tipográficas especiales. Ese ejemplo está sacado de la documentación en línea de Visual studio.

Aquí tienes una captura de la aplicación (usando el código de C#) en tiempo de ejecución mostrando el texto con el formato Xaml.

Figura 2. La aplicación en funcionamiento mostrando el código de ejemplo usado para el formato XAML.
Figura 2. La aplicación en funcionamiento mostrando el código de ejemplo usado para el formato XAML.

Y esto es todo por hoy, que para mí ya es casi mañana… le falta poco más de 15 minutos para que acabe el día 10 Winking smile

Y para la próxima… cómo guardar y leer desde ficheros. Es decir, leer el contenido de un archivo y asignarlo al RichTextBox (usando los tres formatos usados aquí) y guardar el contenido de un RichTextBox en esos tres formatos, pero eso será… ma-ña-na… Sleepy smile

Espero que te sea de utilidad… ya sabes que esa es la idea Be right back

Nos vemos.
Guillermo

Contar las palabras de un texto usando expresiones regulares (RegEx)

Pues eso… que estoy preparando el artículo para usar dos RichTextBox de WPF sincronizados y esta es una de las cosas que allí (cuando lo publique) te explicaré. Hay muchas cosas que te tengo que explicar, así que… hoy empiezo por la más simple o que menos texto necesita Winking smile

Esto es sencillo, pero… lo necesité para mostrar el número de palabras del documento activo (en el ejemplo de los RichTextBox sincronizados voy a usar 2 RichTextBox y un TextBox, los tres sincronizados -si así lo decides- ) y no lo investigué… preferí buscar una solución que alguien haya publicado ya… es que esto de las expresiones regulares, más que regulares son malas jajajajaja.

En serio, encontré un sitio web, en realidad un foro de Microsoft en el que alguien respondía con el código que te voy a mostrar, eso sí, el código original está en C# y para la aplicación base (ya sabes que empiezo escribiendo los ejemplos o mis aplicaciones con Visual Basic para .NET).

Una vez que se ve el código, uno dice… ¡pos qué fácil era! pero como todas estas cosas, hay que hacerlo primero Winking smile

Este es el código de una función que recibe una cadena de texto y devuelve el número de palabras que hay.

La versión de Visual Basic .NET

''' <summary>
''' Contar las palabras de una cadena de texto
''' 
''' Adaptado usando una cadena en vez del Text del RichTextBox
''' (sería del RichTextBox para WinForms)
''' El código lo he adaptado de:
''' https://social.msdn.microsoft.com/Forums/en-US/
'''     81e438ed-9d35-47d7-a800-1fabab0f3d52/
'''     c-how-to-add-a-word-counter-to-a-richtextbox
'''     ?forum=csharplanguage
''' </summary>
Private Function TextWordCount(texto As String) As Integer
    Dim wordColl As MatchCollection = Regex.Matches(texto, "[\W]+")
    Return wordColl.Count
End Function

La versión de C#:

/// <summary>
/// Contar las palabras de una cadena de texto
/// 
/// Adaptado usando una cadena en vez del Text del RichTextBox
/// (sería del RichTextBox para WinForms)
/// El código lo he adaptado de:
/// https://social.msdn.microsoft.com/Forums/en-US/
///     81e438ed-9d35-47d7-a800-1fabab0f3d52/
///     c-how-to-add-a-word-counter-to-a-richtextbox
///     ?forum=csharplanguage
/// </summary>
private int TextWordCount(string texto)
{
    MatchCollection wordColl = Regex.Matches(texto, @"[\W]+");
    return wordColl.Count;
}

Aquí tienes un ejemplo de uso en una aplicación de consola de .NET Core para Visual Basic y la salida del código.

Imports System
Imports System.Text.RegularExpressions

Module Program
    Sub Main(args As String())
        Dim s = "Hello World de las aplicaciones de consola en .net core!"
        Console.WriteLine(s)
        Dim w = TextWordCount(s)

        Console.WriteLine($"la cadena anterior tiene {w} palabras")

        Console.ReadKey()
    End Sub

    ''' <summary>
    ''' Contar las palabras de una cadena de texto
    ''' 
    ''' Adaptado usando una cadena en vez del Text del RichTextBox
    ''' (sería del RichTextBox para WinForms)
    ''' El código lo he adaptado de:
    ''' https://social.msdn.microsoft.com/Forums/en-US/
    '''     81e438ed-9d35-47d7-a800-1fabab0f3d52/
    '''     c-how-to-add-a-word-counter-to-a-richtextbox
    '''     ?forum=csharplanguage
    ''' </summary>
    Private Function TextWordCount(texto As String) As Integer
        Dim wordColl As MatchCollection = Regex.Matches(texto, "[\W]+")
        Return wordColl.Count
    End Function

End Module

Y esta es la salida por la consola de ese código.

Y esto es todo. Smile

¡Mañana más!

Nos vemos.
Guillermo

P.S.
Ese código funciona también en .NET Core, la captura es de una aplicación de consola de .NET Core