Java Lendo dados do Excel via DDE no Java

Aprenda como ler dados do Excel via DDE no Java, utilizando a biblioteca JDDE da Pretty Tools.

Wladimilson M. Nascimento 20 de janeiro de 2021

Todos os nossos cursos possuem suporte e os instrutores sempre procuram auxiliar os alunos, mesmo quando a dúvida não é relacionada ao conteúdo do curso (claro que isso é feito na medida do possível). Esses dias um aluno perguntou como poderia trabalhar com DDE no Java. O DDE é uma tecnologia de comunicação entre programas considerada legada, mas que ainda é muito utilizada em certos ambientes.

Por ser algo legado, este tópico não se encaixa em nenhum dos cursos, mas para auxiliar o aluno, estudei sobre a tecnologia e vou abordá-la nesta artigo para que possa auxiliar outros que também necessitam fazer uso da mesma.

Java - Fundamentos
Curso de Java - Fundamentos
CONHEÇA O CURSO

O que é DDE?

Dynamic Data Exchange, também conhecido como DDE, é uma tecnologia de comunicação de aplicações criada pela Microsoft em 1987. O objetivo desta tecnologia é permitir que aplicações do Windows compartilhem dados dinâmicos.

Por exemplo, o valor de uma célula do Excel pode ser vinculado a um valor de outra aplicação. Quando a aplicação alterar este valor, a célula da planilha também é modificada.

O inverso também é possível, vincular uma aplicação à uma célula do Excel. Quando a célula for modificada, a aplicação será notificada e poderá ler o novo dado dela.

Apesar de ainda ser funcional, atualmente o DDE é considerado um recurso legado, geralmente sendo substituído por tecnologias mais atuais, como OLE (Object Linking and Embedding) e COM (Component Object Model). Mas devido a sua simplicidade e facilidade, ainda é muito utilizado para criar planilhas dinâmicas do Excel.

Criando um cliente DDE em Java

Para compreender a simplicidade do DDE, vamos ver um exemplo simples de leitura de dados de uma planilha do Excel. Para isso, será necessário fazer uso da biblioteca JDDE da Pretty Tools.

Antes de criar um projeto é importante ressaltar que esta biblioteca não é disponibilizada via Maven. Para utilizá-la é necessário adicionar manualmente o arquivo jar no classpath ou adicioná-la em um repositório local. Também é importante que seja adicionado ao PATH, as dlls fornecidas pela biblioteca.

Ao fazer este processo, será possível ler os dados de uma planilha com o código abaixo:

import com.pretty_tools.dde.DDEException;
import com.pretty_tools.dde.DDEMLException;
import com.pretty_tools.dde.client.DDEClientConversation;

public class App 
{
    public static void main( String[] args )
    {
        try
        {
            final DDEClientConversation conversation = new DDEClientConversation();

            conversation.connect("Excel", "Planilha1");
            try
            {
                System.out.println("A1: " + conversation.request("R1C1"));
                conversation.execute("[close()]");
            }
            finally
            {
                conversation.disconnect();
            }
        }
        catch (DDEMLException e)
        {
            System.out.println("DDEMLException: 0x" + Integer.toHexString(e.getErrorCode())
                            + " " + e.getMessage());
        }
        catch (DDEException e)
        {
            System.out.println("DDEException: " + e.getMessage());
        }
        catch (Exception e)
        {
            System.out.println("Exception: " + e.getMessage());
        }
    }
}

Note que inicialmente é indicado que a conexão será realizada com o arquivo do Excel aberto e será lido os dados da planilha nomeada como “Planilha1”.

Caso houver mais de um arquivo do Excel aberto, o arquivo que será lido deve ser indicado:

conversation.connect("Excel", "C:\Planilha.xlsx");

Neste caso, será lido os dados da primeira planilha do arquivo, mas é possível especificar outra:

conversation.connect("Excel", "C:\\[Planilha.xlsx]Planilha2");

