TableModel e Threads - problemas com o Swing

32 respostas
TedLoprao

Acho q a maioria aqui sabe do esquema single thread do Swing, então vejam o meu problema.
Estou utilizando threads para fazer com que o swing não pare de desenhar a tela. Entretanto, meu TableModel recebe um resultado de uma query que é um resultado sob demanda, ou seja, o getValue efetivamente busca o resultado em um buffer no application server. O problema é que quando esse resultado não está no buffer é feita mais uma parte da consulta (a consulta é em lotes) e essa consulta pode ser lenta. Isso faz com que, por exemplo, se eu sair da tela e voltar ele não conseguirá repintar a tela e ficará aquele bloco cinza. Fiz alguns testes, mas nenhum foi realmente eficaz.
Alguém já passou por um problema parecido???

Vallew

32 Respostas

brlima

Pelo que entendi, vc criou uma thread pra repintar a tela separado de tudo, neh ?
Acho que o buraco nao eh na hora de redesenhar, e sim na hora de buscar o valor ( getValue() )…
Ja tentou fazer o getValue() em outra thread() ??? Talvez ai ele consiga repintar enquanto espera o resultado…

TedLoprao

E ai brlima!!! Valeu a resposta!!

Mas veja bem, o getValueAt só é chamado quando necessário…
Por exemplo, se vc criar uma JTable e jogar, por exemplo, 1000 linhas para o seu model. Colocar essa JTable em um JScrollPane e aparecerem apenas 10 linhas, o getValueAt do model será chamado apenas 10 vezes.
E assim funciona o esquema de carga sobre demanda, quando o getValueAt é chamado pelo JTable é feita a carga, e somente ai!

Dessa maneira o JTable só chamará o getValueAt realmente quando estiver sendo pintado…

A única coisa que me ocorreu foi tentar fazer um load prévio de um certo número de dados, mas mesmo assim não é uma solução ideal.

Por enquanto to aqui me quebrando.

brlima

Hmmm… Disso eu sabia…r.s… :roll:
Mas achei que vc queria buscar direto no servidor toda vez que ele desse um getValueAt().

Pq nao faz assim: separa o cara que busca no servidor. Toda vez que ele der um getValueAt() verifica o numero da linha que ele ta chamando, e se for mais que o numero de linhas que vc trouxe, busca mais dados. Acho que deve ser o jeito. Guardar de pouco em pouco num “buffer” interno do modelo. Dai, a cada 50* linhas ele busca mais no Servidor… Ou ateh mesmo bota um timer de 10 segundos… dai a cada 10 segundos vc busca de novo os dados…

Eu implementei algo parecido numa JTable: qdo ele chega na ultima linha, verifica se ainda tem mais coisa pra trazer do banco: ele busca de 50 em 50… e conforme vc vai baixando o scroll ( indo pra linha debaixo ) ele vai trazendo mais se necessario…

Será ue estamos sintonizados no mesmo canal? rssssssss…

TedLoprao

Essa é a idéia, ele só carrega o que precisa, ou seja, ele só carrega quando se rola o scroll. Porém, esse é o problema.
Seguinte, vou te dar uma idéia da estrutura que tenho aqui:
quando faço uma query, por exemplo de um produto, o cliente requisita para o JBoss (através de SessionBean) executar a query, o mesmo devolve um objeto List, bem esse list na verdade possui um buffer interno de n registros (definidos na hora da criação), essa list vai no TableModel. Assim, quando se está pintando a tela ele requisita pro model que pega da list, o list por sua vez verifica se esse index está no seu buffer ou não. Se não estiver ele possui uma referência (já criada no momento que a query é chamada pela primeira vez) para um Session Stateful, esse Stateful tbm possui um buffer, dessa maneira eu possuo um buffer a nível de cliente e um a nível de application server. Com essa referencia, o list do cliente pede blocos de n registros(tbm definidos na criação), até conseguir acessar o indice pediod pelo cliente.
Só que eu não sei quantos registros serão apresentados na tela, pois o JTable vai pedindo diretamente para o TableModel, e quem controla a quantidade é o JTable.

