Autor: elGuille

  • 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

  • El código de VB y C# de: Mostrar todos los formularios de una aplicación

    Pues eso… lo prometido es deuda y aquí tienes el código completo de cómo mostrar todos los formularios de una aplicación (ensamblado) y cómo mostrar un formulario en el segundo monitor.

    Al final de todo te pongo el enlace para descargar los proyectos tanto de Visual Basic como de C#.

    ¡Que te aproveche! Smile

    Estas son las capturas (en funcionamiento) de Visual Basic y C#:

    mostrar_formularios_01
    Figura
    1, la aplicación de Visual Basic

    mostrar_formularios_02
    Figura 2: La aplicación de C#

    El código completo de Visual Basic:

    '------------------------------------------------------------------------------
    ' Mostrar todos los formularios de una aplicación (ensamblado)      (16/Dic/18)
    ' Estén abiertos o no
    ' Usando reflection
    '
    ' Este código está basado en el artículo que publiqué el 13/Sep/2004 en:
    ' http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm
    ' 
    '
    ' (c) Guillermo (elGuille) Som, 2018
    '------------------------------------------------------------------------------
    Option Strict On
    Option Infer On
    
    Imports Microsoft.VisualBasic
    Imports vb = Microsoft.VisualBasic
    Imports System
    Imports System.Text
    Imports System.Collections.Generic
    Imports System.Windows.Forms
    Imports System.Drawing
    Imports System.Diagnostics
    Imports System.Linq
    
    Public Class Form1
        ''' <summary>
        ''' Para recorrer todos los formularios de un ensamblado
        ''' estén abiertos o no
        ''' Basado en el código del elGuille.info:
        ''' http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm
        ''' </summary>
        Private ass As System.Reflection.Assembly
    
        ''' <summary>
        ''' Mostrar todos los formularios de la aplicación actual,
        ''' estén o no en memoria, usando reflection.
        ''' Además de los formularios, mostrará:
        ''' My.Application (solo en VB)
        ''' Y todos los nombres esmpiezan con el espacio de nombres
        ''' ESPACIO_DE_NOMBRES.NombreForm
        ''' </summary>
        Private Sub mostrarForms()
            ' llena una colección con los formularios de esta aplicación
            ' estén o no en memoria.
            ' Muestra el resultado en un listbox
    
            lbForms.Items.Clear()
    
            For Each t As Type In ass.GetTypes()
                Dim nombreTipo = t.BaseType.Name
                ' También tendrá My.Application: (solo en VB)
                ' <espacio de nombres>.My.MyApplication
                If nombreTipo.ToLower().Contains("form") Then
                    lbForms.Items.Add(t.FullName)
                End If
            Next
        End Sub
    
        ''' <summary>
        ''' Muestra el formulario indicado en el argumento,
        ''' este debe ser con el espacio de nombres completo
        ''' 
        ''' Si es el actual no lo muestra.
        ''' Si da error o no es un formulario se avisa.
        ''' </summary>
        Private Sub mostrarFormulario(s As String)
            ' creamos un tipo a partir del nombre
            Dim t As Type = ass.GetType(s)
            ' instanciamos un nuevo objeto en la memoria
            Dim o As Object
    
            ' por si hemos seleccionado algo que no es una clase
            Try
                o = Activator.CreateInstance(t)
            Catch ex As Exception
                MessageBox.Show(ex.Message, "Mostrar formularios")
                Exit Sub
            End Try
    
            ' si no es un formulario, mostramos un aviso y salimos
            If Not (TypeOf o Is Form) Then
                MessageBox.Show(s & ", no es un formulario", "Mostrar formularios")
                Exit Sub
            End If
    
            ' convertimos el objeto en un formulario
            ' como sabemos que si llega aquí es un formulario,
            ' usamos TryCast que hace menos trabajo que CType o DirectCast.
            Dim f As Form = TryCast(o, Form)
            If f Is Nothing Then
                MessageBox.Show(s & ", parece que no es un formulario",
                                "Mostrar formularios")
                Exit Sub
            End If
    
            ' si el nombre es el de este formulario,
            ' lo cerramos y salimos.
            If f.Name = Me.Name Then
                ' no volver a crear este formulario
                'f.Close()
                Me.BringToFront()
    
                MessageBox.Show(s & " es el formulario de inicio.",
                                "Mostrar formularios")
    
                Return
            End If
    
            ' mostramos el formulario.
            f.Show()
    
        End Sub
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ' Mostrar esta ventana en el monitor secundario
            MostrarEnVentana2(Me)
    
            ' mostrar los formularios de esta aplicación
            ass = System.Reflection.Assembly.GetExecutingAssembly()
            mostrarForms()
        End Sub
    
        Private Sub btnAbrir_Click(sender As Object, e As EventArgs) Handles btnAbrir.Click
            ' Por si no hay un nombre indicado
            If String.IsNullOrWhiteSpace(txtForm.Text) Then Return
    
            ' Abrir el formulario indicado en el textbox
            mostrarFormulario(txtForm.Text)
        End Sub
    
        Private Sub btnSalir_Click(sender As Object, e As EventArgs) Handles btnSalir.Click
            Me.Close()
        End Sub
    
        Private Sub lbForms_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lbForms.SelectedIndexChanged
            txtForm.Text = lbForms.SelectedItem.ToString
        End Sub
    
        ''' <summary>
        ''' Mostrar el formulario indicado en la segunda pantalla
        ''' Código adaptado de la respuesta 42 de:
        ''' https://stackoverflow.com/questions/2561104/how-do-i-ensure-a-form-displays-on-the-additional-monitor-in-a-dual-monitor-sc
        ''' </summary>
        Public Shared Sub MostrarEnVentana2(frm As Form)
            Dim myScreen = Screen.PrimaryScreen
            Dim otherScreen = If(Screen.AllScreens.FirstOrDefault(
                                    Function(s) Not s.Equals(myScreen)), myScreen)
    
            ' Si queremos indicar dónde mostrarlo
            ' podemos cambiar los valores de Left y Top
            'frm.Left = otherScreen.WorkingArea.Left + 12
            'frm.Top = otherScreen.WorkingArea.Top + 12
    
        End Sub
    
        Private Sub btnForm2_Click(sender As Object, e As EventArgs) Handles btnForm2.Click
            Dim f As New Form2
            f.Show()
        End Sub
    
        Private Sub btnForm3_Click(sender As Object, e As EventArgs) Handles btnForm3.Click
            Dim f As New Form3
            f.Show()
        End Sub
    End Class
    

    El código completo de C#:

    // ----------------------------------------------------------------------------
    // Mostrar todos los formularios de una aplicación (ensamblado)     (16/Dic/18)
    // Estén abiertos o no
    // Usando reflection
    // 
    // Este código está basado en el artículo que publiqué el 13/Sep/2004 en:
    // http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm
    // 
    // 
    // (c) Guillermo (elGuille) Som, 2018
    // ----------------------------------------------------------------------------
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Security;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.Drawing;
    
    namespace Mostrar_Nombres_Formularios_cs
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            /// <summary>
            /// Para recorrer todos los formularios de un ensamblado
            /// estén abiertos o no
            /// Basado en el código del elGuille.info:
            /// http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm
            /// </summary>
            private System.Reflection.Assembly ass;
    
            /// <summary>
            /// Mostrar todos los formularios de la aplicación actual,
            /// estén o no en memoria, usando reflection.
            /// Además de los formularios, mostrará:
            /// My.Application (solo en VB)
            /// Y todos los nombres esmpiezan con el espacio de nombres
            /// ESPACIO_DE_NOMBRES.NombreForm
            /// </summary>
            private void mostrarForms()
            {
                // llena una colección con los formularios de esta aplicación
                // estén o no en memoria.
                // Muestra el resultado en un listbox
    
                lbForms.Items.Clear();
    
                foreach (Type t in ass.GetTypes())
                {
                    var nombreTipo = t.BaseType.Name;
                    // También tendrá My.Application: (solo en VB)
                    // <espacio de nombres>.My.MyApplication
                    if (nombreTipo.ToLower().Contains("form"))
                        lbForms.Items.Add(t.FullName);
                }
            }
    
            /// <summary>
            /// Muestra el formulario indicado en el argumento,
            /// este debe ser con el espacio de nombres completo
            /// 
            /// Si es el actual no lo muestra.
            /// Si da error o no es un formulario se avisa.
            /// </summary>
            private void mostrarFormulario(string s)
            {
                // creamos un tipo a partir del nombre
                Type t = ass.GetType(s);
                // instanciamos un nuevo objeto en la memoria
                object o;
    
                // por si hemos seleccionado algo que no es una clase
                try
                {
                    o = Activator.CreateInstance(t);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, 
                        "Mostrar formularios");
                    return;
                }
    
                // si no es un formulario, mostramos un aviso y salimos
                if (!(o is Form))
                {
                    MessageBox.Show(s + ", no es un formulario", 
                        "Mostrar formularios");
                    return;
                }
    
                // convertimos el objeto en un formulario
                Form f = o as Form;
                if (f == null)
                {
                    MessageBox.Show(s + ", parece que no es un formulario", 
                        "Mostrar formularios");
                    return;
                }
    
                // si el nombre es el de este formulario,
                // lo cerramos y salimos.
                if (f.Name == this.Name)
                {
                    // no volver a crear este formulario
                    // f.Close()
                    this.BringToFront();
    
                    MessageBox.Show(s + " es el formulario de inicio.", 
                        "Mostrar formularios");
    
                    return;
                }
    
                // mostramos el formulario.
                f.Show();
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                // Mostrar esta ventana en el monitor secundario
                MostrarEnVentana2(this);
    
                // mostrar los formularios de esta aplicación
                ass = System.Reflection.Assembly.GetExecutingAssembly();
                mostrarForms();
            }
    
            private void btnAbrir_Click(object sender, EventArgs e)
            {
                //' Por si no hay un nombre indicado
                if (String.IsNullOrWhiteSpace(txtForm.Text))
                    return;
    
                //' Abrir el formulario indicado en el textbox
                mostrarFormulario(txtForm.Text);
    
            }
    
            private void btnSalir_Click(object sender, EventArgs e)
            {
                this.Close();
            }
    
            private void lbForms_SelectedIndexChanged(object sender, EventArgs e)
            {
                txtForm.Text = lbForms.SelectedItem.ToString();
            }
    
            /// <summary>
            /// Mostrar el formulario indicado en la segunda pantalla
            /// Código adaptado de la respuesta 42 de:
            /// https://stackoverflow.com/questions/2561104/how-do-i-ensure-a-form-displays-on-the-additional-monitor-in-a-dual-monitor-sc
            /// </summary>
            public static void MostrarEnVentana2(Form frm)
            {
                var myScreen = Screen.PrimaryScreen;
                var otherScreen = Screen.AllScreens.FirstOrDefault(s => !s.Equals(myScreen)) 
                                        ?? myScreen;
    
                //' Si queremos indicar dónde mostrarlo
                //' podemos cambiar los valores de Left y Top
                //'frm.Left = otherScreen.WorkingArea.Left + 12
                //'frm.Top = otherScreen.WorkingArea.Top + 12
            }
        }
    }
    
    

    El enlace para descargar el código completo (tanto de Visual Basic como de C#):

    Archivo: Mostar_Nombres_Formularios_20181216_2346.zip (204 KB)
    MD5 Checksum: DA004D15933BD82355684AC0F20680B1

    Nos vemos.
    Guillermo

  • Mostrar los formularios de nuestra aplicación (aunque no estén abiertos)

    Pues eso… que estoy haciendo una aplicación para convertir los Windows Forms en WPF (Xaml) y tengo que recorrer todos los formularios de la aplicación para ir convirtiéndolos y he buscado en Internet cómo hacer eso y… ¡no lo he encontrado!

    Mostrar todos los formularios de un ensamblado (aplicación) estén o no abiertos

    Así que… me dije que yo tenía algún código haciendo Reflection para mostrar una clase y va y resulta que en ese ejemplo tenía la respuesta que buscaba… ¡lo que no esté en el sitio del Guille! Winking smile

    Concretamente en esta página:
    Las clases de un ensamblado usando Reflection (por ejemplo para saber los formularios de nuestra aplicación)

    Y para que no me vuelva a ocurrir lo mismo en un futuro (ese artículo lo escribí el 13 de septiembre de 2004… ¡casi ná! Smile with tongue out) me he decidido a escribir este nuevo…

    No te voy a explicar mucho (eso ya lo hice en ese artículo) solo te mostraré el código de ejemplo (y te pondré el proyecto para que lo puedas descargar).
    Y como siempre el código tanto para Visual Basic como para C#.

    Primero te muestro el código que recorre los formularios del ensamblado o aplicación (que es lo que realmente te puede interesar) y después te mostraré el resto.

    El código para Visual Basic:

        ''' <summary>
        ''' Para recorrer todos los formularios de un ensamblado
        ''' estén abiertos o no
        ''' Basado en el código del elGuille.info:
        ''' http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm
        ''' </summary>
        Private ass As System.Reflection.Assembly
    
        ''' <summary>
        ''' Mostrar todos los formularios de la aplicación actual,
        ''' estén o no en memoria, usando reflection.
        ''' Además de los formularios, mostrará:
        ''' My.Application (solo en VB)
        ''' Y todos los nombres esmpiezan con el espacio de nombres
        ''' ESPACIO_DE_NOMBRES.NombreForm
        ''' </summary>
        Private Sub mostrarForms()
            ' llena una colección con los formularios de esta aplicación
            ' estén o no en memoria.
            ' Muestra el resultado en un listbox
    
            lbForms.Items.Clear()
    
            For Each t As Type In ass.GetTypes()
                Dim nombreTipo = t.BaseType.Name
                ' También tendrá My.Application: (solo en VB)
                ' <espacio de nombres>.My.MyApplication
                If nombreTipo.ToLower().Contains("form") Then
                    lbForms.Items.Add(t.FullName)
                End If
            Next
        End Sub
    

    El código para C#:

            /// <summary>
            /// Para recorrer todos los formularios de un ensamblado
            /// estén abiertos o no
            /// Basado en el código del elGuille.info:
            /// http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm
            /// </summary>
            private System.Reflection.Assembly ass;
    
            /// <summary>
            /// Mostrar todos los formularios de la aplicación actual,
            /// estén o no en memoria, usando reflection.
            /// Además de los formularios, mostrará:
            /// My.Application (solo en VB)
            /// Y todos los nombres esmpiezan con el espacio de nombres
            /// ESPACIO_DE_NOMBRES.NombreForm
            /// </summary>
            private void mostrarForms()
            {
                // llena una colección con los formularios de esta aplicación
                // estén o no en memoria.
                // Muestra el resultado en un listbox
                lbForms.Items.Clear();
    
                foreach (Type t in ass.GetTypes())
                {
                    var nombreTipo = t.BaseType.Name;
                    // También tendrá My.Application: (solo en VB)
                    // <espacio de nombres>.My.MyApplication
                    if (nombreTipo.ToLower().Contains("form"))
                        lbForms.Items.Add(t.FullName);
                }
            }
    

    Solo comentarte que en el código de Visual Basic he añadido 2 botones extras para mostrar los formularios Form2 y Form3 respectivamente. Esto solo es para probar que se pueden mostrar de las dos formas Smile
    Pero en cualquier caso, siempre se muestra un nuevo formulario, esté o no abierto.

    En la figura 1 puedes ver la aplicación en funcionamiento, mostrando una lista de los formularios de la aplicación.

    mostrar_formularios_01.pg
    Figura 1. La aplicación en ejecución mostrando los formularios del ensamblado (aplicación)

    Si te fijas en la figura 1, se muestra también «My.MyApplication» que no es un formulario, pero el tipo «base» es Form, y por eso se muestra. Esto no ocurre en C#, ya que C# no tiene la clase My.

    Nota:
    El código de Visual Basic lo he convertido con una utilidad que hay en la web:
    Telerik Code Converter

    Un extra: Mostrar un formulario en el segundo monitor

    Esto ya lo publicaré otro día (realmente era lo que iba a publicar hoy junto con una explicación de los problemas de nuestro código de Windows Forms y Visual Studio y los monitores HDPI), y es que en el código incluyo cómo hacer que un formulario se muestre en el segundo monitor.

    Actualmente estoy usando un portátil con la resolución 1920×1080 y usando el tamaño del texto (etc.) al 100% y un segundo monitor con 3840×2160 y el tamaño del texto, etc. al 150%.

    Como te explicaré en otra ocasión, yo uso el Visual Studio abriéndolo desde la pantalla principal (la del portátil al 100%), pero después muevo el IDE de Visual Studio al segundo monitor, por tanto cuando ejecuto el código, lo que se muestra lo hace siempre en la pantalla del portátil, por eso busqué cómo hacer que se muestre un formulario en el segundo monitor, y ¡lo encontré!, eso sí, en código para C#, concretamente en una respuesta en el sitio de StackOverflow en un hilo con la siguiente pregunta: How do I ensure a form displays on the “additional” monitor in a dual monitor scenario?
    La respuesta que me sirvió fue la 42, con este código:

    var myScreen = Screen.FromControl(originalForm);
    var otherScreen = Screen.AllScreens.FirstOrDefault(s => !s.Equals(myScreen)) 
                   ?? myScreen;
    otherForm.Left = otherScreen.WorkingArea.Left + 120;
    otherForm.Top = otherScreen.WorkingArea.Top + 120;

    Que traducido a Visual Basic sería algo así:

    Dim myScreen = Screen.PrimaryScreen
    Dim otherScreen = If(Screen.AllScreens.FirstOrDefault(
                            Function(s) Not s.Equals(myScreen)), myScreen)
    

    Y ¡esto es todo amigo!
    Espero , como siempre, que te sea de utilidad y si no… al menos a mí dentro de 14 años me puede ser útil… Winking smile

    Nos vemos.
    Guillermo

    P.S.
    El código de ejemplo completo y el enlace para la descarga de los proyectos (hechos con Visual Studio 2017 y .NET 4.7.2, ninguno de estos requisitos son necesarios para el código) te los muestro en otra página.

    El código de VB y C# de: Mostrar todos los formularios de una aplicación

    Winking smile