Data Macro de After Update

Ao ajudar um colega num grupo de discussão na internet, deparei-me com este vídeo que ensina a criar uma macro de dados (Data Macro) no Access 2010 para editar o valor de um campo conforme o que for digitado em outro (ex.: se digitar Status = Concluído, alterar a Porcentagem de Conclusão para 100%).

http://office.microsoft.com/pt-br/video-criar-uma-macro-de-dados-VA100305331.aspx

 

Ocorre que este vídeo, apesar de estar publicado no site da Microsoft, não funciona como esperado. Ao contrário do que a própria imagem mostra, se seguirmos os passos descritos na apresentação, o resultado será o seguinte erro na macro: “EditRecord failed because the default alias represents a record which is read only.”.

O que está errado no vídeo é que para fazer a edição de um campo conforme o valor de outro, o melhor evento não é o After Update (após atualizar), mas sim o Before Change (antes de alterar), conforme a imagem seguinte. Desta forma funciona corretamente:

 

Por que o After Update não deve ser utilizado? Ele não deve ser utilizado neste caso porque queremos editar a própria linha que está disparando o evento (ou seja, a própria linha que acabou de ser atualizada), e isto faria a linha ser editada de novo e disparar novamente o evento, e assim sucessivamente. Seria um loop infinito, mas que no caso das Data Macros ficaria limitado a 10 execuções, que é o máximo definido pelo produto.

A pergunta que você deve estar se fazendo (e também é a mesma que me faço) é “se o vídeo tem a imagem da macro funcionando, por que ela não funciona?”. Muito provavelmente este vídeo tenha sido feito em cima da versão beta do Access. Acontece que na última hora, já aos 46 do segundo tempo, a Microsoft fez uma alteração justamente nesta funcionalidade, para evitar o comportamento descrito no parágrafo anterior.

Até existe um jeito de usar o evento After Update para esta finalidade (conforme a figura seguinte), mas deveria ser em conjunto com a função Updated, evitando assim a armadilha dos disparos sucessivos e, principalmente, mandando localizar o registro na tabela. A função Updated nos permite saber se determinado campo foi editado.

 

Mesmo funcionando, esta abordagem não é prática, pois a macro precisaria ser replicada também para o After Insert, já que o After Update será disparado apenas pelos registros já existentes. Ou seja, para este cenário, o mais correto é utilizar o Before Change mesmo.

Publicado em Access | Marcado com | Deixe um comentário

Erro na propriedade UniqueTable do Access 2013

Uma boa prática de desenvolvimento no MS-Access com base de dados SQL Server é não usar tabelas vinculadas. Isto é possível definindo a propriedade Recordset do formulário como sendo um objeto Recordset de uma consulta qualquer feita no SQL Server. Exemplo:


Private Sub Form_Open(Cancel As Integer)
Dim cnn As ADODB.Connection
Dim strConn As String
Dim rst As ADODB.Recordset
Dim strSQL As String

On Error GoTo ErrHandler

Set cnn = New ADODB.Connection
strConn = "DRIVER=SQL Server;SERVER=TeuServidor;DATABASE=TeuDatabase;Trusted_Connection=Yes"
cnn.Open strConn

Set rst = New ADODB.Recordset
strSQL = "SELECT Teus Campos FROM TuasTabelas"
rst.CursorLocation = adUseClient
rst.Open strSQL, cnn, adOpenStatic, adLockOptimistic
Set Me.Recordset = rst
Me.UniqueTable = "tbl_TuaTabela"
ExitHere:
Exit Sub

ErrHandler:
MsgBox Err.Description & vbCrLf & Err.Number & vbCrLf & Err.Source, vbCritical, "Form_Open"
Resume ExitHere
End Sub

 