Acho q estamos começando a nos entender :lol: .
É uma situação bem complexa, será que eu tenho como saber quantos registros o JTable precisa, acho que não pois um model pode estar sendo usado por 2 JTable e cada um pedir uma quantidade diferente de registros!!!

Sei lá, vou continuar pensando, se te ocorrer algo me avisa!!!

Vallew

brlima

AQUem controla a quantidade é o TableModel: nao se esqueça do método getRowCount() :smiley:
Esse método retorna o numero de linhas que o modelo tem, para a jtable criar suas linhas.

Bem, uma ideia que posso te dar seria de vc botar um listener no scroll, para lançar um evento qdo atingir a ultima linha “visivel”. Ou seja, qdo ele rolar o scroll ateh embaixo. Dai, nesse evento, va verificar se essa ultima linha eh realmente a ultima linha do buffer, e se nao tem mais nada pra carregar. Se tiver, vai la no server, carrega tudo, volta pro client, carrega MAIS no modelo, e lança que o modelo foi alterado.

Nesse ultimo caso, nao precisa nem mexer no getRowCount. Deixa como esta, pq vai acontece assim: Situação:

Cliente requisita todos os pedidos dele numa JTable:
(FIND): Resultado total: 200 ( mas vamos carregar de 50 em 50 )
carrega o list com os 50 primeiros. retorna o list
carrega o TableModel com o list.

Ok Cliente vendo resultado. Agora ele vai rolando o scroll pra baixo pra ver o pedido 54. Qdo ele atingi a linha 50:
carregaMaisDadosNoModel();{
list = vai no servidor, pega da linha 50 pra frente.
list tem alguma coisa? tem, mais 50…
retorna list dos 50 novos!

modelo = modelo + list.
dispara modelo alterado!
}

pronto. o scroll vai pra linha 51 e carrrega ateh o 100. o cliente pode continuar o scoroll na boa… E assim vai ateh o proximo bloco.

Será que isso funciona ai ??? :roll:

TedLoprao

O q acontece hoje realmente é isso q tu falou o list é responsável por busca mais linhas no server.

Pelo que sei o JTable calcula o scroll baseado no getRowCount, ou não é isso???

Dessa maneira, se tiver 200 registros eu precisos devolver 200 no getRowCount pois se devolver apenas 50 a rolagem não aparecerá ou aparecerá menor do q realmente é.

Mas tudo bem, eu não vou mexer no getRowCount então.

Agora no seu exemplo veja o problema, aparece os 50 e o cliente rola para aparecer até o 54, então se nesse momento eu buscar do server já acontecerá o problema (na verdade é mais ou menos assim que funciona hj), o tempo que estiver buscando no server o swing não poderá pintar a tela.

Foi isso??? Ou agora eu q não pesquei a sua idéia??? :slight_smile:

Vallew

brlima

Pegou sim.
Realmente, o tempo de busca ( demorado ) fica travado… O que vc pode fazer seria meter uma dialog informado que ta obtendo mais…
Fica meio complicado do cara continuar descendo e nao ter o que mostrar, nao concorda ?

Com relação ao scroll, ele mostra mesmo soh o que o TableModel tem, e nao o total…

Vc pode colocar um botao do lado dizendo “Mais dados…” :smiley: pra carregar mais na JTable… hehehehehhe

Nao sei se convem deixar a tela bonitinha sem nada… acho que o usuario vai achar que num tem mais nada pra trazer…

Ou faz que nem o Kazaa: vai buscando de 10 em 10 toda horas :smiley: em uma thread separada… hehehehehee… Ai o scroll vai aumentando ateh buscar tudo~…

Flw!

Luca

Olá

