Pues eso, la primera versión de esta utilidad la publiqué en elguille.info:
Copiar archivos de 2 unidades a una tercera con la descripción: Utilidad creada con WPF (.NET 4.7.2) para copiar archivos de 2 unidades a una tercera creando directorios con el MES y el DÍA del archivo.
Después he hecho modificaciones, pero no lo actualicé en «mi antiguo sitio elguille.info», por eso quiero actualizarla en este blog que aún sigue siendo de mi propiedad 😉
Esa primera versión (la del enlace a elguille.info) la hice con Visual Studio 2017 y después la actualicé en el 2020 con Visual Studio 2019, que en mayo de este año 2026 actualicé un poco para que pudiera usar o reconocer los RAW de la Sony 7 AC II (.ARW), y como con esa cámara he estado haciendo fotos en el mismo día con tomas distintas, para no mezclarlas todas en la misma carpeta del «día», pensé en añadirle un texto extra para indicar qué contenido tiene, de forma que me creara (inicialmente como carpeta independiente) las fotos de esta forma:
05 13 9 AC7ii – 1 sesion y su correspondiente para los RAW: 05 13 9 AC7ii – 1 sesion (RAW), para la siguiente «sesión» de fotos serían: 05 13 9 AC7ii – 2 sesion y 05 13 9 AC7ii – 2 sesion (RAW).
Como tenía problemas con las fechas de los ficheros de fotos, ya que al copiarlos desde la cámara o el móvil, en el disco intermedio se guardaba con la fecha en que la copié, pensé usar los datos EXIF de la imagen y así usar la fecha de la toma, no de creación o último acceso, y al usar esa función dejaba la aplicación como «congelada» mientras leía la información EXIF de los ficheros que hubiera que copiar, normalmente el doble de las fotos que tomaba, ya que siempre las tomo con el formato «RAW» (el negativo) y la foto en formato JPG .
Así que, con la ayuda de Gemini (la IA de Google) añadimos lectura multihilo, esta vez usando el .NET 4.8.1 que es el más reciente de .NET Framework que es el que estaba usando hasta ese momento.
Te muestro el método InfoFoto en el que se lee el valor del EXIF del fichero pasado como argumento:
Aquí tienes las dos versiones, la de Visual Basic y la de C#.
El código de InfoFoto para Visual Basic:
''' <summary> ''' Devuelve un objeto del tipo BitmapMetadata con los valores EXIF del archivo ''' Devuelve NOTHING si no es un archivo de imagen válido ''' </summary> ''' <param name="sImg">El nombre completo del archivo</param> ''' <remarks> ''' (16/feb/18) ''' Comprobar errores y devolver Nothing si no es un archivo de iamgen válido ''' --- ''' Basado en CambiarDataeTaken del 03/jul/2016 ''' </remarks> Private Function InfoFoto(sImg As String) As BitmapMetadata Dim fis = New sio.StreamReader(sImg) Dim bmd As BitmapMetadata '= Nothing ' Si da error es que no es un archivo válido Try Dim img As BitmapSource img = BitmapFrame.Create(fis.BaseStream) ' con Ctype es más rápido pero no se controla el valor nulo del objeto 'bmd = CType(img.Metadata, BitmapMetadata) ' esto asignará un valor nulo si el objeto no se ha podido crear correctamente bmd = TryCast(img.Metadata, BitmapMetadata) 'Dim dateTaken = bmd.DateTaken ' el formato es: "02/07/2016 09:36:16" Catch ex As Exception bmd = Nothing End Try Return bmd End Function
El código de InfoFoto para C#:
private BitmapMetadata InfoFoto(string sImg) { BitmapMetadata bmd = null; // Si da error es que no es un archivo válido try { // El bloque using asegura que el archivo se cierre correctamente en el disco using (var fis = new System.IO.StreamReader(sImg)) { BitmapSource img; img = BitmapFrame.Create(fis.BaseStream); // 'as' es el equivalente exacto a 'TryCast' de VB.NET // Asignará un valor nulo si el objeto no se puede convertir de forma correcta bmd = img.Metadata as BitmapMetadata; // string dateTaken = bmd?.DateTaken; // El formato devuelto suele ser: "02/07/2016 09:36:16" } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); bmd = null; } return bmd;
Nota: Este código HTML está preparado para que se muestre en modo oscuro o claro, según pulses en el botón adecuado encima del código.
Esa función después la he mejorado, pero para la última versión que estoy usando, esta vez creada con Visual Studio 2022 y usando .NET 9.0, después te paso los enlaces.
Aspecto de la aplicación en funcionamiento

…
Esta es una captura de la aplicación de C# en la que en el menú de Opciones elegimos si queremos usar Date Taken (de la información EXIF) y si se añade el texto extra de la «sesión» a cualquiera de las dos unidades.
Resumen de la nueva funcionalidad de la versión 2.0 para .NET Framework 4.8.1
Y este es el resumen que me creó Gemini para la publicación de este artículo o post en mi blog (elguillemola.com).
================================================================== RECOMENDACIÓN DE ENFOQUE Y TEXTO DESCRIPTIVO PARA LA VERSIÓN 2.0 (WPF / .NET 4.8.1) Autor de la propuesta: Gemini (La IA de Google) Destino: Blog elguillemola.com / elguille.info ================================================================== CÓMO ENFOCAR EL ARTÍCULO DE LA VERSIÓN 2.0: El objetivo de esta entrega debe ser mostrar el paso intermedio y necesario: cómo salvar una aplicación de escritorio clásica de Windows del temido «efecto congelado» sin cambiar todavía la base de datos o la interfaz (que siguen siendo fijas para dos unidades). Es el escenario perfecto para introducir a tus lectores en el mundo del multihilo y la asincronía nativa de .NET, demostrando que tanto en C# como en VB.NET se escribe con la misma elegancia. —————————————————————— TEXTO DESCRIPTIVO PROPUESTO PARA EL CUERPO DEL ARTÍCULO: —————————————————————— «En esta evolución hacia la versión 2.0, el programa mantiene su estructura visual clásica de WPF con doble origen fijo para tarjetas o cámaras, pero sufre una cirugía profunda en su motor interno. Las funciones de escaneo de ficheros y el bucle de copia física en disco han sido completamente optimizadas y rediseñadas de forma asíncrona mediante la colaboración de Gemini, la IA de Google. El gran logro técnico de esta versión radica en erradicar por completo el uso del obsoleto e inseguro ‘DoEvents()’. En su lugar, se introduce una arquitectura multihilo moderna basada en ‘Task.Run’ y el patrón ‘Async/Await’. Cuando el usuario pulsa el botón de lectura o copia, el trabajo pesado de procesamiento de metadatos EXIF (‘BitmapMetadata’) y la transferencia binaria de archivos se delegan a hilos secundarios del sistema. Esto permite que el hilo principal de la interfaz de usuario (UI) permanezca 100% libre, elástico y responsivo; el usuario puede mover, minimizar o redimensionar la ventana de la aplicación en cualquier momento mientras el disco trabaja a pleno rendimiento en segundo plano. La sincronización entre hilos se resuelve de forma impecable en ambos lenguajes: – En C#, aprovechando la potencia del ‘await’ y llamadas seguras al ‘Dispatcher’ de WPF para inyectar las actualizaciones de texto en el ‘LabelInfo’ sin provocar conflictos de hilos cruzados. – En VB.NET, mediante la misma infraestructura asíncrona adaptada a la semántica limpia y bilingüe de Visual Basic. Además, en el apartado de lógica pura (que detallo más adelante con el tratamiento de los tipos booleanos anulables), se ha blindado el motor de generación de carpetas mediante un algoritmo de sustitución por ‘.Replace()’. Esto evita que caracteres comunes del nombre de la sesión (como la ‘s’ de ‘sesion’) colisionen con las directivas cronológicas del método ‘.ToString()’ de las fechas, impidiendo la creación de directorios con nombres corruptos o inesperados en el disco duro.» ==================================================================
Enlace al repositorio de GitHub
En GitHub tengo publicado el código completo, tanto de la versión para Visual Basic como para C#.
ImportadorFotos-WPF. (enlace al repositorio de github)
Lo que viene a continuación
Como te he dicho he creado otra versión nueva para Visual Studio 2022 con .NET 9.0 y usando también WPF.
La descripción y cambios te los explicaré en otro post:
El nombre del proyecto es: ImportadorFotos_Dinamico_WPF
Pero el título que Gemini me recomendó sería:
WPF y .NET 9 en Acción: De código estático y bloqueado a una arquitectura multihilo y dinámica con JSON
En cualquier caso, cuando lo publique (si no se me olvida) te pondré aquí el enlace y título de ese nuevo post.
Nos vemos.
Guillermo
Deja una respuesta