sábado, 15 de enero de 2022

Microsoft Outlook desde Excel (V): Imagen en el cuerpo del mensaje (Sin errores)

Durante muchos años en internet se han publicado un sinfín de artículos sobre el envío de correo electrónico a través del VBA de Excel. Yo mismo he publicado varias cosas al respecto haciendo uso de Microsoft Outlook o, mejor dicho, a través del objeto Outlook.

En el sentido de lo mencionado en el párrafo anterior, una de las preguntas recurrentes sobre el tema es sobre cómo dar formato a los textos enviados a través del VBA y el mensaje de correo electrónico, sobre todo incluyendo imágenes en el cuerpo del mensaje y/o como firma. Si bien la respuesta inmediata, y que se puede también encontrar en diversas páginas de internet, es usar la propiedad “HTMLBody” y aplicar algo de HTML (Enlace), aunque a muchas personas les ocurre de manera recurrente que la imagen puede hasta llegar como archivo adjunto, pero no se ve en el cuerpo del mensaje o se ve como un marco vacío (no he encontrado personas que les ocurra si se usa en el cuerpo del mensaje una imagen que ya está guardada en alguna url de internet), sobre todo cuando envían a Gmail o Hotmail y más con las nuevas versiones de Office, Windows y los nuevos niveles de seguridad de los servidores.

Buscando, leyendo, indagando, etc., hace un tiempo encontré una solución al dilema mencionado, usando algunas propiedades del objeto Outlook y accediendo a propiedades de la interfaz de programación de aplicaciones de mensajería del servidor a través de los conocidos Schema de Microsoft, lo que hoy compartiré con todos/as.

Comenzaremos declarando cinco variables que son las que vamos a usar; la primera estoy seguro que ya es conocida por ustedes.

Dim outlookApp As Outlook.Application
Luego las siguientes, que dejaré comentadas para que sepan a que nos ayuda cada una:
‘Objeto mensaje de correo 
Dim mCorreo As MailItem   
‘Colección de objetos Attachment 
Dim colecAttach As Outlook.Attachments 
‘Objeto Attachment, que representa lo que vamos a adjuntar 
Dim objAttach As Outlook.Attachment 
‘Permite crear, obtener, establecer y eliminar propiedades en el objeto Outlook 
Dim proOutlook As Outlook.PropertyAccessor 
Dim Ruta$, nImagen$ 

Ahora, vamos a declarar una constante que es equivalente a una de las propiedades canónicas PidTagAttachContentId. Esta contiene el encabezado de identificación de un archivo adjunto de mensaje de extensiones multipropósito de correo de Internet. Vamos a darle el valor de un Schema de Microsoft (Data and Story Library – DASL).

Const PR_ATTACH_CONTENT_ID = "http://schemas.microsoft.com/mapi/proptag/0x3712001F"

Luego daremos valores a las variables:

'Creamos el objeto Outlook 
Set outlookApp = CreateObject("Outlook.Application") 
'Creamos el mensaje 
Set mCorreo = outlookApp.CreateItem(olMailItem) 
'Creamos el adjunto en el mensaje 
Set colecAttach = mCorreo.Attachments 
'Adjuntamos el archivo/Imagen 
Set objAttach = colecAttach.Add("C:\Users\eavj6\Pictures\pez.jpg") 
'Indicamos que haremos uso de una propiedad del adjunto 
Set proOutlook = objAttach.PropertyAccessor 
Ruta = "C:\Users\eavj6\Pictures\" 
nImagen = "pez.jpg" 

Lo que toca ahora es usar el método SetProperty para el nombre del Schema y el valor que nos interesa..

proOutlook.SetProperty PR_ATTACH_CONTENT_ID, "pez.jpg"

Lo que sigue debería entenderse si leyeron mis artículos anteriores jejeje.