Threads - problemas com o Swing? Seus problemas acabaram! Use Foxtrot, an easy and powerful API to use threads with the JavaTM Foundation Classes (JFC/Swing).

[]s
Luca

TedLoprao

Pois é Luca, eu estava fazendo alguns testes do foxtrot, mas não cheguei a usá-lo para testar com esse problema…
Vou fazer um teste e aviso se funcionar, valeu!!!

Luca

Olá

Ted, pode estar certo que com foxtrot acabam as telas cinza enquanto algo é feito em outtra thread. Se tiver tempo estude a teoria dele, se não tiver tempo de entende-lo aprenda a usa-lo que é bem rápido.

[]s
Luca

TedLoprao

Não sei se eu estou fazendo algo errado, mas o Foxtrot não me ajudou no problema. Na verdade ele consegue pintar a tela, mas então a pintura se perde completamente, pintando partes fora do JInternalFrame.
O que eu fiz foi mais ou menos o seguinte:

  • o getValueAt() do meu TableModel ficou mais ou menos assim
public Object getValueAt(int rowIndex, int columnIndex) {
  if (data != null) {
    final Object obj = data.get(rowIndex);
    Object retorno = Worker.post(new Job() {
      public Object run() {
        //processamento pesado sobre o Obj
        return obj;
      }
    });
    return retorno;
  }
  return null;
}

Pelo que vi no tips & tricks é necessário cuidar nos modelos swing com o acesso de duas threads ao mesmo, mas não conseguik achar o problema!!!

Ahhh, antes que eu me esqueça, durante o paint ele retornou esse erro:

Foxtrot - Exception occurred during event dispatching:
java.lang.NullPointerException
	at javax.swing.SwingUtilities.computeIntersection(SwingUtilities.java:369)
	at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:404)
	at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(SystemEventQueueUtilities.java:117)
	at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:178)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:454)
	at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:201)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:145)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:137)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:100)

Vou continuar tentando aqui, se alguém (Luca???) já passou por esse problema e tiver alguma dica, valeu!

Luca

Olá

Rodrigo, não tenho aqui no escritório exemplos de uso do foxtrot. De noite em casa posso procurar. Mas parece que seu código está diferente do modelo deles. Confira com: http://foxtrot.sourceforge.net/docs/foxtrot.php

[]s
Luca

brlima

Eu dei uma lida nos baratos da homepage la ( le-se tutoriais ) e pelo que entendi, ele trabalha com uma Thread separada pra acessar os dados, :shock: é isso ?

Testei aqui alguns exemplos usando Threads mesmo, tratando tudo bonitinho, funfa bacana… Mas foi um teste basico: abre processo para buscar no banco, soh… durante a busca, deixei tudo congelado ( enabled false ) na tela… ou oderia colocar um jdialog… :roll:

Alguem sabe exeplicar a real vantagem de se trabalhar com esse foxtrote ai ? ao inves de ter Threads ?? :roll: :?: :?: :wink:

TedLoprao

Pelo q entendi, a vantagem é que vc trabalha de uma maneira sincrona, ou seja, no caso de usar threads a linha seguinte a chamada para thread pode ser executada antes, depois ou durante a execução da thread. O foxtrot te garante que o código da task ou job será executada, e apenas após a execução o código seguinte será executado.
Veja o ex.:

btn.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    Worker.post(new Job() {
      public Object run() {
        try {
          Thread.sleep(5000);
        } catch (InterruptedException e) {}
        System.out.println("primeiro Eu");
        return null;
      }
    });
    System.out.println("cheguei");
  }
});

Dessa maneira o ‘primeiro eu’ é impresso e depois é impresso o ‘cheguei’…
Se vc usasse uma thread assim:

btn.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    Thread t = new Thread(new Runnable() {
      public void run() {
         try {
          Thread.sleep(5000);
         } catch (InterruptedException e) {}
         System.out.println("primeiro Eu");
      }
    });
    t.start();
    System.out.println("cheguei");
  }
});

