Archivo de la etiqueta: VB.NET

Temas relacionados con Visual Basic para .NET Framework

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

Sincronizar el scroll vertical y horizontal de varios TextBox

Pues eso, que lo prometido es deuda y como te comenté en el post anterior ahora te voy a ensañar el código Xaml y de VB y C# para sincronizar dos textboxes de forma horizontal y la sincronización vertical la haremos en tres cajas de texto.

Nota:
Este post puede herir la sensibilidad de algunos, sobre todo de los serios y los que se toman la vida demasiado a pecho, aunque yo diría que con un egoísmo que no es práctico ni saludable Sarcastic smile
El que avisa… Nerd smile

En la figura 1 puedes ver una captura en tiempo de ejecución del programa. Como puedes apreciar, hay tres cajas de texto, dos de ellas más grandes con contenido y en el centro otra pero que nos muestra los números de líneas.

Las cajas de la izquierda y de la derecha las vamos a tener sincronizadas tanto vertical como horizontalmente, de forma que cuando te desplaces por el texto de cualquiera de ellas la otra se sincronice o muestre la misma línea y columna.

Figura 1. La aplicación con el contenido de los textboxes sincronizados.
Figura 1. La aplicación con el contenido de los textboxes sincronizados.

No voy a entrar en muchos detalles (salvo que lo considere estrictamente necesario), ya que básicamente lo que te he explicado en el post anterior (Scroll sincronizado en varios TextBox en WPF) te vale para este, así que… te mostraré el código completo tanto de la ventana (Window) el código Xaml; pero solo una de ellas, ya que tanto la de Visual Basic como la de C# son idénticas, salvo por el valor asignado en xmlns:local=»clr-namespace: y en el título de la ventana (asignado en Title).

El código XAML

<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_scroll_completo_tres_textbox_vb"
        mc:Ignorable="d"
        Title="WPF Scroll sincronizado con tres TextBox (VB)" 
        Height="450" Width="800"
        WindowStartupLocation="CenterScreen"
        ResizeMode="CanResizeWithGrip" 
        WindowStyle="ThreeDBorderWindow"
        Loaded="Window_Loaded">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="Auto" MinWidth="36"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" MinHeight="24"/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition Height="Auto" MinHeight="24" MaxHeight="40"/>
        </Grid.RowDefinitions>
        <Label x:Name="lblInfo" 
               Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
               Background="{DynamicResource {x:Static SystemColors.InfoBrushKey}}"
               Margin="4" HorizontalContentAlignment="Center"
               Content="Dos TextBox sincronizados horizontal y verticalmente y otro sincronizado verticalmente"/>

        <!-- Si no queremos que se vean las barras de desplazamiento
             las ocultamos en el ScrollViewer 
             (aunque estén ocultas siguen funcionando) -->
        <ScrollViewer x:Name="svIzquierda" 
                      Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" 
                      HorizontalScrollBarVisibility="Visible"
                      ScrollChanged="Sv_ScrollChanged">

            <!-- Si el TextBox está contenido en un ScrollViewer
                no hace falta indicar en qué columna o fila del Grid está. -->
            <TextBox x:Name="txtIzquierda" 
                     Margin="0,2,2,0" Padding="4,0"
                     TextWrapping="Wrap" 
                     AcceptsReturn="True" 
                     AcceptsTab="True" 
                     TextChanged="TxtCodigo_TextChanged"
                     Text="Aquí irá el texto a mostrar en el primer TextBox "/>
        </ScrollViewer>
        <!-- Si no queremos que se vean las barras de desplazamiento
             las ocultamos en el ScrollViewer 
             (aunque estén ocultas siguen funcionando) -->
        <ScrollViewer x:Name="svFilas" 
                      Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" 
                      Focusable="False"
                      HorizontalScrollBarVisibility="Hidden"
                      VerticalScrollBarVisibility="Hidden"
                      ScrollChanged="Sv_ScrollChanged">

            <!-- Si el TextBox está contenido en un ScrollViewer
                no hace falta indicar en qué columna o fila del Grid está. -->
            <TextBox x:Name="txtFilas" 
                     TextWrapping="Wrap" 
                     Foreground="DarkCyan"
                     AllowDrop="False" Focusable="False" 
                     IsTabStop="False"/>
        </ScrollViewer>
        <ScrollViewer x:Name="svDerecha" 
                      Grid.Column="2" Grid.Row="1" 
                      Grid.RowSpan="2"
                      HorizontalScrollBarVisibility="Visible"
                      ScrollChanged="Sv_ScrollChanged">
            <TextBox x:Name="txtDerecha" 
                     Margin="0,2,2,0" Padding="4,0"
                     TextWrapping="Wrap" 
                     AcceptsReturn="True" 
                     AcceptsTab="True" 
                     TextChanged="TxtCodigo_TextChanged"
                     Text="Aquí irá el texto a mostrar en el segundo TextBox" />
        </ScrollViewer>
        <Label x:Name="lblInfo2" 
               Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3"
               Background="{DynamicResource {x:Static SystemColors.InfoBrushKey}}"
               Margin="4" HorizontalContentAlignment="Center"
               Content="Mueve el scroll vertical u horizontal o la rueda del ratón en el texto, el del centro no es visible, pero puedes desplazarte por el contenido."/>
    </Grid>
</Window>

Todos los eventos que vamos a manejar en la aplicación están definidos en el propio código Xaml así será más fácil para todos, sobre todo para el Guille ya que así la asignación de los métodos de eventos es más fácil al cambiar entre Visual Basic y C# Winking smile

Ya sabes que en Visual Basic podemos definir un método de evento simplemente añadiendo la cláusula Handles después del método, mientras que en C# hay que indicarlo expresamente.

Nota:
En una ocasión que hice un comentario como el anterior le añadí que así era mejor para los de Visual Basic que somos más torpes, y va uno y se enfadó y todo porque él no era torpe, en fin… hay que tomarse las cosas con una sonrisa, sobre todo si es una broma… pero ya sabes que en el mundillo este de la programación, si usas Visual Basic es que no sabes… y yo les digo a esos que así piensan: ¡yaumate!
(yaumate es eso… yaumate, no sé cómo traducirte esta expresión nerjeña, pero más o menos viene a decir ¡y un carajo! o ¡eso es lo que tú te crees!).
En mi caso, puede que yo sepa menos que muchos de los que usan C#, (también se mucho menos que muchos de los que usan VB) que ellos quieren usar el C#, pues muy bien, pero a mí me gusta programar con Visual Basic porque, al menos para mí, es más fácil que hacerlo con C#. Y eso que siempre, siempre, uso Option Strict On Winking smile

Bueno dejemos las discrepancias ente los usuarios de VB y los de C# y veamos el código, sí, para C# y Visual Basic… y empezaré con el de C#… para dejar lo bueno para el postre (jijijiji que malillo soy) In love

// ----------------------------------------------------------------------------
// Scroll sincronizado con dos TextBox con WPF                      (06/Ene/19)
// Sincronizados vertical y horizontalmente
// 
// (c) Guillermo (elGuille) Som, 2019
// ----------------------------------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;

namespace Wpf_scroll_completo_tres_textbox_cs
{
    partial class MainWindow
    {
        private bool inicializando = true;

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            txtIzquierda.Text = @"using System;
using System.Diagnostics;

static class Program {
    public static void Main(string[] args) {
        Console.WriteLine(""Hello World!"");
        Console.WriteLine(""La fecha y hora actual es: {0:dd/MM/yyyy HH:mm:ss:ffff}"", DateTime.Now);
        Console.WriteLine();

        Console.WriteLine(""Usando Tuplas"");
        (Nombre As String, Fecha As DateTime) datos;  // = (""elGuille"", DateTime.Now)
        datos = (""Guillermo"", DateTime.Now);
        Console.WriteLine(""{0} {1:dd/ MM / yyyy HH:mm : ss}"", datos.Nombre, datos.Fecha);
        Console.WriteLine();

        // Probando con las dos formas
        var exe = ""dotnet"";
        var arg = ""--version"";
        Console.Write($""{exe} {arg}  = "");
        //Console.Write(""{0} {1} = "", exe, arg);
        // ejecutar el proceso para saber la versión de dotnet Core
        ejecutarProceso(exe, arg);

        Console.Write(""Versión del compilador: "");
        var csc = @""C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Roslyn\csc.exe"";
        ejecutarProceso(csc, ""-langversion:?"");

        float s = 173.7619f;
        double d = s;

        short s1 = System.Convert.ToInt16(Math.Truncate(s));   // Result: 173
        int i2 = System.Convert.ToInt32(Math.Ceiling(d));      // Result: 174
        int i3 = System.Convert.ToInt32(Math.Round(s));        // Result: 174

        Console.WriteLine(""short s1 = System.Convert.ToInt16(Math.Truncate(s));   // Result: 173\r\n"" + 
            ""int i2 = System.Convert.ToInt32(Math.Ceiling(d));      // Result: 174\r\n"" + 
            ""int i3 = System.Convert.ToInt32(Math.Round(s));        // Result: 174"");
        Console.WriteLine();
        Console.WriteLine($""s1= {s1}, i2 = {i2}, i3 = {i3}"");
        Console.WriteLine(""s1= {0}, i2 = {1}, i3 = {2}"", s1, i2, i3);

        Console.ReadKey();
    }

    private static void ejecutarProceso(string exe, string arg, bool conKill = false) {
        Process p = new Process();

        p.StartInfo.FileName = exe;
        p.StartInfo.Arguments = arg;

        // Indicamos que queremos redirigir la salida
        p.StartInfo.RedirectStandardOutput = true;
        // Para redirigir la salida, UseShellExecute debe ser falso
        p.StartInfo.UseShellExecute = false;

        p.StartInfo.CreateNoWindow = true;

        try {
            // Iniciamos el proceso
            p.Start();

            // Esperar a que el proceso finalice
            // 
            // Esperamos 2 segundos para que le de tiempo a ejecutarse
            p.WaitForExit(2000);

            if (conKill)
                p.Kill();
                
            // Mostrar la salida en la consola
            Console.WriteLine(p.StandardOutput.ReadToEnd());

        } catch (Exception ex) {
            Console.WriteLine(ex.Message);

        }
    }
}
";

            txtDerecha.Text = @"Option Strict On
Option Infer On

Imports Microsoft.VisualBasic
Imports System
Imports System.Diagnostics

Module Program
    Sub Main(args As String())
        Console.WriteLine(""Hello World!"")
        Console.WriteLine(""La fecha y hora actual es: {0:dd/MM/yyyy HH:mm:ss:ffff}"", Date.Now)
        Console.WriteLine()

        Console.WriteLine(""Usando Tuplas"")
        Dim datos As (Nombre As String, Fecha As Date) ' = (""elGuille"", Date.Now)
        datos = (""Guillermo"", Date.Now)
        Console.WriteLine(""{0} {1:dd/MM/yyyy HH:mm:ss}"", datos.Nombre, datos.Fecha)
        Console.WriteLine()
        
        ' Probando con las dos formas
        Dim exe = ""dotnet""
        Dim arg = ""--version""
        Console.Write($""{exe} {arg}  = "")
        'Console.Write(""{0} {1} = "", exe, arg)
        ' ejecutar el proceso para saber la versión de dotnet Core
        ejecutarProceso(exe, arg)

        Console.Write(""Versión del compilador: "")
        Dim vbc = ""C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Roslyn\vbc.exe""
        ejecutarProceso(vbc, ""-langversion:?"")

        Dim s As Single = 173.7619
        Dim d As Double = s

        Dim i1 As Integer = CInt(Fix(s))               ' Result: 173
        Dim b1 As Byte = CByte(Int(d))                 ' Result: 173
        Dim s1 As Short = CShort(Math.Truncate(s))     ' Result: 173
        Dim i2 As Integer = CInt(Math.Ceiling(d))      ' Result: 174
        Dim i3 As Integer = CInt(Math.Round(s))        ' Result: 174

