sexta-feira, 30 de janeiro de 2009

Inicializando o IWForm com alguns controles desabilitados

Continuação do artigo Delphi Intraweb – JavaScript II (Continua)

Eu tenho no IWForm dois IWComboBoxes (CMBTIPOEMAIL e IWComboBox1) e um IWEdit (EDTEMAIL).
Podemos definir na propriedade “ExtraTagParams” valores específicos para cada tag original de um controle Intraweb. Por exemplo:


procedure TZnIWForm.DesabilitaControles;
begin
IWComboBox1.ExtraTagParams.Add('disabled="true"');
IWComboBox1.ExtraTagParams.Add('style=background:#E7E7E7');

EDTEMAIL.ExtraTagParams.Add('disabled="true"');
EDTEMAIL.BgColor := RGB(231, 231, 231);
CMBTIPOEMAIL.ExtraTagParams.Add('disabled="true"');
CMBTIPOEMAIL.ExtraTagParams.Add('style=background:#E7E7E7');
end;

No trecho de código acima, na linha 3, definimos a propriedade “disabled” do objeto, select, que será criado na página HTML, referente ao controle VCL “IWComboBox1”.

Na linha 4, definimos a propriedade “style” do objeto, select, para que o “background” seja cinza.
.
Na linha 5, assim como fizemos na linha 3, definimos a propriedade “disabled” do objeto, input text, que será criado na página HTML, referente ao controle VCL “EdtMail”.
Na linha 6, definimos a cor do input através da propriedade “BgColor”. A função RGB, permite que possamos definir as cores usando números inteiros de 0 a 255 representando as 256 variações de intensidade no o padrão RGB (Red, Green, Blue). OBS: A função “RGB” está definida na unit “Windows”, portanto declare essa unit na seção “uses”.
OBS: Por falar em RGB achei esse artigo sobre converter, em Delphi, RGB para CMYK (Cyan, Magenta, Yellow, Black - Key Plate), se é isso que vc procura veja em(http://delphi.about.com/od/delphitips2007/qt/rgb_cmyk.htm).


Agora, mais uma vez no evento OnCreate do IWForm faça a chamada a este novo método:

procedure TZnIWForm.IWAppFormCreate(Sender: TObject);
begin
LoadCmbTipoEmail;
DesabilitaControles;

IWComboBox



Adicione ao IWForm um IWComboBox (Palheta IWStandard). Em seguida codificaremos um método que irá preenchê-lo. Vou declarar uma seção “private” na definição da classe do IWForm. Nesta seção vou codificar o cabeçalho do método “LoadComboBandas”:

procedure LoadComboBandas;


Codificando o corpo do procedimento ..

procedure TZnIWForm.LoadComboBandas;
begin
IWComboBox1.Items.Add('Police');
IWComboBox1.Items.Add('Man At Work');
IWComboBox1.Items.Add('Pealr Jam');
IWComboBox1.Items.Add('The Smith');
IWComboBox1.Items.Add('U2');
IWComboBox1.Items.Add('Dire Straits');
IWComboBox1.Items.Add('Siouxsie & the Banshees');
end;


No evento OnCreate do IWForm você pode fazer chamada a este método.

procedure TZnIWForm.IWAppFormCreate(Sender: TObject);
begin
LoadCmbTipoEmail;
DesabilitaControles;
(*excuta a chamada ao método que carrega a IWCombo Bandas de Rock*)
LoadComboBandas;
end;

Continua em ...

Artigo completo (View Full Post)

Delphi Intraweb – JavaScript II (Continua)

Quem desenvolve para Web, precisa conhecer bem JavaScript. Quem desenvolve para Web, precisa usar muito JavaScript. Mesmo que ainda não saiba disso, quem desenvolve em Delphi para Web, também precisa ousar usar mais JavaScript.

JavaScript é o caminho o qual trilharemos neste artigo. Não temas, o Intraweb não é e não precisa ser tão feio quanto tem sido apresentado.


Intraweb (Atozed Software)

Será que você ficaria surpreso ao saber que existem pessoas desenvolvendo em Intraweb? Bem, pelo menos é o indica os números no fórum da Embarcadero Technologies. Bem interessante o “Embarcadero Discussion Forums” para quem esta pesquisando sobre o Intraweb recomendo dar uma passada por lá.
Será que a pergunta por que, apesar de tantos pontos fracos, o número de projetos Intraweb vem crescendo? Pelo que percebi, fora do Brasil também. Principalmente, diante do amadurecimento do “.Net” e do JSF, além da competitividade do Rails, será que o Intraweb ainda assim tem fôlego para competir com essas tecnologias? Pretendo dedicar um tempo e investigar em busca dessas repostas.

Neste artigo, vamos ver mais sobre “Delphi Intraweb” com JavaScript. Como conseqüência você poderá aprender mais e conhecer melhor o framework Intraweb.

Em forma geral, sobre Web e programação no lado cliente, percebo cada vez maior a pertinência desse tema. Acredito ser totalmente improvável o sucesso de um sistema Web, o qual não exista uma ênfase no balanceamento do que vai ser processado client-side e o que vai ser processado server-side. Temos discutido bastante sobre isso aqui no Estação Zn.
Portanto, vamos falar mais sobre.
Usaremos JavaScript no Intraeweb para tentar demonstrar como podemos obter um ganho em performance, robustez e elegância no desenvolvimento para Web.


Vamos começar com um exemplo simplório. Adicione no IWForm um IWEdit (Palheta IWStandard). Meu objetivo é mostrar como podemos acessar e manipular no lado cliente (browser) os objetos que adicionamos no IWForm. O desenvolvedor acostumado a pensar puramente em Delphi pode estar interessado em como interagir através do seu mundo “delphiano” com mundo dos navegadores.
Prosseguindo, após adicionar o IWEdit, codifique a função JavaScript exatamente conforme ilustrado abaixo. Muita atenção a caixa das letras digitadas. Não importa a caixa do nome do IWComponente que você definiu no Delphi. O Intraweb sempre vai criar um objeto HTML, em JavaScript na página que ele cria, referente ao IWForm em questão, em caixa ALTA.


function testaEdit(){
alert(document.forms[0].IWEDIT1.value);
document.forms[0].IWEDIT1.value = "";
document.forms[0].IWEDIT1.focus();
}

A função acima, simplesmente trata de exibir o conteúdo digitado no IWEdit1, lembrando que esse processamento é realizado no próprio browser. Ela deve ser adicionada na propriedade “JavaScript” do IWForm.

Para poder acessar o “IWEDIT1” em JavaScript usei a referencia “document.forms[0]” porque a forma mais comum seria usar “document.all. IWEDIT1”. O Problema é que dessa forma o script vai funcionar somente no browser da MS (Internet Explorer). Então, podemos concluir que ao usar “document.forms[0]” nosso JavaScript, pelo menos no que tange a referencias os objetos numa página HTML, estaria compatível com todos os navegadores? Sim, dessa forma nosso JavaScript está compatível. Todavia, isso não funciona no Intraewb 8. Ou melhor, ou talvez pior, ora funciona, ora não funciona. O Intraweb 8 criar várias tags “form” na página HTML. Provavelmente é por isso que volta e meia me deparo com aplicações Intraweb 8 que não estão usando nada de JavaScript.
Então, para garantir o comportamento adequado da função “testaEdit()” vamos modificar o código para:



function testaEdit(){
var ZnEdt = FindElem("IWEDIT1”);
alert(ZnEdt.value);
ZnEdt.value = "";
ZnEdt.focus();
}


Adicione ao IWForm um IWButton (TIWButton, palheta IWStandard). Altere a propriedade “Name” dele para “BtnTestaEdit”. Altere o “Caption” dele para “Testa IWEdit1 JavaScript”. Usaremos o “BtnTestaEdit” para chamarmos essa nova função. Digite na propriedade “ScriptEvent” do BtnTestaEdit, no “Event” OnClick, conforme ilustrado abaixo (adiante veremos como setar essa propriedade dinamicamente):




Server-Side X Client-Side – Delphi Intraweb

Podemos, para ilustrar nosso exemplo, implementar um procedimento em Delphi equivalente ao processamento realizado pela função JavaScript “testaEdit()”. Para isso, adicione mais um IWButton (TIWButton, palheta IWStandard). Altere a propriedade “Name” dele para “BtnTestaEditServerSide”. Altere a propriedade “Caption” para “Testa IWEdit1 Delphi”.



Vejamos, no evento “OnClick” do “BtnTestaEditServerSide” digite:

procedure TIWForm1.BtnTestaEditServerSideClick(Sender: TObject);
begin
Self.ActiveControl := IWEdit1;
WebApplication.ShowMessage(IWEdit1.Text);
IWEdit1.Text := ''
end;


Agora você pode experimentar o efeito da diferença entre processar Server-side frente ao processamento client-side. Para testar, pressione F9. Em seguida, na janela de diálogo do servidor standalone pressione F9 novamente.


VCL X HTML
Não é papo de bêbado, garanto. Conforme mencionei anteriormente, o framework Intraweb gera, a partir de um form Delphi (IWForm - VCL) e de controles (IWControls - VCL) associados a ele, uma página HTML na qual objetos são declarados (em JavaScript) referentes a cada um dos controles VCL dispostos no IWForm que a originou.
Para conhecer todos os objetos que compõem o objeto “form” de uma página HTML:

function percorreTudo(){
var ZnForms = document.getElementsByTagName("form");
alert("Oi");
for (i = 0; i< ZnForms.length;i++) {
alert("Bla!! O nome do form é: " + ZnForms[i].name);
for (j = 0; j< ZnForms[i].elements.length; j++) {
alert("Bla!! O nome do form é: " + ZnForms[i].name + " o elemento é:" + ZnForms[i].elements[j].name);
}
}
}


Essa função pode ser utilizada em qualquer página HTML. No Intrweb, podemos codificá-la na propriedade “JavaScript” do IWForm (TIWForm).



Para chamarmos a função “precorreTudo()” vou adicionar ao IWForm1 um IWButton (palheta IWStandard). Em seguida na propriedade “ScriptEvent”, no evento OnClick digite, conforme ilustrado na imagem abaixo:



Na propriedade “Caption” do IWButton1 vou digitar “PrecorreTudo”. Ok, caro leitor do Estação ZN, mais um teste já pode ser feito. Porem, antes adicione alguns controles no IWForm1. Por exemplo umas IWListBox, uns IWEdits (todos da palheta IWStandard).

Manipulando os objetos HTML via JavaScript no Intraweb

Na próxima função seremos mais ousados. Vamos percorrer todos os objetos do “form” num laço (“for”), atribuindo o nome, tanto do “form”, quanto dos objetos associados a ele, num IWListBox. Veja como o framework Intraweb cria e manipula os objetos, componentes visuais Delphi, numa página HTML.


function percorreTudoSetValue(){
var ZnForms = document.getElementsByTagName("form");
var ZnList = FindElem("IWLISTBOX1");
var ZnMemo = FindElem("IWMEMO1");
for (i = 0; i< ZnForms.length;i++) {
alert("Bla!! O nome do form é: " + ZnForms[i].name);
alert("Bla!! O ID do form é: " + ZnForms[i].id);
alert(ZnList.item(i).innerHTML);
idx = ZnList.options.length++;
ZnList.options[idx].value = ZnForms[i].name;
ZnList.options[idx].text = ZnForms[i].name;
ZnList.setAttribute("Selected","Selected");
document.SubmitForm.IWLISTBOX1.text = "
" + ZnForms[i].name;
ZnMemo.value += ZnForms[i].name;
for (j = 0; j< ZnForms[i].elements.length; j++) {
alert("Bla!! O nome do form é: " + ZnForms[i].name + " o elemento é: " + ZnForms[i].elements[j].name);
idx = ZnList.options.length++;
ZnList.options[idx].value = ZnForms[i].name + " | elemento: " + ZnForms[i].elements[j].name;
ZnList.options[idx].text = ZnForms[i].name + " | elemento: " + ZnForms[i].elements[j].name;
ZnList.setAttribute("Selected","Selected");
ZnMemo.value += "\n" + ZnForms[i].elements[j].name;
}
}
}

Observe que atribuir valor a um objeto “Select” usando JavaScript para isso não é um das coisas mais triviais. Observe também que sabendo fazer isso você pode ganhar bastante performance no seu aplicativo, isso caso você substitua processamento server-side, para montar os valores num “Select”, fazendo através de JavaScript, client-side.
Vamos testar então? O próximo passo: Você vai adicionar um IWListBox e manter a propriedade name dele como “IWLISTBOX1”. Adicione um IWMemo também “IWMEMO1”. O Memo é criado na página HTML como um objeto “TextArea”, ao passo que o IWListBox gera um “Select”.
Agora, adicionaremos mais um IWButton, para fazermos chamada a essa function. No IWButton3, altere a propriedade “Caption” para “Percorre Tudo II”. No evento “ScriptEvent”, semelhante ao que fizemos no IWButton1, no item OnClick, digite “percorreTudoSetValue()”.




OBS: O JavaScript é super, ultra, hiper, case sensitive. Portanto, muito cuidado com a digitação. Nas funções JavaScript que estamos criando, devemos ter sempre a preocupação de usar um JavaScript que seja compatível com todos os navegadores (ou no mínino com os mais conhecidos: IE e FireFox). Por isso, note que não estou usando “document.all.Obj” Esse comando não é suportado pelo Fire Fox.

Propriedade “Name” do Form
Não me refiro ao form VCL (IWForm), mas a tag “form” da página HTML.
A próxima função será para definirmos dinamicamente um nome para o objeto “form” na página HTML:
function NamedForm(){
alert("www.estacaozn.blogspot.com");
document.forms[0].name = "Landjah";
document.forms[0].id = "Landjah";
alert("Estação zn - O nome do form é: " + document.forms[0].name);
document.forms[0].IWEDIT1.value = "Landjah";
}



Vou adicionar outro IWButton ao IWForm1, para a propriedade “Caption” dele vou definir “Nome do Form”. Desta vez, vamos fazer a chamada da função “NamedForm()” em outra propriedade. Faremos isso na propriedade “ExtraTagParams”, do IWButton2:



Gora, se você clicar no IWButton2 antes de fazê-lo no IWBurron1, perceberá que o nome do “form” foi, na verdade pela primeira vez, definido.


Habilitando e Desabilitando controles através de JavaScript.

Pode ser de grande utilidade saber habilitar e desabilitar os controles, inputs de um form HTML, sem que para isso precise ir ao servidor. Veja a função abaixo:


function HabilitaControleEmail(){
var ZnEdtMail = FindElem("EDTEMAIL");
var ZnCmbTipoMail = FindElem("CMBTIPOEMAIL");
var ZnChkBoxMail = FindElem("IWCHECKBOX1");
ZnChkBoxMail.checked = !ZnChkBoxMail.checked;
ZnEdtMail.disabled = !ZnChkBoxMail.checked;
ZnCmbTipoMail.disabled = !ZnChkBoxMail.checked;

if (ZnEdtMail.disabled) {
ZnEdtMail.style.backgroundColor = "#E7E7E7";
ZnCmbTipoMail.style.backgroundColor = "#E7E7E7";
}
else {
ZnEdtMail.style.backgroundColor = "#FFFFFF";
ZnCmbTipoMail.style.backgroundColor = "#FFFFFF";
}
}

Na linha 2, recupero o objeto "EDTEMAIL".

Na linha 3, recupero o objeto Select o qual representa uma IWComboBox "CMBTIPOEMAIL ".

Na linha 4, recupero o objeto CheckBox o qual representa uma IWCheckBox "IWCHECKBOX1".

Na linha 5, simulo o comportamento do CheckBox.

Na linha 6 e 7, atribuímos a propriedade “disabled” dos controles “EDTMAIL” e “CMBTIPOEMAIL” a negação do valor do CheckBox.

A partir da linha 9, controlamos o efeito visual: atribuímos a cor cinza aos controles caso estejam desabilitados. Caso contrário, recebem a cor branca.


Precisamos, então, adicionar outro IWEdit, o qual alteraremos a propriedade “Name” para “EdtEMail”. Adicione um IWComboBox, altere sua propriedade “Name” para “CMBTIPOEMAIL”. Vamos atribuir dinamicamente valores a propridade “Items” do CMBTIPOEMAIL. Na seção “private” declare o procedimento “LoadCmbTipoEmail”. Implemente conforme ilustrado abaixo:


procedure TZnIWForm.LoadCmbTipoEmail;
begin
CMBTIPOEMAIL.Items.Add('Email Comercial');
CMBTIPOEMAIL.Items.Add('Email Residencial');
end;

No envento “OnCreate” do IWForm faça chamada ao método “LoadCmbTipoEmail”.

Adicione também ao IWForm um IWCheckBox. No Intraweb 8, não é possível acessar um CheckBox simplesmente pelo nome que ele esta definido no Delphi. Por exemplo, para você acessar o IWCheckBox1, além de ter usar caixa alta ainda temos que acrescentar “_CHECKBOX” com sufixo do nome do componente. O IWCheckBos1 deve ser referenciado em código como “IWCHECKBOX1_CHECKBOX” . Como alternativa podemos acessar o componente usando “document.all[IWCHECKBOX1]”. Esse comando é compatível tanto com o Mozila, quanto com o IE.
Na propriedade “ExtraTagParams” do IWCheckBox1 digite conforme ilustrado abaixo:



Observe que a propriedade “ExtraTagParams” habilita o “Code Editor” (Delphi 2006 – Intraweb 8).


Continua ....

Artigo completo (View Full Post)

segunda-feira, 26 de janeiro de 2009

Mais Threads, ClassLoader, Classpath, Arquitetura de Plug-ins, Interfaces e mais recursos Java - PARTE 1.

O problema é interceptar determinados eventos em um servidor de download para tarifação em tempo real em um servidor de tarifação. O servidor de download permite a instalação de plug-ins escritos em Java para interceptar os eventos de download.

Esses eventos são disparados no ciclo do download, e são os seguintes: solicitação de download, download iniciado, download terminado com sucesso, download cancelado.

A classe Java não deve sobrecarregar o servidor para não causar indisponibilidade do serviço. Se a tarifação não puder ser efetuada o download não pode ser interrompido.

O problema é conectar o sistema de tarifação ao sistema de download.

Interface Java que deve ser implementada para interceptar os eventos:

public interface MonitorPlugin {

public void downloadRequested(Download download);

public void downloadStarted(Download download);

public void downloadFinished(Download download);

public void downloadCanceled(Download download);
}


A classe Java deve estar dentro do pacote padrão (sem pacote) dentro de um JAR com o mesmo nome no diretório raiz da aplicação.


Exemplo:
import downloadserver.Download;
import downloadserver.MonitorPlugin;

public class MonitorPluginSOP implements MonitorPlugin {

public void downloadRequested(Download download) {
System.out.println("downloadRequested T:" + download.getThreadId() + " U:" + download.getUserId());
}

public void downloadStarted(Download download) {
System.out.println("downloadStarted T:" + download.getThreadId() + " U:" + download.getUserId());
}

public void downloadFinished(Download download) {
System.out.println("downloadFinished T:" + download.getThreadId() + " U:" + download.getUserId());
}

public void downloadCanceled(Download download) {
System.out.println("downloadCanceled T:" + download.getThreadId() + " U:" + download.getUserId());
}

}


A classe MonitorPluginSOP vai estar dentro do JAR MonitorPluginSOP.jar.

O sistema de tarifação possui os comandos de tarifação e estorno. Para tarifação e estorno devem ser informados o numero de identificação do cliente, o comando e o serviço a ser tarifado.

Vamos discutir mais sobre o sistema de tarifação e o plug-in que fará a conexão entre os servidores no próximo post.

Por enquanto vamos ver o servidor de download que é uma simulação de um servidor real.

A classe que representa um download é a seguinte:
package downloadserver;

import java.util.ArrayList;
import java.util.List;

/*
* A classe Download implementa Runnable para possuir o comportamento de uma
* Thread. Para dar a possibilidade de parar a execução a classe possui
* um atributo running, quando running igual a false o objeto para a sua
* execução. O atributo running é alterado através do método cancel.
*/
public class Download implements Runnable {

static private long threadCount = 0;
static public final long DOWNLOAD_SIZE = 16;
private long threadId;
private long bytesSent = 0;
private int userId;
private boolean running = true;
private List monitors = new ArrayList();

public Download() {
threadId = threadCount++;
}

public Download(int userId) {
this.userId = userId;
threadId = threadCount++;
}

public void setUserId(int userId) {
this.userId = userId;
}

public int getUserId() {
return userId;
}

public long getThreadId() {
return threadId;
}

public long getBytesSent() {
return bytesSent;
}

public void request() {
downloadRequested();
}

public void cancel() {
running = false;
}

public void addMonitor(MonitorPlugin monitor) {
monitors.add(monitor);
}

public void addMonitor(List monitors) {
this.monitors.addAll(monitors);
}

private void downloadRequested() {
for (MonitorPlugin m : monitors) {
m.downloadRequested(this);
}
}

private void downloadStarted() {
for (MonitorPlugin m : monitors) {
m.downloadStarted(this);
}
}

private void downloadFinished() {
for (MonitorPlugin m : monitors) {
m.downloadFinished(this);
}
}

private void downloadCanceled() {
for (MonitorPlugin m : monitors) {
m.downloadCanceled(this);
}
}

public void run() {
downloadStarted();
while (running && bytesSent < DOWNLOAD_SIZE) {
bytesSent++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (bytesSent == DOWNLOAD_SIZE) {
downloadFinished();
} else {
downloadCanceled();
}
}
}


E a classe que representa o Servidor é a seguinte, a classe está comentada nos seus principais recursos:
package downloadserver;

import java.io.File;
import java.io.FilenameFilter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;

public class Server extends javax.swing.JFrame {

private int usersCount = 0;
private List monitorPlugins = new ArrayList();

public Server() {
initComponents();
loadMonitorPlugin();
inicia();
}

@SuppressWarnings("unchecked")
//
private void initComponents() {

jButton2 = new javax.swing.JButton();

setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Server");
setBounds(new java.awt.Rectangle(0, 0, 100, 500));
getContentPane().setLayout(new java.awt.FlowLayout());

jButton2.setText("Novo");
jButton2.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton2ActionPerformed(evt);
}
});
getContentPane().add(jButton2);
}//


private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
novo();
}

