Pues eso… Marcelo de SevillaDotNet me ha invitado a un café virtual el próximo miércoles día 7 de abril a las 18:30 hora de la España peninsular (una hora menos en Canarias), así que… si nos quieres acompañar regístrate y asiste siguiendo este enlace: Café Virtual con Guillermo Som.
Nota del martes 6 de abril Si quieres, también puedes verlo en directo por YouTube o bien usar este mismo enlace para después de mañana 7 de abril de 2021:
Si quieres ver qué hora es ahora en España (Madrid) sigue el enlace y así sabrás qué hora será en tu país 😉 Me comentan que al pulsar el enlace te muestra la hora en tu ciudad.
Este es el resumen de la charla cafetera 🙂
En este café virtual vamos a charlar con Guillermo Som. Tras tantos años participando y ayudándonos a todos con su sitio elguille.info, probablemente te haya ayudado en algún momento. Vamos a charlar sobre sus inicios en la informática, sobre el camino que él ha tenido cómo programador, de .NET y su evolución y mucho más.
En esa ocasión me daba error la aplicación de Xamarin.Forms y sin saber porqué… Pero la he vuelto a probar y por ahora, al menos en el IDE de Visual Studio 2019 va bien. De todas formas, ya sabes si usas lo que te voy a explicar aquí, y ves que la aplicación casca, ya sabes porqué es.
La documentación (en inglés) dice esto:
For performance reasons, you should call this method only when you do not know at design time what assembly is currently executing. The recommended way to retrieve an Assembly object that represents the current assembly is to use the Type.Assembly property of a type found in the assembly.
Que en el idioma actualizado de Cervantes viene a decir esto (según Google Translator):
Por motivos de rendimiento, debe llamar a este método solo cuando no sepa en tiempo de diseño qué ensamblado se está ejecutando actualmente. La forma recomendada de recuperar un objeto Ensamblado que representa el ensamblado actual es usar la propiedad Type.Assembly de un tipo que se encuentra en el ensamblado.
Aquí te voy a poner el código (tanto de Visual Basic como de C#) para usar esta forma recomendada de asignar un ensamblado (de la clase System.Reflection.Assembly).
Este código está en bibliotecas (proyectos del tipo Class Library) para usar con .NET Standard 2.0 (de esta forma los ensamblados se podrán usar tanto en .NET Core como en .NET Framework).
Ejemplo para C#
publicstaticstring VersionDLL()
{
var ensamblado = typeof(AboutViewModel).Assembly;
var fvi = FileVersionInfo.GetVersionInfo(ensamblado.Location);
// FileDescription en realidad muestra (o eso parece) lo mismo de ProductName
var s = $"{fvi.ProductName} v{fvi.ProductVersion} ({fvi.FileVersion})" +
$"\r\n{fvi.Comments}";
return s;
}
Ejemplo para Visual Basic
PublicFunction VersionDLL() AsString
Dim ensamblado = GetType(DatosMostrar).Assembly
Dim fvi = FileVersionInfo.GetVersionInfo(ensamblado.Location)
' FileDescription en realidad muestra (o eso parece) lo mismo de ProductName
Dim s = $"{fvi.ProductName} v{fvi.ProductVersion} ({fvi.FileVersion})" &
$"{vbCrLf}{fvi.Comments}"
Return s
EndFunction
Ese código mostrará la versión del ensamblado, la versión del fichero y la descripción, aunque en realidad a la propiedad a la que tienes que acceder es a Comments, ya que FileDesciption muestra lo mismo que ProductName.
Y aquí tienes una captura para la aplicación de Android con los comentarios de las 3 bibliotecas que estoy usando, 2 de ellas escritas con Visual Basic y la tercera (la que le da funcionalidad visual a la aplicación está escrita en C#).
Figura 1. Captura en el emulador de Android.
Y esta otra captura es de la aplicación en el emulador (local) de UWP (Universal Windows Platform) pero con la aplicación real. Aunque en modo depuración.
Figura 2. Captura en el (emulador) de UWP.
Espero que te sea de utilidad… Esa es siempre la idea…
Nos vemos. Guillermo
P.S. El puñetero Jetpack agrega código (de más) a las imágenes y cuando estas se muestran desde www.elguille.info, simplemente no se ven… pero… puedes pulsar en el sitio (en blanco) en el que está la imagen y te la mostrará… Lo mismo quito el Jetpack, que, aparte del «bloque» Markdown y el poder publicar en twitter y mi sitio de facebook, no me sirve de mucho…
Pues eso… aquí tienes una utilidad para cambiar los nombres de los ficheros. No solo cambiar un texto existente en el nombre, si no que también se puede añadir (o quitar) un texto al principio o al final del nombre del fichero.
Los ficheros a procesar los puedes indicar según la extensión de los mismos (o usando un filtro con * y/o ?. En total se pueden procesar 4 directorios/filtros.
En la figura puedes ver la aplicación en modo de ejecución.
Funcionamiento de la aplicación
Como puedes ver en la figura, tiene 4 grupos de directorios/ficheros con idea de que puedas indicar varias extensiones o bien 4 directorios diferentes.
Cada directorio se puede o no procesar (el primero siempre se tendrá en cuenta), por supuesto, si no indicas el path ni la extensión no se procesarán (o así debería ser, pero en realidad no hago ningana comprobación al respecto, así que… para salir de dudas… ¡pruébalo!).
En la parte inferior tienes las opciones de qué hará la aplciación, teniendo estas opciones:
Añadir el texto indicado en Text 1 al principio.
Añadir el texto indicado en Text 1 al final.
Quitar el texto indicado en Text 1 del principio.
Quitar el texto indicado en Text 1 del final.
Cambiar lo que haya en Text 1 por lo que hay en Texto 2
Y para las dos opciones de añadir texto puedes indicar si no se hace el cambio en el caso de que dicho texto (el de Texto 1) ya está.
Cuando se selecciona alguna de las dos opciones de añadir, se habilita ese control. En el resto de opciones está deshabilitado.
Lo mismo ocurre con el texto de Texto 2, que se habilitará solo si se selecciona la opción de cambiar, en las otras 4 opciones estará deshabilitado, y por tanto no se tendrá en cuenta lo que se escriba.
En las cajas de textos (Texto 1 y Texto 2) se pueden indicar espacios al final (y se tendrán en cuenta en la búsqueda y/o reeemplazo) y como visiblemente es difícil de ver si hay uno o más espacios, en caso de que haya más de un espacio al final, el programa los quitará y dejará solo uno. Si no hay espacios al final del texto no se añadirá ninguno.
Nota:
Hay que tener en cuenta, sobre todo al reemplazar texto (opción Cambiar) que el texto indicado en Texto 1 se reemplazará por el que haya en Texto 2 en todo el nombre del fichero (sin tener en cuenta ni el path ni la extensión).
Por tanto, si buscas el y el nombre original es Inclemencias del tiempo y quieres cambiar ese el por la el rtexto resultandte será (en mayúsculas los cambios realizados): IncLAmencias dLA tiempo.
El que avisa…
Controles usados en la utilidad
Esta utilidad (o aplciación) usa 4 controles del tipo BackgroundWorker con idea de usar de forma fácil la operación asíncrona.
El que está enlazado con el primer grupo (el que siempre está activo) siempre se ejecuta. Pero los otros 3 solo si se marca la opción que está arriba de cada grupo.
La barra de progreso tendrá en cuenta todos los ficheros que se vayan a procesar.
Y para saber el total de ficheros a procesar, en cada evento DoWork de cada control BackgroundWorker se añade a una colección definida a nivel del formulario en la que se almacenará los nombres de los ficheros a procesar (teniendo en cuenta el directorio en el que se buscarán los ficheros con el filtro indicado.
Ese filtro se hace usando lo indicado en cada caja de texto etiquetada tras Filtro. Como se pueden usar los comodines * y ? podrás usar cosas como estas:
*.txt para indicar todos los ficheros con la extensión .txt
algo?.xls* esto buscará los ficheros que empiecen con algo y cualquier carácter a continuación y que tengan una extensión que empiece por .xls
Como el código está compilado con .NET Framewwork 4.8 en realidad esto se puede hacer sin indicar el asterisco final, es decir, se puede indicar de esta forma: algo?.xls para que acepte todas las extensiones que empiecen por xls (.xls y .xlsx).
En .NET 5.0 habría que hacerlo como he comentado en el segundo punto, pero no es este el caso de esta aplicación.
Aquí te muestro el código del evento Click del botón Procesar:
PrivateSub btnProcesar_Click(sender AsObject, e AsEventArgs) Handles btnProcesar.Click
ficheros.Clear()
' Si al final del texto a buscar/poner
' hay más de un espacio, cambiarlo por solo 1
If txtTexto1.Text.EndsWith(" ") Then
txtTexto1.Text = txtTexto1.Text.TrimEnd(" "c) & " "
EndIf
If txtTexto2.Text.EndsWith(" ") Then
txtTexto2.Text = txtTexto2.Text.TrimEnd(" "c) & " "
EndIf
If chkProces1.Checked Then
bgwProc1.RunWorkerAsync()
EndIf
If chkProces2.Checked Then
bgwProc2.RunWorkerAsync()
EndIf
If chkProces3.Checked Then
bgwProc3.RunWorkerAsync()
EndIf
' Este siempre se procesa
' Hacer esta llamada al final para que se restauren los valores
' al terminar el trabajo.
bgwProc.RunWorkerAsync()
EndSub
En los eventos DoWork de cada BackgroundWorker se hace la llamada al método … con el siguiente código:
Donde txtDir y txtExtensiones se usarán los nombres correwspondientes a cada grupo, por ejemplo, en el grupo 1 (el cero es el que siempre se ejecuta) se hará la siguiente llamada:
El código de backgroundWorker_DoWork es el siguiente, en donde solo se obtienen los ficheros que corresponden a cada directorio y con el filtro indicado.
'''<summary>
''' Método usado por el método DoWork de los BackgroundWorkers.
'''</summary>
'''<param name="txtDir">El directorio a usar.</param>
'''<param name="filtro">El filtro de los ficheros a usar.</param>
PrivateSub backgroundWorker_DoWork(txtDir AsTextBox,
filtro AsString)
' Aquí se acumulan los ficheros en la colección
' y se procesarán todos al final.
' De esta forma sabemos cuántos ficheros se procesan en total.
Dim fileEnum = System.IO.Directory.EnumerateFiles(txtDir.Text, filtro)
ficheros.AddRange(fileEnum)
EndSub
Una vez que finaliza el trabajo de bgwProc se procesan los ficheros.
Esto último se hace en el evento RunWorkerCompleted de ese control BackgroundWorker (en un momento verás el código).
Ahí se harán los cambios de todos los ficheros (que estarán en la colección ficheros), llamando al método cambiarNombres al que le pasamos como argumento el nombre del fichero a procesar.
Una vez finalizado los cambios, (y para que de tiempo a ver el mensaje), lanzo un temporizador después de tres segundos en el que se limpiarán los mensajes y se ocultará la barra de progreso.
Cabe decir que si alguno de los otros procesos dura má que este último puede que no se procesen bien todos los ficheros, pero… eso es algo que podemos arreglar en una futura revisión de este programa.
Y que a mí se me ocurre que se puede hacer comprobando cuando terminan todos los procesos asíncronos (comprobando cada uno de los eventos RunWorkerCompleted de cada tarea).
Pero por ahora te lo dejo como tarea que debes hacer y que podrás comprobar el día que yo lo publique.
Este es el código del método que hace todo esto (y como tip decirte que ese código tendrá que estar en otro sitio diferente si quieres tener en cuenta lo que he comentado en la nota anterior).
PrivateSub bgwProc_RunWorkerCompleted(sender AsObject,
e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles bgwProc.RunWorkerCompleted
' Procesar los ficheros acumulados en la colección
ProgressBar1.Visible = True
ProgressBar1.Maximum = ficheros.Count
ProgressBar1.Value = 0
For i = 0 To ficheros.Count - 1
cambiarNombres(ficheros(i))
ProgressBar1.Value = i + 1
Next
Dim s AsString
If ficheros.Count = 1 Then
s = "el nombre al fichero."
Else
s = $"los {ficheros.Count} nombres."
EndIf
LabelStatus.Text = $"Finalizado el proceso de cambiar {s}"
Application.DoEvents()
' Mostrar ese mensaje por 3 segundos
Timer1.Interval = 3000
Timer1.Enabled = True
EndSub
PrivateSub Timer1_Tick(sender AsObject, e AsEventArgs) Handles Timer1.Tick
Timer1.Enabled = False
ProgressBar1.Visible = False
LabelStatus.Text = LabelStatus.Tag.ToString()
Application.DoEvents()
EndSub
Por último te muestro el código del método cambiarNombres.
PrivateSub cambiarNombres(file AsString)
Dim fi AsNew System.IO.FileInfo(file)
Dim s AsString = file ' fi.FullName
LabelStatus.Text = $"{fi.Name}"
Application.DoEvents()
If optCambiar.Checked Then
s = fi.Name.Replace(txtTexto1.Text, txtTexto2.Text)
If s <> fi.Name Then
My.Computer.FileSystem.RenameFile(file, s)
EndIf
ElseIf optAñadirPrincipio.Checked Then
If chkNoDuplicar.Checked Then
If fi.Name.StartsWith(txtTexto1.Text) = FalseThen
s = txtTexto1.Text & fi.Name
My.Computer.FileSystem.RenameFile(file, s)
EndIf
Else
s = txtTexto1.Text & fi.Name
My.Computer.FileSystem.RenameFile(file, s)
EndIf
ElseIf optAñadirFinal.Checked Then
If chkNoDuplicar.Checked Then
If fi.Name.EndsWith(txtTexto1.Text) = FalseThen
s = fi.Name & txtTexto1.Text
My.Computer.FileSystem.RenameFile(file, s)
EndIf
Else
s = fi.Name & txtTexto1.Text
My.Computer.FileSystem.RenameFile(file, s)
EndIf
ElseIf optQuitarPrincipio.Checked Then
If fi.Name.StartsWith(txtTexto1.Text) Then
s = fi.Name.Replace(txtTexto1.Text, "")
My.Computer.FileSystem.RenameFile(file, s)
EndIf
ElseIf optQuitarFinal.Checked Then
If fi.Name.EndsWith(txtTexto1.Text) Then
s = fi.Name.Replace(txtTexto1.Text, "")
My.Computer.FileSystem.RenameFile(file, s)
EndIf
EndIf
LabelStatus.Text &= $" --> {s}"
Application.DoEvents()
EndSub
Y esto es todo por ahora… espero que te sea de utilidad, y si quieres indicar alguna mejora o algún fallo, eres libre de hacerlo en los comentarios de este post.
Muchas gracias.
Nos vemos. Guillermo
P.S.
Crearé la versión de C# de esta utilidad (por ahora solo la tengo en Visual Basic) y cuando la tenga finalizada, la publicaré también en GitHub.
Aunque esa versión de CSharp la haré con los cambios que antes te he indicado: para asegurar que todos los procesos asíncronos han finalizado.
P.S.2 (28-feb-2021 02:21)
Ya está el código modificado teniendo en cuenta que se finalicen todos los procesos de los BackgroundWorker y además con el código para C#.
Puedes ver el código fuente en GitHub.
Para hacer lo que comentaba más arriba: comprobar que todos los BackgroundWorkers han finalizado he modificado el código de forma que se hacen estas comprobaciones:
Defino dos variables a nivel de formulario para saber cuántos grupos se van a procesar (cada grupo se corresponde con un BackgroundWorker) y cuántos han finalizado.
En el evento RunWorkerCompleted se usa el siguiente código (te muestro el de C#):
privatevoid bgwProc_RunWorkerCompleted(object sender,
System.ComponentModel.RunWorkerCompletedEventArgs e)
{
cuantosFinalizados += 1;
// Cuando estén todos los procesos finalizados, hacer los cambios.
if (cuantosFinalizados == cuantosProcesos)
finalizarCopia();
}
El código que había antes en ese método de evento ahora está en el método finalizaCopia, tal como puedes ver en el siguiente código que es para C#.
privatevoid finalizarCopia()
{
// Procesar los ficheros acumulados en la colección
ProgressBar1.Visible = true;
ProgressBar1.Maximum = ficheros.Count;
ProgressBar1.Value = 0;
for (var i = 0; i <= ficheros.Count - 1; i++)
{
cambiarNombres(ficheros[i]);
ProgressBar1.Value = i + 1;
}
string s;
if (ficheros.Count == 1)
s = "el nombre al fichero.";
else
s = $"los {ficheros.Count} nombres.";
LabelStatus.Text = $"Finalizado el proceso de cambiar {s}";
Application.DoEvents();
// Mostrar ese mensaje por 3 segundos
timer1.Interval = 3000;
timer1.Enabled = true;
}
En el método de evento Click del botón Procesar ahora se tiene en cuenta las dos nuevas variables para saber cuántos grupos hay que tener en cuenta.
privatevoid btnProcesar_Click(object sender, EventArgs e)
{
ficheros.Clear();
cuantosFinalizados = 0;
cuantosProcesos = 1;
// Si al final del texto a buscar/poner
// hay más de un espacio, cambiarlo por solo 1
if (txtTexto1.Text.EndsWith(" "))
txtTexto1.Text = txtTexto1.Text.TrimEnd(' ') + " ";
if (txtTexto2.Text.EndsWith(" "))
txtTexto2.Text = txtTexto2.Text.TrimEnd(' ') + " ";
if (chkProces1.Checked)
{
cuantosProcesos += 1;
bgwProc1.RunWorkerAsync();
}
if (chkProces2.Checked)
{
cuantosProcesos += 1;
bgwProc2.RunWorkerAsync();
}
if (chkProces3.Checked)
{
cuantosProcesos += 1;
bgwProc3.RunWorkerAsync();
}
// Este siempre se procesa
// Hacer esta llamada al final para que se restauren los valores
// al terminar el trabajo.
bgwProc.RunWorkerAsync();
}
Y esto es todo…
En GitHub está el nuevo código para Visual Basic y el proyecto de C#.
P.S. 3 (01-mar-2021) Actualizado el código de VB y C# para que tenga en cuenta un par de fallillos que había: – al añadir/quitar de los nombres el texto indicado, ya que se usba fi.Name y ahí se incluye la extensión, y la extensión no se debe tener en cuenta al cambiar el nombre. – al marcar/desmarcar las opciones de los grupos, que se deshabilitaba todo, ahora solo se habilitan/deshabilitan los controles que contiene, salvo el propio CheckBox.