Interfaces Gráficas:
Interações Complexas
21
21.5
O contexto gráfico
Os componentes de uma interface gráfica são desenhados na tela do computador a partir
de primitivas que permitem a construção de linhas, ovais e polı́gonos, o preenchimento de
áreas, a escrita de caracteres, além da definição da cor e do tipo de fonte utilizados. Estas
primitivas gráficas fazem parte do contexto gráfico dos componentes e são implementadas
através de objetos da classe Graphics.
Um componente é desenhado quando aparece pela primeira vez na tela e, após isso,
sempre que for redimensionado ou em resposta a qualquer operação que torne visı́vel partes
que antes estavam ocultas.
21.5.1
A classe Graphics
Quando um componente precisa ser desenhado (ou redesenhado, caso já esteja vı́sivel)
o ambiente de execução usa um objeto da classe Graphics, adaptando-o para refletir as
propriedades do componente, e executa as operações de desenho através deste objeto.
Alguns dos métodos da classe Graphics são:
abstract void drawLine(int x1, int y1, int x2, int y2). Desenha, usando a cor
corrente, uma linha que vai da coordenada (x1,y1) à coordenada (x2,y2).
void drawRect(int x, int y, int comp, int alt). Desenha, usando a cor corrente,
o contorno de um retângulo com vértice superior esquerdo na coordenada (x,y),
largura comp e altura alt.
2
Interfaces Gráficas
abstract void fillRect(int x, int y, int comp, int alt). Preenche, usando a
cor corrente, a área de um retângulo com vértice superior esquerdo na coordenada
(x,y), largura comp e altura alt.
abstract void drawOval(int x, int y, int comp, int alt). Desenha, usando a
cor corrente, o contorno de uma oval inscrita no retângulo que tem vértice superior esquerdo na coordenada (x,y), largura comp e altura alt.
abstract void drawString(String str, int x, int y). Desenha a cadeia de caracteres str, usando a fonte corrente, a partir da coordenada (x,y).
abstract Color getColor(). Obtém a cor corrente.
abstract void setColor(Color c). Define c como a cor corrente.
abstract Font getFont(). Obtém a fonte corrente.
abstract void setFont(Font f). Define f como a fonte corrente.
Cada objeto da classe Graphics possui uma área de desenho que inicialmente é determinada pelo componente ao qual o contexto gráfico está associado. Se um componente
possui uma largura de 100 e uma altura de 60 pixels, então o contexto gráfico que o sistema
utiliza para desenhá-lo é adaptado para ter sua área de desenho com essas dimensões. A
área de desenho de um contexto gráfico pode ser obtida e modificada com os métodos:
abstract Rectangle getClipBounds(). Obtém como um objeto da classe Rectangle a
área de desenho usada pelo contexto gráfico. Todo objeto da classe Rectangle possui
os atributos x e y, que definem as coordenadas do seu vértice superior esquerdo, e
width e height, que definem sua largura e altura.
abstract void setClip(int x, int y, int comp, int alt). Define a área de desenho como sendo o retângulo com vétice superior esquerdo na coordenada (x,y),
largura comp e altura alt.
A classe Graphics possui outros métodos que permitem a realização de outras operações
de desenho e o controle do contexto gráfico no qual estas operações ocorrem.
21.5.2
Desenhando componentes
Todo componente possui os métodos paint e getGraphics, herdados de Component:
void paint(Graphics g). Método usado para desenhar o componente através das primitivas gráficas implementadas pelo objeto g.
21.5 O contexto gráfico
3
Figura 21.1. Componente desenhado no Exemplo 21.1
Graphics getGraphics(). Cria um (objeto que implementa o) contexto gráfico para este
componente.
Quando um componente precisa ser desenhado, o ambiente de execução usa um contexto
gráfico adaptado para refletir as caracterı́sticas do componente (cor, fonte, área de desenho)
e invoca o método paint do componente fornecendo este contexto gráfico como argumento.
Se o componente é um contêiner e possui outros componentes como membros, então o
método paint de cada membro é invocado em cascata.
21.5.3
Desenhando novos componentes
Podemos desenhar nossos próprios componentes estendendo um componente já existente e
sobrescrevendo o seu método paint. A classe Canvas serve como base para o desenho de
novos componentes porque apenas define uma região vazia da tela, sem nenhuma decoração.
Exemplo 21.1. O programa a seguir cria um componente no qual são desenhados uma
oval amarela e um segmento de reta azul.
A classe Tela que especifica o novo componente é declarada como subclasse de Canvas
nas linhas 2 a 13. O construtor dessa classe (linhas 3 a 5) apenas define a dimensão inicial
de um objeto Tela. O método paint nas linhas 6 a 12 sobrescreve o método paint (que
para a classe Canvas não possui nenhuma funcionalidade).
1
2
3
4
5
6
7
8
9
10
11
12
import java.awt.*;
class Tela extends Canvas {
public Tela () {
setSize(100,100);
}
public void paint(Graphics g) {
Dimension r = getSize();
g.setColor(Color.yellow);
g.fillOval(r.width/4, r.height/4, r.width/2, r.height/2);
g.setColor(Color.blue);
g.drawLine(r.width/4, r.height/4, 3*r.width/4, 3*r.height/4);
}
4
13
14
15
16
17
18
19
20
21
Interfaces Gráficas
}
public class C21ExemploCG1 {
public static void main(String[] args) {
Frame janela = new Frame();
janela.add(new Tela());
janela.pack();
janela.setVisible(true);
}
}
Um objeto da classe Tela é criado e adicionado à janela do programa, na linha 17.
Quando a janela torna-se visı́vel (linha 19) o seu método paint é invocado pelo ambiente
de execução e, como trata-se de um contêiner, também o método paint de todos os seus
membros.
Ao ser executado, o método paint de um objeto Tela obtém a dimensão do objeto
que o executa (linha 7), define o amarelo como a cor corrente (linha 8), preenche uma oval
ocupando metade da área do componente (linha 9), define o azul como a cor corrente (linha
10) e desenha uma linha cortando a oval já desenhada (linha 11). A janela resultante é
mostrada na Figura 21.1. 21.5.4
Realizando o desenho de componentes
Não é aconselhável que um programa invoque o método paint diretamente, já que não
se pode controlar as demais chamadas que podem ser feitas a este método. Durante a
execução de um programa o método paint pode ser invocado várias vezes, sob o controle
do gerente de janelas, que determina os componentes que precisam ser desenhados.
Se um programa precisa executar o método paint de um determinado componente, o
ideal é que ele registre essa necessidade junto ao gerente de janelas e deixe que o ambiente de
execução determine o momento apropriado para a execução do método. A necessidade de
desenho deve ser registrada através do método repaint, que envia a solicitação ao gerente
de janelas que, por sua vez, invoca o método update. Quando executado, o método update
limpa o fundo do componente e chama o método paint.
A chamada ao método repaint é assı́ncrona; isto é, o programa que a faz prossegue
o processamento, sem esperar pelo término de sua execução. É comum que uma série de
chamadas ao método repaint sejam transformadas em uma única chamada ao método
update do componente.
Exemplo 21.2. O programa abaixo modifica o programa do Exemplo 21.1 para que as
cores da oval e da linha mudem caso o ponteiro do mouse entre na área do componente.
Agora o método paint (linhas 20 a 26) usa os atributos corOval e corLinha, declarados
na linha 4, para definir as cores com que serão desenhadas a oval e a linha. Para responder
aos eventos de entrada e saı́da do mouse, um monitor de eventos da classe MouseEvent é
criado através da classe MouseAdapter e registrado junto ao componente na linha 7.
21.5 O contexto gráfico
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
5
import java.awt.*;
import java.awt.event.*;
class Tela extends Canvas {
Color corOval = Color.yellow, corLinha = Color.blue;
public Tela () {
setSize(100,100);
addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
corOval = Color.yellow; corLinha = Color.blue;
Tela t = (Tela)e.getSource();
t.repaint();
}
public void mouseExited(MouseEvent e) {
corOval = Color.red; corLinha = Color.white;
Tela t = (Tela)e.getSource();
t.repaint();
}
});
}
public void paint(Graphics g) {
Dimension r = getSize();
g.setColor(corOval);
g.fillOval(r.width/4, r.height/4, r.width/2, r.height/2);
g.setColor(corLinha);
g.drawLine(r.width/4, r.height/4, 3*r.width/4, 3*r.height/4);
}
}
public class C21ExemploCG2 {
public static void main(String[] args) {
Frame janela = new Frame();
janela.add(new Tela());
janela.pack();
janela.setVisible(true);
}
}
Sempre que o cursor do mouse entrar na área do componente o método mouseEntered
(linhas 8 a 12) será executado. O componente que gerou o evento é determinado na linha
10 e, através dele, o método repaint é invocado na linha 11. A próxima execução do
método paint usará então as novas cores determinadas na linha 9. Um comportamento
semelhante ocorre, pela execução do método mouseExited, quando o cursor do mouse sair
da área do componente.
Este programa também ilustra, nas linhas 7 a 18, a criação de um objeto de uma classe
cuja especificação é usada uma única vez (para criá-lo). 6
Interfaces Gráficas
21.5.5
Sobrescrevendo outros componentes
Embora o uso mais freqüente das primitivas gráficas seja para a criação de componentes
desenhados como uma extensão da classe Canvas, podemos adicionar elementos gráficos a
componentes que já possuam desenhos, bastando para isso estender a classe do componente
e sobrescrever o seu método paint.
Exemplo 21.3. O programa a seguir estende a classe Button, adicionando duas linhas
vermelhas (no topo e na base) ao desenho de um botão convencional.
Os objetos da classe Botao são criados herdando a funcionalidade e as caracterı́sticas
da classe Button. O método paint da nova classe é definido nas linhas 7 a 12.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.awt.*;
import java.awt.event.*;
class Botao extends Button {
Botao (String rotulo) {
super(rotulo);
}
public void paint(Graphics g) {
Dimension t = getSize();
g.setColor(Color.red);
g.fillRect(1,1, t.width - 2, 2);
g.fillRect(1, t.height - 3, t.width - 2, 2);
}
}
public class C21ExemploCG3 {
public static void main(String[] args) {
Frame janela = new Frame();
janela.add(new Botao("Novo botao"));
janela.pack();
janela.setVisible(true);
}
}
Este programa ilustra o cuidado que se deve ter ao interferir no comportamento especificado para as classes já existentes. Quando a janela principal é tornada visı́vel o novo
botão aparece com as linhas vermelhas nele desenhadas. As novas linhas vermelhas, entretanto, desaparecem caso o botão seja clicado. Isto ocorre porque quando o botão é clicado
ou liberado os métodos que respondem a estes eventos invocam o método paint da classe
Button. Para que as novas linhas vermelhas continuem aparecendo deve-se registrar um
tratador de eventos do mouse para os componentes da classe Botao e através dele chamar
o método repaint, de modo semelhante ao realizado no Exemplo 21.2. 21.5 O contexto gráfico
21.5.6
7
Propagando as ações de desenho
Os componentes de uma interface gráfica são chamados de componentes pesados se possuem
acesso direto aos recursos controlados pelo gerente de janelas, ou de componentes leves se
têm acesso a esses recursos através de outros componentes. Os contêineres, por exemplo,
são componentes pesados.
Quando um componente pesado executa seu método paint o ambiente de execução
invoca o método paint para todos os membros deste componente. Assim, quando se
estende um contêiner e sobrescreve-se o seu método paint, deve-se codificar uma chamada
ao método paint da classe ancestral:
super.paint(g)
Essa chamada ao método ancestral fará com que os membros do objeto que estende o
contêiner também sejam desenhados.
21.5.7
O contexto gráfico com componentes swing
O pacote swing possui componentes cuja estrutura é mais complexa que a dos componentes
do pacote awt, permitindo o desenho de bordas e a especificação de transparência, por
exemplo. Esta complexidade adicional faz com que o desenho destes componentes se torne
mais complexo. Uma das diferenças é que o método paint de um componente swing realiza
o desenho do componente em etapas, invocando os seguintes métodos:
protected void paintComponent(Graphics g). Usado para desenhar o componente.
protected void paintBorder(Graphics g). Usado para desenhar as bordas do componente.
protected void paintChildren(Graphics g). Usado para desenhar os componentes
que dependem do componente sendo desenhado.
Para sobrescrever as operações de desenho de uma classe do pacote swing devemos
sobrescrever o método paintComponent (e não o método paint). Normalmente as ações
relacionadas ao desenho das bordas e à propagação do desenho para outros componentes
não precisam ser modificadas. A solicitação de desenho pelos programas aplicativos deve
continuar sendo feita através do método repaint1 .
Os componentes swing também permitem o controle e a uniformização de sua aparência
através de objetos controladores da interface do usuário (UI delegates). Esses objetos
realizam muito do desenho do componente e o ambiente de execução cuida para que seus
métodos de desenho sejam chamados sempre que necessário.
1
Os componentes swing podem realizar solicitações de desenho sı́ncronas, através do método
paintImmediately, que não são discutidas aqui.
8
Interfaces Gráficas
Quando se estende componentes swing que implementam o controle da aparência
através de objetos controladores (como é o caso de JPanel), o método paintComponent da
classe ancestral deve ser invocado:
super.paintComponent(g)
Esta chamada garante que a aparência da interface continuará conforme definida no
ambiente de execução.
Download

O contexto gráfico