O NeoMatrix Tech está de casa nova!

Você deverá ser redirecionado em 6 segundos. Se não, visite:
http://www.leonelfraga.com/neomatrixtech
e atualize seus favoritos.

Aviso IMPORTANTÍSSIMO!

Aviso aos navegantes:

O NeoMatrix Tech mudou de casa!!!

A partir de agora, acessem pelo novo endereço:

http://www.leonelfraga.com/neomatrixtech

Ué... mas é só o domínio mudou de lugar?

R: Na verdade, não é bem assim hehe. Este domínio que você acessa agora aponta para um blog hospedado no Blogger, enquanto no novo, aponta para um blog na plataforma Wordpress, hospedagem própria, muito mais rápida e com um layout mais agradável de ler ;)

Não vou fechar este domínio igual ao que eu fiz com o NM Light (que já está 100% na nova plataforma). Talvez beeeeeeem depois eu faça isso.

Todos os posts daqui se encontram lá, e novos posts serão colocados somente no novo endereço.
A única coisa que não consegui importar foram os comentários. Mas em breve vai ter um post contando sobre a epopéia que foi migrar o NeoMatrix Tech!

Somente vou fechar a área de comentários daqui. Caso queiram comentar, favor ver o post correspondente no "Novo NeoMatrix Tech" e comentem por lá. É bem melhor! (pena que os permalinks "amigáveis para SEO" não funcionam lá, dá erro 404 e não consigo fazer a configuração funcionar. E olha que eu já vi vários artigos falando desse assunto :( ).

Quem assina o feed, já está lendo o conteúdo do novo NeoMatrix Tech!

sábado, 31 de janeiro de 2009

Lendo um arquivo Excel via ADO.NET (C#, VB...) e importando-o para um DataTable

excel Muitas vezes os sistemas necessitam que dados sejam atualizados por uma planilha do Excel.

Isto acontece porque a visualização e a edição de dados neste programa (e semelhantes) é mais produtiva para o usuário do que pelo nosso sistema. E quando muitos dados precisam ser inseridos ou atualizados, a produtividade com certeza é muito maior mesmo.

Então, nada mais lógico do que implementar uma funcionalidade para importar dados desta fonte para os nossos sistemas!

Criaremos um método em .NET (usarei o C# para demonstrar) que lê uma planilha Excel e importa seus dados em um DataTable, estrutura a qual já estamos bastante familiarizados, e a partir dele, podemos atualizar nosso banco de dados.

Acompanhe o código deste método:

   1: /// <summary>
   2: /// Importa os dados de uma planilha Excel para um datatable
   3: /// </summary>
   4: /// <param name="pWorksheetPath">Caminho do arquivo xls</param>
   5: /// <param name="pPlanName">Nome da planilha a ser importada.</param>
   6: /// <returns></returns>
   7: protected DataTable ExcelPlan2DataTable(string pWorksheetPath, string pPlanName)
   8: {
   9:     string cnnString = String.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=""Excel 8.0;HDR=YES;""", pWorksheetPath);
  10:     string isql = "select * from [{0}$]";
  11:     System.Data.OleDb.OleDbConnection cnn = new System.Data.OleDb.OleDbConnection(cnnString);
  12:     System.Data.OleDb.OleDbDataAdapter da = new System.Data.OleDb.OleDbDataAdapter(String.Format(isql, pPlanName), cnn);
  13:     DataSet ds = new DataSet();
  14:     DataTable dt = new DataTable();
  15:     try
  16:     {
  17:         cnn.Open();
  18:         da.Fill(ds);
  19:         dt = ds.Tables[0];
  20:     }
  21:     finally
  22:     {
  23:         cnn.Close();
  24:         cnn.Dispose();
  25:         da.Dispose();
  26:         ds.Dispose();
  27:     }
  28:     return dt;
  29: }

Como você pode ver, a coisa é bem mais simples do que parece, o segredo mesmo está na string de conexão utilizada:

   1: @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=""Excel 8.0;HDR=YES;"""

Ela utiliza o driver OLEDB do Microsoft Excel versão 8 (podem ser lidas planilhas até a versão 2003 - formato XLS -, o 2007 usa outro formato, o XLSX - OO XML), onde o parâmetro Data Source é o path completo da planilha a ser lida. Esta planilha NÃO deve estar aberta quando fomos requisitar ela; a mesma coisa ocorre quando esta planilha está aberta em nossa aplicação, o Windows Explorer dá uma "travada" quando vamos ver este arquivo.

Com a string de conexão, criamos os objetos do ADO.NET Connection e DataAdapter (do namespace System.Data.OleDb) da mesma forma como fazemos com um banco de dados qualquer.

No construtor do DataAdapter passamos dois parâmetros (na sobrecarga que estamos utilizando): o primeiro é a instrução SQL de Select, e o segundo é o objeto Connection.

Nesta instrução SQL para obter os dados, o nome da tabela na cláusula FROM será o nome de uma planilha da pasta de trabalho (um arquivo XLS, que é uma pasta de trabalho, pode ter N planilhas dentro dele), e o mesmo deve ser acrescido de cifrão ($) no final, e estar entre colchetes. Supondo que você queira pegar os dados da planilha PLAN1, seu nome deve estar escrito assim na cláusula FROM: [PLAN1$].

Depos disso, criamos um DataSet, e através do DataAdapter preenchemos este DataSet com o método Fill.

Com isso, será criada uma tabela no DataSet com os dados da planilha, e enfim, o retorno do método será esta tabela. No código acima, eu criei um objeto DataTable para pegar esta tabela e retornar.

Simples demais, não é? Veremos mais para frente como inserir e atualizar dados, e até criar novas planilhas via ADO.NET. E também fazer uma aplicação ASP.NET para importar e exportar planilhas. Mas já vou adiantando: trabalhe IGUAL como trabalha com um banco de dados! Simples assim mesmo. As únicas coisas que mudaram foram a string de conexão e a maneira como escrevemos o nome da tabela ;-)

PS: Quando procurei por esta solução quando eu estava precisando, achei ela em primeiro lugar neste site, do David Hayden. Um outro adendo interessante, é que antes já fiz coisa igual no Delphi, utilizando o driver ODBC na string de conexão e os componentes da palheta ADO (ADOConnection, ADOQuery, etc). O mesmíssimo processo, a mesma forma de fazer o Select...

Leia o restante deste post...

quarta-feira, 28 de janeiro de 2009

Corrigindo os travamentos da Radeon HD 4870 nos jogos

Sapphire Radeon HD4870

Como você já deve ter visto nos posts anteriores, meu PC é equipado com uma placa Sapphire Radeon HD 4870, que obviamente eu uso seu poder nos jogos que tenho instalados no meu PC, como 8 jogos da série Need For Speed, Crysis, emuladores de Capcom CPS 1 e 2, e recentemente o GTA 4.

Tenho, as vezes, experimentado alguns travamentos em alguns jogos, especialmente os mais pesados como o GTA 4 (incrivelmente, Crysis não travou nenhuma vez! Nem os NFS Pro Street - blergh! - e o Undercover. Já o Underground - 1 e 2 - e o Carbon tem dado umas travadas.).

Meu PC tem boa configuração, o S.O. está bem ajustado e com os últimos updates aplicados, drivers da HD 4870 também atualizados.

Como o GTA 4 está com inúmeros bugs, resolvi dar uma pesquisada no Google relacionando-o com a HD 4870 e em um dos resultados veio um tópico do Fórum da AMD, que indicou que a possível causa desta falha (no que diz respeito à HD 4870, e não dos outros bugs do game) é um problema no BIOS de algumas das placas de vídeo, que algumas vezes deixa de responder quando ocorre uma mudança de clock da GPU, necessitando de um "dedo na viola"!!! (hard reset, no botão mesmo hehe ;-) ).

A GPU, quando no desktop do Windows ou usando algum programa 2D, roda em 500 MHz, e quando em modo 3D, roda em 750 MHz.

O mesmo tópico indica duas soluções para este problema:

Solução 1 (Gambi!): "Travando" o clock da GPU em 750 MHz antes de executar o game.

Esta solução consiste em utilizar algum utilitário de overclock e travar o clock da GPU em 750 MHz antes de executar o jogo, pois assim não há a necessidade da BIOS da placa realizar a mudança de clock quando transita do modo 2D para o 3D e vice-versa.

A. Baixe o seguinte utilitário: RivaTuner, em http://downloads.guru3d.com/download.php?det=163

B. Depois de instalá-lo, execute-o.

C. Clique no símbolo de expansão, no combo-box que mostra a especificação da placa (abaixo do modelo). Uma série de ícones se abrirão, clique no primeiro, que são as "Low Level System Settings".

D. Na tab "Overclocking", marque o checkbox "Enable Low Level Hardware Overclocking". O programa recomenda reinicializar a máquina, mas você pode clicar no botão "Detect Now" na janela em que se abre, e após isso as configurações estarão habilitadas.

E. Coloque o "Core Clock" em 750 MHz.

F. Na seção "Overclocking Profile Settings", coloque um nome de perfil (exemplo: Jogos Pesadões) e clique no ícone de disquete. Feito isso, dê OK para voltar à tela principal.

G. Vá até a tab "Launcher" e clique no sinal de "+".

H. Selecione, na janela em que se abre, "Regular Item" e dê OK.

I. Coloque um nome, e as outras caixas estarão habilitadas. Marque o checkbox "Associated overclocking profile" e selecione o perfil que criamos no passo F.

J. Marque o checkbox "Associated Application" e coloque o caminho do aplicativo a ser executado.

K. Dê OK para salvar as configurações.

Caso você queira, pode aplicar as configurações de overclock no momento da inicialização do Windows, marcando o checkbox "Apply Overclock at Windows Startup" na tela do passo D.

Com isso, travamos o clock da placa em 750 MHz e evitamos que a BIOS faça esta mudança dinamicamente.

RivaTuner

Também experimentei fazer o overclock (na verdade, não se trata de um over propriamente dito, pois iremos trabalhar na máxima frequência da placa de vídeo dentro dos padrões...) da placa de vídeo pelo próprio gerenciador Catalyst do driver da HD 4870. Para isso, se você instalou o Catalyst Control Center (CCC), clique com o botão direito no desktop e selecione "Catalyst (TM) Control Center".

Vá para o modo Advanced, clicando no botão "Advanced" da tela do CCC e selecione a opção "ATI Overdrive" no treeview à esquerda.

Clique no ícone do cadeado para destravar o Overdrive e marque o checkbox "Enable ATI Overdrive".

Mova o slider "GPU Clock" até marcar 750 MHz e clique no botão "Test Custom Clocks".

Espere até o teste terminar e clique no botão "Accept".

Apply e depois OK para fechar o CCC.

ATI Catalyst Control Center

Solução 2: Atualização do BIOS da HD 4870

Esta solução SOMENTE DEVE SER APLICADA nas placas fabricadas pela Sapphire, no modelo Radeon HD 4870 512 MB GDDR5 (que é o meu caso, mas antes de atualizar fiquei testando a primeira opção hehe).

Antes disso, verifique as informações de Part Number, na caixa da placa:

  • PN 188-01E85-001SA
  • SKU# 11133-00-XXX

Somente nestes casos é que esta atualização se aplica. Também verifique as configurações no CCC:

  • Data da BIOS: 24/07/2008
  • Versão da BIOS: 011.006.000.003.000000

Após verificar, faça os seguintes downloads:

Descompacte o WinFlash em alguma pasta no seu HD (que tal C:\WinFlash ?) e feche os aplicativos que mexem com a placa (como o RivaTuner e jogos).

Extraia o arquivo do Update de BIOS para a mesma pasta do WinFlash.

Clique em Iniciar / Executar (ou dê Logo do Windows + R) e na caixa de texto digite "cmd" e tecle ENTER.

Vá até o diretório do WinFlash ("cd C:\WinFlash", se não estiver no drive C, tecle "C:" e dê Enter).

Digite o seguinte comando: atiwinflash.exe -f -p 0 1e8501sa.002 (onde 1e8501sa.002 é o arquivo da BIOS e tem 128 KB) e dê Enter.

Espere até o processo ser concluído, e se falhar, NÃO RESSETE a máquina, tente flashear a BIOS novamente.

Se deu tudo certo, dê um bumba no caneco hehe (reinicie o PC).

winflash

O número "0" na linha de comando, indica o número do slot PCI-e em que a VGA está instalada. Caso tenha mais de uma VGA, um número "1" indicaria o outro slot PCI-e.

Testei a primeira solução, não me senti "confiante" para flashear a BIOS da placa, pois eu não tinha outra placa PCI-e de backup caso desse m* na operação (vi um tópico de como "ressucitar" uma HD 4870 "morta" por causa de um flash de BIOS mal sucedido).

E por um tempo ela funcionou, tanto através do RivaTuner quanto pelo ATI Overdrive, mas em outros dias os travamentos persistiram.

Foi então que criei “coragem” e atualizei o BIOS da placa (possuo exatamente a Sapphire que necessita da atualização do firmware.). Os problemas, por enquanto, cessaram :-)

E aí, se você também experimentou esse problema e fez essa solução, funcionou?

Um abraço!

Leia o restante deste post...

sábado, 24 de janeiro de 2009

Screencast: Clonando HD's via rede com o UDPCast

Imagine que você seja um administrador de infraestrutura (de acordo com as novas regras do "Manoel", tem ou não tem hífen, estou na dúvida hehe) e tenha um parque de, digamos, umas 100 máquinas, todas com o mesmo hardware e de repente um vírus o obriga a reinstalar todo o sistema, em todos esses PC's da sua rede.

Claro, como as boas práticas ditam, você deve ter uma imagem padrão do HD desta máquina "zerada" em algum backup, seja CD, DVD, storage, etc.

Quando eu trabalhava na Procuradoria Regional da República, usávamos um disquete de boot com os drivers da placa de rede e um client da rede Novel. Tudo via DOS :-)
Este disquete tinha o Norton Ghost, daí mapeávamos uma unidade de rede apontando para o servidor e restaurar essa imagem.