/*
* Lê os arquivos de plug-in sem a necessidade de estarem no classepath.
*/
public void loadMonitorPlugin() {

try {
File dir = new File(".");
String[] children;
FilenameFilter filter = new FilenameFilter() {

public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
};
children = dir.list(filter);
if (children != null) {
for (int i = 0; i < children.length; i++) {
String filename = children[i];
try {
String classname = filename.replaceAll("[.]jar", "");

java.io.File file = new java.io.File(filename);
java.net.URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};

ClassLoader loader = URLClassLoader.newInstance(urls, this.getClass().getClassLoader());

Class clazz = loader.loadClass(classname);
MonitorPlugin mp = (MonitorPlugin) clazz.newInstance();
monitorPlugins.add(mp);

System.out.println("Adicionada: " + classname);

} catch (Exception ex) {
ex.printStackTrace();
}
}
}

} catch (Exception ex) {
ex.printStackTrace();
}
}

/*
* Cria um novo download e exibe na tela como um botão. Ao clicar no botão o download é cancelado.
*/
private void novo() {
Download d = new Download(usersCount++);
d.addMonitor(monitorPlugins);
d.request();
print(d);
new Thread(d).start();
}


/*
* Executado no início cria 5 novos downloads.
*/
public void inicia() {
for (int i = 0; i < 5; i++) {
novo();
}
}

