martes, 3 de diciembre de 2013

SQL Server: Agregar un valor predeterminado a una columna

ALTER TABLE AML
ADD CONSTRAINT [DF_Detalle_Trabajos]
DEFAULT (N'Inspección por adaptación al mercado') FOR [Detalle de Trabajos]
GO

SQL Server: agregar una columna como clave foránea

ALTER TABLE AML
ADD [ID Empresa] [int] NULL
GO
ALTER TABLE AML
WITH CHECK ADD  CONSTRAINT [FK_AML_Solicitantes]
FOREIGN KEY([ID Empresa])
REFERENCES Solicitantes ([ID])
GO
ALTER TABLE AML
CHECK CONSTRAINT [FK_AML_Solicitantes]
GO

jueves, 31 de octubre de 2013

¿Cómo detectar si un campo cambió?

Supongamos que queremos ejecutar una acción en Microsoft Access sólo si un campo cambió su valor. Para manejar esta situación, podemos comparar el valor actual con el anterior así:

 If ComboNorma.Value <> ComboNorma.OldValue Then
    i = MsgBox("Algo cambió, ¿desea continuar?", vbYesNo + vbExclamation, "Info")
                If i = vbYes Then
                    ComboSubRubro.Value = ComboNorma.Column(2)
                End If
   End If

lunes, 14 de octubre de 2013

Problemas de casillas de verificación en filtros de hojas de datos

Si estás trabajando en Access, es posible que te encuentres en una vista Hoja de Datos en donde no aparezcan los filtros como casillas de verificación, tal como se ven en la siguiente imagen.



La solución es sencilla. Se configura desde las siguientes opciones:

  • Opciones
  • Current DataBase
  • Filter loockup options for *** Database
  • ODBC fields

lunes, 23 de septiembre de 2013

¿Cómo recorrer controles en Access?

Supongamos que queremos recorrer controles de una pantalla o de un control para realizar una tarea. Por ejemplo inhabilitar los controles de un determinado tipo. Esto se puede hacer con el siguiente código:

        For Each ctrl In [Form_Certificados_Editor]!Page63.Controls
            If ctrl.ControlType = acCheckBox Or ctrl.ControlType = acComboBox Or ctrl.ControlType = acTextBox Or ctrl.ControlType = acSubform Then
                ctrl.Enabled = False
            End If
        Next

¿Cómo hacer una validación antes de aplicar un cambio en la Base de Datos?

Supongamos que queremos realizar una validación luego de que el usuario completó un campo. ¿Cómo hacemos para volver atrás el cambio si no pasamos la validación? Aquí el código:

Private Sub Fecha_finalización_BeforeUpdate(Cancel As Integer)
    If Not Internal.validar_cierre_proceso(Me.ID) Then
        Cancel = True
        Me![Fecha finalización].Undo
    End If
End Sub

Fuente: http://msdn.microsoft.com/en-us/library/office/bb238523(v=office.12).aspx

viernes, 20 de septiembre de 2013

Can Grow no funciona

Supongamos que tenemos un reporte en Access que tiene configurada la opción CanGrow en Yes, pero no está funcionando. Bien, pueden ser muchas las causas del problema. Una de ellas es que en realidad el campo (de tipo memo) esté siendo truncado en el query.

¿Cómo saberlo? 

Leyendo esto: http://allenbrowne.com/ser-63.html (Truncation of Memo fields)

En mi caso el problema erá que estaba usando UNION. Tal como dice el artículo, se resolvió al utilizar UNION ALL. Más info en http://msdn.microsoft.com/en-us/library/office/bb208962(v=office.12).aspx.

Nuevo problema

Sin embargo, apareció un nuevo problema. De pronto para Access, mi query resultó Muy Complejo. Así que opté por lo que debería haber hecho desde un principio. Armar directamente la vista en SQL Server, lo cual es válido en mi proyecto porque Access es sólo el Front End.

Y listo, problema solucionado: reporte sin campos truncados.
Hasta la próxima!

jueves, 19 de septiembre de 2013

¿Cómo cerrar Access desde VBA?