        Console.WriteLine(""Dim i1 As Integer = CInt(Fix(s))               ' Result: 173"" & vbCrLf &
                           ""Dim b1 As Byte = CByte(Int(d))                 ' Result: 173"" & vbCrLf &
                           ""Dim s1 As Short = CShort(Math.Truncate(s))     ' Result: 173"" & vbCrLf &
                           ""Dim i2 As Integer = CInt(Math.Ceiling(d))      ' Result: 174"" & vbCrLf &
                           ""Dim i3 As Integer = CInt(Math.Round(s))        ' Result: 174"")
        Console.WriteLine()
        Console.WriteLine(""i1= {0}, b1 = {1}"", i1, b1)
        Console.WriteLine($""s1= {s1}, i2 = {i2}, i3 = {i3}"")
        Console.WriteLine(""s1= {0}, i2 = {1}, i3 = {2}"", s1, i2, i3)

        Console.ReadKey()
    End Sub

    Private Sub ejecutarProceso(exe As String, arg As String, Optional conKill As Boolean = False)
        Dim p As New Process

        p.StartInfo.FileName = exe
        p.StartInfo.Arguments = arg

        ' Indicamos que queremos redirigir la salida
        p.StartInfo.RedirectStandardOutput = True
        ' Para redirigir la salida, UseShellExecute debe ser falso
        p.StartInfo.UseShellExecute = False

        p.StartInfo.CreateNoWindow = True

        Try
            ' Iniciamos el proceso
            p.Start()

            ' Esperar a que el proceso finalice
            '
            ' Esperamos 2 segundos para que le de tiempo a ejecutarse
            p.WaitForExit(2000)

            If conKill Then
                p.Kill()
            End If
            ' Mostrar la salida en la consola
            Console.WriteLine(p.StandardOutput.ReadToEnd())
            
        Catch ex As Exception
            Console.WriteLine(ex.Message)
            
        End Try
    End Sub
End Module
";
            inicializando = false;
            // que se muestren las líneas
            TxtCodigo_TextChanged(null, null);
        }

        /// <summary>
        /// Sincronizar el scroll vertical de los TextBox
        /// de izquierda, derecha y las filas.
        /// Sincronizar el scroll horizontal de los dos TextBox
        /// de izquierda y derecha.
        /// </summary>
        private void Sv_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if (inicializando)
                return;

            inicializando = true;

            var sv = sender as ScrollViewer;
            if (sv == svIzquierda)
            {
                svDerecha.ScrollToVerticalOffset(e.VerticalOffset);
                svDerecha.ScrollToHorizontalOffset(e.HorizontalOffset);
                svFilas.ScrollToVerticalOffset(e.VerticalOffset);
            }
            else if (sv == svDerecha)
            {
                svIzquierda.ScrollToVerticalOffset(e.VerticalOffset);
                svIzquierda.ScrollToHorizontalOffset(e.HorizontalOffset);
                svFilas.ScrollToVerticalOffset(e.VerticalOffset);
            }
            else if (sv == svFilas)
            {
                svIzquierda.ScrollToVerticalOffset(e.VerticalOffset);
                svDerecha.ScrollToVerticalOffset(e.VerticalOffset);
            }

            inicializando = false;
        }

        /// <summary>
        /// Al cambiar el texto del código actualizar el número de líneas.
        /// Se tiene en cuenta cuál de los dos TextBox tiene más líneas.
        /// </summary>
        private void TxtCodigo_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (inicializando)
                return;

            // Contar las líneas
            var linIzq = txtIzquierda.LineCount;
            var linDer = txtDerecha.LineCount;
            // Comprobar cuál de los dos textBoxes tiene más líneas
            var lin = linIzq > linDer ? linIzq : linDer;
            txtFilas.Text = "";
            for (var i = 1; i <= lin; i++)
                // Indentar el texto a la derecha
                txtFilas.Text += i.ToString("0").PadLeft(4) + "\r";
        }
    }
}

Y ahora el de Visual Basic Be right back

'------------------------------------------------------------------------------
' Scroll sincronizado con dos TextBox con WPF                       (06/Ene/19)
' Sincronizados vertical y horizontalmente
'
' (c) Guillermo (elGuille) Som, 2019
'------------------------------------------------------------------------------
Option Strict On
Option Infer On

Imports Microsoft.VisualBasic
Imports System
Imports System.Windows
Imports System.Windows.Controls

Class MainWindow
    Private inicializando As Boolean = True

    Private Sub Window_Loaded(sender As Object,
                              e As RoutedEventArgs)
        txtIzquierda.Text = "using System;
using System.Diagnostics;

static class Program {
    public static void Main(string[] args) {
        Console.WriteLine(""Hello World!"");
        Console.WriteLine(""La fecha y hora actual es: {0:dd/MM/yyyy HH:mm:ss:ffff}"", DateTime.Now);
        Console.WriteLine();

        Console.WriteLine(""Usando Tuplas"");
        (Nombre As String, Fecha As DateTime) datos;  // = (""elGuille"", DateTime.Now)
        datos = (""Guillermo"", DateTime.Now);
        Console.WriteLine(""{0} {1:dd/ MM / yyyy HH:mm : ss}"", datos.Nombre, datos.Fecha);
        Console.WriteLine();

        // Probando con las dos formas
        var exe = ""dotnet"";
        var arg = ""--version"";
        Console.Write($""{exe} {arg}  = "");
        //Console.Write(""{0} {1} = "", exe, arg);
        // ejecutar el proceso para saber la versión de dotnet Core
        ejecutarProceso(exe, arg);

        Console.Write(""Versión del compilador: "");
        var csc = @""C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Roslyn\csc.exe"";
        ejecutarProceso(csc, ""-langversion:?"");

        float s = 173.7619f;
        double d = s;

        short s1 = System.Convert.ToInt16(Math.Truncate(s));   // Result: 173
        int i2 = System.Convert.ToInt32(Math.Ceiling(d));      // Result: 174
        int i3 = System.Convert.ToInt32(Math.Round(s));        // Result: 174

        Console.WriteLine(""short s1 = System.Convert.ToInt16(Math.Truncate(s));   // Result: 173\r\n"" + 
            ""int i2 = System.Convert.ToInt32(Math.Ceiling(d));      // Result: 174\r\n"" + 
            ""int i3 = System.Convert.ToInt32(Math.Round(s));        // Result: 174"");
        Console.WriteLine();
        Console.WriteLine($""s1= {s1}, i2 = {i2}, i3 = {i3}"");
        Console.WriteLine(""s1= {0}, i2 = {1}, i3 = {2}"", s1, i2, i3);

        Console.ReadKey();
    }

    private static void ejecutarProceso(string exe, string arg, bool conKill = false) {
        Process p = new Process();

        p.StartInfo.FileName = exe;
        p.StartInfo.Arguments = arg;

        // Indicamos que queremos redirigir la salida
        p.StartInfo.RedirectStandardOutput = true;
        // Para redirigir la salida, UseShellExecute debe ser falso
        p.StartInfo.UseShellExecute = false;

        p.StartInfo.CreateNoWindow = true;

        try {
            // Iniciamos el proceso
            p.Start();

            // Esperar a que el proceso finalice
            // 
            // Esperamos 2 segundos para que le de tiempo a ejecutarse
            p.WaitForExit(2000);

            if (conKill)
                p.Kill();
                
            // Mostrar la salida en la consola
            Console.WriteLine(p.StandardOutput.ReadToEnd());

        } catch (Exception ex) {
            Console.WriteLine(ex.Message);

        }
    }
}
"

        txtDerecha.Text = "Option Strict On
Option Infer On

Imports Microsoft.VisualBasic
Imports System
Imports System.Diagnostics

Module Program
    Sub Main(args As String())
        Console.WriteLine(""Hello World!"")
        Console.WriteLine(""La fecha y hora actual es: {0:dd/MM/yyyy HH:mm:ss:ffff}"", Date.Now)
        Console.WriteLine()

        Console.WriteLine(""Usando Tuplas"")
        Dim datos As (Nombre As String, Fecha As Date) ' = (""elGuille"", Date.Now)
        datos = (""Guillermo"", Date.Now)
        Console.WriteLine(""{0} {1:dd/MM/yyyy HH:mm:ss}"", datos.Nombre, datos.Fecha)
        Console.WriteLine()
        
        ' Probando con las dos formas
        Dim exe = ""dotnet""
        Dim arg = ""--version""
        Console.Write($""{exe} {arg}  = "")
        'Console.Write(""{0} {1} = "", exe, arg)
        ' ejecutar el proceso para saber la versión de dotnet Core
        ejecutarProceso(exe, arg)

        Console.Write(""Versión del compilador: "")
        Dim vbc = ""C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Roslyn\vbc.exe""
        ejecutarProceso(vbc, ""-langversion:?"")

        Dim s As Single = 173.7619
        Dim d As Double = s

        Dim i1 As Integer = CInt(Fix(s))               ' Result: 173
        Dim b1 As Byte = CByte(Int(d))                 ' Result: 173
        Dim s1 As Short = CShort(Math.Truncate(s))     ' Result: 173
        Dim i2 As Integer = CInt(Math.Ceiling(d))      ' Result: 174
        Dim i3 As Integer = CInt(Math.Round(s))        ' Result: 174