Uma propriedade pouco conhecida, usada no código acima, é a UniqueTable. Trata-se de uma propriedade do formulário do Access e serve para indicar qual tabela será editada no banco de dados. Se, por exemplo, o seu SELECT incluir múltiplas tabelas (JOINs) com dados relacionados, é preciso especificar qual é a tabela a ser editada, e tal definição é passada pela propriedade UniqueTable. Desta forma, é possível até mesmo ter um Recordset carregado a partir de uma StoredProcedure e usá-lo como base para edição de uma tabela.

O Access 2013, porém, veio com um erro que impossibilita o uso da propriedade UniqueTable: ele não a reconhece. Motivo: por um erro na documentação do Access, a propriedade UniqueTable é listada apenas como um recurso de projetos ADP, e como o Access 2013 não suporta mais projetos ADP, a propriedade foi removida do VBA (embora seu uso não gere erro de compilação). Porém, na prática não se trata de um recurso exclusivo de ADPs, é usado igualmente em MDBs e ACCDBs. Sua remoção quebra aplicativos já existentes e limita o que podemos fazer em novos aplicativos (não podemos ter origem de dados com JOINs em formulários editáveis, o que é uma tremenda limitação). Repetindo: este problema se refere ao uso do Access com o SQL Server (ou outro SGBD), e não ao Access/Access tradicional.

 

 

Para contornar o problema, criei nos meus aplicativos um código para verificar se a versão é a 2013 e, se for, não aplica a propriedade UniqueTable. Obviamente, este código não resolve o problema, apenas foge dele. Se a origem do formulário for um SELECT simples, tudo bem, pois não é necessário definir o UniqueTable, mas se for um JOIN, uma View ou uma Procedure, o Access ficará sem saber qual a tabela a ser editável e o formulário ficará em modo “apenas leitura”.


'Define UniqueTable se o Access for anterior ao 2013
If Split(Application.Version, ".")(0) < 15 Then
frm.UniqueTable = strUniqueTable
End If

 

Após vários meses de análise do problema, a Microsoft enfim disponibilizou um hotfix de correção, disponível neste endereço: http://support.microsoft.com/kb/2863871

 

Após a instalação do hotfix, o Access voltou a reconhecer a propriedade UniqueTable. Não basta instalar na máquina de desenvolvimento: cada usuário final precisa desta atualização.

Publicado em Access, VBA | Marcado com | Deixe um comentário

Função GetOpenFileName para Office 64-bit