With mCorreo
   .Close olSave     
	 .HTMLBody = "<BODY><IMG src=""cid:pez.jpg""> </BODY>" ‘aquí insertamos la imagen adjunta en el cuerpo del mensaje     
	 .Save     
	 .To = "uncorreo@gmail.com"     
	 .Subject = "Prueba"     
	 .Send
End With 

Todo junto podría quedar del siguiente modo:

Option Explicit 
'Todo Sobre Excel 
'Abraham Valencia 
'https://abrahamexcel.blogspot.com/ 
'https://www.facebook.com/TodosobreExcelAV/ 
'https://twitter.com/Todosobre_Excel 
'https://www.youtube.com/channel/UCxEe3aA5uGrtYDdboBT_ptg 
'Lima, Perú 
'Enero del 2022   

Sub ImagenenCuerpo() 

Dim outlookApp As Outlook.Application 
'Objeto mensaje de correo 
Dim mCorreo As MailItem 
'Colección de objetos Attachment 
Dim colecAttach As Outlook.Attachments 
'Objeto Attachment, que representa lo que vamos a adjuntar 
Dim objAttach As Outlook.Attachment 
'Permite crear, obtener, establecer y eliminar propiedades en el objeto Outlook 
Dim proOutlook As Outlook.PropertyAccessor 
Dim Ruta$, nImagen$ 

Const PR_ATTACH_CONTENT_ID = "http://schemas.microsoft.com/mapi/proptag/0x3712001F" 

'Creamos el objeto Outlook 
Set outlookApp = CreateObject("Outlook.Application") 
'Creamos el mensaje 
Set mCorreo = outlookApp.CreateItem(olMailItem) 
'Creamos el adjunto en el mensaje 
Set colecAttach = mCorreo.Attachments 
'Adjuntamos el archivo/Imagen 
Set objAttach = colecAttach.Add("C:\Users\eavj6\Pictures\pez.jpg") 
'Indicamos que haremos uso de una propiedad del adjunto 
Set proOutlook = objAttach.PropertyAccessor 
Ruta = "C:\Users\eavj6\Pictures\" 
nImagen = "pez.jpg" 

proOutlook.SetProperty PR_ATTACH_CONTENT_ID, "pez.jpg" 

With mCorreo     
   .Close olSave     
   .HTMLBody = "<BODY><IMG src=""cid:pez.jpg""> </BODY>" 'aquí insertamos la imagen adjunta en el cuerpo del mensaje     
   .Save     
   .To = "uncorreo@gmail.com@gmail.com"     
   .Subject = "Prueba"     
   .Send 
End With      

MsgBox "Mensaje enviado", vbOKOnly, "Todo Sobre Excel" 

End Subb 

Y listo, eso es todo por hoy. Espero les sea útil. ¡Hasta la próxima!

Abraham Valencia
Lima, Perú

domingo, 19 de diciembre de 2021

¿Estamos cerca a la desaparición del VBA de Excel?

Por mucho tiempo se viene diciendo que ya se acerca el fin del VBA de Excel y que la programación en la nube es lo que hay que aprender y que programar en VBA ya no es útil ¿Es eso cierto? Particularmente yo creo que en gran parte es un error afirmar eso. Aquí les dejo algunos comentarios al respecto.

Entonces amigos/as, espero puedan suscribirse y por supuesto estoy presto a recibir críticas, recomendaciones, sugerencias, etc.

Abraham Valencia
Lima, Perú

domingo, 12 de diciembre de 2021

Canal de Todo Sobre Excel en Youtube

Y hoy estrenamos canal de Youtube cuyo nombre obviamente es TODO SOBRE EXCEL. La idea del canal no es convertirlo en uno más en el cual se enseñen cosas o trucos de Excel a través justamente de videos, sino que creo se debe tener un espacio de crítica, comentarios, opiniones, etc., justamente sobre el uso de nuestro programa favorito:

Microsoft Excel.

Entonces amigos/as, ya lo saben, espero puedan suscribirse y por supuesto estoy presto a recibir críticas, recomendaciones, sugerencias, etc.

