• Saber las unidades externas (USB pendrive fijo o extraíble)

    Pues eso… yo pensaba que todas las unidades (de tipo pendrive) USB devolverían un valor del tipo DriveType.Removable, pero resulta que no. Al igual que ocurre con los discos duros conectados por USB, los pendrives también pueden ser de tipo fijo (DriveType.Fixed).

    Y lo que te voy a mostrar es el código para C# y Visual Basic (usando .NET 5.0 con Visual Studio 2019) para saber todas las unidades conectadas por USB (sean fijas o extraíbles), además de una función para saber si el path indicado está o no en una unidad extraíble.

    También incluyo un ejemplo de cómo saber las unidades (y el tipo de unidad que es) usando el «clásico» método para saber las unidades instaladas con DriveInfo.GetDrives().

    En la siguiente captura puedes ver el programa funcionando en mi equipo.

    Imagen 1. El programa funcionando.

    En mi equipo tengo conectado 2 discos duros por usb (letras S y T), un pendrive de tipo fijo (letra F) y uno de tipo extraíble (letra G).

    En la primera lista (usando el método GetDrives de la clase DriveInfo) el único que se muestra como «extraíble» es el disco G, el resto se muestran como fijos (fixed).
    En la segunda lista se muestran las unidades extraíbles, sean o no fijas.

    Ya sin más rollos te muestro el código tanto para C# como para Visual Basic.

    El código lo puedes descargar de GitHub (UnidadesExternas), pero si quieres crearlo por tu cuenta, decirte que he tenido que añadir una referencia al paquete de Nuget System.Management versión 5.0 ya que estos proyectos los he creado con Visual Studio 2019 y esa versión de Visual Studio no utiliza versiones posteriores a .NET 5.0.

    El tipo de proyecto es Consola de Windows, aunque he tenido que cambiar el tipo del proyecto, ya que al elegir que sea para .NET 5.0 ha intentado usarlo para cualquier plataforma.
    En el fichero del proyecto (de C# o de VB) busca <TargetFramework> y cambia net5.0 por net5.0-windows, ya que System.Management solo se puede usar en el sistema operativo Windows.

     

    Nota:
    Comentarte que el código (de C#) está basado en dos ejemplos encontrados en la red y de esos dos códigos de ejemplo he sacado el código del método GetUsbDriveLetters.
    Estos son los enlaces que he usado:
    El primero es prácticamente el usado en ese código, pero añadiendo la comprobación de que MediaType también pueda ser External, que saqué del segundo.
    https://stackoverflow.com/a/31560283/14338047
    https://stackoverflow.com/a/10018438/14338047

     

    Código de C# de la utilidad para saber las unidades externas

    public class USBUtil
    {
        /// <summary>
        /// Comprueba si es una unidad externa.
        /// </summary>
        /// <param name="elPath">Path completo del que se extraerá la letra de unidad.</param>
        /// <returns></returns>
        public static bool EsUnidadExterna(string elPath)
        {
            var disco = Path.GetPathRoot(Path.GetFullPath(elPath));
            var usbD = GetUsbDriveLetters();
            return usbD.Contains(disco);
        }
    
        // Código basado en dos ejemplos de "la red".
        // https://stackoverflow.com/a/31560283/14338047
        // https://stackoverflow.com/a/10018438/14338047
    
        /// <summary>
        /// Las letras de las unidades externas conectadas por USB.
        /// </summary>
        /// <returns></returns>
        public static List<string> GetUsbDriveLetters()
        {
            // Hay que usar External para que también tenga en cuenta los USB de tipo fijo.
            var usbDrivesLetters = from drive in new ManagementObjectSearcher("select * from Win32_DiskDrive WHERE MediaType like '%External%' OR InterfaceType='USB'").Get().Cast<ManagementObject>()
                                   from o in drive.GetRelated("Win32_DiskPartition").Cast<ManagementObject>()
                                   from i in o.GetRelated("Win32_LogicalDisk").Cast<ManagementObject>()
                                   select string.Format("{0}\\", i["Name"]);
    
            return usbDrivesLetters.ToList();
        }
    
    }
    

     

    Código de Visual Basic de la utilidad para saber las unidades externas

    Public Class USBUtil
    
        ''' <summary>
        ''' Comprueba si es una unidad externa.
        ''' </summary>
        ''' <param name="elPath">Path completo del que se extraerá la letra de unidad.</param>
        ''' <returns></returns>
        Public Shared Function EsUnidadExterna(elPath As String) As Boolean
            Dim disco = Path.GetPathRoot(Path.GetFullPath(elPath))
            Dim usbD = GetUsbDriveLetters()
            Return usbD.Contains(disco)
        End Function
    
        ''' <summary>
        ''' Las letras de las unidades externas conectadas por USB.
        ''' </summary>
        ''' <returns></returns>
        Public Shared Function GetUsbDriveLetters() As List(Of String)
            ' Hay que usar External para que también tenga en cuenta los USB de tipo fijo.
            Dim usbDrivesLetters = From drive In New ManagementObjectSearcher("select * from Win32_DiskDrive WHERE MediaType like '%External%' OR InterfaceType='USB'").[Get]().Cast(Of ManagementObject)()
                                   From o In drive.GetRelated("Win32_DiskPartition").Cast(Of ManagementObject)()
                                   From i In o.GetRelated("Win32_LogicalDisk").Cast(Of ManagementObject)()
                                   Select String.Format("{0}\", i("Name"))
            Return usbDrivesLetters.ToList()
        End Function
    
    

     

    Nota:
    Debes añadir importación de los espacios de nombres:
    System
    System.Collections.Generic
    System.IO
    System.Linq
    System.Management (de NuGet)

     

    Código de ejemplo usar estas los métodos de USBUtil.

    class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "Ejemplo de Visual C# usando .NET 5.0 (net5.0-windows) y System.Management Version=5.0.0";
    
            Console.WriteLine("Mostrar las unidades usando DriveInfo.GetDrives().");
            foreach (var dr in DriveInfo.GetDrives())
                Console.WriteLine("Unidad: {0}, tipo: {1}", dr.Name, dr.DriveType);
    
            Console.WriteLine();
    
            Console.WriteLine("Mostrar las unidades extraíbles.");
            var usb = USBUtil.GetUsbDriveLetters();
            foreach (var s in usb)
                Console.WriteLine("{0}", s);
    
            Console.WriteLine();
            Console.ReadKey();
        }
    

    Module Program
        Sub Main(args As String())
            Console.Title = "Ejemplo de Visual Basic usando .NET 5.0 (net5.0-windows) y System.Management Version=5.0.0"
    
            Console.WriteLine("Mostrar las unidades usando DriveInfo.GetDrives().")
            For Each dr In DriveInfo.GetDrives()
                Console.WriteLine("Unidad: {0}, tipo: {1}", dr.Name, dr.DriveType)
            Next
            Console.WriteLine()
    
            Console.WriteLine("Mostrar las unidades extraíbles.")
            Dim usb = USBUtil.GetUsbDriveLetters()
    
            For Each s In usb
                Console.WriteLine("{0}", s)
            Next
    
            Console.WriteLine()
            Console.ReadLine()
        End Sub
    End Module
    

    Y esto es todo, espero te resulte de utilidad 😉

    Nos vemos.
    Guillermo

  • Lo que no me gusta de Visual Basic .NET (con Visual Studio)

    Pues eso… a pesar de los pesares, hay algo bueno al crear los proyectos para Visual C# desde Visual Studio que en Visual Basic (por aquello de que debemos estar protegidos de tocar donde no se debe, o eso supongo). Es algo muy simple, que introdujeron (creo) en Visual Studio 2005 y es lo de que el método Sub Main sea autogenerado por el Visual Studio y no te permita crear uno propio… o al menos eso me ha pasado con Visual Studio 2022 Version 17.2.0 Preview 1.0 y al final he tenido que crear el proyecto con C# para poder poner lo que me dé la gana en el método Main. En fin…

    Y es que, aunque haya definido mi propio método Sub Main, el Visual Studio me daba error de compilación de que ya estaba definido (o estaba definido más de una vez), no recuerdo bien qué me dijo… pero corté por lo sano, descarté el proyecto de Visual Basic y lo creé con C# (para .NET 6.0 y Windows Desktop).
    Ese proyecto solo se encarga de llamar a otro que es el principal y por tanto, lo que necesitaba hacer es poder llamar al formulario adecuado para que se iniciara ahí la aplicación (el que tengo definido en otro proyecto diferente al de inicio).
    Esto es para poder tener proyectos de inicio diferentes para cada «cliente» que quiera usar mi aplicación. Y en esos proyectos asignar los parámetros personalizados de cada cliente. Todo esto último, solo para aclarar. 😉

    Buscaré en la documentación si hay alguna forma de «pasar» de esta automatización (que, seguro que la hay, desde el propio Visual Studio, ya que creando el proyecto desde la línea de comandos con dotnet seguro que se puede). Y si la hay ya te lo contaré.

    Nota:
    Antes de publicar esto, he probado a crear un proyecto con dotnet:
    He creado el directorio con el nombre del proyecto, he cambiado a ese directorio y he escrito esto en la línea de comandos:
    dotnet new winforms -lang VB -f net6.0
    Y el proyecto se ha creado con el fichero Program.vb (con la definición de Sub Main) y un formulario Form1. Y ahora no dice nada el Visual Studio si lo cargo. Creo que el truco está en no crear los directorios ni asignaciones a My Project, etc.
    Lo probaré desde Visual Studio a ver…

    Nota 2:
    Para solucionar esto en un proyecto creado desde Visual Studio.
    He seguido estos pasos:
    He comentado (también las puedes borrar) las secciones (cada una está dentro de <ItemGroup>) :
    <Compile Update="My Project\Application.Designer.vb"> y
    <None Update="My Project\Application.myapp">.

    He comentado <MyType>WindowsForms</MyType> en el ItemGroup principal y de paso he eliminado la carpeta My Project. Y el fichero ApplicationEvents.vb.
    Esto realmente es lo que da el problema de que haya dos Sub Main.

    Y finalmente he creado un fichero Program.vb con el código «personalizado» para Sub Main.
    En fin… complicaciones por intentar facilitar las cosas a los que preferimos Visual Basic.

    Figura 1. Para quitar el error debes comentar MyType.

    Nota 3:
    Aunque los proyectos de C# creados con Visual Studio también tienen código autogenerado… por ejemplo: ApplicationConfiguration.Initialize.
    Que lo que hace es lo que siempre (o casi) han hecho los métodos Main de las aplicaciones de Windows Forms:
    public static void Initialize()
    {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.SetHighDpiMode(HighDpiMode.SystemAware);
    }

    Y ya está… tenía que decirlo y lo he dicho 😉

    Nos vemos.
    Guillermo

  • Lo que estoy maquinando sobre expresiones lambda (métodos anónimos), programación asíncrona (tareas en otros hilos), cancelación de tareas, Parallel.For, consultas LINQ, DataGridView virtual y más, tanto para C# como para Visual Basic

    Pues eso… ¡peasso de título! y seguramente se me olvidará algo, por eso he puesto al final «y más». Pero la idea que tengo en mente hoy 24 de febrero de 2022 (con la guerra de Ucrania fresquita, aunque es algo que, lamentablemente no es tan fresca y que ya viene de largo), es explicarte en una serie de posts o entradas en el blog todo lo indicado en el título.

    Sobre las expresiones lambda o métodos anónimos, he pensado en ponerte un par de videos que Héctor de León publicó a principios del año pasado en su canal de YouTube (HDELEON.NET), pero como el código que él utiliza es para C# .NET, te mostraré también el equivalente para VB.NET así, si eres de los que utilizan C# lo puedas entender mejor. Todo esto contando con que él me autorice mostrar su código de C# y el equivalente (en la medida de lo posible) de Visual Basic para punto NET.
    Actualización de las 17:33: Autorización que ya me ha dado 😉 ¡Gracias Héctor!

    En cuanto a la programación asíncrona, te explicaré cómo crear tareas en otros hilos (principalmente con Task.Run) y el uso de async y await. Esas tareas se lanzarán o se procesarán también en el hilo principal de una aplicación de tipo Windows Forms (todo el código creado para .NET Core versiones 5 y/o 6) de forma que sepas qué cosas debes saber para el uso de controles, etc. entre hilos, cosa que solucionaré con InvokeRequired y la llamada a un delegado mediante Invoke. Por supuesto verás cómo definir delegados y cómo usarlos en el código.
    Con todo esto verás cómo acceder a parte del código que se ejecuta en el hilo principal (el creado para mostrar el formulario de inicio) desde otro hilo (o tarea).

    También verás cómo cancelar esas tareas y cómo controlarlas, todo ellos mediante el uso de objetos CancellationTokenSource y CancellationToken (tanto en su forma normal y anulable). Y lo que debes hacer para comprobar en el código cuando se ha cancelado y cómo tratar esa cancelación, algo que harás pasándole a la tarea (Task) el objeto de tipo CancellationToken.

    Y como algunas de las tareas (ya sean asíncronas o no) puede que tenga que acceder a objetos de una colección, verás algunos casos de cómo usar Parallel.For para repartir en tareas el proceso de comprobación del contenido de esa colección.

    La mayoría de las cosas que se hará en el código de ejemplo requerirá de las expresiones lambda (o métodos anónimos) y su uso en expresiones de LINQ. Ya sabes: Where, Any, Select, etc.

    Por último, parte del código de ejemplo lo haré usando un control DataGridView y en ese caso te mostraré cómo crearlo para usar una caché con los datos que manejará y todo ello usando el modo virtual de ese control, de esa forma, al menos en mi caso, he logrado agilizar (sobre todo acelerar) mostrar los datos en ese grid (o cuadrícula). La caché usada estará preparada para el tipo de datos que voy a usar en ese ejemplo.

    Todo esto, lo iré publicando poco a poco, entre otras cosas, porque lo estoy usando en una aplicación que utiliza datos o tipos muy concretos. Si te sirve de algo, es una aplicación que estoy migrando de MS-DOS a Windows, y la mayoría de los datos los obtiene de ficheros de texto… ¡Sí, así de vieja es! 🙂
    Pero haré que el código acceda a colecciones de datos más simples, que en principio no se obtendrá de una base de datos, pero no descarto que también haga alguna modificación para acceder a una base de datos, y si es remota, mejor. Pero eso ya lo veré en su momento.

    Bueno, te dejo por ahora y ya iré poniendo cosas… seguramente pondré los enlaces en este mismo post, pero si se me olvida hacerlo… pues… ¡busca en fechas posteriores al 24 de febrero de 2022! 😉

    Y ya sabes… si me quieres invitar a un cafelillo o refresco virtual, puedes usar el enlace de DONAR con PayPal. Gracias de antemano.

    Nos vemos.
    Guillermo