Aqui o cheguei será impresso antes.

No link que o Luca mandou tem uma lista de vantagens que a abordagem te tráz.

Entretanto, as vezes vc precisa de uma abordagem assincrona, ai vc usa threads…

Fallow

Luca

Olá

A principal vantagem é não deixar aquela tela cinza horrível (The GUI freeze problem) enquanto executa alguma função por baixo dos panos. A idéia começou com a classe SwingWorker. Veja:[list]How to Use Threads

SwingWorker Update Fixes Subtle Bug

Customize SwingWorker to improve Swing GUIs [/list]A API foxtrot dá uma modificada nos conceitos de SwingWorker. É preciso ler a documentação e comparar com os problemas de SwingWorker para perceber as vantagens.

[]s
Luca

brlima

Nao por isso Ted…
Trabalhando com Threads, vc pode dar um yield() no processo principal, deixando o processo em espera executar primeiro logo depois de dar um start() … Fiz um teste aqui e ele fez igual o exemplo que vc me mostrou.

Tentei usar join(), e nesse caso ele esperou a paralela terminar para voltar a principal ( processo normal da coisa… )

Acho q na verdade vc teria que ter processos de paint separado dos processos de ação na aplicação… Assim ele pintaria tudo idependemente de estar aguardando um processo disparado pelo evento de clicar no botao.

TedLoprao

Como ficou o seu código com o Yeld???

brlima

Vo posta aqui, pq testei numa aplicação minha… :smiley: Que faz consulta no banco e carrega uma list…

Runnable processo = new Runnable(){
              public void run(){
                  
                  System.out.println("Rodei...");
                   /* Processo do banco de dados demorado... */

              }
          };
          


         Thread t = new Thread(processo);
          t.start();

          /*try{
            t.join();
          }catch(InterruptedException ie){
              ie.printStackTrace();
          }*/

          Thread.yield();
          System.out.println("Iniciei");

Comentei o join(), mas se vc descomenta e comentar o yield, vera que ele espera o processo anterior terminar… o yield, como diz nos livros, libera o processador para um processo rodar. Entra na fila de novo pra rodar. :smiley:

TedLoprao

Hmmm, estranho!!!
Eu fiz o teste com o yeld, porém ele imprimiu o cheguei primeiro… :?

brlima

Bem, nao eh nada garantido pela VM :roll:
Só o join garente que primeiro roda o process, pra em seguida continuar… :?

Luca

Olá

Ted, aí vai um exemplo. Só que não tive tempo de procurar muito. Então vai nu e cru. Espero que entenda o modo de usar:
import foxtrot.Task;
import foxtrot.Worker;

   .  .  .  .  .  .  .  .

	private void btnGetPassword_ActionPerformed() {
		firePropertyChange(BUTTON, NOT_PRESSED, PRESSED);
		lblPassIcon.setVisible(false);

		Task task = new Task() {
			public Object run() throws InterruptedException {
				readPass();
				return null;
			}
		};
		try {
			Worker.post(task);
		} catch(Exception ignored) {
			firePropertyChange(BUTTON,PRESSED,NOT_PRESSED);
			DebugTrace.println(ignored);
	  }
	}
O Modelo é quase sempre o mesmo:
import foxtrot.Task;
import foxtrot.Worker;

   .  .  .  .  .  .  .  .

		 Task task = new Task() {
			 public Object run() throws InterruptedException {
				pegaUmObjeto().processaAlgumaCoisa();
				return null;
			}
		};
		try {
			Worker.post(task);
		} catch(Exception ignored) {
			DebugTrace.println(ignored);    // Imprime exceção
		}
	}

[]s
Luca

TedLoprao

é galera, não rolou!!!

Se meu tableModel tiver carga sob demanda vou ter que me acustumar com as telas cinzas…