Abraham Valencia
Lima, Perú

miércoles, 8 de diciembre de 2021

Usando Crystal Reports en Excel

Sin duda aquellos que programábamos en Visual Basic 6.0 tenemos buenos y bonitos recuerdos de Crystal Reports, sus bonitos informes y, sobre todo, la forma en que se su diseñador se podía integrar al querido VB 6.0 facilitando todo. Otra de las ventajas de Crystal Reports era que, a través del uso de la librería CRAXDRT y otras, se podía usar objetos Crystal Reports en los Userform de VBA de Excel y así visualizar e interactuar con los informes incluso mandándoles parámetros.

Fue Crystal Reports XI R2 (11.5) la última versión que contó con la librería (*.dll) CRAXDRT, ninguna de las versiones posteriores la tiene y solo las ediciones que se integran con Visual Studio son del tipo Runtime, con lo que no es posible integrar Crystal Reports, desde la versión 2008 (12.0) hasta la actual (2020 – 14.3), con programas como Excel de modo directo.

Si aún tienen una versión de Crystal Reports en su edición “desarrollador” (las ediciones tipo “profesional” no tienen Runtime) de las que tienen CRAXDRT, es posible que hayan intentado hacer funcionar dicha librería u objetos ActiveX en programas como Excel, pero lamentablemente por ejemplo el Crystal ActiveX Report Viewer Control 11.0 (que es el que tengo yo con Crystal Reports XI R2) no es compatible con las últimas versiones de Excel/Office y mucho menos con las ediciones de 64 bits (Crystal Reports está basado en 32 bits – excepto la versión 2020 de 64 bits).

Entonces ¿No es posible usar un control de Crystal Reports en Excel? Pues ahora veremos cómo solucionar dicho dilema y poder tener informes de Crystal Report en nuestro Userform de Excel.

Para comenzar, además de una versión de Crystal Reports (desarrollador) que contenga la librería mencionada (yo por ejemplo uso Crystal Reports XI R2), deben tener una versión más reciente (yo uso Crystal Reports 2020)

Ahora vamos a crear el reporte/reporte en el mismo CR (en la versión que deseemos). Yo usaré una base de datos en Excel (asumo que saben cómo crearlo). No olviden que solo funcionará en Excel de 32 bits. Yo para este caso usaré, además, Excel 2019.

El reporte se llamará "InformeBDExcel.rpt" y el archivo Excel con los datos "DatosparaCrystal.xlsx". Ambos archivos deberán estar en la misma carpeta. Como ven, en el reporte de CR están todos los registros del archivo Excel que estamos usando como base de datos.

En este momento voy a suponer que, si manejan CR, también saben usar ADO y al menos algo de sentencias SQL y conexiones a archivos Excel, así como el uso de Recorset. Habiendo supuesto eso, activen esta referencia de VBA:

Microsoft ActiveX Data Objects 6.1 Library

No olviden que la versión puede variar dependiendo de su Excel. Después activen estas otras dos referencias:

Crystal Reports ActiveX Designer run Time Library 11.5 (esta puede varias dependiendo de su versión de CR "antigua")

