Etiqueta: .NET

Temas relacionados con .NET Framework (todas las versiones)

  • Recomendaciones para adaptar los controles (y la fuente) en aplicaciones WPF (2ª parte el código para C#)

    Pues eso… lo prometido es deuda… aquí tienes el código para C# de las Recomendaciones para adaptar los controles (y la fuente) en aplicaciones WPF.
    También te enseñaré el código XAML modificado o rectificado desde la última vez, es decir, desde que ayer publiqué el original Winking smile

    Esta es la ventana de la aplicación en modo diseño (figura 1).

    Figura 1. La ventana principal en modo diseño
    Figura 1. La ventana principal en modo diseño

    Los cambios en el código XAML que he puesto son menores, para que los botones de seleccionar de los paneles superiores no se peguen demasiado a las esquinas de la derecha, para ello he cambiado el valor de Padding del control GroupBox y he reducido el margen (Margin) de la parte izquierda de los botones. También he puesto el ancho de la primera columna del ListView a 220, ya que hay espacio suficiente (la ventana principal tiene asignado un valor mínimo de 830 de ancho.

    Por si te sirve de ayuda, sobre todo si has usado padding y margin en los estilos CSS.

    Comentarte que los valores de Padding y Margin en Xaml se refieren a las posiciones:
    left, top, right, bottom,
    a diferencia de las usadas en los estilos CSS, que hacen referencia a:
    top right bottom left.
    De nada Be right back

    Aquí tienes el código XAML (del diseñador de WPF) del panel superior izquierdo, el de la derecha es prácticamente igual.

    <GroupBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="1" 
              Header="Unidad 1" Margin="0" Padding="0,0,4,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,4,0,0" />
            <TextBox Name="txtDir1" Grid.Row="0" Grid.Column="1" 
                     Grid.ColumnSpan="3" Grid.RowSpan="1"
                     Margin="0,6,0,0" KeyDown="txtDir1_KeyDown"
                     TextWrapping="NoWrap" Text="S:\iPhone"/>
            <Button Name="btnSel1" Content=" Seleccionar... " 
                    Grid.Column="4" Grid.ColumnSpan="1"
                    Margin="4,6,0,0" Click="BtnSel_Click" />
            <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 ListView (concretamente cada GridViewColumn) definen un enlace (Binding) a Nombre y Fecha, de esa forma podremos agregar a cada elemento del ListView un objeto definido por nosotros (ItemFic) con esas dos propiedades y se asignarán correctamente a cada una de las columnas del ListView.

    Esta es la definición de las clases ItemFic y ItemDir, esta última la usaré para mostrar el nombre del directorio del ListView definido en el panel inferior.

    El método llenar es el que se encarga de asignar los elementos a los ListView.

    class ItemDir
    {
        public string Nombre { get; set; }
    }
    
    class ItemFic : ItemDir
    {
        public DateTime Fecha { get; set; }
    }
    
    /// <summary>
    /// Llenar un listview con los ficheros del directorio
    /// </summary>
    private void llenar(ListView lvFiles, string dir)
    {
        lvFiles.Items.Clear();
        var dirI = new sio.DirectoryInfo(dir);
        if (dirI.Exists == false)
            return;
    
        foreach (var fi in dirI.GetFiles())
        {
            var lvi = new ItemFic() { Nombre = fi.Name, Fecha = fi.LastWriteTime };
            lvFiles.Items.Add(lvi);
        }
    }
    

    Nota:
    sio es un alias al espacio de nombres System.IO ya que Path también está definido en System.Windows.Shapes y así no tenemos conflictos de nombres, al menos si usas C#, ya que al crear un proyecto WPF se añade esa importación de espacios de nombres.

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

    Para gestionar el cambio de tamaño de la ventana y las letras de cada control he añadido un par de menús a la aplicación.

    Recuerda que para que los tamaños de las letras (FontSize) se hagan medio-automáticamente, tal como lo hago en esta aplicación, deben tener el valor predeterminado de las fuentes, es decir, NO añadas ningún valor a las fuentes de los controles o de los contenedores, si no, no funcionará.

    Este es el código XAML de la definición de los menú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>
    

    Aquí tienes el código de C# para cambiar el tamaño de las fuentes, se suma o sustrae el valor que indiquemos (si este último es negativo), y en el tamaño de la ventana principal añadimos (o restamos) el porcentaje indicado.

    private double meHeight;
    private double meWidth;
    private double meFontSize;
    
    private void BtnAplicarVentana_Click(object sender, RoutedEventArgs e)
    {
        // Para cambiar el tamaño a partir del valor inicial
        if (meHeight == 0)
            meHeight = this.Height;
        if (meWidth == 0)
            meWidth = this.Width;
    
        double d = 0;
        double.TryParse(txtTamañoVentana.Text, out d);
        this.Height = nuevoTamaño(meHeight, d);
        this.Width = nuevoTamaño(meWidth, d);
    }
    
    private void BtnAplicarFuente_Click(object sender, RoutedEventArgs e)
    {
        if (meFontSize == 0)
            meFontSize = this.FontSize;
        double d = 0;
        double.TryParse(txtTamañoFuente.Text, out d);
        this.FontSize = meFontSize + d;
    }
    
    
    private double nuevoTamaño(double actual, double incremento)
    {
        return actual * incremento + actual;
    }
    

    Quitar el botón maximizar de la ventana usando API

    Para terminar, te mostraré el código para quitar el botón de maximizar la ventana, ya que WPF no tiene opciones para ocultar ese botón, salvo que cambiemos el tipo de ventana, pero en este caso yo la tengo definida asignando a la propiedad ResizeMode el valor CanResizeWithGrip. Por tanto, puedo maximizar, minimizar, cambiar el tamaño, etc.

    Pero siempre queda bien eso de que el usuario no pueda maximizar de golpe, si quiere la ventana más grande, que le cambie el tamaño poco a poco Winking smile

    // --------------------------------------------------------------------------
    // 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 static extern int GetWindowLong(IntPtr hWnd, int nIndex);
    
    [DllImport("user32.dll")]
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
    
    private const int GWL_STYLE = -16;
    private const int WS_MAXIMIZEBOX = 0x10000;
    
    private void Window_SourceInitialized(object sender, EventArgs e)
    {
        var hwnd = new WindowInteropHelper((Window)sender).Handle;
        var value = GetWindowLong(hwnd, GWL_STYLE);
        SetWindowLong(hwnd, GWL_STYLE, (int)(value & ~WS_MAXIMIZEBOX));
    }
    

    Los eventos, SourceInitialized y otros, están definidos en el código Xaml de la aplicación.

    Height="500" Width="850"
    MinHeight="460" MinWidth="830"
    WindowStartupLocation="CenterScreen"
    ResizeMode="CanResizeWithGrip"
    
    SourceInitialized="Window_SourceInitialized"
    
    Loaded="MainWindow_Loaded">
    

    Y esto es todo lo que quería contarte usando los puntos y comas In love

    Te recomiendo que te leas lo escrito en la primera parte, ya que aunque el código mostrado es para Visual Basic, las explicaciones, particularmente para el código XAML, te van a servir, seguro Smile

    Y para que vayas a la otra página, allí está el enlace para descargar el zip con el código completo, tanto para Visual Basic como para C#.

    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