Já se deparou com uma demora excessiva quando seleciona um arquivo no Windows Explorer? Ou demora para aparecer o menu suspenso quando clica com a direita sobre determinado arquivo ? Ou, pior ainda, sempre que seleciona uma pasta ou arquivo, o Explorer é encerrado com a mensagem de arrepiar a espinha "Windows Explorer encontrou um problema e precisa ser encerrado. Desculpe-nos o inconveniente.".
Todos esses problemas podem ter a mesma causa: erro no Context Menu Handler ou Manipulador de Menu de Contexto. O Windows permite que se estenda suas funcionalidades através da criação de componentes COM que são chamados de Shell Extensions. Há diversos tipos de extensões, sendo muito comuns o próprio Context Menu Handler - para incluir uma opção no menu de contexto do Windows Explorer - e também os Browser Helper Object - para estender as funções do Internet Explorer. Uma lista dos tipos possíveis pode ser encontrada aqui (há uma tabela no meio da página).
O Windows instala por padrão uma série de extensões - algumas importantes para o correto funcionamento do sistema - mas diversos fornecedores de software também o fazem. Por exemplo, o antivírus costuma disponibilizar no menu de contexto de pastas e arquivos uma opção para procurar vírus nessa(s) pasta(s) ou arquivo(s). O mesmo ocorre com softwares de compactação (como o Winzip e o 7Z), tocadores de áudio e vídeo, etc. Um outro exemplo é o Acrobat Reader que tem uma extensão para permitir a leitura de arquivos PDF no Internet Explorer.
Voltando ao assunto do começo do post, o que fazer quando esses comportamentos estranhos começam a pipocar? Uma boa solução é a ferramenta Shell Extension Manager para Windows. Ela mostra as extensões de todos os tipos que estiverem instaladas em seu computador, trazendo informações sobre cada uma, incluindo o fabricante, número da versão e o caminho do programa responsável por executar a extensão. O ShellExView não deixa de fora nem mesmo as extensões da própria Microsoft.
Esta ferramenta permite desativar e reativar facilmente as extensões. Portanto, se ocorrer algum dos erros descritos no primeiro parágrafo, basta desativar a extensão que se suspeita estar causando o problema. Há colunas com o nome e a descrição fornecida por cada fabricante para a sua extensão para facilitar a identificação das suspeitas. Há ainda uma coluna com os tipos de arquivo sobre os quais a extensão pode trabalhar.
Normalmente, são as extensões de terceiros as causadoras de problema. Tanto que o ShellExView emite um aviso quando se tenta desativar uma extensão da própria Microsoft, indicando que aquilo é parte do sistema operacional e que desativá-la pode causar problemas no Windows.
Depois de identificado quem é o real culpado pelo problema, pode-se optar por mantê-lo desativado ou desinstalá-lo. Ainda, se o programa é de uma fonte confiável, pode-se tentar a reinstalação.
Problemas com o Internet Explorer também podem ser resolvidos assim, inibindo o Browser Helper Object que possa impedí-lo de funcionar quando se navega em uma página específica.
5 de junho de 2009
Resolvendo problema no Windows Explorer ao acessar certos arquivos
4 de junho de 2009
Automatizando a DI API com scripts
A DI API - Data Interface API - é o mecanismo disponibilizado pela SAP para acesso aos objetos de negócio do Business One, pemitindo a criação de extensões ao ERP que agem nos dados exatamente como o próprio Business One.
Como esta API foi desenvolvida na plataforma COM da Microsoft, é possível automatizar tarefas relativas ao SAP Business One usando scripts que, por sua vez, podem ser agendados no Windows ou utilizados numa aplicação HTML.
O ponto de entrada para o Business One é o objeto Company. É com ele que se faz login no sistema e é ele que dá acesso aos demais objetos de negócio do ERP. Então, o script deve ser iniciado com a obtenção desse objeto. Veja um exemplo em VBScript:
Para fazer a conexão, informe o endereço do servidor de licenças, o Company DB, e o nome e senha do usuário. No exemplo abaixo, estou considerando que o servidor é o próprio computador que está executando o script. Os demais dados são os padrões para a base de demonstração que vem com o ERP.
Como segurança sempre é uma questão importante, é uma boa ideia arrumar uma forma de passar para o script informações sensíveis como a senha do usuário. Montar uma aplicação HTML pode ser uma opção mas, se quiser agendar a execução do script, provavelmente terá que criar outro mecanismo - um arquivo encriptado, por exemplo.
Agora que o Company está conectado, podemos obter qualquer objeto publicado pelo Business One. No trecho de código que segue, obtenho um RecordSet e navego por todos os registros existentes:
Atribuir nothing ao RecordSet se não formos mais usuá-lo é imprescindível para liberar os recursos de máquina que ele consome. Só depois disso é que podemos desconectar o Company sem problemas.
Outro tipo de objeto que pode ser usado são aqueles relacionados às regras de negócio propriamente ditas. Por exemplo, o trecho de script abaixo recupera o código de item trazido no RecordSet do exemplo anterior. Com esse código, posiciona o objeto de negócio Items do Business One, alimenta a descrição em outra língua com a própria descrição do item, sinaliza que o item pode ser movimentado e manda gravar essas alterações no banco de dados, segundo as próprias regras do ERP.
O objeto Company disponibiliza também funções para controlar transações no banco de dados - StartTransaction e EndTransaction - caso seja necessário. Apenas tenha em mente que o Business One aborta sozinho as transações caso aconteça algum erro. Use o método InTransaction para saber se ainda há uma transação ativa.
Observação: Para executar esses exemplos é preciso ter a DI API instalada. Consulte a documentação do Business One para saber como fazer isso.
Como esta API foi desenvolvida na plataforma COM da Microsoft, é possível automatizar tarefas relativas ao SAP Business One usando scripts que, por sua vez, podem ser agendados no Windows ou utilizados numa aplicação HTML.
O ponto de entrada para o Business One é o objeto Company. É com ele que se faz login no sistema e é ele que dá acesso aos demais objetos de negócio do ERP. Então, o script deve ser iniciado com a obtenção desse objeto. Veja um exemplo em VBScript:
Dim Company
Set Company = CreateObject ("SAPbobsCOM.Company")
Set Company = CreateObject ("SAPbobsCOM.Company")
Para fazer a conexão, informe o endereço do servidor de licenças, o Company DB, e o nome e senha do usuário. No exemplo abaixo, estou considerando que o servidor é o próprio computador que está executando o script. Os demais dados são os padrões para a base de demonstração que vem com o ERP.
Company.CompanyDB = "SBODemo_Brazil"
Company.Password = "manager"
Company.UserName = "manager"
Company.Server = "(local)"
ret = Company.Connect
if (ret <> 0) then
MsgBox Company.GetLastErrorDescription(), 0, "Erro"
end if
Company.Password = "manager"
Company.UserName = "manager"
Company.Server = "(local)"
ret = Company.Connect
if (ret <> 0) then
MsgBox Company.GetLastErrorDescription(), 0, "Erro"
end if
Como segurança sempre é uma questão importante, é uma boa ideia arrumar uma forma de passar para o script informações sensíveis como a senha do usuário. Montar uma aplicação HTML pode ser uma opção mas, se quiser agendar a execução do script, provavelmente terá que criar outro mecanismo - um arquivo encriptado, por exemplo.
Agora que o Company está conectado, podemos obter qualquer objeto publicado pelo Business One. No trecho de código que segue, obtenho um RecordSet e navego por todos os registros existentes:
const BoRecordset = 300
Dim oRs
Set oRs = Company.GetBusinessObject (BoRecordset)
oRs.DoQuery "SELECT * FROM OITM"
oRs.MoveFirst
while (oRs.EoF = False)
' Incluir aqui tratamento p/ cada item
' Exemplo: exportar p/ uma planilha
oRs.MoveNext
wend
Set oRs = nothing
Company.Disconnect
Set oCompany = nothing
Dim oRs
Set oRs = Company.GetBusinessObject (BoRecordset)
oRs.DoQuery "SELECT * FROM OITM"
oRs.MoveFirst
while (oRs.EoF = False)
' Incluir aqui tratamento p/ cada item
' Exemplo: exportar p/ uma planilha
oRs.MoveNext
wend
Set oRs = nothing
Company.Disconnect
Set oCompany = nothing
Atribuir nothing ao RecordSet se não formos mais usuá-lo é imprescindível para liberar os recursos de máquina que ele consome. Só depois disso é que podemos desconectar o Company sem problemas.
Outro tipo de objeto que pode ser usado são aqueles relacionados às regras de negócio propriamente ditas. Por exemplo, o trecho de script abaixo recupera o código de item trazido no RecordSet do exemplo anterior. Com esse código, posiciona o objeto de negócio Items do Business One, alimenta a descrição em outra língua com a própria descrição do item, sinaliza que o item pode ser movimentado e manda gravar essas alterações no banco de dados, segundo as próprias regras do ERP.
const oItems = 4
Dim oItem, cProd
Set oItem = Company.GetBusinessObject (oItems)
c_prod = oRs.Fields.Item("ItemCode").Value
if ( oItem.GetByKey (c_prod) ) Then
oItem.ForeignName = oItem.ItemName
oItem.Frozen = 0
if (oItem.Update <> 0) then
MsgBox Company.GetLastErrorDescription(), 0, "Erro"
end if
End If
' Avisa que não vai mais usar
Set oItem = nothing
Dim oItem, cProd
Set oItem = Company.GetBusinessObject (oItems)
c_prod = oRs.Fields.Item("ItemCode").Value
if ( oItem.GetByKey (c_prod) ) Then
oItem.ForeignName = oItem.ItemName
oItem.Frozen = 0
if (oItem.Update <> 0) then
MsgBox Company.GetLastErrorDescription(), 0, "Erro"
end if
End If
' Avisa que não vai mais usar
Set oItem = nothing
O objeto Company disponibiliza também funções para controlar transações no banco de dados - StartTransaction e EndTransaction - caso seja necessário. Apenas tenha em mente que o Business One aborta sozinho as transações caso aconteça algum erro. Use o método InTransaction para saber se ainda há uma transação ativa.
Observação: Para executar esses exemplos é preciso ter a DI API instalada. Consulte a documentação do Business One para saber como fazer isso.
3 de junho de 2009
Automatizando o Excel com scripts - Leitura
Há uma máxima que corre entre os fabricantes de ERP que diz que o maior concorrente dos ERPs é o Excel. Por sua versatilidade em fazer cálculos, sumarizar informações e apresentar resultados em gráficos, as planilhas Excel são uma opção difícil de bater. Mesmo quando o próprio ERP disponibiliza de forma nativa os mesmos recursos, sempre há os que se sentem mais confortáveis manipulando planilhas.
Com a ABC71 isso não é diferente. E como em casa de ferreiro o espeto é de pau, nós também temos nossas planilhas. Vou usar como exemplo um VBScript que usamos para carregar no banco de dados uma planilha de previsão de tempos de migração de fontes para Web, mostrando como ler o conteúdo. Um VBScript é um arquivo texto comum - a única diferença é que ao invés de TXT ele tem extensão VBS, podendo ser executado com um duplo-clique pelo Windows Explorer.
O primeiro passo é criar um objeto que representa a aplicação Excel.
Isso carrega o Excel, permitindo que o script acesse seus comandos. Como quero trabalhar com um planilha que já existe, o passo seguinte é instruir o Excel a carregá-la. O Excel mantem uma lista das planilhas abertas em sua propriedade Workbooks; ela também é responsável pela abertura de planilhas:
O layout da minha planilha incluir uma folha (Sheet) para cada um dos módulos do ERP da ABC71. Vou, então, montar um laço que me permita percorrer as informações contidas em cada uma das folhas:
Nesse trecho, usei a propriedade Sheets do Workbook (planilha) aberto. Ela lista todas as folhas da planilha atual. Count é a quantidade de folhas existentes na lista e Item permite acessar cada uma delas de forma independente, através da posição (índice) que ocupam na lista. Esse índice vai de 1 até a quantidade de folhas na lista.
Dentro de cada folha, tenho colunas com as previsões para a migração dos fontes pertencentes ao módulo. Há um cabeçalho na primeira e na segunda linha; portanto, vou começar a leitura na linha 3. Como não sei de antemão quantas linhas têm que ser processadas, vou percorrê-las até que não possuam mais dados.
Uso a sintaxe Cells(lin,col) para obter o valor inserido numa célula específica - aquela que está onde a linha lin cruza com a coluna col. É importante observar que o valor retornado por Cells(lin,col) deve ser tratado com o tipo de dado apropriado. Isto é, as datas são do tipo Date, números são de um dos tipos numéricos (inteiros ou com decimais) e textos são do tipo string. Dependendo do contexto, será preciso realizar conversões entre os tipos de dados. Por exemplo, um comando SQL a ser submetido ao banco de dados é necessariamente um texto e dados que são numéricos - como Tempo em minha planilha - devem ser convertidos através da função CStr antes que possam ser concatenados ao comando:
Para finalizar, é preciso fechar o Workbook que foi aberto e descarregar o Excel quando ambos não forem mais necessários ao script:
Como isso é um script, também pode ser agendado como uma tarefa no Windows ou ainda ser incluído numa aplicação HTML.
Com a ABC71 isso não é diferente. E como em casa de ferreiro o espeto é de pau, nós também temos nossas planilhas. Vou usar como exemplo um VBScript que usamos para carregar no banco de dados uma planilha de previsão de tempos de migração de fontes para Web, mostrando como ler o conteúdo. Um VBScript é um arquivo texto comum - a única diferença é que ao invés de TXT ele tem extensão VBS, podendo ser executado com um duplo-clique pelo Windows Explorer.
O primeiro passo é criar um objeto que representa a aplicação Excel.
Set objExcel = CreateObject("EXCEL.APPLICATION")
Isso carrega o Excel, permitindo que o script acesse seus comandos. Como quero trabalhar com um planilha que já existe, o passo seguinte é instruir o Excel a carregá-la. O Excel mantem uma lista das planilhas abertas em sua propriedade Workbooks; ela também é responsável pela abertura de planilhas:
Set objWorkBook = objExcel.Workbooks.Open("c:\Web\tempos.xls")
O layout da minha planilha incluir uma folha (Sheet) para cada um dos módulos do ERP da ABC71. Vou, então, montar um laço que me permita percorrer as informações contidas em cada uma das folhas:
For I = 1 To objWorkBook.Sheets.Count
Set folha = objWorkBook.Sheets.Item(I)
' Incluir aqui o processamento
' dos dados da folha atual
Next
Set folha = objWorkBook.Sheets.Item(I)
' Incluir aqui o processamento
' dos dados da folha atual
Next
Nesse trecho, usei a propriedade Sheets do Workbook (planilha) aberto. Ela lista todas as folhas da planilha atual. Count é a quantidade de folhas existentes na lista e Item permite acessar cada uma delas de forma independente, através da posição (índice) que ocupam na lista. Esse índice vai de 1 até a quantidade de folhas na lista.
Dentro de cada folha, tenho colunas com as previsões para a migração dos fontes pertencentes ao módulo. Há um cabeçalho na primeira e na segunda linha; portanto, vou começar a leitura na linha 3. Como não sei de antemão quantas linhas têm que ser processadas, vou percorrê-las até que não possuam mais dados.
lin = 3
while Trim (folha.Cells (lin, 1)) <> ""
on error resume Next
' Tratar dados da linha aqui
lin = lin + 1
wend
while Trim (folha.Cells (lin, 1)) <> ""
on error resume Next
' Tratar dados da linha aqui
lin = lin + 1
wend
Uso a sintaxe Cells(lin,col) para obter o valor inserido numa célula específica - aquela que está onde a linha lin cruza com a coluna col. É importante observar que o valor retornado por Cells(lin,col) deve ser tratado com o tipo de dado apropriado. Isto é, as datas são do tipo Date, números são de um dos tipos numéricos (inteiros ou com decimais) e textos são do tipo string. Dependendo do contexto, será preciso realizar conversões entre os tipos de dados. Por exemplo, um comando SQL a ser submetido ao banco de dados é necessariamente um texto e dados que são numéricos - como Tempo em minha planilha - devem ser convertidos através da função CStr antes que possam ser concatenados ao comando:
tempoStr = CStr (folha.Cells (lin, 5))
Para finalizar, é preciso fechar o Workbook que foi aberto e descarregar o Excel quando ambos não forem mais necessários ao script:
objWorkBook.Close True
Set objWorkBook = Nothing
objExcel.Quit
Set objExcel = Nothing
Set objWorkBook = Nothing
objExcel.Quit
Set objExcel = Nothing
Como isso é um script, também pode ser agendado como uma tarefa no Windows ou ainda ser incluído numa aplicação HTML.
Componente Delphi para embutir arquivos numa aplicação
Recentemente, um amigo me perguntou se havia jeito de inserir um arquivo inteiro num formulário do Delphi/C++ Builder. A ideia dele era construir uma aplicação que, entre outras coisas, tocasse um MP3 mas não queria ter que distribuir o tal MP3 junto com a aplicação.
Um excelente recurso das linguagens de programação visual (Delphi, VB, Visual C#) é a facilidade de componentização, isto é, criar um pedaço de código funcional que pode ser usado em diversos projetos. Assim, ajudei esse amigo a montar um componente no Delphi que é capaz de embutir um arquivo qualquer num Form ou Data Module. Da mesma forma que o arquivo externo, esse conteúdo embutido é acessível através de um Stream.
Para construir o componente, criei um novo projeto de pacote Win32 no Delphi mas, se você possuir um pacote, pode incluir o novo componente nele mesmo. Criei um novo componente Win32, escolhi TComponent como ancestral (classe base) e dei-lhe o nome de TWRecurso. Depois, adicionei-o ao projeto do pacote.
A interação desse componente no ambiente de programação é restrita a uma única propriedade exibida no Object Inspector: o nome do arquivo a ser embutido. Ao modificar esse nome, o componente inserirá no Form o conteúdo do arquivo indicado. Na classe TWRecurso, crio a propriedade FileName e um MemoryStream para guardar o conteúdo do arquivo:
FileName está na área published pois será visível no Object Inspector. Já Dados (o TMemoryStream) está na área public, onde poderá ser acessado via programa mas não pelo Inspector. Como, então, Dados será embutido no Form?
A mágica está no mecanismo de persistência dos componentes no Delphi. A função DefineProperties, que é parte desse mecanismo, nos dá a oportunidade de acrescentar ao Form o que for necessário. Reproduzo abaixo a sobrecarga da função para esse exemplo:
O código mostra que estamos definindo uma nova propriedade Dados, indicando as funções que devem ser usadas para gravação e leitura e informa que esta propriedade só deve ser gravada quando nosso MemoryStream interno tiver algum dado.
As funções para gravação e leitura recebem um Stream como parâmetro, bastando gravar nele nosso conteúdo - ou ler dele, na função de leitura. Um detalhe é que não há como saber de antemão a quantidade de bytes que têm que ser lidos; então, esta informação também tem que ser incluída.
Se mudar o nome do arquivo, o nosso Memory Stream deve carregar o contéudo do novo arquivo:
Não mostrei, mas o construtor de TWRecurso deve instanciar FDados e iniciar FFileName com brancos, enquanto o destrutor faz a limpeza necessária.
Montei esse exemplo com Delphi2005 mas não deve haver diferenças profundas para outras versões. Lembro, ainda, que o C++ Builder é capaz de compilar e instalar componentes feitos em pascal. Basta criar um pacote no C++ Builder e incluir o fonte WRecurso.pas para ter o componente também nesse ambiente.
Com esse componente, é possível embutir qualquer tipo de arquivo num Form: XML, MP3, TXT, etc.
Um excelente recurso das linguagens de programação visual (Delphi, VB, Visual C#) é a facilidade de componentização, isto é, criar um pedaço de código funcional que pode ser usado em diversos projetos. Assim, ajudei esse amigo a montar um componente no Delphi que é capaz de embutir um arquivo qualquer num Form ou Data Module. Da mesma forma que o arquivo externo, esse conteúdo embutido é acessível através de um Stream.
Para construir o componente, criei um novo projeto de pacote Win32 no Delphi mas, se você possuir um pacote, pode incluir o novo componente nele mesmo. Criei um novo componente Win32, escolhi TComponent como ancestral (classe base) e dei-lhe o nome de TWRecurso. Depois, adicionei-o ao projeto do pacote.
A interação desse componente no ambiente de programação é restrita a uma única propriedade exibida no Object Inspector: o nome do arquivo a ser embutido. Ao modificar esse nome, o componente inserirá no Form o conteúdo do arquivo indicado. Na classe TWRecurso, crio a propriedade FileName e um MemoryStream para guardar o conteúdo do arquivo:
TWRecurso = class(TComponent)
private
FFileName: String;
FDados: TMemoryStream;
protected
procedure SetFileName (AFileName: String);
public
{ ... }
property Dados: TMemoryStream read FDados;
published
property FileName: String read FFileName write SetFileName;
end;
private
FFileName: String;
FDados: TMemoryStream;
protected
procedure SetFileName (AFileName: String);
public
{ ... }
property Dados: TMemoryStream read FDados;
published
property FileName: String read FFileName write SetFileName;
end;
FileName está na área published pois será visível no Object Inspector. Já Dados (o TMemoryStream) está na área public, onde poderá ser acessado via programa mas não pelo Inspector. Como, então, Dados será embutido no Form?
A mágica está no mecanismo de persistência dos componentes no Delphi. A função DefineProperties, que é parte desse mecanismo, nos dá a oportunidade de acrescentar ao Form o que for necessário. Reproduzo abaixo a sobrecarga da função para esse exemplo:
procedure TWRecurso.DefineProperties(Filer: TFiler);
function DeveGravar: Boolean;
begin
Result := (FDados.Size > 0);
end;
begin
inherited;
Filer.DefineBinaryProperty('Dados', LoadDadosProperty, StoreDadosProperty, DeveGravar);
end;
function DeveGravar: Boolean;
begin
Result := (FDados.Size > 0);
end;
begin
inherited;
Filer.DefineBinaryProperty('Dados', LoadDadosProperty, StoreDadosProperty, DeveGravar);
end;
O código mostra que estamos definindo uma nova propriedade Dados, indicando as funções que devem ser usadas para gravação e leitura e informa que esta propriedade só deve ser gravada quando nosso MemoryStream interno tiver algum dado.
As funções para gravação e leitura recebem um Stream como parâmetro, bastando gravar nele nosso conteúdo - ou ler dele, na função de leitura. Um detalhe é que não há como saber de antemão a quantidade de bytes que têm que ser lidos; então, esta informação também tem que ser incluída.
procedure TWRecurso.LoadDadosProperty(Reader: TStream);
var lSize : Int64;
begin
FDados.Clear;
Reader.Read(lSize, sizeof (lSize));
FDados.CopyFrom (Reader, lSize);
end;
procedure TWRecurso.StoreDadosProperty(Writer: TStream);
var lSize : Int64;
begin
FDados.Position := 0;
lSize := FDados.Size;
Writer.Write(lSize, sizeof (lSize));
Writer.CopyFrom(FDados, FDados.Size);
end;
var lSize : Int64;
begin
FDados.Clear;
Reader.Read(lSize, sizeof (lSize));
FDados.CopyFrom (Reader, lSize);
end;
procedure TWRecurso.StoreDadosProperty(Writer: TStream);
var lSize : Int64;
begin
FDados.Position := 0;
lSize := FDados.Size;
Writer.Write(lSize, sizeof (lSize));
Writer.CopyFrom(FDados, FDados.Size);
end;
Se mudar o nome do arquivo, o nosso Memory Stream deve carregar o contéudo do novo arquivo:
procedure TWRecurso.SetFileName (AFileName: String);
begin
if FileName <> AFileNAme then begin
FFileName := AFileName;
FDados.Clear;
if (FileExists (AFileName) ) then
FDados.LoadFromFile(AFileName);
end;
end;
begin
if FileName <> AFileNAme then begin
FFileName := AFileName;
FDados.Clear;
if (FileExists (AFileName) ) then
FDados.LoadFromFile(AFileName);
end;
end;
Não mostrei, mas o construtor de TWRecurso deve instanciar FDados e iniciar FFileName com brancos, enquanto o destrutor faz a limpeza necessária.
Montei esse exemplo com Delphi2005 mas não deve haver diferenças profundas para outras versões. Lembro, ainda, que o C++ Builder é capaz de compilar e instalar componentes feitos em pascal. Basta criar um pacote no C++ Builder e incluir o fonte WRecurso.pas para ter o componente também nesse ambiente.
Com esse componente, é possível embutir qualquer tipo de arquivo num Form: XML, MP3, TXT, etc.
Postado por
Luís Gustavo Fabbro
às
11:30
Marcadores:
C++ Builder
,
Componentes
,
Delphi
14
comentários