Crystal ActiveX Report Viewer Library 14.0 (esta puede varias dependiendo de su versión de CR ""nueva")

En el caso de la primera referencia, nos permitirá usar el objeto ActiveX Viewer de la versión más actual de CR que sí puede manipularse, a su vez, en versiones actuales de Excel. En el caso de la segunda, es justamente la que nos permitirá manipular el objeto CR (así sea la versión más antigua de CR).

Agrega un Userform y en el Cuadro de Herramientas agrega el Crystal ActiveX Report Viewer Control 14.0 (o el 12.0 o 13.0, depende de tu versión de CR. Ojo, no usar el 11.5 o inferior).

Luego agrega dicho control a tu Userform.

Ahora vamos a declarar las variables.

Dim crApp As CRAXDRT.Application ‘Para el objeto CR 
Dim crRpt As CRAXDRT.Report ‘Para el reporte del objeto CR 
Dim cnn As ADODB.Connection ‘Para establecer la conexión 
Dim rst As ADODB.Recordset ‘Para los registros 

Luego Crearemos la conexión y el Recordset, claro, solo obtendremos los registros de la tienda ubicada en San Borja.

Set cnn = New ADODB.Connection 
cnn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source= " & ThisWorkbook.Path & "\DatosparaCrystal.xlsx;Extended Properties=""Excel 12.0 Xml;HDR=YES;""" 

Set rst = New ADODB.Recordset      

With rst     
	.CursorLocation = adUseClient     
	.CursorType = adOpenStatic     
	.LockType = adLockOptimistic     
	.Open "SELECT * FROM [Hoja2$A1:E97] Where Tienda = 'San Borja'", cnn, , , adCmdText 
End With 

Como ven, en las sentencias SQL uso de modo directo la palabra “San Borja” para el campo/columna Tienda, pero bien podríamos ahí poner cualquier otro valor o usar otro campo o campos, incluso usando los valores obtenidos desde otros objetos como un combobox, por ejemplo, pero esa ya es tarea de ustedes.

Ahora vamos a crear el objeto CR y el reporte correspondiente basado en el que tenemos en nuestra carpeta.

Set crApp = New CRAXDRT.Application 
Set crRpt = crApp.OpenReport(ThisWorkbook.Path & "\InformeBDExcel.rpt", 1) 

Luego de eso, cargaremos el reporte con los datos del Recordset y lo guardaremos.

crRpt.Database.SetDataSource rst 
crRpt.DiscardSavedData 

Por último, vamos a cargar el objeto Viewer con el reporte guardado, vamos a darle el zoom que deseamos y vamos a volverlo visible.

Me.CrystalActiveXReportViewer1.ReportSource = crRpt 
Me.CrystalActiveXReportViewer1.Zoom 1 
Me.CrystalActiveXReportViewer1.ViewReport 

La macro competa la vamos a colocar en el evento Activate del Userform.

Option Explicit 
'Todo Sobre Excel 
'Abraham Valencia 
'https://abrahamexcel.blogspot.com/ 
'Lima, Perú 'Diciembre del 2021 
Private Sub UserForm_Activate() 

Dim crApp As CRAXDRT.Application 'Para el objeto CR 
Dim crRpt As CRAXDRT.Report 'Para el reporte del objeto CR 
Dim cnn As ADODB.Connection 'Para establecer la conexión 
Dim rst As ADODB.Recordset 'Para los registros 

Set cnn = New ADODB.Connection 

cnn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source= " & ThisWorkbook.Path & "\DatosparaCrystal.xlsx;Extended Properties=""Excel 12.0 Xml;HDR=YES;""" 

Set rst = New ADODB.Recordset      

With rst
	.CursorLocation = adUseClient     
	.CursorType = adOpenStatic     
	.LockType = adLockOptimistic     
	.Open "SELECT * FROM [Hoja2$A1:E97] Where Tienda = 'San Borja'", cnn, , , adCmdText 
End With 

Set crApp = New CRAXDRT.Application 
Set crRpt = crApp.OpenReport(ThisWorkbook.Path & "\InformeBDExcel.rpt", 1)      

crRpt.Database.SetDataSource rst 
crRpt.DiscardSavedData 

Me.CrystalActiveXReportViewer1.ReportSource = crRpt 
Me.CrystalActiveXReportViewer1.Zoom 1 
Me.CrystalActiveXReportViewer1.ViewReport 

End Sub 

Luego, si corremos la macro, tendremos esto en nuestro Userform:

Una última cosa, cuando descarguen los archivos y quieran ver el código les podría salir este aviso:



Espero les guste, hasta la próxima.

Abraham Valencia
Lima, Perú

Descargue el ejemplo aquí