Imagine fazer isso em cada máquina...

Um bom tempo depois, precisando clonar um servidor e graças as maravilhas que o Código Aberto produzem derivadas do Kernel Linux, conheci o UDPCast.

O UDPCast é uma mini-distribuição Linux que tem a finalidade de copiar os dados de um HD inteiro, ou de uma partição, para uma outra máquina que esteja rodando o mesmo sistema em modo de escuta.

Ela pode ser obtida no endereço http://udpcast.linux.lu ,  onde você pode personalizar a sua distribuição com a inclusão de drivers de placas de rede e HD's. O bom é que dependendo, cabe até em um disquete.

O screencast a seguir mostra a utilização do UDPCast para realizar esta clonagem de máquinas. Você pode clonar uma, duas, três... várias máquinas AO MESMO TEMPO!!!

O screencast é dividido em duas partes, e sua duração total é de 17 minutos.

Boa aula :-)

Leia o restante deste post...

quarta-feira, 21 de janeiro de 2009

Tipos (Structs) "Nuláveis" em C# (exemplo: DateTime?) (Nullable Types)

Uma vez, quando fiquei alocado para arrumar um problema de desempenho em um programa em C# (um Windows Service que faz acesso a um banco Oracle e a um webservice) encontrei algo que me deixou curioso, uma declaração da seguinte forma:

   1: public DateTime? MinhaData;