        Console.WriteLine(""Dim i1 As Integer = CInt(Fix(s))               ' Result: 173"" & vbCrLf &
                           ""Dim b1 As Byte = CByte(Int(d))                 ' Result: 173"" & vbCrLf &
                           ""Dim s1 As Short = CShort(Math.Truncate(s))     ' Result: 173"" & vbCrLf &
                           ""Dim i2 As Integer = CInt(Math.Ceiling(d))      ' Result: 174"" & vbCrLf &
                           ""Dim i3 As Integer = CInt(Math.Round(s))        ' Result: 174"")
        Console.WriteLine()
        Console.WriteLine(""i1= {0}, b1 = {1}"", i1, b1)
        Console.WriteLine($""s1= {s1}, i2 = {i2}, i3 = {i3}"")
        Console.WriteLine(""s1= {0}, i2 = {1}, i3 = {2}"", s1, i2, i3)

        Console.ReadKey()
    End Sub

    Private Sub ejecutarProceso(exe As String, arg As String, Optional conKill As Boolean = False)
        Dim p As New Process

        p.StartInfo.FileName = exe
        p.StartInfo.Arguments = arg

        ' Indicamos que queremos redirigir la salida
        p.StartInfo.RedirectStandardOutput = True
        ' Para redirigir la salida, UseShellExecute debe ser falso
        p.StartInfo.UseShellExecute = False

        p.StartInfo.CreateNoWindow = True

        Try
            ' Iniciamos el proceso
            p.Start()

            ' Esperar a que el proceso finalice
            '
            ' Esperamos 2 segundos para que le de tiempo a ejecutarse
            p.WaitForExit(2000)

            If conKill Then
                p.Kill()
            End If
            ' Mostrar la salida en la consola
            Console.WriteLine(p.StandardOutput.ReadToEnd())
            
        Catch ex As Exception
            Console.WriteLine(ex.Message)
            
        End Try
    End Sub
End Module
"
        inicializando = False
        ' que se muestren las líneas
        TxtCodigo_TextChanged(Nothing, Nothing)
    End Sub

    ''' <summary>
    ''' Sincronizar el scroll vertical de los TextBox
    ''' de izquierda, derecha y las filas.
    ''' Sincronizar el scroll horizontal de los dos TextBox
    ''' de izquierda y derecha.
    ''' </summary>
    Private Sub Sv_ScrollChanged(sender As Object,
                                 e As ScrollChangedEventArgs)
        If inicializando Then Return

        inicializando = True

        Dim sv = TryCast(sender, ScrollViewer)
        If sv Is svIzquierda Then
            svDerecha.ScrollToVerticalOffset(e.VerticalOffset)
            svDerecha.ScrollToHorizontalOffset(e.HorizontalOffset)
            svFilas.ScrollToVerticalOffset(e.VerticalOffset)
        ElseIf sv Is svDerecha Then
            svIzquierda.ScrollToVerticalOffset(e.VerticalOffset)
            svIzquierda.ScrollToHorizontalOffset(e.HorizontalOffset)
            svFilas.ScrollToVerticalOffset(e.VerticalOffset)
        ElseIf sv Is svFilas Then
            svIzquierda.ScrollToVerticalOffset(e.VerticalOffset)
            svDerecha.ScrollToVerticalOffset(e.VerticalOffset)
        End If

        inicializando = False

    End Sub

    ''' <summary>
    ''' Al cambiar el texto del código actualizar el número de líneas.
    ''' Se tiene en cuenta cuál de los dos TextBox tiene más líneas.
    ''' </summary>
    Private Sub TxtCodigo_TextChanged(sender As Object,
                                      e As TextChangedEventArgs)
        If inicializando Then Return


        ' Contar las líneas
        Dim linIzq = txtIzquierda.LineCount
        Dim linDer = txtDerecha.LineCount
        ' Comprobar cuál de los dos textBoxes tiene más líneas
        Dim lin = If(linIzq > linDer, linIzq, linDer)
        txtFilas.Text = ""
        For i = 1 To lin
            ' Indentar el texto a la derecha
            txtFilas.Text &= i.ToString("0").PadLeft(4) & vbCr
        Next
    End Sub

End Class

El tocho de código ese que está en el evento Window_Loaded es solo para que haya bastante contenido en las dos cajas de texto, precisamente un código de C# y otro de VB.

Las diferencias en el código del post anterior y este es que aquí manejamos tres controles desplazables y por tanto tenemos que tener en cuenta los tres ScrollViewer cuando se produce el evento ScrollChanged en alguno de ellos.

Las dos cajas de textos grandes, la de la izquierda (txtIzquierda) y la de la derecha (txtDerecha) en la figura 1, están sincronizadas tanto verticalmente como horizontalmente por eso si el control ScrollViewer que produce el evento ScrollChanged es cualquiera de los dos que contienen esas cajas de texto los sincronizamos horizontalmente asignando el valor ScrollToHorizontalOffset del otro ScrollViewer para que coincida con el que ha producido el evento. Como la caja de textos con los números solo queremos sincronizarla verticalmente, solo asignamos el valor a ScrollToVerticalOffset. Por supuesto ese valor también se lo asignamos a la otra caja de textos.

Y en caso de que el control de desplazamiento que produce el evento de cambio de desplazamiento sea svFilas (el de los números de líneas) no es necesario sincronizar la posición horizontal.

En el evento TextChanged tenemos en cuenta cuál de los dos TextBox tienen más líneas, para asignar ese valor a los números a mostrar.

El número de líneas lo obtenemos de la propiedad LineCount del TextBox.

Y ya está… en otra ocasión te pondré un ejemplo parecido con dos RichTextBox y además te explicaré cómo obtener el texto que contenga y cómo asignar un nuevo texto (en formato RTF evidentemente).

Pero eso será otro día.

Nos vemos.
Guillermo

El ZIP con el código de Visual Basic y C# (solución de Visual Studio 2017)

El ZIP contiene la solución para Visual Studio con 4 ejemplos, dos del post anterior y los dos (VB y C#) de este.

El zip: Wpf_scroll_textbox.zip (50.3 KB)

MD5 Checksum: E226C01209749FB18ABD9F17C5E3FD80

Nota:

Este es un nuevo zip con el código actualizado a fecha del 07/Ene/2018 02.04 hora de España.

Scroll sincronizado en varios TextBox en WPF

Pues eso… que en el programa que publiqué ayer en mi sitio: Compilar y ejecutar (desde una aplicación) utilizo dos textbox con desplazamiento vertical sincronizado, es decir, cuando me desplazo por uno de los TextBox (sea pulsando en la barra de scroll o con la rueda del ratón o con pulsaciones de teclas) el otro se desplaza también.

Los preliminares

Aquí te voy a mostrar dos ejemplos, el primero como el de la aplicación esa que te he comentado antes (que solo usa dos controles TextBox, uno de ellos sin mostrar las barras de desplazamiento) (ver figura 1) y el otro en el que hay dos TextBox que se sincronizan tanto vertical como horizontalmente y un tercero que solo se sincroniza verticalmente (ver figura 2).

Figura 1. Dos textbox sincronizados con el scroll vertical
Figura 1. Dos textbox sincronizados con el scroll vertical
Figura 3. Tres textbox sincronizados, dos horizontalmente y los tres verticalmente

¿Qué hacer para sincronizar los TextBox?

Lo que necesitamos es detectar que se hace scroll (desplazamiento) en los controles a sincronizar. Yo he usado el objeto ScrollViewer que contiene un TextBox y por tanto se encarga de sincronizar el contenido del TextBox con las barras de desplazamiento.

Debemos añadir un evento que detecte que se ha desplazado la barra de scroll (por no repetir desplazamiento) y esto lo podemos hacer en el diseñador XAML indicando que queremos crear el evento que corresponda, en nuestro caso ScrollChanged.

Para hacerlo, escribimos el nombre del evento y el diseñador de WPF de Visual Studio nos muestra la opción de agregar uno nuevo o usar uno que ya exista (cuando muestra métodos de evento existentes será porque la firma es la misma, es decir, tiene los mismos tipos de argumentos). En la figura 3 puedes ver lo que te estoy comentado.

Figura 3. Indicamos el evento que queremos usar en un control.
Figura 3. Indicamos el evento que queremos usar en un control.

El evento de sincronización, perdón de desplazamiento, cambiado (ScrollChanged) lo tenemos que poner en todos los ScrollViewer que tengamos.

Y para nuestro ejemplo, solo necesitamos un método de evento, en él haremos las comprobaciones correspondientes para saber qué ScrollViewer ha lanzado el evento.

Nota:
Para poder detectar que ha cambiado el desplazamiento (con scroll en las barras) debemos poner el control que queramos (TextBox, ListBox, RichTextBox, etc.) dentro de un ScrollViewer. Ya que esos controles no implementan el evento ScrollChanged.

Si no quieres detectar ese evento, entonces no hace falta que lo pongas dentro de un ScrollViewer.

El código de VB y C# para sincronizar los TextBox

Aquí te muestro el código del evento ScrollChanged que hemos definido en los dos ScrollViewer que tenemos.
Este es el del ejemplo 1 (Figura 1) en el que solo tenemos dos cajas de texto, una para mostrar los números (así ves mejor que las líneas del otro TextBox están sincronizadas).

''' <summary>
''' Sincronizar el scroll vertical del código y las filas
''' </summary>
Private Sub SvFilas_ScrollChanged(sender As Object,
                                  e As ScrollChangedEventArgs)
    If inicializando Then Return

    inicializando = True

    Dim sv = TryCast(sender, ScrollViewer)
    If sv Is svFilas Then
        svCodigo.ScrollToVerticalOffset(e.VerticalOffset)
    Else
        svFilas.ScrollToVerticalOffset(e.VerticalOffset)
    End If

    inicializando = False
End Sub
/// <summary>
/// Sincronizar el scroll vertical del código y las filas
/// </summary>
private void SvFilas_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    if (inicializando)
        return;

    inicializando = true;

    var sv = sender as ScrollViewer;
    if (sv == svFilas)
        svCodigo.ScrollToVerticalOffset(e.VerticalOffset);
    else
        svFilas.ScrollToVerticalOffset(e.VerticalOffset);

    inicializando = false;
}

Fíjate que tengo una variable llamada inicializando que he declarado a nivel de clase, esta nos servirá para que no se repita en cascada el evento. Yo normalmente la pongo a False cuando el formulario ha terminado de cargarse.

Como puedes comprobar lo que hacemos es cambiar o asignar el valor de la propiedad ScrollToVerticalOffset del otro ScrollViewer.

¡Así es como se sincronizan! Smile

Ajustar el contenido del TextBox con los números de líneas

En el ejemplo que estoy usando una de las cajas de texto muestra el número de línea en el que está actualmente. Esas líneas las tendremos que modificar en consonancia con las líneas que tenga el otro TextBox.

Así que, tendremos que detectar cuándo se cambia el contenido de la caja de textos normal y agregar las líneas a la otra.

Lo primero que tenemos que hacer es añadir un método para detectar que el contenido de la caja de textos txtCodigo ha cambiado y asignar a la otra los números para las líneas.

El evento en cuestión es TextChanged y el código a usar puede ser el que te muestro a continuación.

El código de VB y C# para procesar el evento TextChanged

En el siguiente código contaremos las líneas del TextBox utilizando la propiedad LineCount, la cual indica cuántas líneas tiene el control, al menos si es multilínea, es decir, que tenga más de una línea, eso se consigue con la propiedad TextWrapping con un valor Wrap o WrapWithOverflow, el valor predeterminado es NoWrap, que no vale para este ejemplo.

Nota:
Los TextBox multilínea de WindowsForms tienen una propiedad Lines que devuelve un array de tipo String con el texto del control. cosa que no ocurre con los TextBox de WPF que solo puedes saber el número de líneas que tiene.

''' <summary>
''' Al cambiar el texto del código actualizar el número de líneas
''' </summary>
Private Sub TxtCodigo_TextChanged(sender As Object,
                                  e As TextChangedEventArgs)
    ' Contar las líneas
    Dim lin = txtCodigo.LineCount
    txtFilas.Text = ""
    For i = 1 To lin
        ' Indentar el texto a la derecha
        txtFilas.Text &= i.ToString("0").PadLeft(4) & vbCr
    Next
End Sub
/// <summary>
/// Al cambiar el texto del código actualizar el número de líneas
/// </summary>
private void TxtCodigo_TextChanged(object sender, TextChangedEventArgs e)
{
    // Contar las líneas
    var lin = txtCodigo.LineCount;
    txtFilas.Text = "";
    for (var i = 1; i <= lin; i++)
        // Indentar el texto a la derecha
        txtFilas.Text += i.ToString("0").PadLeft(4) + "\r";
}

El PadLeft es para añadir espacios a la izquierda de forma que tenga la cantidad que indicamos, de esta forma los números se alinean bien. No te lo puedo demostrar con las imágenes que he puesto, ya que ninguna empieza por líneas con menos de 2 cifras Smile with tongue out

El código XAML completo

Aquí tienes todo el código Xaml para que veas cómo están configurados los controles y de paso te sirva para tener una idea (si no la tienes ya) de cómo ajustar los controles al Grid.

<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_scroll_dos_textbox_vb"
        mc:Ignorable="d"
        Title="WPF Scroll sincronizado con dos TextBox (VB)" 
        Height="450" Width="800"
        WindowStartupLocation="CenterScreen"
        ResizeMode="CanResizeWithGrip" 
        WindowStyle="ThreeDBorderWindow"
        Loaded="Window_Loaded">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" MinWidth="36"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" MinHeight="24"/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition Height="Auto" MinHeight="24" MaxHeight="40"/>
        </Grid.RowDefinitions>
        <Label x:Name="lblInfo" 
               Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
               Background="{DynamicResource {x:Static SystemColors.InfoBrushKey}}"
               Margin="4,0" HorizontalContentAlignment="Center"
               Content="Dos TextBox sincronizados verticalmente"/>
        
        <!-- Si no queremos que se vean las barras de desplazamiento
             las ocultamos en el ScrollViewer 
             (aunque estén ocultas siguen funcionando) -->
        <ScrollViewer x:Name="svFilas" 
                      Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" 
                      Focusable="False"
                      HorizontalScrollBarVisibility="Hidden"
                      VerticalScrollBarVisibility="Hidden"
                      ScrollChanged="SvFilas_ScrollChanged">
            
            <!-- Si el TextBox está contenido en un ScrollViewer
                no hace falta indicar en qué columna o fila del Grid está. -->
            <TextBox x:Name="txtFilas" 
                     TextWrapping="Wrap" 
                     FontFamily="Consolas" FontSize="12" 
                     Foreground="DarkCyan"
                     AllowDrop="False" Focusable="False" IsTabStop="False"/>
        </ScrollViewer>
        <ScrollViewer x:Name="svCodigo" 
                      Grid.Column="1" Grid.Row="1" 
                      Grid.ColumnSpan="2" Grid.RowSpan="2"
                      ScrollChanged="SvFilas_ScrollChanged">
            <TextBox x:Name="txtCodigo" 
                     Margin="0,0,2,0" Padding="4,0"
                     TextWrapping="Wrap" 
                     FontFamily="Consolas" FontSize="12" 
                     Text="Aquí irá el texto a mostrar"
                     AcceptsReturn="True" 
                     AcceptsTab="True" 
                     TextChanged="TxtCodigo_TextChanged"/>
        </ScrollViewer>
        <Label x:Name="lblInfo2" 
               Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3"
               Background="{DynamicResource {x:Static SystemColors.InfoBrushKey}}"
               Margin="4,0" HorizontalContentAlignment="Center"
               Content="Mueve el scroll vertical o la rueda del ratón en el texto, el de la izquierda no es visible, pero puedes desplazarte por el contenido."/>
    </Grid>
</Window>

El código de VB y C# ya está casi completo y como lo que yo he añadido es un texto de ejemplo que ocupa mucho, pues… me lo ahorro. Ese código lol asigno en el evento Load (Window_Loaded).

Y esto es todo. Espero que lo sincronices bien Winking smile

Que no, que no me he olvidado del otro ejemplo con tres controles y la sincronización tanto vertical como horizontal. Pero lo dejo para otro post, que este ya está bien cargadito Smile

En el otro post (pondré aquí el enlace) te pondré un ZIP con el código completo tanto para Visual Basic como C# en una solución para Visual Studio 2017 con .NET 4.7.2.

Ahora voy a comer algo y después sigo con el otro post o entrada del blog.
¡Buen provecho!
¡Gracias!

Nos vemos.
Guillermo

Recomendaciones para adaptar los controles (y la fuente) en aplicaciones WPF

Nota: (02/Ene/2019)
Ya puedes ver la segunda parte de este artículo, principalmente con el código para C# y un par de cambios en el código XAML, así como un consejo sobre la diferencia en los valores de Padding y Margin entre el código XAML y el de las hojas de estilo CSS.
He puesto un ZIP (al final de esta página) con el código completo para Visual Basic y C#.

Pues eso… lo primero, lo primero: ¡FELIZ AÑO NUEVO! 🙂
Aunque esto lo estoy escribiendo el 31 de diciembre de 2018 (17:21), pero lo pondré para que se publique el 1 de enero de 2019… eso si es que me da tiempo a escribirlo entero hoy… guiño

Pero te digo lo que contendrá esto que acabas de empezar a leer: Unas indicaciones sobre cómo añadir los controles para que al cambiar el tamaño del formulario o ventana, éstos se adapten al nuevo tamaño, y de paso, veremos cómo cambiar también el tamaño de las fuentes y comprobaremos que como ocurría en las aplicaciones de Windows Forms (o casi) los controles también se adaptan al nuevo tamaño.

Lo más importante es: NO ASIGNAR FUENTES PERSONALIZADAS a los controles (al menos en el tipo de fuente), no, no te estoy gritando, es que quiero que quede claro, ya que esa ha sido una de mis pesadillas… y después resulta que todo es más fácil si no usaba mis propias fuentes… guiño

La batallita del Guille o los antecedentes

Para que no te lleves a engaños, te voy a explicar cómo yo lo estoy haciendo ahora y sobre todo pensando que siempre (o casi) he programado con los formularios usando WinForm de Visual Studio y con Visual Basic en un 99,99% de las aplicaciones que he empezado a hacer, después las convierto a C#, pero las empiezo con Visual Basic.

Cuando diseño el formulario, suelo poner los controles en sitios específicos, si viene al caso suelo usar controles TabControl y GroupBox y escasamente paneles y casi nunca TableLayoutPanel, que ya me he dado cuenta de que no existe el equivalente en WPF y por tanto, las veces que lo he usado para facilitar el paso de WinForms a WPF, pues… no me ha servido de mucho 🙂

Nota:
El TableLayoutPanel tiene como equivalencia el Grid de los controles de WPF.

Para facilitar la transición de WinForms a WPF/XAML he probado de todo, lo más cercano a lo que quería era usar Canvas y posicionar directamente los controles donde yo los quería, pero esto no vale… ya que lo que siempre he ido buscando es poder adaptar los controles al cambiar el tamaño del formulario.

Lo mejor que he encontrado es el control Grid, que si se tiene cuidado al añadir los controles dentro y se configuran (más o menos) bien las definiciones de filas y columnas (y el nombre de la fuente o lo que es lo mismo la propiedad FontFamily) se pueden conseguir cosas decentemente aceptables. Y eso es lo que te voy a explicar aquí… 🙂

Como verás, te estoy poniendo enlaces a las clases que menciono en la documentación de Microsoft y con referencia a .NET 4.7.2 (espero que tarden en romper los enlaces 🙂 )

Una aplicación WPF de ejemplo (y de paso útil)

El otro día pensé hacer una aplicación para copiar el contenido (las fotos) de los dos móviles que tengo y pensé en complicarme la vida haciendo la aplicación con WPF guiño

No voy a entrar en detalles, pero quise acceder al contenido de los móviles, pero no hubo forma, y eso que busqué cómo hacerlo, pero… no di con la tecla, así que… se me ocurrió crear dos carpetas en un disco externo de 5 TB que tengo y desde ahí copiarlas al portátil. El hacer este paso intermedio es porque yo guardo las fotos en una carpeta del año y dentro de esa carpeta las fotos las guardo según el día y el móvil usado. Por ejemplo, para las fotos hechas hoy día 31 de diciembre crearía una carpeta llamada 12 31 1 iP7 para el iPhone y otra llamada 12 31 2 P2XL para el Pixel 2. El 1 y el 2 es porque cuando voy creando carpetas del mismo día las voy enumerando, ya que cuando tenía la GoPro (R.I.P.) también las añadía a una carpeta. Ahora al hacer la aplicación que automatiza la creación de las carpetas he dejado como fijo el 1 para el iPhone de Apple y el 2 para el Pixel de Google.

El diseño empecé haciéndolo con Grid y creando 2 filas y dos columnas, con idea de en la parte superior izquierda poner un grupo de controles para el iPhone con el directorio donde están las fotos, un botón de seleccionar y un ListView mostrando las fotos a copiar; y en el de la derecha los mismos controles para el Pixel 2. Y en la parte inferior la selección de la fecha desde la que quiero empezar a copiar y el directorio donde almacenarlas, aparte de un ListView mostrando las carpetas creadas y algunas cosillas más, que te mostraré ahora cuando veas el formulario en tiempo de diseño.

El formulario en tiempo de diseño

Figura 1. La ventana en tiempo de diseño con WPF.
Figura 1. La ventana en tiempo de diseño con WPF.

Como puedes ver en la figura 1, están los controles que te he comentado, aparte el botón de Iniciar Copia, y las cajas de texto para indicar el formato de los nombres de los directorios según sean para el iPhone o para el Pixel 2. Además del habitual botón para Salir o cerrar la aplicación, cosas de la costumbre, ya que en realidad no es necesario, pero bueno…

Fíjate también que he añadido un menú llamado Tamaño y Fuente, que nos servirá para hacer las pruebas de cambiar el tamaño de la fuente y del formulario o ventana (me acostumbraré a decir ventana, ya que en WPF es una clase Window y no un Form como en WinForms).

En los tres paneles que hay en esa ventana he usado un control del tipo GroupBox. Cada uno de esos contenedores definen también un Grid. Y los controles están distribuidos en las columnas y filas de cada una de esas rejillas.

Uno de los problemas que me encontré fue que en WPF no existe el equivalente a FolderBrowserDialog de System.Windows.Forms. y se ve que no hay equivalencia, lo más que he encontrado es un control de usuario que hace las veces (o eso dice la página donde lo encontré, pero no he llegado a probarlo). Buscando más por internet, he leído que en el espacio de nombres Microsoft.Win32 existen clases para acceder a los diálogos comunes, al menos al de selección de ficheros, pero tampoco lo he comprobado (acabo de mirarlo y no, no hay para seleccionar directorios), aunque me lo he apuntado para ver cómo funciona y qué es lo que hay, pero eso será tema de otro artículo.

Para poder usar el FolderBrowserDialog debes añadir una referencia a System.Windows.Forms y si es necesario añadir una importación del espacio de nombres, yo he optado por añadir esa importación llamándola frm de esa forma estaré seguro que las clases y demás cosas que define no se mezclan con las clases que se recomiendan usar con las aplicaciones de WPF.

Esta sería la declaración de ese espacio de nombres (por ahora solo en VB ya que aún no tengo convertida la aplicación en C#, pero lo haré guiño)

' Para FolderBrowserDialog y OpenFileDialog
Imports frm = System.Windows.Forms

Y ya que estoy con esto, me vas a permitir que antes de ver todo el tema del diseño te muestre el código para seleccionar el directorio, ya que es interesante, porque como los tres botones Seleccionar hacen lo mismo, solo que interactúan con distintas cajas de texto, pues… ahí tienes un truco, que lo mismo ya lo conoces, pero… guiño

Private Sub BtnSel_Click(sender As Object, e As RoutedEventArgs) Handles _
                                    btnSel1.Click, btnSel2.Click, btnSelDest.Click
    Dim dir = ""
    If sender Is btnSel1 Then
        dir = txtDir1.Text
    ElseIf sender Is btnSel2 Then
        dir = txtDir2.Text
    Else
        dir = txtDirDest.Text
    End If

    Dim desc = If(sender Is btnSel1, " para la unidad 1",
                    If(sender Is btnSel2, " para la unidad 2", "de destino"))

    Dim fb As New frm.FolderBrowserDialog
    With fb
        .Description = "Selecciona el directorio" & desc
        .RootFolder = Environment.SpecialFolder.MyComputer
        .SelectedPath = dir
        .ShowNewFolderButton = False
        If .ShowDialog() = frm.DialogResult.OK Then
            dir = .SelectedPath
            If sender Is btnSel1 Then
                txtDir1.Text = dir
                My.Settings.dir1 = dir
                llenar(lvFics1, dir)
            ElseIf sender Is btnSel2 Then
                txtDir2.Text = dir
                My.Settings.dir2 = dir
                llenar(lvFics2, dir)
            Else
                My.Settings.dir3 = dir
                txtDirDest.Text = dir
            End If
        End If
    End With
End Sub

No te voy a explicar nada más, espero que sea fácil de comprender viendo el código.

Pasemos al tema del diseño.

Las cosas a tener en cuenta al diseñar usando el control Grid

Lo importante es no asignar estas cosas a los controles que pongamos dentro del Grid: FontName, Width ni Height. Dejar el valor predeterminado para HorizontalAlignment y VerticalAlignment que es Stretch. Ni al Grid tampoco.

Con este sencillo consejo conseguirás todo lo que deseas conseguir, ¡seguro! guiño

Seguramente te preguntarás: ¿Cómo posicionar los controles donde queramos?
Pues… usando Grid.Column y Grid.Row para indicar en que cuadrícula estará cada control y si viene al caso (por ejemplo para que la caja de textos o el ListView) ocupen más espacio usaremos Grid.ColumnSpan y Grid.RowSpan.

Además de la propiedad Margin, para que deje un poco de espacio donde lo necesitemos.

Veamos cómo está definido el Grid del panel superior izquierdo.

Este es el código XAML del diseñador de la ventana.

<GroupBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="1" 
          Header="Unidad 1" Margin="0">
    <Grid DockPanel.Dock="Top" Margin="0" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="4"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Content="Directorio:" Grid.ColumnSpan="1" Margin="0" />
        <TextBox Name="txtDir1" Grid.Row="0" Grid.Column="1" 
                 Grid.ColumnSpan="3" Grid.RowSpan="1"
                 Margin="0,6,0,0" TextWrapping="NoWrap" Text="S:\iPhone"/>
        <Button Name="btnSel1" Content=" Seleccionar... " 
                Grid.Column="4" Grid.ColumnSpan="1"
                Margin="8,6,0,0" />
        <ListView Name="lvFics1" Grid.Row="2"
                  Grid.ColumnSpan="5" Grid.RowSpan="1" 
                  Margin="0,8,0,0">
            <ListView.View>
                <GridView >
                    <GridViewColumn Header="Archivo" Width="220"
                                    DisplayMemberBinding="{Binding Nombre}"/>
                    <GridViewColumn Header="Fecha" Width="Auto"
                                    DisplayMemberBinding="{Binding Fecha}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>

 </GroupBox>

El GruopBox está contenido en el Grid principal, el valor de Grid.Row = «1» es porque en la fila cero está el menú. Aquí le he puesto Grid.Column = «0» pero realmente no es necesario, ya que si no se indica qué fila o columna ocupa, será siempre cero. Lo mismo ocurre con Grid.ColumnSpan = «1», como mínimo espanea (¿existe ese palabro?) una columna o una fila.

Los controles de este GroupBox están contenidos en un Grid. Yo he definido 5 columnas y 3 filas. El poner 5 columnas, principalmente es para que el TextBox ocupe 3 y el Label de la izquierda y el Button de la derecha ocupen una columna cada uno. El ListView ocupa una fila completa (antes tenía más filas, pero no es necesario). La fila pequeña, con 4 de alto es para dejar un espacio entre los controles de arriba con la lista, pero esto también se podría haber solucionado con los márgenes, pero… bueno… ahí está 🙂

Con los Margin lo que logro es que tanto el botón como el TextBox no se hagan muy altos y se alineen mejor con la etiqueta.

Como puedes comprobar, el único Width (ancho) específico que he puesto es el de la primera columna del ListView. Pero más que nada para que al verlo en tiempo de diseño o al iniciar la aplicación y estando vacío se vea más espacioso, ya que al llenarse, se ajustará al texto que contenga.

En las figuras 2 y 3 tienes la diferencia de asignar Auto al tamaño de la columna (figura 2) y ponerlo como te muestre en este código con 220 de ancho (figura 3). En el panel derecho está en Auto y se ajusta al nombre de la imagen.

Figura 2. El ancho de las columnas del ListView se ajustan automáticamente al contenido.
Figura 2. El ancho de las columnas del ListView se ajustan automáticamente al contenido.
Figura 3. El ancho de la columna izquierda está puesto a 220 y el de la derecha en Auto.
Figura 3. El ancho de la columna izquierda está puesto a 220 y el de la derecha en Auto.

La cuestión es que prefiero darle como mínimo unos 200 o 220 pixeles de ancho guiño

Otro punto a resaltar en ese código es cómo se maneja el contenido del ListView, no voy a entrar en mucho detalle porque ya lo expliqué en mi sitio hace 6 años y 34 días (el 27 de diciembre de 2012) en este artículo: Ejemplo de ListView y equivalencia a subitems, solo decirte que he creado dos clases (dentro de la clase principal) para manejar dicho contenido.

Este es el código de las dos clases (por ahora en VB):

Class ItemDir
    Public Property Nombre As String
End Class

Class ItemFic
    Inherits ItemDir
    Public Property Fecha As Date
End Class

Y estas clases las uso de esta forma a la hora de asignar el contenido de los ListView ligados con los directorios de mas fotos y con el de las carpetas creadas, por eso hay dos clases, una para los directorios y la segunda para los ficheros. El código para mostrar las fotos está en el método llenar.

''' <summary>
''' Llenar un listview con los ficheros del directorio
''' </summary>
Private Sub llenar(lvFiles As ListView, dir As String)
    lvFiles.Items.Clear()
    Dim dirI = New DirectoryInfo(dir)
    If dirI.Exists = False Then Exit Sub

    For Each fi In dirI.GetFiles()
        Dim lvi = New ItemFic With {.Nombre = fi.Name, .Fecha = fi.LastWriteTime}
        lvFiles.Items.Add(lvi)
    Next
End Sub

Como ves, solo se añade el objeto creado a cada elemento del ListView y el Binding, concretamente el DisplayMemberBinding, se encarga del resto.

La parte inferior de la ventana es más de lo mismo. No te la voy a explicar al completo porque ya sería muy pesado, podrás ver el código completo cuando lo publique, aunque no sé si esperaré a que esté el de C# o publicaré primero el de Visual Basic y después el de C#.

Cambiar el tamaño de la ventana principal y las letras

En el menú he puesto dos opciones (ahora te enseño el código XAML) una para poder cambiar el tamaño de la ventana principal y el otro para cambiar el tamaño de las letras.

A dicho menú le he añadido una etiqueta, una caja de textos y un botón (esto también se puede hacer en los menús de WinForms).

Este es el código XAML de la definición del menú principal y los dos submenús:

<Menu x:Name="menu" Grid.Row="0" Grid.Column="0" 
      Grid.ColumnSpan="2" 
      Height="24" Margin="0,0,0,4">
    <MenuItem x:Name="mnuCambiarFuente" 
              Header="_Tamaño y Fuente">
        <MenuItem Header="Tamaño _Ventana">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <Label Content="Cambiar tamaño" 
                       ToolTip="Puedes usar un valor negativo para reducir" />
                <TextBox x:Name="txtTamañoVentana" Text="0,2" 
                         Grid.Column="1" Width="40"
                         Margin="8,6,0,0"/>
                <Button x:Name="btnAplicar" Content=" Aplicar " 
                        Click="BtnAplicarVentana_Click"
                        Grid.Column="2" Margin="8,6,0,0" />
            </Grid>
        </MenuItem>
        <MenuItem Header="Tamaño _Fuente">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <Label Content="Cambiar tamaño" 
                       ToolTip="Puedes usar un valor negativo para reducir" />
                <TextBox x:Name="txtTamañoFuente" Text="1,0" 
                         Grid.Column="1" Width="40"
                         Margin="8,6,0,0"/>
                <Button x:Name="btnAplicarFuente" Content=" Aplicar " 
                        Click="BtnAplicarFuente_Click"
                        Grid.Column="2" Margin="8,6,0,0" />
            </Grid>
        </MenuItem>
    </MenuItem>
</Menu>

Como ves, hay Grid hasta en la sopa 🙂

El guión bajo en el Header de los menús es para indicar que se resalte esa letra (la que le sigue) al pulsar la tecla Alt, es lo mismo que el ampersand (&) en las aplicaciones de WinForms.

Veamos el código para Visual Basic para cada uno de los botones:

Private meHeight As Double
Private meWidth As Double
Private meFontSize As Double

Private Sub BtnAplicarVentana_Click(sender As Object, e As RoutedEventArgs)
    ' Para cambiar el tamaño a partir del valor inicial
    If meHeight = 0 Then
        meHeight = Me.Height
    End If
    If meWidth = 0 Then
        meWidth = Me.Width
    End If

    Dim d As Double = 0
    Double.TryParse(txtTamañoVentana.Text, d)
    Me.Height = nuevoTamaño(meHeight, d)
    Me.Width = nuevoTamaño(meWidth, d)
End Sub

Private Sub BtnAplicarFuente_Click(sender As Object, e As RoutedEventArgs)
    If meFontSize = 0 Then
        meFontSize = Me.FontSize
    End If
    Dim d As Double = 0
    Double.TryParse(txtTamañoFuente.Text, d)
    Me.FontSize = meFontSize + d
End Sub

Como ves ninguno de los dos métodos de evento tienen la cláusula Handles, esto es porque en el código XAML ya hemos indicado qué método se usará para cada uno de los botones.

En el tamaño de la fuente simplemente añadimos el valor indicado al tamaño que ya tuviera, mientras que el alto y ancho de la ventana usamos un incremento porcentual (o casi).

Este es el código del método nuevoTamaño:

Private Function nuevoTamaño(actual As Double, incremento As Double) As Double
    Return actual * incremento + actual
End Function

En el código hay también unas llamadas al API de Windows para ocultar el botón Maximizar de la ventana, ya que de forma nativa WPF no tiene forma de ocultar ese botón, al menos si la ventana se puede cambiar de tamaño, que es el caso de la que usa este programa, que tiene el valor CanResizeWithGrip asignado a la propiedad ResizeMode.
Aunque como leí en uno de los foros que estuve consultando: ¿Para qué quieres quitar el botón maximizar si tu formulario puede cambiar de tamaño? Y tiene razón, pero… en fin… costumbres heredadas de los formulari0s de Windows Forms guiño

Este es el código para Visual Basic de esas API

'--------------------------------------------------------------------------
' Código para quitar el botón de maximizar
' Adaptado de la versión de C# publicada en:
' https://stackoverrun.com/es/q/5101958
' 
<DllImport("user32.dll")>
Private Shared Function GetWindowLong(ByVal hWnd As IntPtr,
                                      ByVal nIndex As Integer) As Integer
End Function

<DllImport("user32.dll")>
Private Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer,
                                      ByVal dwNewLong As Integer) As Integer
End Function

Private Const GWL_STYLE As Integer = -16
Private Const WS_MAXIMIZEBOX As Integer = &H10000

Private Sub Window_SourceInitialized(ByVal sender As Object,
                                     ByVal e As EventArgs) Handles Me.SourceInitialized
    Dim hwnd = New WindowInteropHelper(CType(sender, Window)).Handle
    Dim value = GetWindowLong(hwnd, GWL_STYLE)
    SetWindowLong(hwnd, GWL_STYLE, CInt((value And Not WS_MAXIMIZEBOX)))
End Sub
'
'--------------------------------------------------------------------------

Y esto es todo.

Son las 22:55 del lunes 31 de Diciembre de 2018, pero… ya sabes lo pondré en automático para que se publique maña uno de enero de 2019 guiño

Y mañana o pasado pondré el enlace al código completo para Visual Basic y espero que también el de C#.

¡Feliz Año Nuevo!

Nos vemos.
Guillermo


El código completo del ejemplo
(solución para Visual Studio usando .NET 4.7.2)

El ZIP con el código completo (una solución de Visual Studio 2017 con los proyectos de Visual Basic y C#

El zip: Copiar_archivos_de_2_unidades.zip (214 KB)

MD5 Checksum: 2E3B3F34410F37D051DC5AED8FBBCE1A


Interceptar el cambio del tamaño de los controles de un formulario en otros formularios de la aplicación

Esta sería la versión 2.0 del artículo que publiqué hace un par de días: Cambiar el tamaño de los controles de un formulario automáticamente, en esta ocasión lo que te voy a enseñar es cómo interceptar en los restantes formularios de la aplicación esos cambios y actuar consecuentemente, es decir, que también cambie el tamaño de sus controles. Y todo esto aunque el resto de formularios ya estén abiertos.

Nota:
Por si no te lo dije en el artículo anterior (es que es muy largo y me da pereza tener que leerlo al completo 🙂 ), para que los controles cambien de tamaño, el tipo de fuente debe ser la predeterminada, si usas una fuente definida de forma explícita, ese control no cambiará, salvo que hayas definido la propiedad Anchor.

En este segundo artículo lo comprobarás en uno de los controles, en el que el tamaño de la fuente está asignado de forma explícita.

Lo que hay que hacer para que esto funcione es definir un evento en el formulario principal (en el ejemplo que te mostraré aquí dicho formulario se llama Form1. Por tanto, si tu formulario principal se llama de otra forma, cambia Form1 por el nombre de tu formulario principal.

En Visual Basic se crea de forma predeterminada una colección con todos los formularios que tenemos en nuestro proyecto y no es necesario instanciarlos para acceder a ellos; pero como eso no es así en C# y te voy a mostrar el código tanto para C# como para Visual Basic, pues… el código lo escribiré para que sea re-utilizable en C#. Además de que de esta forma, creando y accediendo a los formularios por medio de una variable que nosotros instanciemos, nos evitamos el problema de tener (sin darnos casi cuenta) el mismo formulario abierto dos (o más) veces. Esto último debería resaltarlo, ya que creo que muchos usuarios de Visual Basic le puede ocurrir eso de tener el mismo formulario abierto más de una vez, una de forma automática al llamarlo directamente,. por ejemplo Form2.Show y la otra si previamente hemos creado una variable para acceder a dicho Form2. Ahora verás el ejemplo.

Si necesitamos acceder a la instancia en ejecución del formulario principal (en mi caso Form1) lo que yo hago es definir una variable compartida (Shared en VB y static en C#) llamada Current que es del mismo tipo que el formulario y que instanciamos, por ejemplo en el constructor.

Veamos el código de ejemplo que hay que añadir en el formulario principal (Form1) para definir esa instancia predeterminada y de paso crear el evento y demás métodos que serán necesarios para comunicar al resto de formularios que la fuente del formulario principal ha cambiado.

Empezaré con el código de Visual Basic y después con el de C#, que es algo diferente, al menos en la definición del evento. Pero igualmente te explicaré qué hace cada línea de código 😉

Nota:
Doy por hecho que tienes el código que puse en el artículo anterior, ya que aquí solo te diré (y mostraré) dónde tienes que añadir el código nuevo. Al menos en el Form1, ya que voy a crear otro formulario nuevo (Form2) y ese si te mostraré lo que debes hacer.

El código de Visual Basic a añadir al principio del formulario (en realidad da igual donde lo pongas, pero…)

Public Class Form1

    ''' <summary>
    ''' Instancia del formulario principal
    ''' </summary>
    Public Shared Current As Form1

    ''' <summary>
    ''' Evento para detectar el cambio de fuente
    ''' y avisar a los oyentes.
    ''' </summary>
    Public Event FuenteCambiada(fnt As Font)

    ''' <summary>
    ''' Método privado para lanzar el evento
    ''' </summary>
    Protected Sub OnFuenteCambiada(fnt As Font)
        RaiseEvent FuenteCambiada(fnt)
    End Sub

    Private Sub Form1_FontChanged(sender As Object, e As EventArgs) Handles _
                                                 Me.FontChanged
        OnFuenteCambiada(Me.Font)
    End Sub

Este es el mismo código pero para C#, ya sabes, casi lo mismo pero con puntos y comas 😉

public partial class Form1 : Form
{
    /// <summary>
    /// Instancia del formulario principal
    /// </summary>
    public static Form1 Current;

    /// <summary>
    /// Evento para detectar el cambio de fuente
    /// y avisar a los oyentes.
    /// </summary>
    public event FuenteCambiadaEventHandler FuenteCambiada;

    public delegate void FuenteCambiadaEventHandler(Font fnt);

    /// <summary>
    /// Método privado para lanzar el evento
    /// </summary>
    protected void OnFuenteCambiada(Font fnt)
    {
        FuenteCambiada?.Invoke(fnt);
        //if (FuenteCambiada != null)
        //    FuenteCambiada(fnt);
    }

    private void Form1_FontChanged(object sender, EventArgs e)
    {
        OnFuenteCambiada(this.Font);
    }

Como te dije, Current hará referencia al formulario Form1 que hayamos instanciado al iniciar la aplicación, el hacerlo compartido es para poder acceder usándolo de esta forma: Form1.Current. La asignación la tenemos que hacer en el constructor de la clase (Form1).

'
' El constructor del formulario
'
Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.
    Form1.Current = Me
//
// El constructor del formulario
//
public Form1()
{

    // This call is required by the designer.
    InitializeComponent();

    // Add any initialization after the InitializeComponent() call.
    Form1.Current = this;
    this.FontChanged += Form1_FontChanged;

En C# definimos el evento FontChanged del formulario, cosa que en Visual Basic no es necesario ya que añadiendo al final de la definición del método Handles Me.FontChanged es suficiente.

Sigamos con las explicaciones del código de los listados anteriores.
Definimos un evento llamado FuenteCambiada que define un argumento de tipo Font, en el método OnFuenteCambiada invocamos a dicho evento. En el código de C# te muestro las dos formas de hacerlo, la abreviada y la «clásica».

Ese evento lo lanzaremos cuando la fuente del formulario principal cambie y precisamente el evento FontChanged del formulario es el que nos avisa de que la fuente ha cambiado.

Y esto es todo lo que debes cambiar en el formulario principal.

Ahora vamos a crear un nuevo formulario llamado Form2.

En tiempo de diseño tendrá el siguiente aspecto (figura 1)

Figura 1. El formulario Form2 en tiempo de diseño
Figura 1. El formulario Form2 en tiempo de diseño

Ajusta los valores de Anchor tal como te indico a continuación:
La caja de texto es: Top, Left, Right, el botón: Top, Right y el RichTextBox es: Top, Bottom, Left, Right.

Salvo el RichTextBox, todos los controles tienen la fuente predeterminada, la caja de texto enriquecido lo he puesto con esta fuente: Segoe Script; 36pt; style=Bold.

Los nombres de los controles son: txtSaludo, RichTextBox1 y btnMostrar.

Y este es el código que tendrás que poner en ese formulario o en cualquier otro en el que quieras que se cambien las fuentes al cambiarlas en el formulario principal.

Te muestro primero el código de Visual Basic y después el de C#.

Public Class Form2

    '--------------------------------------------------------------------------
    ' Código a añadir a cada formulario
    ' en los que queramos que sincronicen la fuente
    '
    ' Solo cambiar Form1 por el nombre del formulario
    ' que define el evento FuenteCambiada
    '
    Private Sub FuenteCambiada(fnt As Font)
        Me.Font = fnt
    End Sub

    Private Sub FuenteCambiada_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.Font = Form1.Current.Font

        AddHandler Form1.Current.FuenteCambiada, AddressOf FuenteCambiada
    End Sub

    Private Sub FuenteCambiada_FormClosing(sender As Object, e As FormClosingEventArgs) Handles _
                                                            MyBase.FormClosing
        RemoveHandler Form1.Current.FuenteCambiada, AddressOf FuenteCambiada
    End Sub
    '
    ' Fin del código para sincronizar la fuente
    '--------------------------------------------------------------------------

    Private Sub btnMostrar_Click(sender As Object, e As EventArgs) Handles btnMostrar.Click
        RichTextBox1.Text = txtSaludo.Text
    End Sub

public partial class Form2 : Form
{
    public Form2()
    {
        InitializeComponent();

        //Form1.Current.FuenteCambiada += FuenteCambiada;

        this.Load += FuenteCambiada_Load;
        this.FormClosing += FuenteCambiada_FormClosing;
    }

    // --------------------------------------------------------------------
    // Código a añadir a cada formulario
    // en los que queramos que sincronicen la fuente
    // 
    // Solo cambiar Form1 por el nombre del formulario
    // que define el evento FuenteCambiada
    // 
    private void FuenteCambiada(Font fnt)
    {
        this.Font = fnt;
    }

    private void FuenteCambiada_Load(object sender, EventArgs e)
    {
        this.Font = Form1.Current.Font;

        Form1.Current.FuenteCambiada += FuenteCambiada;
    }

    private void FuenteCambiada_FormClosing(object sender, FormClosingEventArgs e)
    {
        Form1.Current.FuenteCambiada -= FuenteCambiada;
    }
    //
    // Fin del código para sincronizar la fuente
    // --------------------------------------------------------------------

    private void btnMostrar_Click(object sender, EventArgs e)
    {
        RichTextBox1.Text = txtSaludo.Text;
    }

¡Y esto es todo lo que hay que hacer!

Si ejecutas el programa (recuerda que puedes usar el código que te puse en el artículo anterior y solo añadir el nuevo formulario con el código que te acabo de mostrar).

En la figura 2 puedes ver cómo el segundo formulario (Form2) muestra el tamaño que hayamos indicado en el formulario principal (Form1).

Esto lo consigues cambiando el tamaño en el formulario principal.

Figura 2. La aplicación en funcionamiento mostrando los dos formularios.
Figura 2. La aplicación en funcionamiento mostrando los dos formularios.

Como ves el segundo formulario muestra las fuentes y tamaño de los controles según el porcentaje indicado en el formulario principal. El ojo ese que ves parcialmente es de la foto que tengo de fondo de escritorio 😉

Fíjate también que el RichTextBox solo se ha adaptado al nuevo tamaño del formulario, pero la fuente es del mismo tamaño que estaba definida en tiempo de diseño. Recuerda lo que te dije antes: sólo se cambian las fuente que tiene predeterminadas el diseñador de formularios de Visual Studio.

Solo me falta mostrarte cómo llamar al segundo formulario. Eso lo haremos desde el botón Mostrar Form2 que hay bajo Restablecer fuente. El código es el siguiente:

Private Sub btnForm2_Click(sender As Object, e As EventArgs) Handles btnForm2.Click
    Dim f2 As New Form2
    f2.Font = Me.Font
    f2.Show()

End Sub
private void btnForm2_Click(object sender, EventArgs e)
{
    var f2 = new Form2();
    f2.Font = this.Font;
    f2.Show();
}

Y ahora sí, esto es todo… después (o más tarde) te pongo el enlace con la solución para Visual Studio 2017 con la aplicación completa, tanto para Visual Basic como para C#.

Espero que te sea de utilidad, esa es siempre la intención 😉

Nos vemos.
Guillermo

El código completo del ejemplo (solución para Visual Studio usando .NET 4.7.2)


El ZIP con el código completo (una solución de Visual Studio 2017 con los proyectos de Visual Basic y C#

El zip: Cambiar_tamano_controles_v2.zip (260 KB)

MD5 Checksum: D9C7439F1580CE64228383E7B66BDADA

Cambiar el tamaño de los controles de un formulario automáticamente

Pues eso… que lo que publiqué anteayer sobre ajustar automáticamente la fuente de un formulario según la configuración del sistema (Cambiar automáticamente las fuentes de nuestro formulario a las de Windows), estaba muy bien, pero no era lo que yo andaba buscando… yo lo que quería era que los controles también cambiaran.

La batallita del Guille

Y me puse a ello… como soy un poquillo torpe, ya sabes, no tengo estudios informáticos ni de otros tipos, pues… empecé a escribir el código para cambiar cada uno de los controles del formulario, y por supuesto también el del formulario, ahí si llego ;-). Sí, lo hice, escribí todo el código, con métodos recursivos, etc. En fin… :-/

La cuestión es que pensé que también estaría guay (cool) poder cambiar las fuentes, así todo estaría más grande… o más pequeño, ya que en el formulario de prueba (ver figura 1) puse la opción de ampliar o reducir.

Figura 1. La aplicación en modo inicial (usando la configuración del usuario de Windows)
Figura 1. La aplicación en modo inicial (usando la configuración del usuario de Windows)

Pero aquello era un caos, cuando ampliaba, lo hacía según el ancho y el alto y claro, si los controles no tienen todos los Anchor necesarios, pues… se te quedaba espacio vacío por aquí y por allá… no tengo capturas (¡gracias a Dios! si no, ya no vendrás más a visitarme 😛 )

Con decirte que hice hasta una clase para que manejase el tema este del cambio de los controles y así hacerlo más cool… con un evento que indicaba que se estaban haciendo los cambios, etc.; sí, lo que yo te diga, en fin…

Bueno, vale… otro día hago la captura y así ves cómo se puede uno embrollar con lo que después resultó ser algo muy simple de codificar.

La solución (muy fácil y sencilla)

Pues eso, la solución, para esto de que se cambien los controles de tamaño según indiquemos, es muy simple, tanto que… bueno… mejor me callo… ¡ay zeñó!

Y todo surgió porque los controles quedaban muy mal al cambiar el tamaño, y me dio por probar (esto aún podría estar en la batallita del Guille, pero bueno…) quitando el cambio del tamaño del formulario y de los controles, y dejar solo el cambio del tamaño de la fuente y… ¡voilà, todo va a la perfección!. No sabes la de cabezazos que me hubiera dado contra la pared… menos mal que ahora me he pelado al 2 y no era plan… 🙂

Te muestro un par de capturas con el 25% de ampliación y el 50% para que te hagas una idea de lo bien que funciona 🙂 y después te muestro el código tanto para Visual Basic como para C#.

Figura 2. Aspecto de la aplicación en funcionamiento usando el 25% de ampliación
Figura 2. Aspecto de la aplicación en funcionamiento usando el 25% de ampliación
Figura 3. Aspecto de la aplicación en funcionamiento usando un 50% de ampliación
Figura 3. Aspecto de la aplicación en funcionamiento usando un 50% de ampliación

Nota:
Según parece, la imagen 1 y 2 son iguales, pero si te fijas en la figura 3, en la que muestro el IDE de Visual Studio, comprobarás que el tamaño del formulario es significativamente más grande que el mostrado en el diseñador de VS.

Pulsa en las imágenes para verlas en grande.

El código para cambiar el tamaño de los controles de un formulario

Comentarte que todo está en cambiar el tamaño de la fuente del formulario, por tanto debes tener asignado el valor Font a la propiedad AutoScaleMode del formulario (es el valor predeterminado al crear un nuevo formulario).

El código para Visual Basic

'--------------------------------------------------------------------------
' Métodos para cambiar el tamaño del formulario
' y de sus controles
'--------------------------------------------------------------------------

''' <summary>
''' Cambia el tamaño del formulario (o control) indicado,
''' ampliando o reduciendo según un porcentaje.
''' Recomendado es solo cambiar el formulario.
''' </summary>
Private Sub cambia(ctr As Control, ampliar As Boolean, por As Integer)
    If ampliar = False Then
        por = -por
    End If

    '----------------------------------------------------------------------
    ' El ancho y alto es mejor no cambiarlo,
    ' las proporciones de los controles se verán bien
    ' pero si el Anchor no está "pensado" para todos
    ' los controles, quedarán espacios vacíos con
    ' respecto al diseño original.
    ' Al cambiar la fuente del formulario,
    ' el aspecto original no se pierde.
    '----------------------------------------------------------------------


    '----------------------------------------------------------------------
    ' Al cambiar el tamaño de la fuente del formulario
    ' los controles se adaptan al nuevo tamaño de fuente,
    ' sin perder el aspecto del diseño original.
    '
    ' El formulario debe tener asignada la propiedad
    ' AutoScaleMode = Font
    '----------------------------------------------------------------------

    '
    ' Se debería poner un máximo y mínimo a las fuentes
    '

    Dim fntSize = ctr.Font.Size
    fntSize = calculaPorcentaje(fntSize, por)

    ' No admitir valores menores de uno
    ' ni valores mayores de 3 veces la fuente
    If fntSize < 1 OrElse fntSize > ctr.Font.Size * 3 Then
        fntSize = ctr.Font.Size
    End If

    ctr.Font = New Font(ctr.Font.FontFamily,
                        fntSize, ctr.Font.Style,
                        ctr.Font.Unit, ctr.Font.GdiCharSet,
                        ctr.Font.GdiVerticalFont)
End Sub

''' <summary>
''' Calcula el porcentaje a partir de un valor Single,
''' para el tamaño de la fuente.
''' </summary>
Private Function calculaPorcentaje(valor As Single, porcentaje As Integer) As Single
    Return valor + (valor * porcentaje / 100)
End Function

El código para C#

// --------------------------------------------------------------------------
// Métodos para cambiar el tamaño del formulario
// y de sus controles
// --------------------------------------------------------------------------

/// <summary>
/// Cambia el tamaño del formulario (o control) indicado,
/// ampliando o reduciendo según un porcentaje.
/// Recomendado es solo cambiar el formulario.
/// </summary>
private void cambia(Control ctr, bool ampliar, int por)
{
    if (ampliar == false)
        por = -por;

    // ----------------------------------------------------------------------
    // El ancho y alto es mejor no cambiarlo,
    // las proporciones de los controles se verán bien
    // pero si el Anchor no está "pensado" para todos
    // los controles, quedarán espacios vacíos con
    // respecto al diseño original.
    // Al cambiar la fuente del formulario,
    // el aspecto original no se pierde.
    // ----------------------------------------------------------------------


    // ----------------------------------------------------------------------
    // Al cambiar el tamaño de la fuente del formulario
    // los controles se adaptan al nuevo tamaño de fuente,
    // sin perder el aspecto del diseño original.
    // 
    // El formulario debe tener asignada la propiedad
    // AutoScaleMode = Font
    // ----------------------------------------------------------------------

    // 
    // Se debería poner un máximo y mínimo a las fuentes
    // 

    var fntSize = ctr.Font.Size;
    fntSize = calculaPorcentaje(fntSize, por);

    // No admitir valores menores de uno
    // ni valores mayores de 3 veces la fuente
    if (fntSize < 1 || fntSize > ctr.Font.Size * 3)
        fntSize = ctr.Font.Size;

    ctr.Font = new Font(ctr.Font.FontFamily, fntSize, 
                        ctr.Font.Style, ctr.Font.Unit, 
                        ctr.Font.GdiCharSet, ctr.Font.GdiVerticalFont);
}

/// <summary>
/// Calcula el porcentaje a partir de un valor Single,
/// para el tamaño de la fuente.
/// </summary>
private float calculaPorcentaje(float valor, int porcentaje)
{
    return valor + (valor * porcentaje / 100);

}

Al método cambia le pasamos el control o formulario (recomendable el formulario) en el que queremos hacer el cambio de tamaño, el valor de ampliar es si queremos ampliar (true) o reducir (false) y por es el porcentaje, que es un valor entero y puede ser 0 (cero) para no hacer nada o dejarlo como estaba, o cualquier otro tamaño (no te recomiendo un valor mayor de 175 si no, pues… te faltará pantalla… 🙂

Como puedes comprobar, he puesto una comprobación para que el tamaño de la fuente no sea menor de 1 ni mayor de 3 veces el tamaño inicial.
Si quieres cambiarlo, hazlo, pero al menos deja la comprobación de que no sea menor de uno (o cero) ya que so es cero (0) te dará error.

Y ahora pasemos al código que he usado para cambiar el tamaño del formulario y sus controles.

El código de ejemplo para cambiar el tamaño según un porcentaje

En el ejemplo que he hecho (ver cualquiera de las figuras) he puesto la opción de Ampliar o Reducir, un combo con los porcentajes a usar y el botón Cambiar para aplicar el cambio.

También he puesto un botón para restaurar el tamaño al inicial (Restablecer fuente) pero en realidad no es necesario, ya que al indicar 0% se deja todo como estaba al inicio.

Lo que si hago es que ese porcentaje sea fijo, es decir, siempre que pulses en, por ejemplo, 50% se cambiará al 50% del valor inicial.

Te aclaro esto porque inicialmente (ya sabes: la torpeza del Guille) ese porcentaje lo aplicaba al valor que ya hubiese de antes, por tanto, si inicialmente lo habías ampliado al 10% y después seleccionabas el 50% este último se aplicaba sobre la ampliación (o reducción) anterior, y… bueno, que no quedaba nada bien.

Para conseguir esto, que siempre se aplique el porcentaje según la fuente inicial, he creado una variable llamada miWinFont, esa es la fuente que tiene el sistema, ya que he usado el mismo código que te mostré en el artículo anterior (Cambiar automáticamente las fuentes de nuestro formulario a las de Windows) para utilizar la fuente indicada en Windows.

La asignación de esa variable se hace al inicio del programa (en el constructor) y después se usa para reiniciar el tamaño de la fuente antes de hacer el cambio, ahora lo verás en el código.

Como tip te puedo decir que puedes usar otra forma de hacerlo, por ejemplo usando el valor de fntSize a partir del valor de miWinFont en vez de la fuente del formulario, pero no he cambiado el código porque eso se me acaba de ocurrir mientras escribo el artículo, y ya no es plan de cambiar el código 😉

Vamos a lo que vamos.

Empecemos con el código de Visual Basic, pero tanto en el de VB como en el de C# lo que hago es lo mismo: hacer el cambio usando el evento Click del botón Cambiar.

El código para Visual Basic de los métodos de evento

'
' Los métodos de evento para el cambio de la fuente
'
Private Sub btnCambiarTamaño_Click(sender As Object, e As EventArgs) Handles _
                                            btnCambiarTamaño.Click
    ' Cambiar solo el tamaño de la fuente del formulario
    txtInfo.Text = "Cambiando el tamaño de los controles..."
    Application.DoEvents()

    Me.Hide()

    ' restablecer a la fuente inicial
    Me.Font = miWinFont

    ' hacer el cambio de tamaño
    cambia(Me, optAmpliar.Checked, CInt(cboTamaños.SelectedItem))

    Me.Show()

    txtInfo.Text = "Cambiado el tamaño de los controles."
    Application.DoEvents()
End Sub

Private Sub btnRestablecerFuente_Click(sender As Object, e As EventArgs) Handles _
                                                btnRestablecerFuente.Click
    ' Restablecer siempre a la fuente del sistema
    ' si no queremos usar la del sistema,
    ' asignar al valor de miFuente (la original al diseñar)
    Me.Font = miWinFont


End Sub

Ahora después te muestro dónde declaro miWinFont y dónde la asigno.

El código para C# de los métodos de evento

//
// Los métodos de evento para el cambio de la fuente
//

private void btnCambiarTamaño_Click(object sender, EventArgs e)
{
    // Cambiar solo el tamaño de la fuente del formulario
    txtInfo.Text = "Cambiando el tamaño de los controles...";
    Application.DoEvents();
    this.Hide();
    
    // restablecer a la fuente inicial
    this.Font = miWinFont;
    
    // hacer el cambio de tamaño
    cambia(this, optAmpliar.Checked, Convert.ToInt32(cboTamaños.SelectedItem));

    this.Show();
    txtInfo.Text = "Cambiado el tamaño de los controles.";
    Application.DoEvents();

}

private void btnRestablecerFuente_Click(object sender, EventArgs e)
{
    // Restablecer siempre a la fuente del sistema
    // si no queremos usar la del sistema,
    // asignar al valor de miFuente (la original al diseñar)
    this.Font = miWinFont;

}

Este es el código donde se define y se asigna miWinFont.

Adivina cuál es el de VB y cuál el de C# 😉

Public Class Form1
    ''' <summary>
    ''' La fuente del sistema
    ''' </summary>
    Private miWinFont As Font

[...]

'
' El constructor del formulario
'
Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.
    miFont = Me.Font

    Me.Font = SystemFonts.IconTitleFont
    AddHandler SystemEvents.UserPreferenceChanged,
                AddressOf SystemEvents_UserPreferenceChanged
    miWinFont = Me.Font

    infoFuentes()
End Sub

'
' Los métodos de evento para interactuar con la configuración de Windows
'
Private Sub SystemEvents_UserPreferenceChanged(ByVal sender As Object,
                                               ByVal e As UserPreferenceChangedEventArgs)
    If e.Category = UserPreferenceCategory.Window Then
        Me.Font = SystemFonts.IconTitleFont
        miWinFont = Me.Font
    End If

    mostrarInfoForm()
    txtInfo.Text = String.Format("UserPreferenceChanged.Category = {0} ({1:HH:mm:ss}){2}{3}",
                                 e.Category.ToString(), Date.Now,
                                 Microsoft.VisualBasic.vbCrLf,
                                 txtInfo.Text)

End Sub
public partial class Form1 : Form
{
    /// <summary>
    /// La fuente del sistema
    /// </summary>
    private Font miWinFont;

[...]

//
// El constructor del formulario
//
public Form1()
{

    // This call is required by the designer.
    InitializeComponent();

    // Add any initialization after the InitializeComponent() call.
    miFont = this.Font;

    this.Font = SystemFonts.IconTitleFont;
    SystemEvents.UserPreferenceChanged +=
                                SystemEvents_UserPreferenceChanged;
    this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);

    // Para que tenga dos m\f2étodos de evento el evento Load
    this.Load += new System.EventHandler(this.Form1_Load);

    miWinFont = this.Font;

    infoFuentes();
}

//
// Los métodos de evento para interactuar con la configuración de Windows
//

private void SystemEvents_UserPreferenceChanged(object sender,
                                        UserPreferenceChangedEventArgs e)
{
    if (e.Category == UserPreferenceCategory.Window) {
        this.Font = SystemFonts.IconTitleFont;
        miWinFont = this.Font;
    }

    mostrarInfoForm();
    txtInfo.Text = string.Format("UserPreferenceChanged.Category = {0}"+
                                 "({1:HH:mm:ss}){2}{3}",
                                 e.Category.ToString(), DateTime.Now,
                                 "\r\n", txtInfo.Text);

}

Nota:
No te preocupes por la definición de los métodos infoFuentes ni mostrarInfoForm, ese código está en el ZIP que te pondré al final con el código completo.

Y esto es todo… ya ves qué simple… 🙂

Espero que te sea de utilidad, ¡’esa es siempre la idea!

Nos vemos.
Guillermo.

P.S.
Comentarte que en el código completo (sí, para VB y C#) la aplicación lo que hace es permitir que selecciones un fichero (archivo) y te muestre las propiedades, nombre completo, directorio, fecha de creación, etc.

El código completo del ejemplo (solución para Visual Studio usando .NET 4.7.2)

El ZIP con el código completo (una solución de Visual Studio 2017 con los proyectos de Visual Basic y C#

ZIP: Cambiar tamaño controles.zip (254 KB)

MD5 Checksum: 0B5DDD8A525D61647F10070EFC198DCA

P.S. 2 (31/Dic/18)
Aquí tienes un ejemplo de cómo hacer que el resto de formularios de nuestra aplicación utilicen el tamaño asignado en el formulario principal:
Interceptar el cambio del tamaño de los controles de un formulario en otros formularios de la aplicación.

Cambiar automáticamente las fuentes de nuestro formulario a las de Windows

Pues eso… que como ahora «ando liado» con dos monitores, uno, el principal en el portátil (laptop) y el segundo uno de más resolución, pues… resulta que quiero que en el monitor se vean las fuentes más grandes y también el formulario y los controles, y antes de empezar a hacer «manualidades» (adaptar las cosas por la cuenta de la vieja, es decir, a mano) me he decidido a buscar en Internet a ver si había algo y resulta que sí, que lo hay (o casi), seguramente habrá más cosas pero lo que en principio me ha parecido una buena opción es lo que he encontrado en Cómo: Responder a los cambios de las combinaciones de fuentes en una aplicación de Windows Forms, concretamente en la sección: Para usar la fuente del escritorio y responder a los cambios de esquema de fuentes y concretamente el código mostrado en: Para cambiar manualmente la combinación de fuentes en Windows XP.

Por supuesto el código de ejemplo solo se muestra en C#, pero… no te preocupes aquí estoy yo para mostrarte cómo hacer eso que ahí dice en Visual Basic .NET (sí, y también en C# 😉 )

El código básicamente es como está en esa página que te he indicado, lo único que yo he añadido al ejemplo que te voy a poner es la opción de mostrar las fuentes originales (iniciales del formulario) o bien usar las que Windows te indique.

Para que te hagas una idea de lo que el código hace, te muestro dos capturas del formulario (Form1) en ejecución, la figura 1 es con las letras «normales» y la segunda captura (figura 2) es usando el código que hace que se adapte a las fuentes de Windows.

Nota:
Este código solo cambia el tamaño de las fuentes del formulario, no la de los controles.
Al cambiar la fuente del formulario, este cambia también de tamaño y «reubica los controles».

 

El formulario usando las fuentes normales
Figura 1. El formulario con las fuentes normales


El formulario usando las fuentes de Windows
Figura 2. El formulario usando las fuentes de Windows

No te asustes porque haya muchos controles 😉

Es que ese formulario lo tengo para hacer unas pruebas para convertir de Windows Forms a WPF / XAML y tengo que probar con prácticamente todos los controles (al menos los que yo suelo usar), lo que importa es que veas que el formulario de la figura 2 ha cambiado de tamaño y también el contenido de algunos de los controles (salvo los menús y la barra de botones).


No te voy a explicar en detalle cómo funciona esto, ya que en la página que te indiqué ya lo hacen 🙂 pero si te voy a mostrar el código necesario para que funcione así.


Si estás usando Visual Basic tendrás que agregar el código del constructor (Sub New) (el IDE de Visual Studio agrega el contenido necesario de forma automática) y añadimos las siguientes líneas de código después de la llamada a InitializeComponent:

El código de Visual Basic para el constructor (Sub New):

Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.

    Me.Font = SystemFonts.IconTitleFont
    AddHandler SystemEvents.UserPreferenceChanged, AddressOf SystemEvents_UserPreferenceChanged

End Sub

El código de C# para el constructor:

public Form1()
{
    InitializeComponent();

    this.Font = SystemFonts.IconTitleFont;
    SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(SystemEvents_UserPreferenceChanged);
    this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
}

Si te fijas un poco, en el código de VB no he puesto el «manenejador de eventos» para el evento Form.Closing, ya que no es necesario, aunque se podría haber hecho como en C#, pero si Visual Basic facilita la creación de eventos, ¿por qué no usarla? (ya sabes, en la declaración del evento se añade la cláusula Handles y a continuación el nombre del evento a «capturar».


Te explico brevemente lo que hace ese código del constructor:

Asigna a la fuente del formulario la que hay definida en Windows, esto funciona desde Windows XP hasta el actual Windows 10 (actual en las fechas que estoy escribiendo esto, es decir el 27 de diciembre de 2018).


Después añade el manejador de eventos para la clase UserPreferenceChanged de SystemEvents, que es una clase definida en el espacio de nombres Microsoft.Win32, por tanto en el formulario hay que importar ese espacio de nombres, ya sabes:
Imports Microsoft.Win32 para Visual Basic o using Microsoft.Win32; para C#.


En C# también añade el controlador de eventos para el evento Form.Closing.


En realidad en el evento Form.Closing solo se desliga el manejador de eventos para UserPreferenceChanged.

Aquí tienes el código para VB y C#.

El código para Visual Basic:

Private Sub SystemEvents_UserPreferenceChanged(ByVal sender As Object,
                                               ByVal e As UserPreferenceChangedEventArgs)
    If e.Category = UserPreferenceCategory.Window Then
        Me.Font = SystemFonts.IconTitleFont
    End If
End Sub

Private Sub Form1_FormClosing(ByVal sender As Object,
                              ByVal e As FormClosingEventArgs) Handles Me.FormClosing
    RemoveHandler SystemEvents.UserPreferenceChanged,
            AddressOf SystemEvents_UserPreferenceChanged
End Sub

 

El código para C#:

void SystemEvents_UserPreferenceChanged(object sender, 
                                        UserPreferenceChangedEventArgs e)
{
    if (e.Category == UserPreferenceCategory.Window)
    {
        this.Font = SystemFonts.IconTitleFont;
    }
}

void Form1_FormClosing(object sender, 
                       FormClosingEventArgs e)
{
    SystemEvents.UserPreferenceChanged -= 
        new UserPreferenceChangedEventHandler(SystemEvents_UserPreferenceChanged);
}

Y esto es todo amigo 🙂


Si me decido a poner algún ejemplo completo, actualizaré esta «entrada» (o post) poniendo el enlace para la descarga.



Espero que te sirva 😉

Nos vemos.
Guillermo

P.S.
Si quieres que los controles de tu formulario cambien
al tamaño que tu indiques (un porcentaje), mira esta entrada:
Cambiar el tamaño de los controles de un formulario automáticamente

Números aleatorios con decimales en .NET

Pues eso… aquí te explicaré cómo generar números aleatorios con decimales y cómo indicar los valores entre los que quieres que estén esos números usando la clase Random de .NET y con ejemplos tanto para Visual Basic .NET como para C# (e incluso para BASIC, bueno… al menos para Visual Basic 6.0 y anteriores).

Pero primero te contaré la «batallita del Guille» para que sepas el porqué a estas alturas de la vida te explico algo tan «elemental» Winking smile

Si no te quieres leer la batallita, pues… pasa un poco más abajo que ahí te muestro el código de ejemplo tanto para Visual Basic .NET como para C# e incluso para VB6.

La batallita del Guille

Estaba yo el otro día escribiendo un código en el que necesitaba generar dos números aleatorios (en realidad varios).
Dos de ellos eran números enteros, en esos no tuve mayor problema, ya que tenía este artículo publicado en mi sitio: Random.Next ese incomprendido  y me lo leí, y todo a la perfección… Winking smile

El problema me surgió al crear los números aleatorios con decimales.
Yo sabía que usando NextDouble de la clase Random era como tenía que hacerlo (la certeza era porque yo lo decía al final del artículo ese que te he comentado en el párrafo anterior Winking smile).
Y curiosamente en ese mismo comentario decía que lo más parecido a la función Rnd() de Visual Basic 6.0 era precisamente NextDouble.
Pero… ¿quieres creerte que ya no me acordaba cómo se usaba la función RND de BASIC? jajajaja, en fin… seguramente serán cosas de la edad o de que en los últimos años no tengo la programación como una de mis tareas diarias… en fin…

La cuestión es que volvía a buscar en la web (indicando elGuille) cómo usar RND o casi… y me encontré como mi Curso Básico de Programación en Visual Basic, concretamente en las soluciones de la décima entrega, lo explicaba… ¡menos mal que el Guille aún mantiene su sitio después de los 22 años que cumplió hace pocos días! Winking smile

Generar un número aleatorio con decimales entre dos cifras

El método NextDouble genera un número que será igual o mayor que 0.0 y menor (pero no igual) que 1.0.
Por tanto si lo multiplicaras, por ejemplo por 6, generaría un valor que iría desde 0.0 a 5.99.

Nota:
En Visual Basic 6.0 (o incluso en VB.NET y si me apuras hasta en C#) se utilizará la función Rnd para hacer el mismo papel que NextDouble, pero con valores de tipo Single en lugar de Double.

Tal como explico en las respuestas del curso básico, si quieres un número entero entre 20 y 50 debes escribir un código como este (este ejemplo es de VB6, las variables están declaradas en otra parte… supongo)

T = Int(Rnd * 31) + 20 ‘Número de rascadas, T valdrá de 20 a 50

Es decir la función Rnd (o el método NextDouble) debes multiplicarlo por  31 (50-20+1) y después añadirle el mínimo (20). De esa forma se genera el rango de valores que queremos usar.
En ese ejemplo se usa la función Int para convertir el resultado en un número entero, pero recuerda que ¡es código de VB6!

Ahora veremos ese mismo ejemplo pero haciendo las cosas bien y con .NET.

El código de ejemplo

El siguiente código creará un número aleatorio (con decimales) entre 20 y 50 (ambos incluidos).

Inserta este código en el método Main de una aplicación de consola.

Código para Visual Basic .NET

Dim r As New Random()

Dim minimo As Integer = 20
Dim maximo As Integer = 50

Dim res = (r.NextDouble() * (maximo - minimo + 1)) + minimo
Console.WriteLine("Un número Double entre {0} y {1} (incluidos)", minimo, maximo)
Console.WriteLine("(r.NextDouble * ({0} - {1} + 1)) + {1} = {2}", maximo, minimo, res)
Console.WriteLine()

Console.ReadLine()

Código para C#

var r = new Random();

int minimo = 20;
int maximo = 50;

var res = (r.NextDouble() * (maximo - minimo + 1)) + minimo;
Console.WriteLine("Un número Double entre {0} y {1} (incluidos)", minimo, maximo);
Console.WriteLine("(r.NextDouble * ({0} - {1} + 1)) + {1} = {2}", maximo, minimo, res);
Console.WriteLine();

Console.ReadLine();

La salida mostrada será algo así:

numeros_aleatorios_vb_02
Figura 1. Salida del código de ejemplo

Casualmente el número generado aleatoriamente es 50 y pico… para que veas que el máximo también se incluye Smile

Resumiendo

Para generar un número entre dos valores (ambos incluidos) usa el cálculo siguiente:

(r.NextDouble() * (maximo – minimo + 1)) + minimo

o este otro que también hace lo mismo:

(r.NextDouble() * (minimo – maximo)) + maximo

Resaltar aquí, que si quieres la misma funcionalidad que el método Next de la clase Random, es decir que el valor generado sea igual o mayor que mínimo pero menor que máximo tendrías que hacerlo de esta forma:

(r.NextDouble() * (maximo – minimo)) + minimo

Para finalizar, recordarte que le eches un vistazo a las explicaciones que doy en el artículo que te indiqué al principio:
Random.Next ese incomprendido

Espero que te sirva Winking smile

Nos vemos.
Guillermo

ejemplo de ExecuteScalar para leer una columna de una tabla

 

Pues eso… que esto seguro que lo tengo mil veces publicado en mi sitio, pero hoy lo he ido a buscar, y no lo he encontrado de forma concreta, así que… aquí está para que "yo" lo encuentre… jejeje

¿Por qué buscaba esto?

El caso es el siguiente: necesitaba saber el ID (u otro campo) de una tabla de una base de datos de SQL Server Express 2014 (el tipo de base de datos es indiferente siempre que tenga el método que te comento) haciendo una consulta, concretamente usando LIKE en la propia cadena de selección o consulta.

La consulta es la siguiente:

String.Format( "SELECT ID FROM Clientes WHERE Nombre LIKE '%{0}%' AND Apellidos LIKE '%{1}%'",

nombre, apellidos)

No indico la forma de asignar esa cadena para que uses VB o C#, en el primer caso tendrías que indicar Dim sel = y en el segundo var sel =

A continuación creamos un objeto del tipo Connection y otro del tipo Command y hacemos una llamada al método ExecuteScalar y el valor que devuelva será (en este caso concreto) el ID que estamos buscando o CERO si no lo ha encontrado.

Simplificando (sin tratamiento de error y esas cosas), si el acceso a la base de datos es de SQL Server (en este ejemplo en una instancia de SQLEXPRESS) y usamos las clases definidas en System.Data.SqlClient, el código sería algo así:

Visual Basic:

Public Shared Function BuscarCliente2(nombre As String, apellidos As String) As Integer
    Dim elID As Integer = 0

    Dim sel =
        String.Format(
            "SELECT ID FROM Clientes WHERE Nombre LIKE '%{0}%' AND Apellidos LIKE '%{1}%'",
            nombre, apellidos)

    Dim sCon = ".\sqlexpress;Initial Catalog=LaBaseDeDatos;Integrated Security=True"

    Using con As New SqlConnection(sCon)
        Dim cmd As New SqlCommand(sel, con)

        con.Open()

        elID = CInt(cmd.ExecuteScalar())

        con.Close()
    End Using

    Return elID
End Function

 

C#:

public static int BuscarCliente2(string nombre, string apellidos)
{
    int elID = 0;

    var sel =
        String.Format(
            "SELECT ID FROM Clientes WHERE Nombre LIKE '%{0}%' AND Apellidos LIKE '%{1}%'",
            nombre, apellidos);

    var sCon = @".\sqlexpress;Initial Catalog=LaBaseDeDatos;Integrated Security=True";

    using (SqlConnection con = new SqlConnection(sCon))
    {
        SqlCommand cmd = new SqlCommand(sel, con);

        con.Open();

        elID = Convert.ToInt32(cmd.ExecuteScalar());

        con.Close();
    }

    return elID;
}

 

Y esto es todo amigos… 🙂

 

Nos vemos.

Guillermo