i = MsgBox("No tiene permiso para utilizar esta aplicación. Contacte a su administrador.", vbOKOnly, "Error")
Application.Quit

¿Cómo crear usuarios en SQL Server con TSQL?

Imaginemos que en nuestro proyecto de Access con SQL Server como base de datos, queremos crear usuarios usando scripts. Simplemente ejecutamos:


CREATE LOGIN ezequiel WITH PASSWORD = 'blablabla'

Y dentro de nuestra base de datos:

CREATE USER ezequiel FROM LOGIN ezequiel
GO
SP_ADDROLEMEMBER 'db_datawriter', 'ezequiel';
GO
SP_ADDROLEMEMBER 'db_datareader', 'ezequiel';
GO


¿Cómo obligar al pedido de credenciales al conectar Access con SQL Server?

Supongamos el siguiente escenario:

  • Access como front end, SQL Server como base de datos
  • Trabajamos en un ambiente de desarrollo con un usuario administrador
  • Al abrir nuestra aplicación Access no nos pide credenciales
¿Por qué?

La razón es simple. Nuestro usuario de Windows tiene acceso directo a SQL Server, con lo cual está ignorando el pedido de credenciales del ODBC. Está almacenado dentro de nuestra aplicación.

¿Cómo resolverlo?

Configuramos el ODBC así:


[ODBC]
DRIVER=SQL Server
UID=blabla
DATABASE=blabla
WSID=blabla
APP=Microsoft Office 2010
SERVER=blabla

Pero no funciona, aún así no nos pide las credenciales

¿Cómo solucionarlo?
  1. Cambiamos el driver de SQL Server a SQL Native Client
  2. Volvemos a configurar las vinculaciones desde 
    1. External Data
    2. Link Table Manager
    3. Opción "allways prompt for new location"
Veremos luego que nos pedirá las credenciales cada vez que ingresemos a Access.

viernes, 23 de agosto de 2013

The Default 'DF_XXX' does not exist on the server.

En SQL Server Management Studio se obtiene el siguiente mensaje de error al tratar de eliminar un constraint:

The Default 'DF_Certificados_Observaciones' does not exist on the server.

La solución es sencilla, intentar borrarlo con una instrucción ALTER:

ALTER table Certificados drop DF_Certificados_Observaciones

Esto funcionará sin problemas.

jueves, 22 de agosto de 2013

There isn't enough free memory to update the display. Close unneeded programs and try again.

¿Qué hacer ante el siguiente error?

There isn't enough free memory to update the display. 
Close unneeded programs and try again.

La solución que al menos a mi me ha funcionando es utilizar menos formularios u organizarlos de otra forma para que no se abran muchos al mismo tiempo.

jueves, 8 de agosto de 2013

¿Cómo agregar un parámetro como filtro en una DataSheet view?

Supongamos que queremos tener un filtro ingresado por el usuario en una vista de hoja de datos, tal como se ven en la siguiente imagen:



Una forma simple de resolverlo es agregando entre corchetes el nombre del parámetro en la vista SQL o en la vista de Diseño:

WHERE (((dbo_Reporte.[Vencimiento])>DateAdd('d',-[Ingrese la cantidad de días anteriores a la Fecha de Vencimiento:],Now())));


Fuente: http://office.microsoft.com/en-us/access-help/using-parameters-with-queries-and-reports-HA001117077.aspx


domingo, 14 de julio de 2013

Cargar un combo en función de otro

En el siguiente código vemos cómo cargar un combo en función de otro:

Private Sub ComboNorma_Exit(Cancel As Integer)
    'Si hay algún valor en el combo PADRE
    If ComboNorma.ListIndex <> -1 Then
        'Y no hay ningún valor en el combo HIJO
        If ComboSubRubro.ListIndex = -1 Then
            'Elijo el valor del HIJO tomando la tercera columna del combo PADRE
            ComboSubRubro.Value = ComboNorma.Column(2)
        End If
    End If
End Sub

Referencias:

martes, 11 de junio de 2013

Run-time error 3146 ODBC--cal failed

¿Qué hacer si aparece el error Run-time error 3146 ODBC--cal failed?