Fiquei curioso pelo fato do ponto de interrogação que sucede o tipo DateTime (DateTime?). Nunca tinha visto este tipo de declaração.

Pesquisando o que significava, pensando que é algum tipo de modificador, descobri que se sucedermos um tipo de dados pelo ponto de interrogação, significa que poderemos atribuir o estado null para ele.

Como assim?

Se, por exemplo, atribuirmos null para uma variável do tipo DateTime ou int (só para citar mais de um), ocorrerá um erro de compilação (ou execução...) com a seguinte mensagem:

Cannot convert null to 'System.DateTime' because it is a value type

Os tipos DateTime e int dos nossos exemplos são implementados com struct, e de acordo com a mensagem acima, ele é um tipo por valor, ou seja, um tipo primitivo (armazena somente valores, como datas e números).

Tipos primitivos, embora tenhamos métodos sobre eles, não são objetos, que são tipos por referência (se você atribui um objeto A para o B, qualquer modificação no B refletirá em A, pois B faz uma referência a A - apontam para o mesmo endereço de memória -, e não é uma cópia de A - em outro ponto da memória.). Por causa dos métodos que temos é que eles são implementados sendo struct (que pode ter métodos, como uma classe). Só para ter uma noção da coisa, o tipo string do .NET é implementado como uma classe, ou seja, é um objeto, e portanto, é um tipo por referência e o estado nulo pode ser atribuído.