Com a conexão realizada, solicitamos os dados da primeira linha e primeira coluna:

System.out.println("Célula A1: " + conversation.request("R1C1"));

Note que a solicitação segue o padrão “Row1 Column 1”. Assim, caso deseje ler todos os dados de uma coluna, basta especificar apenas o seu número:

System.out.println("Coluna 1: " + conversation.request("C1"));

Para escrever algo em uma célula, pode ser utilizado o método poke:

conversation.poke("R1C1", "Nova informação!");

Após ler ou escrever no arquivo, a conexão precisa ser fechada executando o comando close():

conversation.execute("[close()]");

Evitando assim qualquer problema.

Criando um ouvinte DDE em Java

Uma área que ainda faz muito uso do DDE são instituições financeiras que operam em bolsa de valores. Neste ambiente, segundos podem significar milhões de reais, então ter os dados o quanto antes faz muita diferença. Desta forma, com o DDE “traders” conseguem criar planilhas dinâmicas, para sempre ter dados atualizados e podem tomar as decisões mais acertivas.

Neste cenário, com as células de uma planilha podendo mudar a todo momento, podemos criar um ouvinte que será informado quando os dados forem alterados:

import com.pretty_tools.dde.DDEException;
import com.pretty_tools.dde.client.DDEClientConversation;
import com.pretty_tools.dde.client.DDEClientEventListener;
import com.pretty_tools.dde.DDEMLException;

public class App 
{
    public static void main( String[] args )
    {
        try
        {
            final DDEClientConversation conversation = new DDEClientConversation();

            conversation.setEventListener(new DDEClientEventListener()
            {
                public void onDisconnect()
                {
                    System.out.println("onDisconnect()");
                }

                public void onItemChanged(String topic, String item, String data)
                {
                    System.out.println("onItemChanged(" + topic + "," + item + "," + data.trim() + ")");
                }
            });

            System.out.println("Connecting...");
            conversation.connect("Excel", "Planilha1");
            try
            {
                conversation.startAdvice("R1C1");

                System.out.println("Precione qualquer tecla para finalizar!");
                System.in.read();

                conversation.stopAdvice("R1C1");
            }
            finally
            {
                conversation.disconnect();
            }
        }
        catch (DDEMLException e)
        {
            System.out.println("DDEMLException: 0x" + Integer.toHexString(e.getErrorCode())
                            + " " + e.getMessage());
            e.printStackTrace();
        }
        catch (DDEException e)
        {
            System.out.println("DDEException: " + e.getMessage());
        }
        catch (Exception e)
        {
            System.out.println("Exception: " + e.getMessage());
        }
    }
}

Note que a forma de conexão não muda, entretanto agora é definido um ouvinte:

conversation.setEventListener(new DDEClientEventListener()
{
    public void onDisconnect()
    {
        System.out.println("onDisconnect()");
    }

    public void onItemChanged(String topic, String item, String data)
    {
        System.out.println("onItemChanged(" + topic + "," + item + "," + data.trim() + ")");
    }
});

Que sempre será chamado quando a célula da linha 1 e coluna 1 for modificada:

 conversation.startAdvice("R1C1");

Criando o seu servidor DDE

Spring Framework - Templates com Thymeleaf
Curso de Spring Framework - Templates com Thymeleaf
CONHEÇA O CURSO

Caso deseje fornecer dados para outras aplicações via DDE, a biblioteca também permite a criação de um servidor. Para isso, basta estender a classe abstrata DDEServer:

import com.pretty_tools.dde.ClipboardFormat;
import com.pretty_tools.dde.DDEException;
import com.pretty_tools.dde.DDEMLException;
import com.pretty_tools.dde.server.DDEServer;