1) Creamos una función como la siguiente:

Function TestODBCErr()
If Errors.Count > 1 Then
For Each errX In DAO.Errors
Debug.Print "ODBC Error"
Debug.Print errX.Number
Debug.Print errX.Description
Next errX
Else
Debug.Print "VBA Error"
Debug.Print Err.Number
Debug.Print Err.Description
End If
End Function

Fuente: http://www.utteraccess.com/forum/Runtime-Error-3146-t1993453.html&p=2285193

2) Insertamos esa función desde el menú Insert / Module del editor de VBA

3) Ejecutamos el programa hasta que aparezca el error y presionamos DEBUG

4) Abrimos una ventana Immediate Window con Ctrl+G o desde el menú View

5) Insertamos el código ? TestODBCErr() y presionamos enter

6) Luego obtenemos un mensaje detallado que varía de acuerdo a nuestro problema específico. Un ejemplo es:

ODBC Error
 2601 
[Microsoft][ODBC SQL Server Driver][SQL Server]Cannot insert duplicate key row in object 'dbo.Contactos' with unique index 'IX_Contactos'.
ODBC Error
 3621 
[Microsoft][ODBC SQL Server Driver][SQL Server]The statement has been terminated.
ODBC Error
 3146 
ODBC--call failed.

Espero les resulte útil!

martes, 30 de abril de 2013

No current record

Repentinamente aparece en una base de datos Access el siguiente error:

No current record

Buscando posibles causas en Internet, no encontré una solución a este problema. Revisando las tareas que había realizado en forma previa a que el error apareciera, encontré que:

  • Había creado varias columnas nuevas en una tabla existente en SQL Server
  • Luego había utilizado Link Table Manager para refrescar este cambio
  • Y luego había eliminado en SQL Server una de esas columnas
Aparentemente el paso tres fue la causa del problema, a pesar de que la columna no la estaba usando dentro de Access, con lo cual el problema se resolvió de la siguiente forma:
  1. Utilicé nuevamente Link Table Manager
  2. Luego reparé la base Access
Y todo volvió a la normalidad. Espero que a alguien le sirva.
Hasta la próxima!

viernes, 19 de abril de 2013

Access 2010 se cae al usar VBA - Código 1033

Problema:

Access se cae al procesar código VBA arrojando un mensaje de error como el siguiente:

Problem signature:  Problem Event Name: APPCRASH
  Application Name: MSACCESS.EXE
  Application Version: 14.0.6024.1000
  Application Timestamp: 4d83e4fc
  Fault Module Name: VBE7.DLL
  Fault Module Version: 7.0.16.27
  Fault Module Timestamp: 4f86f5aa
  Exception Code: c0000005
  Exception Offset: 00013a05
  OS Version: 6.0.6002.2.2.0.272.7
  Locale ID: 11274
Additional information about the problem:  LCID: 1033
  skulcid: 1033
Read our privacy statement:  http://go.microsoft.com/fwlink/?linkid=50163&clcid=0x0409

Solución:

Utilizar el comando "decompile" tal como se explica en: http://notasaccess.blogspot.com.ar/2013/02/the-expression-on-click-you-entered-as.html

martes, 19 de marzo de 2013

jueves, 14 de marzo de 2013

¿Cómo hacer consultas SQL desde VBA en Access?

Ejemplo 1: lectura secuencial

Dim db_m As Database
    Dim rs_m As DAO.Recordset
    Dim strSQL_m As String
    Dim strResult_m As String
    Set db_m = CurrentDb
    strSQL_m = "SELECT ..."
    Set rs_m = db_m.OpenRecordset(strSQL_m, dbOpenForwardOnly)
    If rs_m.RecordCount <> 0 Then
        While Not rs_m.EOF
            strResult = strResult & rs_m![Modelo]
            rs_m.MoveNext
        Wend
    End If

Ejemplo 2: lectura de un dato en particular

Dim db As Database
    Dim rs As DAO.Recordset
    Dim strSQL As String
    Dim strResult As String
    Dim VarRecords As Variant
    
    Set db = CurrentDb
    
    strSQL = "SELECT Snippet FROM dbo_Snippets WHERE ID = '" & Id_Snippet & "'"
    
    Set rs = db.OpenRecordset(strSQL)
    VarRecords = rs.GetRows(1)