O ponto de interrogação "transforma" este tipo por valor em um tipo por referência, ou seja, significa que poderemos atribuir o estado null para variáveis dele.

Uma declaração do tipo abaixo não irá gerar erros de compilação ou execução:

   1: public DateTime? MinhaData = null;

Isto é útil principalmente quando atribuimos nestas variáveis valores oriundos de um banco de dados, que podem estar nulos ou não, e não queremos trabalhar a toda hora com variáveis string, como comumente é feito.

Como nem tudo na vida são flores, isso tem algumas desvantagens. Uma delas é que não temos mais as propriedades e métodos do struct DateTime (como AddDays, Month, Year, entre outros) diretamente. Veja a imagem abaixo, demonstrando o code-insight em uma variável DateTime? :

insight-1

Duas propriedades importantes deste novo tipo, como em qualquer outro tipo "nulável", são as propriedades Value e a HasValue. A primeira é a que conterá o tipo original em questão: Se for DateTime, ela será do tipo DateTime, se for int, será int.

Dentro da propriedade Value é que estarão todos os métodos do tipo original:

insight-2

A propriedade HasValue, do tipo bool, indica se a variável não é nula. Isto é importantíssimo no momento de fazermos ou uma atribuição para um tipo original, quanto para fazer comparações, no nosso caso, demonstrarei como um if a mais no código exemplo abaixo:

   1: DateTime x = DateTime.MinValue;
   2: DateTime? y = null;
   3:  
   4: if (x.Equals(y))
   5: {
   6:     MessageBox.Show("alguma coisa");
   7: }
   8:  
   9: if (x < y)
  10: {
  11:     MessageBox.Show("outra coisa");
  12: }

No código acima, devemos tomar alguns cuidados: Null é um ESTADO, e não um VALOR. Portanto, comparar as duas variáveis é um tiro no pé. No segundo if, quem é menor e quem é maior?

Esta pergunta não tem resposta! Null não é nem menor, nem igual, nem maior a um valor. A única comparação que será verdadeira será a da diferença. Não vai gerar exception no código acima, mas ele está ilógico. Já o exemplo abaixo vai dar um erro de execução, mas vai ser compilado:

   1: DateTime x = DateTime.MinValue;
   2: DateTime? y = null;
   3:  
   4: if (x.Equals(y))
   5: {
   6:     MessageBox.Show("alguma coisa");
   7: }
   8:  
   9: if (x > y.Value)
  10: {
  11:     MessageBox.Show("outra coisa");
  12: }

O erro será a seguinte mensagem:

Nullable object must have a value.

Isso significa, que antes de fazer o If, temos que verificar se existe algum valor na variável y. O correto a fazer é:

   1: if (y.HasValue)
   2: {
   3:     if (x > y.Value)
   4:     {
   5:         MessageBox.Show("outra coisa");
   6:     }
   7: }

Tivemos um trabalho a mais neste código.

Como vimos, este é um recurso que embora muito útil, devemos utilizá-lo com bastante cuidado para não termos surpresas lá na frente ;-)

Para saber mais: http://msdn.microsoft.com/en-us/library/1t3y8s4s(VS.80).aspx

Um abraço!

Leia o restante deste post...

sábado, 17 de janeiro de 2009

Gerando Thumbnails em .NET (Upload em ASP.NET com jqModal e Efeitos Ajax Pt. 4)

Lembra do tutorial de Upload de fotos com AJAX, aquele onde fazemos o upload e exibimos as fotos em um gridview. Para você não se sentir perdido em alguns pontos deste artigo, sugiro a leitura dos artigos anteriores sobre esse assunto ;-)