Com o Foxtrot, a tela ficava totalmetne perdida e com threads o máximo que deu para fazer foi trazer as linhas em branco em preencher conforme as mesmas fossem buscadas. Mas o efeito foi muito estranho, acho q vou deixar com os freeze ao menos nessa parte do sistema.

Valeu a força Luca e brlima

Luca

Olá

Pena.

Mas com foxtrot rola. Um dia com mais calma dê uma outra tentada. Na aplicação em que usei tinha também TableModel. Pena que a aplicação seja muito complexa e é meio difícil separar exemplos.

[]s
Luca

brlima

Sinceramente, achei meio estranho esse negocio de separar o processo de ir buscar os dados celula a celula… Nao consegui imaginar a situação ainda… :roll: Só vendo mesmo…

Mas imaginei algo do tipo separando o processo de criar a linha no tablemodel. Dai, nao ficaria esse negocio de ter a linha em branco e ir jogando os valores… A linha iria aparecer qdo estivesse carrega jah…

Essa busca sob demanda dos valores acho q seria melhor fazer em outro lugar ou de outro jeito…

Nao tem como ?

TedLoprao

O problema é o seguinte, supomos q faça uma consulta de 3000 linhas de retorno. O rowcount vem através de um count dessas linhas, que é rápido. Entretanto se eu fizesse a query dos 3000 resultados isso ficaria muito lento, então a idéia foi a seguinte, buscar em blocos, ou seja, a primeira vez é buscado 200 linhas, quando o cliente requisitar mais (através da Scrollbar) será feita a busca. Mas veja bem que a scrollbar já tem rolagem pros 3000.

Vou fazer mais uns testes aqui, mas o estranho é que fiz um exemplo com o foxtrot simples, substitui a busca por um Thread.sleep() e não funcionou, a pintura da tela ficou muito estranha.

Luca

Olá

[size=“7”]<meiosemjeitodeperguntar>[/size]Você está usando rowset?[size=“7”]</meiosemjeitodeperguntar>[/size]

[]s
Luca

TedLoprao

Nop… Na verdade eu tenho uma classe que implementa List, dai meu tableModel trabalha diretamente com Lists…

Só q esse meu list faz a carga de acordo com o que vc pede (só que através de blocos)…

Hmmm, não se essa informação te ajuda???

Ahhh, só para complementar, o acesso aos dados é feito através do Hibernate, mas no meio disso tudo o List acessa um SessionBean para buscar os dados!!!

Luca

Olá

Na verdade rowsets não tem nada a ver com freeze GUIs. Mas se vc usasse jdbc direto para buscar pingadinho na base seria meio ruim.

[]s
Luca

TedLoprao

hahaha, isso tá parecendo um chat!!!

Bem, a idéia do cache é mais ou menos assim o List (vamos chamá-lo de ValueList) requisita linhas para um SessionBean Stateful. O sateful pede um bloco grande para o banco (supomos que seja 200 registros) e retorna um cache de, por exemplo, 100 para o ValueList. Quando o get do ValueList pedir o objeto 101, o mesmo ira acessar o stateful que retornará mais 100 direto do cache. Agora se o ValueList pedir o objeto 201, o stateful fará uma consulta ao banco trazendo do 200 ao 400 e retornará novamente do 200 até o 300 para o ValueList.
Não sei se consegui me explicar…

Segue abaixo um tableModel de exemplo que eu fiz para testar, mas o mesmo causa um loucura na pintura da tela:

class Model extends AbstractTableModel &#123;
        private String&#91;&#93;&#91;&#93; data = &#123; &#123; "T", "A" &#125;, &#123;
                "R", "S" &#125;, &#123;
                "L", "M" &#125;, &#123;
                "N", "O" &#125;, &#123;
                "T", "U" &#125;, &#123;
                "A", "B" &#125;, &#123;
                "T", "A" &#125;, &#123;
                "R", "S" &#125;, &#123;
                "L", "M" &#125;, &#123;
                "N", "O" &#125;, &#123;
                "T", "U" &#125;, &#123;
                "A", "B" &#125;, &#123;
                "T", "A" &#125;, &#123;
                "R", "S" &#125;, &#123;
                "L", "M" &#125;, &#123;
                "N", "O" &#125;, &#123;
                "T", "U" &#125;, &#123;
                "A", "B" &#125;, &#123;
                "T", "A" &#125;, &#123;
                "R", "S" &#125;, &#123;
                "L", "M" &#125;, &#123;
                "N", "O" &#125;, &#123;
                "T", "U" &#125;, &#123;
                "A", "B" &#125;, &#123;
                "T", "A" &#125;, &#123;
                "R", "S" &#125;, &#123;
                "L", "M" &#125;, &#123;
                "N", "O" &#125;, &#123;
                "T", "U" &#125;, &#123;
                "A", "B" &#125;, &#123;
                "T", "A" &#125;, &#123;
                "R", "S" &#125;, &#123;
                "L", "M" &#125;, &#123;
                "N", "O" &#125;, &#123;
                "T", "U" &#125;, &#123;
                "A", "B" &#125;, &#123;
                "T", "A" &#125;, &#123;
                "R", "S" &#125;, &#123;
                "L", "M" &#125;, &#123;
                "N", "O" &#125;, &#123;
                "T", "U" &#125;, &#123;
                "A", "B" &#125;, &#123;
                "T", "A" &#125;, &#123;
                "R", "S" &#125;, &#123;
                "L", "M" &#125;, &#123;
                "N", "O" &#125;, &#123;
                "T", "U" &#125;, &#123;
                "A", "B" &#125;
        &#125;;
        public int getColumnCount&#40;&#41; &#123;
            return 2;
        &#125;

        /* &#40;non-Javadoc&#41;
         * @see javax.swing.table.TableModel#getRowCount&#40;&#41;
         */
        public int getRowCount&#40;&#41; &#123;
            return data.length;
        &#125;

        /* &#40;non-Javadoc&#41;
         * @see javax.swing.table.TableModel#getValueAt&#40;int, int&#41;
         */
        public Object getValueAt&#40;int rowIndex, int columnIndex&#41; &#123;
            Task task = new Task&#40;&#41; &#123;
                public Object run&#40;&#41; throws InterruptedException &#123;
                    Thread.sleep&#40;500&#41;;
                    return null;
                &#125;
            &#125;;
            try &#123;
                Worker.post&#40;task&#41;;
            &#125; catch &#40;Exception ignored&#41; &#123;&#125;
            return data&#91;rowIndex&#93;&#91;columnIndex&#93;;
        &#125;

    &#125;

Veja que estou usando o sleep para simular a busca de dados.

Fallow

brlima

“Luca”:
Olá

Na verdade rowsets não tem nada a ver com freeze GUIs. Mas se vc usasse jdbc direto para buscar pingadinho na base seria meio ruim.

[]s
Luca

pq?

Luca

Olá

Bruno, antes de existirem os rowsets, a gente fazia uma consulta grande e o próprio driver ficava indo várias vezes ao banco paginando a consulta. Isto implicava em longa espera e o tempo todo com a conexão aberta. Agora com os rowsets se pode ir ao banco (tantas vezes qto o driver precisar), popular um arraylist e fechar a conexão. Para a tela vai o resultado do arraylist.

Bem, mas isto é papo para quem ainda usa JDBC (como infelizmente aqui).

[]s
Luca

brlima

:? :roll: Aqui tb…
O projeto ja eh um pouco antigo… entao…
Vou ver se acho alguns exemplos de rowset por ai… Eu montei um SQL pra ficar pegando de tanto em tanto no banco, mas acho q soh funfa no oracle… eh bonzinho, tah em produção ai ja algum tempo , mas …

Flw!

Criado 28 de maio de 2004
Ultima resposta 1 de jun. de 2004
Respostas 32
Participantes 3