/*
* Exibe um download na tela como um botão, adiciona um ouvinte (monitor)
* ao download para atualizar a interface e adiciona ao evento de click
* a operação de cancelar o download.
*/
private void print(final Download d) {
final JButton elementoGrafico = new JButton();
elementoGrafico.setText("T:" + d.getThreadId() + " U:" + d.getUserId());
getContentPane().add(elementoGrafico);

elementoGrafico.addMouseListener(new java.awt.event.MouseAdapter() {

@Override
public void mouseClicked(java.awt.event.MouseEvent evt) {
d.cancel();
}
});

d.addMonitor(
new MonitorPlugin() {

public void downloadRequested(Download download) {
}

public void downloadStarted(Download download) {
}

public void downloadFinished(Download download) {
elementoGrafico.setBackground(new java.awt.Color(51, 204, 0));
}

public void downloadCanceled(Download download) {
elementoGrafico.setBackground(new java.awt.Color(255, 0, 51));
}
});
setVisible(true);
}

public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {

public void run() {
new Server().setVisible(true);
}
});
}

// Variables declaration - do not modify
private javax.swing.JButton jButton2;
// End of variables declaration
}


Bem, é isso. Vou tentar postar a continuação o mais breve possível.

O arquivo para download.

Abraços, Rodrigo Alencar.

Artigo completo (View Full Post)

terça-feira, 13 de janeiro de 2009

Java: Threads concorrentes, swing, observable para conectar objetos a GUI, herança, interface e mais algumas coisas.