Bem no final do artigo foi comentado de que poderíamos gerar um thumbnail a partir da imagem que vem do Banco de Dados. Vamos fazer isso agora?

O código completo pode ser obtido no link no final do artigo :-)

No artigo original, definimos hard-coded o tamanho das fotos que irão aparecer no gridview. Veja a declaração abaixo:

   1: <img runat="server" id="foto" src='<%#Bind("FotoID","visImagem.aspx?fid={0}")%>' alt="foto" width="50" height="50" />

Esta linha foi retirada do gridview da página Default.aspx do projeto original. E qual a desvantagem desta abordagem, já que o tamanho da foto vai ser reduzido?

Sim, mas de que tamanho estamos falando? Estamos falando das dimensões da foto, e não do seu tamanho em bytes!

A redução das dimensões será feita pelo navegador após o download das fotos. Uma foto de grandes dimensões, maior será seu tamanho em bytes, mais tempo leva para download, mais franquia de download gasta-se do provedor (caso o seu provedor tenha limite de download...).

Então, o que vamos fazer aqui, é recuperar a foto do BD, reduzir as suas dimensões, gerando um novo arquivo com dimensões reduzidas na memória do servidor, e enviando este novo arquivo para o usuário.

Para isso, acrescentei na classe TCadastroClsConn um método chamado GetThumbnail, que retorna um array de bytes com o novo arquivo e recebe como parâmetros as dimensões do novo arquivo.

Veja o código deste método:

   1: public byte[] GetThumbnail(int TamX, int TamY)
   2: {
   3:     //Obtendo a foto original do BD
   4:     MemoryStream sImgOriginal = new MemoryStream();
   5:     BinaryWriter bw = new BinaryWriter(sImgOriginal);
   6:     bw.Write(this.Foto);
   7:     bw.Flush();
   8:  
   9:     //Cria a miniatura
  10:     Image Original = Image.FromStream(sImgOriginal, true);
  11:     Image Thumbnail = Original.GetThumbnailImage(TamX, TamY, new Image.GetThumbnailImageAbort(ThumbCallback), IntPtr.Zero);
  12:  
  13:     //Saída para Array de Bytes
  14:     MemoryStream sOutput = new MemoryStream();
  15:     Thumbnail.Save(sOutput, ImageFormat.Jpeg);
  16:  
  17:     byte[] bOutput = new byte[sOutput.Length];
  18:     sOutput.Position = 0;
  19:     sOutput.Read(bOutput, 0, (int)sOutput.Length);
  20:  
  21:     sImgOriginal.Dispose();
  22:     Original.Dispose();
  23:     Thumbnail.Dispose();
  24:     return bOutput;
  25: }
  26:  
  27: protected bool ThumbCallback()
  28: {
  29:     return false;
  30: }

Antes, acrescentamos a referência do assembly System.Drawing ao projeto da biblioteca de classes, e além disso acrescentamos os namespaces System.Drawing e System.Drawing.Imaging na cláusula using.

Carregamos a propriedade Foto, que irá recuperar a foto do banco de dados, em um MemoryStream chamado sImgOriginal. Fazemos isso através de um BinaryWriter, que irá efetivamente escrever o conteúdo da propriedade Foto no stream.

Após isso, criamos um objeto do tipo Image, chamado Original, que inicializamos com o conteúdo da imagem no stream sImgOriginal, feito através do método estático FromStream da classe Image, que recebe como parâmetros o stream onde a imagem se encontra e um booleano que indica se será utilizado o gerenciamento de cores que está no arquivo original.

Criamos também outro objeto do tipo Image, o qual chamamos Thumbnail, que é inicializado com a saída do método GetThumbnailImage do objeto Image, que no nosso caso é a variável Original.

O método GetThumbnailImage gera uma miniatura da imagem contida em um objeto Image, e pede como parâmetros a largura, altura (inteiros), um delegate (olha ele aí) que aponta para um método de callback que representa o abort da função de geração de imagens, e a constante IntPtr.Zero.

Eu disse acima que a função GetThumbnailImage pede como um dos parâmetros um delegate, e vimos no artigo sobre Eventos que um delegate é um ponteiro para um método, não é?

Dentro da classe Image, temos o delegate Image.GetThumbnailImageAbort, que tem como assinatura o retorno de um booleano sem parâmetros. No seu construtor, informamos uma função que retorna um booleano.

No nosso caso, já que precisamos somente da referência da função, criamos a função TumbCallback, retornando false e não exigindo nenhum parâmetro. Apenas isso é o suficiente.

Pronto, geramos o thumbnail da imagem que veio do BD, e agora precisamos transformar esse novo bitmap em um array de bytes, para que possamos aproveitar todo o código restante, com pouquíssimas alterações.

Primeiramente criamos um MemoryStream para salvar o objeto Image em um Stream. Isto é feito pelo método Save() do objeto Image, que pede como parâmetros em uma das sobrecargas o stream de saída e o formato do arquivo, que é representado pela classe ImageFormat. Iremos no nosso caso utilizar o JPEG.