import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class App 
{
    private static final String SERVICE = "TreinaServer";
    private static final String TOPIC = "TopicoExemplo";
    private static final String MY_ADVISE_ITEM = "MeuAdvise";
    private static final String MY_REQUEST_ITEM = "MinhaRequisicao";
    private static final String MY_POKE_ITEM = "MeuPoke";
    private static final List<String> ITEMS = Arrays.asList(MY_ADVISE_ITEM, MY_REQUEST_ITEM, MY_POKE_ITEM);


    public static void main( String[] args )
    {
        try
        {
            // Evento para esperar pela disconexão
            final CountDownLatch eventStop = new CountDownLatch(1);
            final AtomicInteger num = new AtomicInteger(1);

            final DDEServer server = new DDEServer(SERVICE) {
                @Override
                protected boolean isTopicSupported(String topicName)
                {
                    return TOPIC.equalsIgnoreCase(topicName);
                }

                @Override
                protected boolean isItemSupported(String topic, String item, int uFmt)
                {
                    return isTopicSupported(topic)
                           && ITEMS.contains(item)
                           && (uFmt == ClipboardFormat.CF_TEXT.getNativeCode() || uFmt == ClipboardFormat.CF_UNICODETEXT.getNativeCode());
                }

                @Override
                protected boolean onExecute(String command)
                {
                    System.out.println("onExecute(" + command + ")");

                    if ("stop".equalsIgnoreCase(command))
                        eventStop.countDown();

                    return true;
                }

                @Override
                protected boolean onPoke(String topic, String item, String data)
                {
                    System.out.println("onPoke(" + topic + ", " + item + ", " + data + ")");

                    return true;
                }

                @Override
                protected boolean onPoke(String topic, String item, byte[] data, int uFmt)
                {
                    System.out.println("onPoke(" + topic + ", " + item + ", " + data + ", " + uFmt + ")");

                    return false; // Não suportado
                }

                @Override
                protected String onRequest(String topic, String item)
                {
                    System.out.println("onRequest(" + topic + ", " + item + ")");

                    return item + " data " + num;
                }

                @Override
                protected byte[] onRequest(String topic, String item, int uFmt)
                {
                    System.out.println("onPoke(" + topic + ", " + item + ", " + uFmt + ")");

                    return null; // Não suportado
                }
            };

            System.out.println("Iniciando...");
            server.start();

            final Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run()
                {
                    num.incrementAndGet();
                    try {
                        System.out.println("Notificando clientes");
                        server.notifyClients(TOPIC, MY_ADVISE_ITEM);
                    } catch (DDEException e) {
                        System.out.println("DDEClientException: " + e.getMessage());
                        cancel();
                    }
                }
            }, 1000L, 500L);

            System.out.println("Esperando para parar...");
            eventStop.await();
            System.out.println("Parando...");
            timer.cancel();
            server.stop();
            System.out.println("Finalizando");
        }
        catch (DDEMLException e)
        {
            System.out.println("DDEMLException: 0x" + Integer.toHexString(e.getErrorCode())
                            + " " + e.getMessage());
            e.printStackTrace();
        }
        catch (DDEException e)
        {
            System.out.println("DDEException: " + e.getMessage());
        }
        catch (Exception e)
        {
            System.out.println("Exception: " + e.getMessage());
        }
    }
}

Note que a classe DDEServer requer a implementação de métodos que serão executados quando o cliente solicitar um dado (onRequest) e quando ele requerer uma atualização (onPoke). A implementação de cada método irá depender das regras de negócio da aplicação.

O servidor acima pode ser testado utilizando os exemplos anteriores, bastando informar os dados de conexão dele:

conversation.connect("TreinaServer", "TopicoExemplo");

Mesmo ainda estando funcional, o DDE só deve ser utilizado quando não for fornecida outras formas de comunicação. Atualmente existem várias opções de comunicação entre aplicações mais seguras que o DDE.

Por hoje é só. Até a próxima!

Deixe seu comentário

Conheça o autor desse artigo

  • Foto Autor Wladimilson M. Nascimento
    Wladimilson M. Nascimento

    Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.

    Posts desse Autor

Artigos relacionados