Más información

miércoles, 6 de marzo de 2013

Error #type al pasar valores nulos en una función de VBA

Si se obtiene un error de tipo (#type) al pasar un valor nulo a una función en VBA, es posible que el problema sea haber definido los parámetros como String. En su lugar, podemos usar Variant que soporta:

  • Empty
  • Null
  • Error

Más información en http://msdn.microsoft.com/en-us/library/aa716185(v=vs.60).aspx

martes, 26 de febrero de 2013

¿Cómo saber si un archivo existe?

El siguiente código nos puede ayudar a saber desde Access si un archivo existe:

If Dir("C:\Prueba\Prueba.doc") = "" Then
  Msgbox "El archivo no existe"
Else
  Msgbox "El archivo existe"
End If

Más información en:

¿Cómo incluir SQL en un Control Source?

En caso que necesitemos incluir SQL en la propiedad Control Source de un Text Box, podemos usar la función DLookUp tal como se muestra en el siguiente código:

=DLookUp("campo","tabla","un_campo = 'un_valor'")

Más información y otras opciones en: http://stackoverflow.com/questions/1311885/sql-as-control-source-for-access-form-field

¿Cómo eliminar un archivo desde Access?

Kill nos puede ayudar a eliminar un archivo desde Access con una simple línea de código:

Kill("TestFile")

Más información en: http://msdn.microsoft.com/en-us/library/ey1w10ch(v=vs.80).aspx

viernes, 22 de febrero de 2013

¿Cómo pasar una parámetro a un Reporte?

Supongamos que queremos pre-filtrar un informe, por ejemplo pasando un ID como parámetro. Esta es la forma en que podemos hacerlo:

DoCmd.OpenReport "Anexo_AML", acViewPreview, , "[ID]=" & Me("ID")

jueves, 21 de febrero de 2013

Automatizar el envío de correo electrónico con SQL Server (parte 1)

El propósito de este artículo es describir un método para automatizar el envío de correos electrónicos a partir de información disponible en una base de datos SQL Server, lo cual puede ser muy útil si utilizan Access con SQL Server. 

En este primer artículo, explicaremos cómo configurar el servicio de DatabaseMail de SQL Server 2008 R2 Express. Probaremos dos configuraciones:

  • Una con un servicio gratuito como GMail
  • Otra con un servicio de correo provisto por un Hosting, en este caso BlueHost

En un segundo artículo, vamos a explicar como enviar los datos a partir de información disponible en nuestra base de datos, cómo darle formato al correo y cómo automatizar su envío.

Comencemos… 

 

1. Habilitar DatabaseMail en SQL Server 2008 R2 Express

En la versión Express de SQL Server no disponemos del asistente para configurar DatabaseMail, por lo cual deberemos hacer el trabajo en forma manual. Primero abrimos SQL Server Management Studio y nos conectamos:

image

Con el botón derecho del mouse sobre el nombre de nuestro servidor ir a la opción Facets. Es posible que esta opción se demore en abrir:

image

Luego vamos a la opción Surface Area Configuration / DatabaseMailEnabled.

Ponemos el valor True.

image

Luego reiniciamos SQL Server con el botón derecho del mouse sobre el servidor, haciendo clic en Restart.

image

 

2. Configurar DatabaseMail para ser usado con GMail

Dentro de la base msdb vamos a utilizar un conjunto de procedimientos almacenados para configurar el correo, en este caso usando GMail como SMTP:

sysmail_add_account_sp  
     @account_name =  'Prueba',
     @email_address =  'micuenta@gmail.com' ,
     @display_name =  'Prueba DataBaseMail' ,
     @replyto_address =  'noresponder@gmail.com' ,
     @mailserver_name =  'smtp.gmail.com',
     @mailserver_type =  'SMTP' ,
     @port =  587,
     @username =  'micuenta@gmail.com',
     @password =  '-----',
     @enable_ssl =  TRUE

sysmail_add_profile_sp @profile_name = 'Profile de prueba'

sysmail_add_profileaccount_sp
    @profile_name = 'Profile de prueba' ,
    @account_name = 'Prueba',
    @sequence_number = 1

 

3. Enviar un correo de prueba

Para enviar un correo de prueba, podemos utilizar un procedimiento como el siguiente, siempre dentro de la base de datos msdb:

EXEC sp_send_dbmail @profile_name='Profile de prueba',
@recipients='jpussacq@gmail.com',
@subject='Mensaje de prueba',
@body='Mi primer prueba de Database Mail'

Y obtendremos un mensaje como el siguiente:

Mail queued.

Si todo funcionó bien, recibiremos el correo sin problemas. Sino, podemos explorar algunas de las opciones de la siguiente sección.

 

4 Análisis de problemas

Las siguientes consultas, nos pueden ayudar a hacer un análisis de los problemas y a conocer en qué estado quedaron nuestros correos:

select * from msdb.dbo.sysmail_sentitems
select * from msdb.dbo.sysmail_unsentitems
select * from msdb.dbo.sysmail_faileditems

Esta consulta nos puede brindar más información si el estado es failed:

SELECT
items.subject,
items.last_mod_date,
l.description
FROM dbo.sysmail_faileditems as items
INNER JOIN dbo.sysmail_event_log AS l
ON items.mailitem_id = l.mailitem_id

Si reciben un error como el siguiente “The server response was: 5.7.0 Must issue a STARTTLS command first”, lo más probable es que hayamos olvidado configurar la opción de SSL en la cuenta.

 

5. Configurar DatabaseMail para ser usado con BlueHost

Es similar al caso de Gmail, pero con las opciones de Blue Host. 

USE msdb
GO
sysmail_add_account_sp  
     @account_name =  'Blue Host Mail',
     @email_address =  'cuenta@midominio.com' ,
     @display_name =  '---' ,
     @replyto_address =  'noresponder@---' ,
     @mailserver_name =  'mail.midominio.com',
     @mailserver_type =  'SMTP' ,
     @port =  26,
     @username =  'cuenta@midominio.com',
     @password =  '---',
     @enable_ssl =  FALSE
GO
sysmail_add_profile_sp @profile_name = 'Perfil Blue Host'
GO
sysmail_add_profileaccount_sp
    @profile_name = 'Perfil Blue Host' ,
    @account_name = 'Blue Host Mail',
    @sequence_number = 1
GO
EXEC sp_send_dbmail @profile_name='Perfil Blue Host',
@recipients='jpussacq@gmail.com',
@subject='Mensaje de prueba',
@body='Este es un mensaje de prueba del sistema'

 

Artículos relacionados:

¿Cómo evitar el clic en un vínculo si es un registro nuevo?

En ocasiones colocamos valores en una grilla de tipo "vínculos" que disparan una acción al hacer clic sobre ellos. Este puede ser el caso para imprimir un reporte o conocer información más detallada sobre un registro.

Sin embargo, a veces queremos que la acción de clic no se ejecute cuando aún no terminamos de crear el registro, como se ve en la siguiente imagen:


En esos casos podemos utilizar el siguiente código:

Private Sub Reporte_Click()
    Dim intnewrec As Integer
    intnewrec = Me.NewRecord
    If intnewrec = False Then
        On Error Resume Next
        DoCmd.OpenReport "Nota_Componentes", acViewPreview, , "[ID Nota]=" & Me("ID")
        If Err = 2501 Then Err.Clear
        DoCmd.Maximize
        DoCmd.RunCommand acCmdZoom100
    End If
End Sub

¿Cómo abrir un reporte en modo vista previa al 100% de zoom?

¿Cómo abrir un reporte en modo vista previa al 100% de zoom?

DoCmd.OpenReport "Nota_Componentes", acViewPreview, , "[ID Nota]=" & Me("ID")
DoCmd.Maximize
DoCmd.RunCommand acCmdZoom100

¿Cómo evitar que un informe genere un error 2501 si no hay datos?

Es posible que si no tenemos la información suficiente para generar un reporte, se produzca un error 2501:

Run-time error 2501 - The OpenReport action was canceled

Si queremos atrapar el error para que nuestra aplicación no cancele, usamos el siguiente código:

On Error Resume Next
DoCmd.OpenReport "Nota_Componentes", acViewPreview, , "[ID Nota]=" & Me("ID")
If Err = 2501 Then Err.Clear

Si además queremos generar un mensaje específico al usuario, aclarando que no hay información suficiente, usamos el siguiente código:

Private Sub Report_NoData(Cancel As Integer)
    MsgBox "No existen datos suficientes para generar el reporte."
    Cancel = True
End Sub

Más información en:

The expression On Click you entered as the event property setting produced the following error: Return withouth GoSub

Al abrir una base de datos Access con la que vienes trabajando sin problemas, recibes el siguiente mensaje:


The expression On Click you entered 
as the event property setting produced the following error: 
Return withouth GoSub


La forma de resolverlo es:

  1. Ejecutar el comando "C:\Program Files\Microsoft Office\Office14\MSACCESS.EXE /decompile"
  2. Luego ejecutar la acción que producía el inconveniente
  3. Luego ejecutar el comando "Compact and Repair Database"
Cerrar Access, volver a abrir nuestro proyecto y a seguir trabajando. 
Espero les resulte útil!

Enlaces:

martes, 19 de febrero de 2013

¿Cómo generar reportes de Access en Word?

Microsoft Access no soporta la exportación de reportes al formato de Microsoft Word, exceptuando la exportación a RTF (Rich Text Format), que tiene limitaciones como el uso de tablas o imágenes. En este artículo veremos una técnica muy poderosa para generar documentos Word desde Access, utilizando HTML y CSS.

Ventajas y desventajas

La lista de ventajas:
  • El reporte se visualiza en Word, donde puede ser modificado posteriormente a su generación.
  • Se permiten estilos más complejos que los soportados en Access. Por ejemplo, que el formato de un párrafo no sea todo igual y se permitan poner algunas palabras en negrita o colores y tamaños específicos.
  • Se permiten almacenar los formatos en tablas, lo que simplifica la utilización por ejemplo de encabezados similares para todos los reportes. Y facilita también un cambio en los mismos en forma centralizada.
  • El documento generado puede ser visible tanto en Microsoft Word como en un Browser. Podría ser por ejemplo publicado en una página web o en una extranet.
  • El reporte se genera usando la herramienta de reportes de Access lo cual nos brinda toda la potencia de manejar el detalle del reporte con mucha facilidad. Esto lo hace una mejor opción frente a otras como la combinación de documentos con Word y la generación de un documento vía programación.
  • El documento generado no posee ninguna dependencia con los datos a diferencia de una combinación con Word, lo cual lo hace menos dependiente a futuros cambios en la base de datos.
Las desventajas
  • Se requieren conocimientos de HTML y CSS

Enfoque de desarrollo

El enfoque de desarrollo tiene una serie de pasos conceptuales que son importantes:
  1. Se parte de la creación de un informe en Access
  2. Ese informe genera un documento HTML
  3. En lugar de mostrar el informe por pantalla, lo almacenamos como documento HTML
  4. Utilizando en forma programática las opciones de Word, convertimos el HTML a DOCX
  5. Previamente convertimos las imágenes vinculadas, como los logos, en imágenes incrustadas dentro del documento.
  6. Como último paso, abrimos Word y le mostramos al usuario el reporte creado.

Lo que ve el usuario

Para el usuario, es todo transparente. En principio, desde un lugar de la aplicación, tendrá un botón, para generar el reporte:


Luego, simplemente verá el documento creado en Word. En la imagen pueden ver detalles como el formato de la tabla, el encabezado y el uso de un logo:


Este pequeño log, nos muestra lo que el programa fue haciendo, no lo verá el usuario final:



El formato del archivo HTML

Uno de los puntos más importantes es entender cómo armar el formato HTML. En principio, podemos armarlo como cualquier página web con HTML y estilos CSS dentro de la misma página. Sólo tendremos que tener en cuenta los siguientes detalles:
  • Utilizar correctamente las opciones de encabezado y pie de página.
  • Utilizar campos como número de página si nos interesa.
  • Darle la opción al documento HTML de que se abra en el modo de vista de impresión y no en el modo de página web
Esto lo pueden probar desde cualquier editor de texto y luego abrir la página desde Word. A continuación les dejo un formato válido de archivo, que lo pueden utilizar incluso para cualquier aplicación web que desee tener una opción de descarga del documento en formato Word.

En este ejemplo están comentadas las opciones de primera página diferente, pero las dejé porque pueden ser útiles:
<html
xmlns:o='urn:schemas-microsoft-com:office:office'
xmlns:w='urn:schemas-microsoft-com:office:word'
xmlns='http://www.w3.org/TR/REC-html40'>
<head><title>Reporte 1</title>

<!--[if gte mso 9]><xml>
 <w:WordDocument>
  <w:View>Print</w:View>
  <w:Zoom>90</w:Zoom>
</w:WordDocument>
</xml><![endif]-->


<style>
p.MsoFooter, li.MsoFooter, div.MsoFooter
{
    margin:0in;
    margin-bottom:.0001pt;
    mso-pagination:widow-orphan;
    tab-stops:center 3.0in right 6.0in;
    font-size:12.0pt;
}

@page Section1
{
    size:8.5in 11.0in;
    margin:1.0in 1.0in 1.0in 1.0in;
    mso-header-margin:.5in;
    mso-footer-margin:.5in;
/*
    mso-title-page:yes;
*/
    mso-header: h1;
    mso-footer: f1;
/*
    mso-first-header: fh1;
    mso-first-footer: ff1;
*/
    mso-paper-source:0;
}


div.Section1
{
    page:Section1;
}

table#hrdftrtbl
{
    margin:0in 0in 0in 900in;
    width:1px;
    height:1px;
    overflow:hidden;
}

</style></head>

<body lang=EN-US style='tab-interval:.5in'>
<div class=Section1>

<p> CONTENT
<p>hola mundo</p>
<p>hola mundo</p>
<p>hola mundo</p>
<p>hola mundo</p>
<p>hola mundo</p>
<p>hola mundo</p>

</p>

<br/>
    <table id='hrdftrtbl' border='0' cellspacing='0' cellpadding='0'>
    <tr><td>

    <div style='mso-element:header' id=h1 >
        <p class=MsoHeader ><p>&nbsp;CABECERA</p></p>
    </div>

    </td>
    <td>
    <div style='mso-element:footer' id=f1>

        <p>&nbsp;PIE</p>
        <p class=MsoFooter>
        <span style=mso-tab-count:2></span>
            Pag. <span style='mso-field-code: PAGE '><span style='mso-no-proof:yes'></span></span> de <span style='mso-field-code: NUMPAGES '></span>
        </p>

    </div>


<!-- PRIMERA PAGINA DISTINTA

    <div style='mso-element:header' id=fh1>

    <p class=MsoHeader><span lang=EN-US style='mso-ansi-language:EN-US'>&nbsp;CABECERA (primera hoja)<o:p></o:p></span></p>

    </div>

    <div style='mso-element:footer' id=ff1>

    <p class=MsoFooter><span lang=EN-US style='mso-ansi-language:EN-US'>&nbsp;PIE (primera hoja)<o:p></o:p></span></p>

    </div>

-->
    </td></tr>
    </table>

</body></html>


El armado del informe en Access

Esta parte consiste en crear un reporte desde cero en el que completaremos tres secciones:
  • Encabezado del informe (no de la página)
  • Detalle
  • Pie del informe (no de la página)
Agregamos una etiqueta en el encabezado en donde incluiremos como contenido el HTML completo desde el inicio hasta la cabecera del contenido del detalle. Por ejemplo un tag TABLE.

Luego en el detalle agregamos un cuadro de texto en donde mezclamos tags HTML con campos de la base de datos.

Finalmente cerramos el HTML en el Pie del informe. En la siguiente imagen, se puede ver el informe en tiempo de diseño:



La visión en tiempo de ejecución carece de sentido, porque mostrará código HTML. Pero no es problema, porque no la usaremos.

Un punto importante es que el código puede ser guardado en parte en tablas, para facilitar la re-utilización. En este artículo lo hemos dejado dentro del reporte con fines didácticos.


El código para generar el documento

Finalmente, debemos generar el documento. El siguiente código nos muestra cómo hacerlo. Los puntos conceptuales más importantes son:
  • La exportación del informe a un archivo de texto para crear el HTML.
  • La utilización del SaveAs de Word para convertir el documento a DOCX.
  • La incrustación de las imágenes, tanto del documento, domo de los encabezados.
Dim camino
Dim archivo
Dim archivoHTML
Dim archivoDOCX

' Obtengo el path en donde se ejecuta la aplicación
camino = CurrentProject.Path + "\"
archivo = "temporal_30"
archivoHTML = camino + archivo + ".html"
archivoDOCX = camino + archivo + ".docx"
Log.Caption = "Path detectado." + vbNewLine

' Abro el archivo HTML
DoCmd.OutputTo acOutputReport, "Informe1", acFormatTXT, archivoHTML
Dim theWord As Object
Set theWord = CreateObject("Word.Application")
Dim theDoc As Object
Set theDoc = theWord.Documents.Open(archivoHTML)
Log.Caption = Log.Caption + "Archivo HTML abierto." + vbNewLine

' Incrusto las imágenes en el documento
For Each shapeLoop In theDoc.InlineShapes
 With shapeLoop
  .LinkFormat.SavePictureWithDocument = True
  .LinkFormat.BreakLink
  Log.Caption = Log.Caption + "Imagen incrustada (InlineShape)." + vbNewLine
 End With
Next shapeLoop

' Incrusto las imágenes en el documento (Headers)
For Each sectionsLoop In theDoc.Sections
 Log.Caption = Log.Caption + "Sección detectada." + vbNewLine
 With sectionsLoop
 For Each headersLoop In .Headers
  Log.Caption = Log.Caption + "Header detectado." + vbNewLine
  With headersLoop
  For Each shapeheaderLoop In .Range.InlineShapes
   With shapeheaderLoop
   .LinkFormat.SavePictureWithDocument = True
   .LinkFormat.BreakLink
   Log.Caption = Log.Caption + "Imagen incrustada (InlineShape / Header)." + vbNewLine
   End With
  Next shapeheaderLoop
  End With
 Next headersLoop
 End With
Next sectionsLoop

' Convierto el archivo HTML en DOCX
theDoc.SaveAs FileName:=archivoDOCX, FileFormat:=wdFormatDocument
Log.Caption = Log.Caption + "Archivo DOCX generado." + vbNewLine

' Cierro todo
theWord.Documents(archivoDOCX).Close
theWord.Quit
Set theWord = Nothing
Log.Caption = Log.Caption + "Archivo DOCX cerrado." + vbNewLine

' Abro Word con el archivo DOCX convertido
Application.FollowHyperlink archivoDOCX
Log.Caption = Log.Caption + "Documento abierto en Word." + vbNewLine


Conclusión

Aquí finaliza. Este enfoque nos brinda mucho poder según mi criterio porque:
  • Permite al usuario modificar los reportes en su herramienta preferida: Word.
  • Permite estilos complejos.
  • Genera dos formatos por el mismo precio.

Bibliografía

A continuación les dejo un conjunto de artículos que me han servido enormemente para vencer todos los problemas que fui encontrando. Se los recomiendo, especialmente el primero, que es el de la idea:

Hasta la próxima!

domingo, 17 de febrero de 2013

¿Cómo posicionarse dentro de un control de navegación?

Imaginen que poseen un control de navegación como el que muestra la imagen y necesitan posicionarse mediante programación en una solapa especifica:


Usamos el siguiente código:

DoCmd.OpenForm "Inicio", acNormal

DoCmd.BrowseTo ObjectType:=acBrowseToForm, ObjectName:="Inicio_Propuestas", PathToSubformControl:="Inicio.NavigationSubform"

En este caso se abrirá el formulario "Inicio_Propuestas" que se encuentra en la opción "Gestión de Propuestas" de las páginas verticales.