Com isso, criamos o nosso array de bytes que será o retorno do método, a variável bOutput. Feito isso, escrevemos o conteúdo do MemoryStream sOutput no nosso array de bytes, utilizando o método Read(), que em uma das sobrecargas recebe o array de bytes onde os dados serão escritos, a posição inicial e a quantidade de bytes a ser escrita.

Como o método Read() pede a quantidade de bytes como um inteiro, e a propriedade Length do stream nos retorna um long, precisamos fazer um cast.

Feito isso, liberamos os objetos da memória e retornamos o array de bytes com o thumbnail gerado.

Agora, na classe TCadastroClsConn temos a propriedade Foto que retorna o arquivo original e o método GetThumbnail() que retorna com dimensões personalizadas. Veja agora como ficou a linha do gridiview da página Default.aspx:

   1: <img runat="server" id="foto" src='<%#Bind("FotoID","visImagem.aspx?fid={0}&thumb=1")%>' alt="foto" />

Na querystring da página visImagem.aspx (a que carrega a foto, lembra?), acrescentamos o parâmetro thumb. Se existir esse parâmetro, ele chama a função GetThumbnail ao invés da propriedade Foto. Veja o código da página visImagem.aspx neste ponto:

   1: BinaryWriter bw = new BinaryWriter(Response.OutputStream);
   2: if (Request.Params["thumb"] == null)
   3: {
   4:     bw.Write(cad.Foto);
   5: }
   6: else
   7: {
   8:     bw.Write(cad.GetThumbnail(120, 120));
   9: }
  10: bw.Flush();
  11: bw.Close();

Somente foi acrescentado o if, mais nada!

Simples, não foi?

Exemplo Upload com Ajax + Thumbnail (229 KB)

Um abraço a todos :-)

Leia o restante deste post...

quarta-feira, 14 de janeiro de 2009

Coincidência Tech I

Uma rapidinha para terminar o dia :-)

Passando pela Av. Lins de Vasconcelos (bairo da Vila Mariana, em São Paulo - SP) hoje a tarde (14/01/2009), eis que me deparo com um sebo que em seu logotipo possui uma fênix. Deem (agora não tem mais acento, né?) uma olhada na foto que tirei sem pensar duas vezes:

sebo_simbolo_firebird 

A primeira coisa que me veio a mente, como todo bom nerd, foi o banco de dados open-source Firebird (o símbolo oficial está na imagem à esquerda, com fundo branco).

Ah o Firebird! O primeiro banco de dados que eu trabalhei profissionalmente. Linguagem Delphi + BD Firebird, carinhosamente apelidado de "vira-bosta", pelo fato do logotipo ser um pássaro e das limitações do banco.

Já vi em muitas coisas variações do logotipo da Apple. Mas do Firebird, essa foi a primeira vez!!!

Um abraço :-)

Leia o restante deste post...

sábado, 10 de janeiro de 2009

Convertendo MIDI para WAV de forma simples e com qualidade total, usando o Winamp

Recentemente comprei um aparelho de videokê, modelo VMP 300D da RAF Electronics (em breve, uma análise do mesmo aqui no NM Tech), e como os DVD's do mesmo custam o olho da cara (eu ainda não descobri como autorar um :-( ), resolvi montar meus próprios CD's utilizando o padrão CD+G (basicamente, um CD de áudio comum que utiliza um canal não utilizado na gravação de dados para colocar informações sobre gráficos, ou seja, a letra da música sincronizada) a partir de alguns arquivos .kar que tenho aqui.

Só que o programa de autoria de CD+G pede arquivos no formato wave (ele mesmo faz a conversão, mas não é muito prática), que então é sincronizada com a letra contida no .kar.

Pesquisei por programas que fazem a conversão direta de MIDI para WAVE, porém, nenhum teve resultado satisfatório, pois distorciam o som em determinadas notas musicais.

Olhando o Santo Google, descobri como fazer esta conversão através do Winamp, através de um plugin já conhecido de outras épocas, o Disk Writer. Tentava utilizá-lo sem sucesso, e aí descobri uma pequena configuração que ficou faltando.

Vamos ao passo-a-passo:

1. Para o plugin Disk Writer escrever a saída do player de MIDI no Winamp, devemos alterar algumas configurações do plugin de entrada MIDI Player. Para isso, entre no menu Options -> Preferences, e na parte esquerda, no ramo Plugins selecione Input. No lado direito, selecione Nullsoft MIDI Player 3.17 [in_midi.dll] (ou o seu correspondente) com um duplo clique:

ss1

2. Nas configurações do plugin de entrada, selecione a aba Device e no combo-box Device selecione a opção Direct Music / Microsoft Synthesizer (with output) e dê OK.

ss2 

3. Agora, vamos configurar o Winamp para escrever a saída de som em um arquivo wave no disco. No menu principal, selecione Options -> Preferences. No lado direito, o ramo Plugins, selecione Output, e do lado direito, selecione Nullsoft Disk Writer v2.14 (out_disk.dll) (ou o correspondente) com um duplo clique.

ss3