Pessoal,

Meu primeiro post no Estação ZN!

Numa manhã sem muito trabalho escrevi um programa em JAVA para ilustrar alguns recursos. Tem threads concorrentes, swing, observable para conectar objetos a GUI, herança, interface e mais algumas coisas.

O programa ilustra a concorrência de dois objetos tentando se conectar para formar uma parceria e se reproduzir em um novo objeto que vai continuar com esse ciclo.

Acredito que esse programa possa ajudar a quem está começando ou quem quer ver um exemplo dos recursos que citei.

Como funciona:

Existe uma classe Elemento que define o objeto principal e que será herdada por Macho e Fêmea. Os Objetos da classe Macho e Fêmea são associados a um objeto da classe Ambiente. A classe Ambiente define o ambiente onde os objetos viverão. A classe Elemento implementa Runnable e no método run executa o código necessário para dar vida aos objetos.

Abaixo um diagrama simplificado dessas classes.


A classe Main é um JFrame que exibe os objetos criados como JButtons. A classe Main possui um método simular que inicia a simulação.

A seguir as principais classes do programa.

Classe: Elemento

package simulador;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

public abstract class Elemento implements Runnable {

private static int contador = 0;
private int id;
private Elemento mae;
private Elemento pai;
private long nascimento;
private long morte;
private int folego = 3 * 1000;
private int idadeMaxima = 2 * 60 * 1000;
private boolean running = true;
private boolean vivo = true;
private boolean ocupado;
protected Ambiente ambiente;
protected Elemento parceiro;
private ArrayList memoria = new ArrayList();
private ArrayList observadoresVida = new ArrayList();
private ArrayList observadoresMorte = new ArrayList();
private ArrayList observadoresParceria = new ArrayList();

public Elemento(Ambiente ambiente) {
id = contador++;
setAmbiente(ambiente);
}

public int getFolego() {
return folego;
}

public void setFolego(int folego) {
this.folego = folego;
}

public int getIdadeMaxima() {
return idadeMaxima;
}

public void setIdadeMaxima(int idadeMaxima) {
this.idadeMaxima = idadeMaxima;
}

public Elemento getMae() {
return mae;
}

public void setMae(Elemento mae) {
this.mae = mae;
}

public Elemento getPai() {
return pai;
}

public void setPai(Elemento pai) {
this.pai = pai;
}

public int getId() {
return id;
}

public ArrayList getMemoria() {
return memoria;
}

protected void memorizar(String s) {
memoria.add(id + ": " + new Date() + ": " + s);
}

public void setAmbiente(Ambiente ambiente) {
this.ambiente = ambiente;
}

public void setRunning(boolean running) {
this.running = running;
}

public boolean isRunning() {
return running && ambiente.isRunnig() && !expirou();
}

public boolean expirou() {
long idade = System.currentTimeMillis() - nascimento;
return idade > idadeMaxima;
}

public long idadeMorte() {
long idade = morte - nascimento;
return idade;
}

public boolean isVivo() {
return vivo;
}

public Elemento getParceiro(){
return parceiro;
}

private synchronized boolean ocupar() {
if (!ocupado) {
ocupado = true;
return true;
} else {
return false;
}
}

private void liberar() {
ocupado = false;
}

private boolean aceitar(Elemento outro) {
if (solteiro() && desejo()) {
String s1 = "Aceitei o convite para ser parceiro do " + outro.id + ".";
notificarEncontrarParceiro(s1);
parceiro = outro;
outro.parceiro = this;
return true;
} else {
String s2 = "N?o aceitei o convite para ser parceiro do " + outro.id + ".";
notificarEncontrarParceiro(s2);
return false;
}
}

private void encontrarParceiro() {
if (ocupar()) {
Random rand = new Random();
int quantidadeDeElementos = candidatos().size();
int aleatorio = rand.nextInt(quantidadeDeElementos);
Elemento outro = candidatos().get(aleatorio);
if (!equals(outro)) {
String s1 = "Escolhi o " + outro.id + " para ser parceiro.";
notificarEncontrarParceiro(s1);
if (!outro.vivo) {
String s2 = "O " + outro.id + " estava morto.";
notificarEncontrarParceiro(s2);
} else if (outro.ocupar()) {
String s3 = "Convidando o " + outro.id + " para ser parceiro.";
notificarEncontrarParceiro(s3);
if (outro.aceitar(this)) {
String s4 = "O " + outro.id + " aceitou ser parceiro.";
notificarEncontrarParceiro(s4);
} else {
String s5 = "O " + outro.id + " n?o aceitou ser parceiro.";
notificarEncontrarParceiro(s5);
}
outro.liberar();
} else {
String s6 = "O " + outro.id + " estava ocupado.";
notificarEncontrarParceiro(s6);
}
}

liberar();
}
}

protected abstract Elemento reproduzir();

protected abstract List candidatos();

protected boolean desejo() {
Random rand = new Random();
int aleatorio = rand.nextInt(2);
if (aleatorio == 1) {
return true;
} else {
return false;
}
}

public boolean equals(Object o) {
Elemento outro = (Elemento) o;
if (outro == this) {
return true;
}
if (outro.id == id) {
return true;
}
return false;
}

public boolean solteiro() {
return parceiro == null;
}

public void run() {
notificarNascimento();
nascimento = System.currentTimeMillis();

while (isRunning()) {
if (solteiro()) {
if (desejo()) {
encontrarParceiro();
}
} else { // Possui um parceiro, pode se reproduzir.

if (desejo() && parceiro.isVivo() && parceiro.desejo()) {
Elemento novo = reproduzir();
if (novo != null) {
try {
new Thread(novo).start();
} catch (OutOfMemoryError e) {
ambiente.setRunning(false);
throw e;
}
}
}
}
if (isRunning()) {
try {
Thread.sleep(folego);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
morte = System.currentTimeMillis();
vivo = false;
notificarMorte();
}

public ArrayList getObservadoresVida() {
return observadoresVida;
}

public ArrayList getObservadoresMorte() {
return observadoresMorte;
}

public ArrayList getObservadoresParceria() {
return observadoresParceria;
}

public void addObservadoresVida(Ouvinte o) {
observadoresVida.add(o);
}

public void addObservadoresMorte(Ouvinte o) {
observadoresMorte.add(o);
}

public void addObservadoresParceria(Ouvinte o) {
observadoresParceria.add(o);
}

private void notificarNascimento() {
memorizar("Nasci.");
for (Ouvinte e : observadoresVida) {
e.atualizar(this, "Nasci.");
}
}

private void notificarMorte() {
memorizar("Morri.");
for (Ouvinte e : observadoresMorte) {
e.atualizar(this, "Morri.");
}
}

private void notificarEncontrarParceiro(String s) {
memorizar(s);
for (Ouvinte e : observadoresParceria) {
e.atualizar(this, s);
}
}

protected void notificarNascimento(Elemento e, String s) {
memorizar(s);
for (OuvinteNascimento o : ambiente.getObservadoresNascimento()) {
o.atualizar(this, e, s);
}
}
}

Classe: Macho
package simulador;

import java.util.List;

public class Macho extends Elemento {

public Macho(Ambiente ambiente){
super(ambiente);
ambiente.getElementosMachos().add(this);
ambiente.getElementos().add(this);
}

protected Elemento reproduzir() {
return null;
}

protected List candidatos() {
return ambiente.getElementosFemeas();
}

}

Classe: Femea
package simulador;

import java.util.List;

public class Femea extends Elemento {

public Femea(Ambiente ambiente) {
super(ambiente);
ambiente.getElementosFemeas().add(this);
ambiente.getElementos().add(this);
}

protected List candidatos() {
return ambiente.getElementosMachos();
}

protected Elemento reproduzir() {
Elemento e;
if (desejo()) {
e = new Femea(ambiente);
notificarNascimento(e, "Pari a femea " + e.getId());
} else {
e = new Macho(ambiente);
notificarNascimento(e, "Pari o macho " + e.getId());
}
e.setMae(this);
e.setPai(parceiro);
return e;
}
}

Classe: Main
package simulador;

public class Main extends javax.swing.JFrame {

public Main() {
initComponents();
}

/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
//
private void initComponents() {

setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Simulador");
getContentPane().setLayout(new java.awt.FlowLayout());

pack();
}//


/**
* @param args the command line arguments
*/
public static void main(String args[]) {
final Main f = new Main();
java.awt.EventQueue.invokeLater(new Runnable() {

public void run() {
f.setVisible(true);
}
});

f.simular();
}
Detalhe detalhe;

public Detalhe detalhe() {
if (detalhe == null) {
detalhe = new Detalhe();
}
return detalhe;
}

public void print(final Elemento e) {
javax.swing.JButton elementoGrafico = new javax.swing.JButton();
elementoGrafico.setText("" + e.getId());
getContentPane().add(elementoGrafico);

elementoGrafico.addMouseListener(new java.awt.event.MouseAdapter() {

@Override
public void mouseClicked(java.awt.event.MouseEvent evt) {
detalhe().setDetalhe(e.getMemoria());
detalhe().setVisible(true);
}
});

pack();
}

public void simular() {
int TEMPO_DE_VIDA = 2 * 60 * 1000;

Ambiente ambiente = new Ambiente();

ambiente.addObservadoresNascimento(new OuvinteNascimento() {

public void atualizar(Elemento mae, Elemento filho, String s) {
print(filho);
}
});

Elemento m = new Macho(ambiente);
print(m);
new Thread(m).start();

Elemento f = new Femea(ambiente);
print(f);
new Thread(f).start();

try {
Thread.sleep(TEMPO_DE_VIDA);
} catch (InterruptedException e) {
e.printStackTrace();
}

ambiente.setRunning(false);

while (ambiente.vivo()) {
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}


System.out.println("Fim.");


}

// Variables declaration - do not modify
// End of variables declaration
}

Não está comentado, mas começando da classe Main ninguém se perde. O programa vem como um projeto Netbeans, mas vai rodar fácil no Eclipse.

Vou continuar melhorando o programa, aceito comentários, sugestões, melhorias e correções!


Obs: A tela de detalhe não se atualiza sozinha.


Abraços, Rodrigo Alencar.

Artigo completo (View Full Post)

terça-feira, 30 de dezembro de 2008

Oracle - Pl/SQL No Alter Table: Constraints e Checks, sem usar o comando

No artigo anterior demonstrei varias possibilidades de usar o camando “ALTER TABLE” no Oracle. Contudo, algumas vezes acho mais conveniente para mim definir as constraints e checks imediatamente quando estou criado a tabela. Acho que para a prática de documentação, versionamento de código/script, se toda a definição estiver no mesmo comando é mais eficiente o versionamento do script da tabela durante a evolução e manutenção do banco de dados.
A partir do que foi exemplificado no artigo anterior, vou definir todas as constraints e checks na criação das tabelas “ENDERECO_ZN” e “Tipo_EnderecoZN”. Veja a seguir ...



DROP TABLE TIPO_ENDERECOZN;
CREATE TABLE TIPO_ENDERECOZN (
TIPO_ENDERECOID NUMBER(10) NOT NULL,
TIPO_ENDERECO VARCHAR2(100) NOT NULL,
DATACADASTRO DATE NOT NULL,
CONSTRAINT PK_TIPO_ENDEZN PRIMARY KEY (TIPO_ENDERECOID),
CONSTRAINT CHK_TIPO_ENDERECO CHECK (LENGTH(TIPO_ENDERECO) > 5),
CONSTRAINT CHK_DATACADASTRO_TP CHECK (DATACADASTRO > TO_DATE('31/12/2000', 'DD/MM/YYYY'))
);

Na linha 1, primeiro excluo a tabela do banco, para em seguida recriá-la.
a partir da linha 6 começamos a definir as constraints.
Na Lina 6, defino quem é a chave primária.
Na linha 7 validação para a coluna “TIPO_ENDERECO”.
Na linha 8 validação para a coluna “DATACADASTRO”.

Agora vamos fazer o mesmo para a tabela “ENDERECO_ZN”:

DROP TABLE ENDERECO_ZN;
CREATE TABLE ENDERECO_ZN
(
ENDERECO_ZNID NUMBER(10) NOT NULL,
TIPO_ENDERECOID NUMBER(10),
LOGRADOURO VARCHAR2(130) NOT NULL,
COMPLEMENTO VARCHAR2(170),
BAIRRO VARCHAR2(50),
CIDADE VARCHAR2(50),
ESTADO VARCHAR2(2),
CEP VARCHAR2(10),
NUMERO VARCHAR2(10),
DATACADASTRO DATE NOT NULL,
CONSTRAINT PK_ENDERECOZN PRIMARY KEY (ENDERECO_ZNID),
CONSTRAINT FK_ENDERECO_ZN_TP_END FOREIGN KEY (TIPO_ENDERECOID) REFERENCES TIPO_ENDERECOZN (TIPO_ENDERECOID),
CONSTRAINT CHK_ESTADO CHECK (LENGTH(ESTADO) > 1),
CONSTRAINT CHK_CIDADE CHECK (LENGTH(CIDADE) > 4),
CONSTRAINT CHK_DATACADASTRO CHECK (DATACADASTRO > TO_DATE('31/12/2000', 'DD/MM/YYYY'))

);


Artigo completo (View Full Post)

Oracle - Pl/SQL Alter Table (Variações sobre o tema): Modify, Add Constraint, Add, Drop e Rename

Com o comando “ALTER TABLE” podemos realizar diversas modificações estruturais em tabelas de banco de dados. A seguir, experimentaremos como isso pode ser feito em PL/SQL.


Trabalharemos com a tabela exemplificada abaixo:

CREATE TABLE ENDERECO_ZN
(
CODENDERECO_ZN NUMBER(10) NOT NULL,
ID_TIPO_ENDERECO NUMBER(10),
LOGRADOURO VARCHAR2(100),
COMPLEMENTO VARCHAR2(50),
BAIRRO VARCHAR2(50),
CIDADE VARCHAR2(50),
ESTADO VARCHAR2(2),
CEP VARCHAR2(10),
NUMERO VARCHAR2(10)
);


A primeira coisa que vou fazer é alterar o nome de uma das colunas:

Alterando o nome da coluna: De “CODENDERECO_ZN” para “ENDERECO_ZNID”

ALTER TABLE
ENDERECO_ZN
RENAME COLUMN
CODENDERECO_ZN
TO
ENDERECO_ZNID


Agora vamos alterar algumas outras coisas numa coluna. Que tal o tamanho e torná-la obrigatória?


-- Alterando o tamanho da coluna e tornando-a obrigatória

ALTER TABLE ENDERECO_ZN MODIFY COMPLEMENTO varchar2(150) not null;

Veja como está a nossa tabela após essas primeiras alterações ...
-- Create table
create table ENDERECO_ZN
(
ENDERECO_ZNID NUMBER(10) not null,
ID_TIPO_ENDERECO NUMBER(10),
LOGRADOURO VARCHAR2(100),
COMPLEMENTO VARCHAR2(150) not null,
BAIRRO VARCHAR2(50),
CIDADE VARCHAR2(50),
ESTADO VARCHAR2(2),
CEP VARCHAR2(10),
NUMERO VARCHAR2(10)
);


Podemos aplicar modificações em mais de uma coluna no mesmo comando. Veja ilustrado abaixo como fazer:

-- Alterando mais de uma coluna no mesmo comando
ALTER TABLE ENDERECO_ZN MODIFY (COMPLEMENTO varchar2(170) null, LOGRADOURO varchar2(130) not null);


No exemplo acima alteramos tanto o tamanho das duas colunas, quanto a obrigatoriedade das mesmas. Aprimeira definimos para aceitar valores nulos e a segunda para não aceitar. Vejamos como ficou a tabela “ENDERECO_ZN”.


create table ENDERECO_ZN
(
ENDERECO_ZNID NUMBER(10) not null,
ID_TIPO_ENDERECO NUMBER(10),
LOGRADOURO VARCHAR2(130) not null,
COMPLEMENTO VARCHAR2(170),
BAIRRO VARCHAR2(50),
CIDADE VARCHAR2(50),
ESTADO VARCHAR2(2),
CEP VARCHAR2(10),
NUMERO VARCHAR2(10)
);


Podemos também adicionar mais de uma coluna com o mesmo comando
-- Adicionando novas colunas

Alter table ENDERECO_ZN
ADD ( Pais varchar2(50),
DataCadastro Timestamp not null);


Mais uma vez, a tabela ENDERECO_ZN refletindo o comando anterior:
     
create table ENDERECO_ZN
(
ENDERECO_ZNID NUMBER(10) not null,
ID_TIPO_ENDERECO NUMBER(10),
LOGRADOURO VARCHAR2(130) not null,
COMPLEMENTO VARCHAR2(170),
BAIRRO VARCHAR2(50),
CIDADE VARCHAR2(50),
ESTADO VARCHAR2(2),
CEP VARCHAR2(10),
NUMERO VARCHAR2(10),
PAIS VARCHAR2(50),
DATACADASTRO TIMESTAMP(6) not null
)


Supondo que a coluna “Pais” não seja adequada ao modelo de negócio em questão, vamos excluí-la da tabela.


-- Dropnado uma coluna
Alter table ENDERECO_ZN drop column Pais


Agora vamos alterar o tipo de dado de uma das colunas

-- Alterando o tipo de dado de uma coluna
ALTER TABLE ENDERECO_ZN MODIFY DATACADASTRO Date;


Outra vez, alteramos a tabela ...

create table ENDERECO_ZN
(
ENDERECO_ZNID NUMBER(10) not null,
ID_TIPO_ENDERECO NUMBER(10),
LOGRADOURO VARCHAR2(130) not null,
COMPLEMENTO VARCHAR2(170),
BAIRRO VARCHAR2(50),
CIDADE VARCHAR2(50),
ESTADO VARCHAR2(2),
CEP VARCHAR2(10),
NUMERO VARCHAR2(10),
DATACADASTRO DATE not null
)



O Comando “ALTER TABLE” também é uma forma de definirmos constraits. Vejamos ...


Definindo uma Chave primária: Suponha que você esta trabalhando com uma tabela que foi crianda a muitos anos por outros programadores. Esses programadores não definiram nenhuma coluna como chave primária. Mas hoje, você precisa fazer isso. Hoje você precisa que esta tabela possua uma PK. Vaja uma forma de fazer isso ilustrada abaixo:



-- Pk
alter table ENDERECO_ZN add primary key (ENDERECO_ZNID);


Note que eu preparei a tabela para estar relaciona a outra. A coluna “ID_TIPO_ENDERECO” deveria ser uma chave estrangeira para uma tabela que armazenasse os tipos de Endereço. Esse tipo de modelagem atende a uma normalização muito comum. Portanto, antes de declararmos a chave estrangeira vamos criar a tabela de tipo de endereço:


-- Crinado a tabela de Tipo de Endereço
Create Table Tipo_EnderecoZN (
ID_TIPO_ENDERECO NUMBER(10) not null,
Tipo_Endereco Varchar2(100) not null
);

alter table Tipo_EnderecoZN add primary key (ID_TIPO_ENDERECO);

Na linha 7 do trecho de código acima, repeti o mesmo comando, que usei para criar a chave primária para tabela Enreco_ZN, para definir a coluna “ID_TIPO_ENDERECO” como PK (Primary Key – Chave primária) em “Tipo_EnderecoZN”.

Agora vamos criar a constraint que garantirá a integridade referencial entre as tabelas:


Alter table Endereco_ZN add constraint FK_Endereco_TpEndereco foreign key (ID_TIPO_ENDERECO) references Tipo_EnderecoZN (ID_TIPO_ENDERECO);


Qualidade


Para garantir a qualidade dos dados que serão persistidos nessas tabelas podemos definir regras de validação dos dados (Veja mais sobre isso em “Banco de Dados ou Bando de Dados? ”).


-- Definido regras de validação de dados - checks
Alter table Tipo_EnderecoZN Add CONSTRAINT chk_Tipo_Endereco CHECK (LENGTH(Tipo_Endereco) > 5);
Alter table Endereco_ZN Add CONSTRAINT chk_DATACADASTRO CHECK (DATACADASTRO > TO_DATE('01/01/2000', 'DD/MM/YYYY'));
Alter table Endereco_ZN Add CONSTRAINT chk_BAIRRO CHECK (LENGTH(BAIRRO) > 3);
Alter table Endereco_ZN Add CONSTRAINT chk_ESTADO CHECK (LENGTH(ESTADO) > 1);


Qunado for necessário "dropar" uma constraint ...

ALTER table Endereco_ZN drop constraint FK_Endereco_TpEndereco;


Observe que não referenciei o nome da constraint entre plics.

Obviamente, você deve abusar de ser criterioso ao definir uma regra como essas. Visto que, se ela não definida com exatidão em total harmonia com as regras de negócio (da empresa, para qual o sistema gerenciador de banco de dados está sendo desenvolvido) você terá sérios problemas. Por exemplo, com relação ao trecho de código anterior:
Na linha 1 definimos que na tabela Tipo_EnderecoZN, a coluna Tipo_Endereco não vai aceitar nenhum valor cujo número de caracteres seja menor que 5. Portanto, se tentarem inserir o valor “Resid” (abreviação de “Residencial”) será frustrado. Isso tanto pode ser muito útil, evitando que valores lixo sejam inseridos no banco, mas pode ser prejudicial caso exista alguma possibilidade de uma valor de, por exemplo, três caracteres precise ser inserido na coluna em questão.
Na linha 2, definimos um valor mínimo para a coluna “DataCadastro”, da tabela “ENDERECO_ZN”. Isso vai evitar valores de data do tipo “01/02/0009”. Esse valor de data não é uma valor inválido, entretanto pode causar problema.


Artigo completo (View Full Post)

segunda-feira, 15 de dezembro de 2008

Trabalhando com Controles Lookup

Trabalhando com Controles Lookup

No Delphi chamamos de “Lookup” o mecanismo que representa o relacionamento entre duas tabelas dinamicamente e de forma automatizada para a manipulação de uma delas. Isso consiste em duas operações:
A primeira é de exibir a descrição de um dado a qual não existe no registro da tabela que esta sendo manipulada.
A segunda é, ao mesmo tempo que lista a descrição, sincronizadamente, atribui o valor do campo chave, de relacionamento entra as tabelas envolvidas no lookup, para a tabela que esta sendo manipulada.



Por exemplo, vejamos o relacionamento entre duas tabelas: “Produtos” e “Categoria de Produtos” (as quais usamos no artigo anterior). Primeiro a tabela Producs, abaixo:




Temos o ID da Categoria, mas não temos a descrição dela nesta tabela. A descrição da Categoria encontra-se na tabela Categories.



Existe um relacionamento entre elas ... (Mas isso não é obrigatório para se fazer o Lookup). Essa relação entre os campos pode ser feita pela aplicação, basta que exista um relacionamento lógico entre as colunas das tabelas que serão relacionadas.



O que essa estrutura me força previamente entender? _ Devo entender que para atualizar a tabela de “Produtos” dependerei sempre dos valores existentes na tabela “Categorias”. Isso porque, os valores possíveis para a categoria de produtos está armazenado na tabela Categoria, e a constraint que define a integridade referencial entre as duas tabelas jamais permitirá que seja inserido um valor para categoryID na tabela “Products” que não exista em “Categories”. Isso é muito bom que seja dessa forma.

O Lookup permite reproduzirmos essa relação, respeitando a referencia entre as tabelas de forma automática, suprimindo totalmente a necessidade da codificação de sequer uma linha de código para isso.


Os Controles de Lookup mais comuns são:

DBLookupComboBox: TDBLookupComboBox;
DBLookupListBox: TDBLookupListBox;

Intraweb Framework
IWDBLookupComboBox: TIWDBLookupComboBox;
IWDBLookupListBox: TIWDBLookupListBox;

Dev Express
cxDBLookupComboBox: TcxDBLookupComboBox;
cxDBCheckComboBox: TcxDBCheckComboBox;

Jedi
JvDBLookupComboEdit: TJvDBLookupComboEdit;

Campo Virtual LookUp

Tfield fkLookup: A mesma lógica descrita acima é aplicada também aos Datasets, especificamente quando, através deles trabalhamos com o que a literatura chama de campos persistentes. Trata-se de objetos da classe Tfield. Quando manipulamos dados usando Datasets (TDataset) podemos criar objetos que representam a referência linha/coluna de uma tabela. Esses objetos chamamos de campos de um Dataset, eles nos permitem acessar um determinado dado de um registro. Os Tfields podem ser de vários tipos. Cada tipo determina um grupo de operações que poderá realizar o Tfield. A Propriedade FieldKind determina o tipo do Tfield.
fkData, fkCalculated, fkLookup, fkInternalCalc, fkAggregate são os valores possiveis para a propriedade FieldKind. A Classe TFieldKind, definida na unit DB, define esses valores:



type TFieldKind = (fkData, fkCalculated, fkLookup, fkInternalCalc, fkAggregate);


O Tfield usado para fazermos Lookup é do tipo “fkLookup”. Isso quer dizer que a propriedade FieldKind é “fkLookup”.

Usamos a técnica de Lookups com os Fields (TFild) de um Dataset (Um TclientDataSet por exemplo) quando existe a necessidade de acessarmos (disponibilizarmos, visualizarmos) dados provenientes de tabelas diferentes num único resultset. Como por exemplo, ao apresentar uma listagem de “Pedidos” precisamos explicitamente exibir na tela: O nome do cliente que efetuou a compra, a data do pedido e a descrição dos produtos vendidos. Obviamente, a primeira coisa que vem a mente é o recurso de utilizarmos um join em SQL.


SELECT
C.NomeCliente,
P.DataPedido,
I.Quantidade,
I.PrecoVenda,
(I.Quantidade * I.PrecoVenda) Total,
P.DescricaoProduto
FROM
Clientes C
inner join Pedidos P on
C.ClienteID = P.ClikenteID
inner join Itens I on
I.PedidoID = P.PedidoID
inner join Produtos Pr on
Pr.ProdutoID = I.ProdutosID


Contudo, num Dataset, isto impossibilitaria a utilização do mesmo para qualquer atualização nestes dados (Obviamente, isso é verdadeiro se não consideramos a técnica de gerenciarmos explicitamente, via código, o evento “BeforeUpdateRecord” do DataSetProvider – Falaremos sobre está técnica mais adiante).
No Delphi, o campo (TField) “virtual” lookUp funciona, no que tange a visualização de dados, de forma semelhante ao join. Ou seja, podemos configurar um determinado Dataset trazendo dados através de um comando SQL genérico (Select * from ) de uma única tabela, como o exemplo anterior, “Products”, e adicionarmos a ele um campo virtual que irá conter informações de um outro Dataset. A exemplo da relação entre Products e Categories, demonstrado acima. A grande vantagem desta técnica é porque mesmo disponibilizando dados de tabelas diferentes e ainda assim podermos atualizar os dados usando os comandos convencionais do próprio Dataset. Para fazermos uso do campo LookUp basta que os Datasets possuam um campo de relacionamento.

Vejamos agora, a utilização do campo LookUp:

Suponha que neste momento queremos criar um cadastro (CRUD) de produtos. Mas temos um problema, o banco apresenta um normalização para a tabela produtos de maneira que o dado categoria do produto esta em outra tabela. A tabela Categorias (no caso Categories) é uma tabela de referência. Ela não é transacional e possui um número relativamente pequeno de registros.
Lembre-se que, na hora de incluirmos novos Produtos precisamos digitar, na interface, os dados necessários da tabela de produtos, sendo que para os campos “CategoryID” e “SupplierID” presimos apresentar as descrições de cada um dos campos. Entretanto, o dado, descrição da categoria, por exemplo, encontra-se na tabela de Categories. Ou seja, temos que ler da tabela de Categories: a descrição (Categories.Description), e o valor do respectivo vampo de relacionamento (Categories.CategoryID). Para, então, atribuir a Produtos (Products.CategoryID).

Para isso usaremos um Dataset, o CdsProdutos, criaremos nele um campo lookUp que apontará para o campo Description de Categories no CdsCategorias.
Existe o campo de relacionamento entre essas duas tabelas, esses dois campos estão presentes tanto no CdsProdutos (Products.CategoryID), quanto no CdsCategorias (Categories.CategoryID) possibilitando desta forma um “join virtual”, entre CdsProdutos e CdsCategorias. Consequentimente, podemos ainda assim a atualizar o recordset do CdsProdutos. Outro bonus fundamental dessa técnica é, o que que propositalmente guardei para relembrar no final, de que com o campo LookUp a atribuição de valor ao campo “Products.CategoryID” e feita automaticamente sem a necessidade de código para isso, proveniente do link feito com Categories.CategoryID. Isso ocorre imediatamente a ação do usuário selecionar a descrição da categoria no controle lookup disponibilizado na interface.




As linhas vermelhas indicam as propriedades envolvidas na configuração de um de um controle DBLookup (DBLookupComboBox). AS Linhas verdes indicam os valores, e suas origens, que devem ser associadas as respectivas propriedades.

Criando o Campo Virtual LookUp

Com o botão direito do mouse sobre o CdsProdutos, selecione “Fields Editor ...”



Para criar um novo Field, botão direito no “Fields Editor” e selecione “New Field” (Ou digite “Ctrl + N”).




No Diálogo de definição do novo Field configure as propriedades conforme ilustrado na figura abaixo.




Na propriedade “Name” será definido o nome da coluna/campo virtual. Automaticamente, enquanto você digitar o Delphi gera o nome do objeto Tfield em “Component”. A propriedade “Type” define o tipo de dado que o Tfield irá trabalhar, internamente isso irá definir a propriedade DataType cujo o tipo é “TfieldType”, por sua vez isso refletirá na definição de uma classe que especializa o Tfield a qual o Field criado instanciará.
Dependendo do tipo escolhido em “Type” será imprescindível definir o tamanho do Field. Isso quer dizer, por exemplo, se o campo for do tipo String, o limite máximo quanto ao número de caracteres que o Field poderá armazenar. Logo, na propriedade “Size” digite um valor adequado para o tamaho, limite de caracteres, no campo “CategoriaVirt”. É mais que aconselhável você conferir no banco qual o tamanho da coluna “Categories.CategoryName”, visto que será os valores armazenados por ela listados por esse novo campo.




Em Field Type marque Lookup, em resposta a essa ação os campos de “Lookup definition” serão habilitados, em “Key Fields” e “Lookup Keys” selecionamos as colunas de relacionamento entre os Datasets envolvidos no Lookup. Todavia, para habilitar a propriedade Dataset (onde devemos definir o Dataset que disponibilizara os dados para Lookup, no nosso caso CdsCategorias), temos que selecionar primeiro a coluna do CdsProdutos que faz o relacionamento com Categorias (CategoryID).

Na propriedade DataSet, repetindo, é onde definimos o Dataset de Lookup. Selecione CdsCategorias.
Na propriedade “Lookup Keys”, selecionamos a coluna, do Dataset o qual escolhemos na propriedade “Dataset”, que se relaciona com o CdsProdutos (CategoryID).
Por fim, na propriedade “Result Field” selecionamos a coluna do CdsCategorias que servirá para listar os dados da descrição da categoria. Ou melhor, a coluna para a qual nosso campo virtual “CategoriaVirt” apontará. Selecione “CategoryName”.




Portanto, olhando a tamanho da coluna em “Disign Table” no Enterprise Manager, podemos definir o valor 15 para a propriedade “Size”.


Para finalizar, após definir todas as propriedades, click em “OK”

Se você fizer um Drag and Drop do novo Field virtual Lookup para o Form veja que será criado um controle DbLookupCombobox associado ao CdsProdutos pelo DataSource “DsProdutos”.






Para testar, adicione um DbGrid ao Form1, associe ele ao DataSource DsProdutos (através da propriedade “DataSource” do DbGrid, no Object Inspector). No Evento OnCreate Do Form1 abra os dois Cds (CdsProdutos e CdsCategorias).



procedure TForm1.FormCreate(Sender: TObject);
begin
CdsCategotias.Open;
CdsProdutos.Open;
end;




Observações:

Embora apresente um ganho extraordinário para o desenvolvimento no front-end ,em situações onde trabalhamos com CRUD que envolve tabelas normalizadas, a técnica de lookup nem sempre será adequada. Observamos diversas situações em que o emprego do Lookup trará grande inconveniência, tanto no para usabilidade quanto para outros pontos importantes como: Performance e manutenibilidade. Um caso onde o Lookup é desaconselhado é quando a tabela de lookup possui muitos registros.

Em substituição a técnica de Lookup devemos usar, em conjunto, mais de uma técnica. Um exemplo comum é o join no próprio SQL do Dataset que manipula os dados, para tratar o pacote transacional posteiormente no evento BeforeUpdateRecord do DatasetProvider. Mas isso exige conhecimento intermediário em DataSnap. Em Breve abordaremos esse tema!

Artigo completo (View Full Post)

Delphi Intraweb 8 – Acessando dados – BDS2006

Neste artigo pretendo demonstrar como usar os componentes dataware do framwork Intraweb 8.
Tenho observado muitos problemas com essa versão da Intraweb. Graças a Deus parece que ninguém ousou usar esse framework, com algumas raras exceções. Se você veio aqui procurando saber se deve ou não iniciar um projeto com essa tecnologia, minha sugestão é: Procure outra coisa, ou na falta de alternativa use Delphi 7 com a versão 5.o do Intraweb.

Vamos construir uma aplicação que vai acessar a base de dados Northwind do SQL Server. Northwind, é uma base criada por default no SQL server, na instalação, cujo objetivo é didático. Ela é usada nos treinamentos oficiais da Microsoft, e exemplifica integralmente um banco de dados usado em um cenário real, profissional



Explicando sobre a base de dados

OBS:
Caso você já conheça a base Northwind, pode pular essa parte indo direto para “Iniciando um projeto Intraweb”.


Vamos trabalhar com as tabelas Categories (Refere-se as categorias dos produtos) e Products (Produtos).
Com o botão direito do mouse sobre “Diagrams”, na tree view, dentro do nó “Northwind”, podemos criar um DER (Diagram Entidade Relacionamento) das tabelas e entender como os dados se relacionam neste data base.



Nosso exemplo estará restrito ao contexto demonstrado na figura abaixo:



O relacionamento demonstrado acima indica que uma “Categoria” pode classificar muitos produtos. Sendo que o “Produto” somente pode ser classificado por uma “Categoria”. Este tipo de relacionamento chama-se “Um para N” (1-N). No relacionamento “Um para N” a chave (campo de relacionamento) aponta para a tabela “N”, no caso “Produtos”.
Isso quer dizer que na tabela Producs, existe uma coluna que referencia a tabela “Categories”. Em outras palavras: A coluna “CategoryID”, de “Producs” aponta para a “CategoryID” de “Categories”.


Iniciando um projeto Intraweb

Inicie um novo projeto do DBS2006: No menu File ►New ► Other ►Intraweb, selecione “Intraweb Application Wizard”.



No diálogo seguinte, “Intraweb Application Wizard”, definiremos alguns parâmetros da nova aplicação que criaremos. Configure conforme ilustrado na imagem abaixo:



Adicione na Unit1 os seguintes componentes:

IWDBEdit1: TIWDBEdit
IWDBListbox1: TIWDBListbox
IWDBNavigator1: TIWDBNavigator
IWDBLookupComboBox1: TIWDBLookupComboBox
IWButton1: TIWButton
IWCheckBox1: TIWCheckBox
ADOConnection1: TADOConnection
ADODataSet: TADODataSet, altere a propriedade “Name” para: AdsCategotias.
DataSetProvider: TdataSetProvider , altere a propriedade “Name” para: DspCategotias
ClientDataSet: TClientDataSet, altere a propriedade “Name” para: CdsCategotias
DataSource: TDataSource, altere a propriedade “Name” para: DsCategotias
ADODataSet: TADODataSet, altere a propriedade “Name” para: AdsProdutos
DataSetProvider: TDataSetProvider, altere a propriedade “Name” para: DspProdutos
ClientDataSet: TClientDataSet, altere a propriedade “Name” para: CdsProdutos
DataSource: TDataSource, altere a propriedade “Name” para: DsProdutos
OBS: A partir do DBS 2005 foi inroduzido na IDE do Delphi um novo conceito para a “Tool Palette”. Para vizualizá-la acesse o menu Vew ► Tool Palette. Hot key “Ctrl + Alt + P” ou digite “Alt” seguido de “V” seguido de “L”.



Uma forma fácil de adicionar um novo componente ao seu projeto é clickar sobre o ícone de filtro na Tool Palette e digitar o nome do componente. Veja imagem abaixo:



Veja o meu Form como ficou em tempo de projeto:




Conecte os componentes de acesso a dados da seguinte forma:

Selecione o IWEdit1, acesse o Objetc Inspector (F11). Associe IWDbEdit1, através da propriedade DataSource, ao DsProdutos. Defina a coluna (campo) que será associada ao IWEdit na propriedade “DataField”. Selecione “ProductName”.

Selecione o DsProdutos, acesse o Objetc Inspector (F11). Associe DsProdutos, através da propriedade DataSet, ao CdsProdutos.

Selecione o CdsProdutos, acesse o Objetc Inspector (F11). Associe CdsProdutos, através da propriedade ProviderName, ao DspProdutos.

Selecione o DspProdutos, acesse o Objetc Inspector (F11). Associe DspProdutos, através da propriedade “DataSet”, ao AdsProdutos.

Selecione o AdsProdutos, acesse o Objetc Inspector (F11). Associe AdsProdutos, através da propriedade “Connection”, ao ADOConnction1.


Conectando o acesso a categoria de produtos:

Selecione o DsCategotias, acesse o Objetc Inspector (F11). Associe DsCategotias, através da propriedade DataSet, ao CdsCategotias.

Selecione o CdsCategotias, acesse o Objetc Inspector (F11). Associe CdsCategotias, através da propriedade ProviderName, ao DspCategotias.

Selecione o DspCategotias, acesse o Objetc Inspector (F11). Associe DspCategotias, através da propriedade “DataSet”, ao AdsCategotias.

Selecione o AdsCategotias, acesse o Objetc Inspector (F11). Associe AdsCategotias, através da propriedade “Connection”, ao ADOConnction1.


Conectando o IWDBLookupComboBox1:


Selecione o IWDBLookupComboBox1, acesse o Objetc Inspector (F11). Associe IWDBLookupComboBox1, através da propriedade DataSource, ao DsProdutos. Defina a coluna (campo) que será associada ao IWDBLookupComboBox1 na propriedade “DataField”. Selecione “CategoryID”. A associação ao CdsCategorias deve ser feita através das propriedades:

1 – “ListSource”, nela selecione “DsCategotias”.
2 - “KeyField”, nela selecione “CategoryID”.
3 - “ListField”, nela selecione “CategoryName”

Esses três campos são do CdsCategorias. Veja mais detalhes sobre a técnica de Lookup ...




Conectando o IWDBListbox1:

Selecione o IWDBListbox1, acesse o Objetc Inspector (F11). Associe IWDBListbox1, através da propriedade DataSource, ao DsProdutos. Defina a coluna (campo) que será associada ao IWDBListbox1 na propriedade “DataField”. Selecione “Discontinued”. Este campo indica se o produpo foi descontinuado ou não.


Definindo os valores no DBListBox:

Na propriedade “Items”, click para acessar o editor. Digite “True”, em seguida, na linha de baixo, digite “False”. Como o CdsProdutosDiscontinued é um “TbooleanField” isso vai funcionar.





Na propriedade ItemsHaveValues, Selecione “False”.

Para a propriedade Height digite 41.


Conectando o IWDBNavigator:


Selecione o IWDBNavigator1, acesse o Objetc Inspector (F11). Associe IWDBNavigator1, através da propriedade DataSource, ao DsProdutos.


String connection no ADOConnection


Provider=SQLOLEDB.1;Persist Security Info=True;User ID=sa;Initial Catalog=Northwind;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=Landjah;Use Encryption for Data=False;Tag with column collation when possible=False

Conectei no SQL Server, usarei as tabelas Categories e Products do banco “Northwind”. Ele é o banco padrão do SQL Server para exemplo e estudo.

Comando SQL nos Ads:
AdsCategorias
Propriedade CommandText:
select CategoryID, CategoryName from Categories

AdsProdutos
Propriedade CommandText:
select * from Products


Codificando o evento OnClick do IWButton1:
procedure TTADo.IWButton1Click(Sender: TObject);
begin
CdsCategotias.Open;
CdsProdutos.Open;
end;


Podemos também trabalhar com o evento OnCreate do Form.
procedure TTADo.IWAppFormCreate(Sender: TObject);
begin
IWButton1Click(IWButton1);
end;

Agora você pode executar a aplicação e testar, navegando, alterando, inserindo, ou excluindo dados.


Veja a imagem ilustrando as conexões data ware no Delphi:



Para rodar o exemplo pressione F9, quando aparecer o diálogo referente ao servidor, pressione F9 novamente.



No IWCheckBox1, defina para a propriedade Caption = 'Editar Produto'. No evento OnClick programe:
procedure TTADo.IWCheckBox1Click(Sender: TObject);
begin
if IWCheckBox1.Checked then
CdsProdutos.Edit;
end;


Agora no evento AfterPost do CdsProdutos sicronise o comportamento do IWCheckBox:
procedure TTADo.CdsProdutosAfterPost(DataSet: TDataSet);
begin
IWCheckBox1.Checked := False;
end;


Testando o comando do IWCheckBox



Salvando as alterações realizadas




Sucesso!

Artigo completo (View Full Post)

 
BlogBlogs.Com.Br