No artigo anterior, criei uma função BrowseFolder (equivalente à API BrowseFolder do Windows) para ser usada tanto em Office 32-bit como em Office 64-bit. Agora neste artigo mostro como fazer a migração da função GetOpenFileName (http://access.mvps.org/access/api/api0001.htm), que abre a janela de diálogo do Windows para o usuário selecionar um (ou múltiplos) arquivos. Utilizei novamente o objeto FileDialog do Office, compatível com as versões 32-bit e 64-bit.

Ao contrário da função BrowseFolder, optei por mudar a sintaxe e os parâmetros da GetOpenFileName, pois eu não gostava da implementação original (que usava constantes públicas para as opções do diálogo, e requeria uma função auxiliar para preparar o filtro de arquivos). Preferi transformar as opções do diálogo em argumentos da função, e a definição dos filtros de tipo de arquivo em duas strings separadas por pipes (“|”).

A função ficou assim:

Public Function GetOpenFileName(blnAllowMultiSelect As Boolean, strFiltersDesc As String, strFiltersExt As String, _
intDefaultFilter As Integer, strDefaultFolder As String, strTitle As String) As String
'Informações dos argumentos:
'strFiltersDesc = passar a descrição dos tipos de arquivo separada por pipe. Exemplo:
'"Arquivos Excel (*.xls,*.xlsx,*.xlsm,*.xlsb)|Arquivos CSV (*.csv)"
'strFiltersDesc = passar os grupos de extensão dos tipos de arquivo separados por pipe. Exemplo:
'"*.xls;*.xlsx;*.xlsm;*.xlsb|*.csv"
'intDefaultFilter: código do filtro padrão, começando por 1.
'strDefaultFolder: se o valor terminar por "\", entende como pasta padrão. Caso contrário, pasta padrão e início do nome do arquivo.
Dim dlg As Object
Dim varArrayFilterDesc As Variant
Dim varArrayFilterExt As Variant
Dim intQtFilter As Integer
Dim i As Integer
Dim strReturn As String

On Error GoTo ErrHandler

Set dlg = Application.FileDialog(3) 'msoFileDialogFilePicker = 3

'Matriz de descrição de filtro
varArrayFilterDesc = Split(strFiltersDesc, "|")
varArrayFilterExt = Split(strFiltersExt, "|")
If UBound(varArrayFilterDesc) &gt;= UBound(varArrayFilterExt) Then
    intQtFilter = UBound(varArrayFilterExt)
Else
    intQtFilter = UBound(varArrayFilterDesc)
End If

With dlg
    .AllowMultiSelect = blnAllowMultiSelect
    For i = 0 To intQtFilter
        .Filters.Add varArrayFilterDesc(i), varArrayFilterExt(i)
    Next i
    .FilterIndex = intDefaultFilter
    .InitialFileName = strDefaultFolder
    .Title = strTitle
    .Show
    For i = 1 To .SelectedItems.Count
        strReturn = strReturn &amp; .SelectedItems(i) &amp; ";"
    Next i
    If Len(strReturn) &gt; 0 Then
        strReturn = Left(strReturn, Len(strReturn) - 1)
    End If
End With

GetOpenFileName = strReturn

ExitHere:
Exit Function

ErrHandler:
'MsgBox Err.Description &amp; vbCrLf &amp; Err.Number &amp; vbCrLf &amp; Err.Source, vbCritical, "GetOpenFile"
Err.Raise Err.Number, Err.Source, Err.Description
Resume ExitHere
Resume
End Function
Publicado em Access, Excel, Office, VBA | Marcado com , , | Deixe um comentário

Declarações de APIs para VBA7 – Office 64-bit

Duas boas referências com as declarações de APIs do Windows para quem precisa deixar o aplicativo compatível com Office x64:

http://www.jkp-ads.com/articles/apideclarations.asp

http://www.utteraccess.com/wiki/index.php/Category:API

Publicado em Access, Excel, Office, VBA | Deixe um comentário

Função BrowseFolder em Office 64-bit

Uso há muitos anos a função BrowserFolder, disponível no site http://access.mvps.org/access/api/api0002.htm, para capturar o endereço de uma pasta selecionada pelo usuário em tempo de execução.

Um usuário tentou rodar um aplicativo meu que usa esta função, e recebeu a seguinte mensagem de erro:

Este problema ocorre não porque o Windows é 64-bit, mas porque o Office é 64-bit. Embora não seja comum encontrarmos empresas dispostas a usar Office x64 (pois há muitos problemas de compatibilidade, e raramente há um real benefício em troca), alguns departamentos inadvertidamente acabam instalando tal versão.

As chamadas às DLLs 32-bit do Windows precisam ser refeitas para que funcionem no Office 64-bit, conforme é explicado com maior detalhe neste documento: http://msdn.microsoft.com/en-us/library/ee691831(office.14).aspx

No caso da função BrowseFolder, porém, há uma maneira mais simples de resolver: a mesma funcionalidade da API pode ser obtida por meio do objeto FileDialog do Office, que funciona tanto nas versões x86 (32-bit) como nas versões x64 (64-bit). Assim, criei uma função com o mesmo o nome e parâmetro da anterior, para não ter que fazer manutenção em todas as implementações, apenas na definição da própria função.

Public Function BrowseFolder(strDialogTitle As String) As String
Dim dlg As Object

On Error GoTo ErrHandler

Set dlg = Application.FileDialog(4) 'msoFileDialogFolderPicker=4
dlg.Title = strDialogTitle
dlg.Show
If dlg.SelectedItems.Count > 0 Then
   BrowseFolder = dlg.SelectedItems(1)
End If

ExitHere:

Exit Function

ErrHandler:
'MsgBox Err.Description & vbCrLf & Err.Number & vbCrLf & Err.Source, vbCritical, "BrowseFolder"
Err.Raise Err.Number, Err.Source, Err.Description
Resume ExitHere
Resume
End Function

Notem que não usei a constante nomeada msoFileDialogFolderPicker, mas sim o seu valor correspondente (4). Fiz isto porque o Access (ao contrário do Excel e Word) não traz a biblioteca do Office referenciada por padrão, portanto a constante nomeada daria erro de compilação.

Publicado em Access, Excel, Office, Uncategorized, VBA | Deixe um comentário

Como desbloquear anexos do Access no Outlook

Eu vejo com alguma frequência usuários do Outlook se queixando com o remetente de e-mails de que “faltou o anexo”, em especial quando este anexo é um arquivo do MS-Access. O que muitos não sabem é que a causa do problema não está no remetente e nem no servidor de e-mails, mas sim na própria máquina do destinatário, já que o Outlook bloqueia diversos tipos de arquivo.

O fato do Outlook bloquear o arquivo não significa que ele contenha um vírus, apenas que potencialmente pode conter vírus. O Outlook não é rastreador de vírus, então bloqueia todos os arquivos que tenham determinadas extensões (*.exe, *.scr, *.bat, etc.). O problema é que entre as extensões bloqueadas estão as do MS-Access, muito embora seu potencial de risco seja baixíssimo (não é maior que de arquivos Excel, que não são bloqueados).

Enfim, para desbloquear as extensões do MS-Access, podemos criar uma chave no registro do Windows com as extensões a serem desbloqueadas:

  1. Feche o Outlook.
  2. No menu Iniciar, digite regedit para abrir o editor do registro do Windows.
  3. Procure a pasta HKEY_CURRENT_USER > Software > Microsoft > Office > 14.0 (para Office 2010; se for o Office 2007, a pasta sera 12.0) > Outlook > Security.
  4. Se não existir um registro chamado Level1Remove, crie um: Edit > New > String Value. Dê o nome de Level1Remove (respeitando maiúsculas e minúsculas) e coloque no campo de valor uma lista com as extensões a serem desbloqueadas (separadas por ponto-e-vírgula): .mdb;.mde;.adp;.accdb;.accde;.accdr
  5. Clique em OK, feche o editor de registros e reabra o Outlook.

Publicado em Office, Outlook | Marcado com | 1 Comentário

Como redimensionar os comentários no Excel (VBA)

Os comentários do Excel são objetos Shape. Para redimensioná-los, use os métodos ScaleWidth e ScaleHeight do objeto Shape.

Identificar quais são os Shapes de comentários (dentre todos os Shapes da Worksheet) não é difícil, pois o objeto Comment tem uma propriedade Shape que retorna o objeto Shape correspondente ao comentário.

Este código de exemplo percorre todos os comentário da planilha Plan1, deixando-os com o mesmo tamanho:


Sub ResizeComments()

'Padroniza o tamanho dos comentários da Plan1

Dim shp As Shape

Dim cmt As Comment


On Error GoTo ErrHandler


'Percorre todos os comentários da planilha Plan1

For Each cmt In ThisWorkbook.Worksheets("Plan1").Comments

    'Captura o objeto Shape do comentário

    Set shp = cmt.Shape

    'Altera a altura

    shp.ScaleHeight 0.5, msoFalse

    'Altera a largura

    shp.ScaleWidth 0.5, msoFalse

Next cmt


ExitHere:

Exit Sub


ErrHandler:

MsgBox Err.Description & vbCrLf & Err.Number & vbCrLf & Err.Source, vbCritical, "ThisWorkbook-ResizeComments"

Resume ExitHere

Resume

End Sub

Publicado em Excel, VBA | Marcado com , , | 2 Comentários