4. O duplo clique dado na tela anterior irá abrir as opções de configuração da saída em disco. No grupo Output File Location, indique a pasta onde os arquivos serão salvos. Se o checkbox Enabled, no grupo Single File Mode for checado, uma playlist inteira será salva como um único arquivo wave. Também lá pode ser especificado o formato do arquivo e seu nome.

ss4

5. Para configurarmos o formato (qualidade) do arquivo WAV a ser gerado, marcamos o checkbox Convert To Format e clicamos no botão abaixo, para defimirmos a qualidade do arquivo. Na situação que eu mencionei, o mesmo deve ser formato PCM, taxa de amostragem de 44,1 KHz, 16 bits, Estéreo, que corresponde ao formato de CD.

ss5

Dê OK, feche a tela anterior, e coloque a sua playlist para tocar.

Uma desvantagem desse processo, especificamente no caso de MIDI, é que o arquivo é lido pelo Winamp como se estivéssemos ouvindo, ou seja, ele toca o arquivo inteiro, e com isso, o tempo de conversão será o tempo da playlist.

Mas em compensação, a qualidade será total.

Um abraço a todos! :-)

Leia o restante deste post...

sexta-feira, 2 de janeiro de 2009

Eventos em C# de maneira simples

Olá pessoal!

Como já tinha dito em outro post, vamos ao nosso artigo que tratará de explicar de maneira bem simplificada sobre Eventos em classes.

Por que eu disse "em classes" ao invés de "em componentes"?

Quando eu me refiro a componentes, estou me referindo a elementos visuais, ou seja, que colocamos na interface de usuário, tais como botões, caixas de texto, etc.

Quando eu me refiro a somente classe, estou me referindo a uma classe genérica mesmo, que não é um elemento visual.

Então vamos trabalhar com eventos em classes não visuais? Isso mesmo :-)

Para acompanhar melhor, sugiro que façam o download do exemplo, cujo link está no final do post.

Um evento é uma ação que algum objeto "manda executar" quando acontece alguma coisa com ele. Vamos citar o exemplo de um evento OnClick codificado em um botão:

- Através da caixa de propriedades do botão, codificamos seu evento OnClick, que será executado quando o usuário clicar sobre o botão. O que aparece na propriedade OnClick é o nome do método que será executado.
- O usuário clica sobre o botão.
- Se a propriedade OnClick estiver codificada, o programa executará o que codificamos previamente.

Hum... notou alguma coisa aí?

Citei que "o que aparece na propriedade OnClick é o nome do método que será executado". Sim, um evento é um tipo de propriedade de uma classe!

E o que é armazenado nesta propriedade?

Esta propriedade, que é representada pela figura de um raio quando pesquisamos no code-insight, armazena a referência do método que será executado fora da classe quando alguma ação em especial é executada. Um evento, bem dizer, é um [terror dos programadores]ponteiro[/terror dos programadores] para um método externo à classe.

E como ele representa um método, ele deve ter uma assinatura. Esta assinatura de método é previamente definida como um novo tipo de dados, a qual é dado o nome de delegate. Sim, um delegate representa a assinatura de um método, podendo ser ele anônimo (utilizado em rotinas de ordenação, por exemplo), ou a assinatura de um método de evento.

Um delegate é criado da seguinte forma:

   1: public delegate void MeuDelegate(<tipo> parametro1, <tipo> parametro2...);

onde MeuDelegate é o novo tipo de dados que representa uma assinatura de método.

Para declarar um evento, normalmente usamos a seguinte declaração:

   1: public delegate void MinhaAssinaturaDeEventoEventHandler(object sender, EventArgs e);

Por convenção, ao final do nome do tipo colocamos as palavras "Event Handler", indicando que a assinatura de método em questão é um manipulador de evento; e dois parâmetros: um do tipo object, que representará a instância da classe que está disparando o evento, e um do tipo EventArgs (ou de uma classe derivada), que é uma classe básica do .NET Framework para passar informações do evento que está sendo executado, podendo alterar seu comportamento.

Só para fazer uma comparação, na linguagem Delphi declaramos um delegate da seguinte forma:

   1: type TMeuDelegate = procedure (Sender: Object; e: EventArgs) of object;

Para declarar uma propriedade de evento dentro da nossa classe, utilizamos o modificador event. Veja:

   1: public event <delegate> <nome do evento>;

Um evento deve ser público, o tipo de dado dele é event, em seguida vem a assinatura do método deste evento, e por fim o seu nome. Um exemplo prático:

   1: public event EventHandler Click;

Um método onClick típico, sendo que EventHandler é o nome do delegate associado.

E como mandamos a classe executar esta rotina dentro do programa?

Um evento é um método. Quando dentro de nossa classe ocorrer a ação que este método irá disparar, simplesmente executamos a variável como um método, passando seus parâmetros: no parâmetro "sender", passamos a instância da classe que está disparando, neste caso, a variável "this". Em EventArgs, passamos uma instância da classe EventArgs, ou uma classe derivada, caso você queira e essa classe derivada esteja na assinatura do delegate. Veja abaixo:

   1: private void AlgumaCoisa()
   2: {
   3:     ...
   4:     if(MeuManipuladorDeEvento != null)
   5:     {
   6:         MeuManipuladorDeEvento(this,new EventArgs());
   7:     }
   8:     ...
   9: }

Antes de mais nada, vamos lembrar que um evento é um ponteiro para um método. Sendo um ponteiro, ele deve estar apontando para algum lugar.

Por isso, antes de executar o método através deste ponteiro, devemos checar se ele é nulo. Se não for nulo, aí sim executaremos o método em questão.

E como faço este ponteiro apontar para algum lugar dentro do meu programa?

Veja na prática o que devemos fazer:

   1: public Classe1 MeuObjeto = new Classe1();
   2:  
   3: //Dentro do construtor de um form, por exemplo
   4: public Form1()
   5: {
   6:     InitializeComponent();
   7:  
   8:     //Atribuindo manualmente os manipuladores de evento
   9:     MeuObjeto.MeuEvento += new MinhaAssinaturaDeEventoEventHandler(MeuMetodo);
  10: }
  11:  
  12: public MeuMetodo(object sender, EventArgs e)
  13: {
  14:     //Faça o que tem que fazer...
  15: }

Para fazer a atribuição do médodo ao ponteiro, devemos utilizar o operador "+=" e atribuir uma instância do delegate que representa a assinatura do método em questão, passando em seu construtor o nome do método que será executado.

Vamos construir uma classe e dar uma analisada:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4:  
   5: namespace MinhaClasseTeste
   6: {
   7:     #region Classe de argumentos de evento
   8:     public class PensamentosEventArgs : EventArgs
   9:     {
  10:         public string Pensamento = "";
  11:         public bool SeguirEmFrente = true;
  12:  
  13:         public PensamentosEventArgs()
  14:         { }
  15:  
  16:         public PensamentosEventArgs(string pPensamento, bool pSeguirEmFrente)
  17:         {
  18:             this.Pensamento = pPensamento;
  19:             this.SeguirEmFrente = pSeguirEmFrente;
  20:         }
  21:     }
  22:     #endregion
  23:  
  24:     public delegate void OnGarotaOlhaEventHandler(object sender, EventArgs e);
  25:     public delegate void OnPensarEmAlgoEventHandler(object sender, PensamentosEventArgs e);
  26:     
  27:     public partial class LeoVirtual
  28:     {
  29:         #region Eventos
  30:         public event OnGarotaOlhaEventHandler OnGarotaOlha;
  31:         public event OnPensarEmAlgoEventHandler OnPensarEmAlgo; 
  32:         #endregion
  33:  
  34:         #region Métodos
  35:         public void PaquerarGata()
  36:         { 
  37:             //Algum processamento aqui...
  38:             if (OnGarotaOlha != null)
  39:             {
  40:                 OnGarotaOlha(this, new EventArgs());
  41:             }
  42:             //Algum processamento acolá...
  43:         }
  44:  
  45:         public void PensarEmAlgo()
  46:         { 
  47:             //Criando os argumentos do evento:
  48:             PensamentosEventArgs e = new PensamentosEventArgs("SEXO", true);
  49:             
  50:             if (OnPensarEmAlgo != null)
  51:             {
  52:                 OnPensarEmAlgo(this, e);
  53:             }
  54:             
  55:             //Depois do processamento ser feito fora da classe...
  56:             if (e.SeguirEmFrente.Equals(false))
  57:             {
  58:                 throw new Exception(":-(");
  59:             }
  60:         }
  61:         #endregion
  62:     }
  63: }

Criamos uma classe chamada PensamentosEventArgs, derivada de EventArgs, que tem como campos duas variáveis.

Em segida, declaramos dois delegates e que em um deles, como parâmetros do evento utilizamos a classe PensamentosEventArgs.

Dentro da classe LeoVirtual, temos a declaração dos eventos OnGarotaOlha e OnPensarEmAlgo.

No método PaquerarGata, caso a propriedade OnGarotaOlha esteja apontando para algum método em um objeto que instancie a classe LeoVirtual, este método será executado através do ponteiro OnGarotaOlha, passando a instância da classe LeoVirtual e uma nova instância da classe base EventArgs.

No método PensarEmAlgo, primeiro instanciamos uma nova classe PensamentosEventArgs com alguns parâmetros. Caso o ponteiro OnPensarEmAlgo não for nulo, o método apontado será executado, e após a execução, verificamos se o valor da propriedade SeguirEmFrente da variável de argumentos do evento for false, disparamos uma exeption.

Sim, o EventArgs serve justamente para verificarmos e alterarmos propriedades do objeto no evento em questão. Só que para fazer isso, devemos criar uma classe herdada de EventArgs, pois ela nua e crua não serve para muita coisa, é mais para seguir as convenções de declaração de eventos.

Só para termos um exemplo prático, olhe o argumento do evento OnRowDataBound de um Gridview :-)

Façam o download do exemplo, brinquem com ele, e vocês não terão mais dúvidas de que Eventos não são um bicho de sete cabeças! E para o artigo ficar melhor entendido também :-)

  Exemplo de Eventos em C# (53 KB)

Abraços e até mais!

Leia o restante deste post...

  © Blogger templates ProBlogger Template by Ourblogtemplates.com 2008 - Editado e configurado por Leonel F.

Voltar ao TOPO