quinta-feira, 25 de agosto de 2011

Trabalhando com Microsoft.Office.Interop.MSProject (Parte 2 [1/2] – Solução do Problema)

Olá!
No post anterior eu mostrei qual era o problema, e a partir de agora iremos solucioná-lo. Esta segunda parte também será dividida em duas partes, devido à complexidade. Nesta primeira parte mostraremos o básico, onde será possível realizar uma importação de uma estrutura hierárquica para nossa base de dados (ou qualquer outro lugar).

No sistema que desenvolvi temos os projetos e suas atividades. As atividades podem também ter outras atividades filhas de modo que temos infinitos níveis. Como todas as atividades tem a mesma informação, seja filha ou pai, foi criado então somente uma tabela no banco para guardar as informações destas atividades e também sua hierarquia. Para preservar a hierarquia na tabela foi feito um auto-relacionamento.
Para nosso exemplo, vamos denominar o nome da tabela: PROJETO . A tabela PROJETO possui as seguintes colunas: CD_PROJETO (PK), DS_PROJETO (nome da atividade), DT_INICIO, DT_FIM e CD_PROJETO_PAI (FK do auto-relacionamento).

O responsável por fazer a hierarquia é exatamente a coluna CD_PROJETO_PAI, onde a mesma armazena o código da atividade que é pai desta. Se a atividade é o projeto então esta coluna fica nula.
A figura abaixo mostra os dados na tabela que descrevemos e o resultado na forma hierárquica:


Inicialmente, a necessidade era de um plugin no MS Project que realizasse a importação das atividades para nosso sistema (para a tabela de projetos). Para criar um Plugin através do Visual Studio, com direito a instalador e tudo, acesse o link:  _ . No plugin, foi criado um botão no Menu do MS Project para iniciar a importação.

Quando você adiciona um projeto do tipo Project Add-In no Visual Studio, ele cria uma classe chama ThisAddIn ,  onde ao abrir você já encontra 2 métodos onde pode começar a trabalhar. O ThisAddIn_Startup  e o ThisAddIn_Shutdown. No ThisAddIn_Startup    você já pode começar a colocar o código fonte que quiser, para utilidade de seu plugin. Este método é chamado assim que o MS Project é aberto e por esta razão que foi colocado um botão para iniciar a importação, exatamente porque nem sempre que o MS Project é aberto, ele tem algum projeto (o arquivo do MS Project pode ser um novo por exemplo). Vamos então ao que interessa.

No método responsável pelo evento click do botão de importar, eu preciso saber como capturar através do código fonte o projeto e suas atividades que foram criados no MS Project. Se você colocar o this e ponto, irá ver o atributo Application e dentro deste existe alguns métodos, Propriedades, Eventos, etc. É interessante ressaltar que dentro deste atributo podemos realizar certos controles sobre a aplicação do MS Project. E para resolver algumas questões deste desafio, utilizei de alguns artifícios deste atributo.

No atributo Application  existe a propriedade chamada Projects, que contém uma coleção de projetos (uma coleção de itens do tipo Project). Certamente você irá trabalhar com apenas um projeto por vez, e com isto pode capturar o projeto em questão utilizando o seguinte código:

Project projeto = this.Application.Projects[0];

Caso haja necessidade de trabalhar com mais de um projeto, então pode controlar a coleção Projects com um foreach.

Pronto! Neste ponto temos nas mãos o projeto do MS Project que está aberto, no caso está sendo representado pela variável projeto, do tipo Project. Dos vários atributos, métodos, etc, o que vai nos interessar é a propriedade Tasks, onde esta é uma coleção de tarefas (do tipo Task). Estas tarefas são exatamente cada linha do projeto. O tipo Task contém todas as informações da linha: Nome do Projeto, ID, Datas de início e fim, percentual concluído, etc, e você pode utilizá-las da maneira que bem entende. No meu caso, utilizei de algumas colunas como exemplo o Nome e as datas para alimentar o meu sistema.
Para varrer as linhas podemos utilizar o foreach. Segue exemplo:

foreach (Task tarefa in projeto.Tasks)
{
      
}

Como havia dito, a variável tarefa contém todas as informações da linha que está sendo varrida. Com esta informação, precisamos saber que este foreach não varre todas as linhas do projeto. Porque? Por causa da hierarquia! Ou seja, dentro de cada tarefa, existem outras tarefas e estas outras tarefas também são coleções que devem ser varridas. Com isto, o projeto deve ser varrido utilizando o conceito de recursividade. Como fazer isto?

A variável tarefa (que é do tipo Task) possui todas as informações da linha. Uma destas informações é uma propriedade chamada OutlineChildren (do tipo Tasks) que também é uma coleção de tarefas. Esta coleção possui todas as tarefas no nível abaixo da tarefa que está sendo trabalhada. Utilizando o exemplo da figura anterior, como fazemos para capturar as informações da
tarefa SubAtiv. 1.2?  Rodando o código na mão, seria o seguinte:

//Este foreach irá apenas rodar uma vez,
//no caso teremos a atividade 'Meu Projeto'.
foreach (Task tarefa in projeto.Tasks)
{
    //Nesta linha estamos pegando os filhos
    //de 'Meu Projeto' e colocando na variável
    //subTarefas. No caso, terá as tarefas:
    //Atividade 1, Atividade 2 e Atividade 3.
    Tasks subTarefas = tarefa.OutlineChildren;

    //A atividade que queremos é a 'SubAtiv. 1.2'
    //que é filha da tarefa' Atividade 1'. Como
    //esta é a primeira, então podemos fazer o seguinte:
    Task tarefa1 = subTarefas[0];
    //tarefa1 contém todas as informações da tarefa 'Atividade 1'

    //Agora vamos colocar todos os filhos de tarefa1
    //em uma outra variável chamada tarefasFilhasAtv1
    Tasks tarefasFilhasAtv1 = tarefa1.OutlineChildren;

    //Como a tarefa 'Atividade 1' tem dois filhos
    //podemos separar o segundo em uma outra variável:
    Task tarefaSubAtiv12 = tarefasFilhasAtv1[1];

    //Pronto, neste momento a variável tarefaSubAtiv12
    //Contém todas as informações da tarefa 'SubAtiv. 1.2'
}

Porém, podemos facilmente ‘caminhar’ entre as tarefas utilizando recursividade. Podemos utilizar o seguinte exemplo:
private void VarrendoAtividades(Tasks tarefas)
{
     //Realizamos uma varredura sobre estas tarefas
     foreach (MSProject.Task tarefa in tarefas)
     {
          //Aqui utilizamos a tarefa para nossas necessidades
          //realizando por exemplo uma cópia da tarefa:
          String nome = tarefa.Name;
          DateTime dtInicio = Convert.ToDateTime(tarefa.Start);
          DateTime dtFim = Convert.ToDateTime(tarefa.Finish);
          //ETC

          //Logo após, chamo novamente o método VarrendoAtividades
          //verificando se há filhos para esta tarefa. Se sim, então
          //seus filhos são passados como tipo Tasks
          if (tarefa.OutlineChildren.Count > 0)
          {
               VarrendoAtividades(tarefa.OutlineChildren);
          }
    }
}

Com isto, temos nas mãos todas as atividades independente do nível, e a cada linha podemos simplesmente realizar uma ‘cópia’ de todas e após isto incluir todas as linhas no seu banco de Dados. No nosso caso, preferimos abrir uma sessão do banco de dados, colocar todos os comandos de insert em uma transação, e quando termina de varrer tudo, validamos a transação com todos os inserts e todas as tarefas são importadas para nossa base.
A base da solução foi mostrada neste post. No próximo, iremos avançar mais para concretização dos requisitos citados no primeiro post. Mostraremos mais detalhes sobre as classes utilizadas na construção deste Plugin.

Até mais.

Sócrates de Halley

quinta-feira, 4 de agosto de 2011

Trabalhando com Microsoft.Office.Interop.MSProject (Parte 1 – Demonstração do problema)

Olá!

Neste post, gostaria de compartilhar algo que sofri muito para fazer. Pesquisei muito sobre e não achei exemplos claros. Com isto, tive que desenvolver meu objetivo através do método de tentativa e erro! Irei então separar este post em duas partes. Na primeira parte, irei apenas demonstrar meu problema. Na segunda parte, mostro como foi resolvido.

A situação era a seguinte:
Lá na empresa temos uma aplicação que faz o gerenciamento de projetos, através de atividades. Estes projetos possuem N atividades filhas, assim como estas filhas possuem também N outras atividades filhas.. Ou seja, uma árvore, uma hierarquia de atividades.
Com a usabilidade, os clientes tiveram a necessidade de importar os projetos que gerenciavam através do Microsoft Office Project para nossa aplicação. Então, um amigo chamado José Roberto, desenvolveu um PLUGIN para o MS Project utilizando uma biblioteca chamada Microsoft.Office.Interop.MSProject . A função deste PLUGIN era, através da biblioteca e utilizando o C#, pegar cada atividade do MS Project e criar um registro desta atividade em nossa aplicação, respeitando a hierarquia.

Até então estava ok, pois o PLUGIN funcionava conforme foi desenvolvido e os clientes utilizavam normalmente.

Porém, surgiu uma nova necessidade. E com isto Eis o problema:
Os clientes solicitaram um MERGE entre o MS Project e nossa aplicação, onde o sistema teria de garantir que o MERGE iria atualizar somente as alterações mais recentes.
Lembra aquelas ferramentas que fazem merge de código fonte? WinMerge, Beyond Compare? Pronto! É algo semelhante! Porém, estas ferramentas trabalham com linhas e meu objetivo era um MERGE hierárquico..

Imaginemos que o seguinte projeto foi criado no MS Project:


Porém, este projeto vai sendo alterado na ferramenta que foi desenvolvida. E aqui surge a necessidade: quando o projeto e suas atividades são alterados, deseja-se comparar os dois projetos no sentido de verificar e atualizar os dois lados (MS Project e aplicação) sempre com a alteração mais recente.
Exemplo:




Na figura acima, temos na cor vermelha as alterações que foram feitas nas informações de uma atividade. Como exemplo, no MS Project temos a atividade Tarefa 1, e no Sistema o nome da atividade foi alterado para Tarefa primeira. O objetivo da implementação é identificar neste conflito qual a alteração maios recente e atualizar conforme seja. Se a alteração para a segunda atividade (Tarefa 1 no MS Project) mais recente foi no MS Project, então a atividade Tarefa 1 vai mudar o nome para Tarefa Primeira. Caso a alteração mais recente seja no Sistema, então o nome desta mesma atividade no sistema vai mudar para Tarefa 1. Daí a regra vai para todas as colunas.

O leitor deve ter percebido a complexidade do problema, pois se a estrutura (digo estrutura no sentido de a quantidade de atividade ser a mesma em ambos os lados, com os mesmos níveis da árvore) for a mesma em ambos os lados, fica muito fácil pois é só varrer a lista de maneira hierárquica (utilizando recursividade) e comparar linha a linha. Porém, e se eu apagar uma certa atividade no sistema? E se no MS Project eu resolver adicionar mais uma atividade em um certo ponto do projeto?

Resumindo, o merge tem que atender os seguintes requisitos:

- Comparar alteração de campo da atividade mais recente para modificar o campo mais antigo
- Verificar se a data da exclusão de uma atividade no MS Project é mais atual que a data de modificação desta mesma atividade no Sistema. Se sim, então exclui também no sistema. Se não, então restaura a atividade no MS Project.
- Verificar se a data da exclusão de uma atividade no Sistema é mais atual que a data de modificação desta mesma atividade no MS Project. Se sim, então exclui também no MS Project. Se não, então restaura a atividade no Sistema.
- Quando criar uma atividade no MS Project, criar também no Sistema, obedecendo a hierarquia.
- Quando criar uma atividade no Sistema, criar também no MS Project, obedecendo a hierarquia.
- Quando mover uma atividade no MS Project para uma outra atividade, fazer também a mudança no Sistema.
- Quando mover uma atividade no Sistema para uma outra atividade, fazer também a mudança no MS Project.
- Para cada requisito acima, mostrar na tela uma tela onde mostra cada situação. Exemplo: Se foi excluído do MS Project e é mais recente, então perguntar: Atividade “X” excluída do MS Project. Deseja excluir no sistema? Sim, Sim para Todos, Não e Não para Todos. Se usuário clicar em Sim para Todos, o sistema irá colocar sim para todas as situações acima. No caso da comparação de alteração de campo, mostra tela com todos os campos e informações, e mostrar qual foi a alteração.

A princípio parecem fácil os requisitos. Mas devemos lembrar que estamos trabalhando com hierarquia. Ou seja, no exemplo de uma atividade excluída pode ser que esta atividade seja Pai de N outras atividades. Na figura acima, imagine se faço a exclusão da atividade Tarefa 1. Quando faço a exclusão desta atividade, tudo que está abaixo também será excluído. Ou seja, as atividades Subtarefa 1 e Subtarefa 2 também serão excluídas.

Como será o merge desta situação?:


Meu objetivo neste post, é demonstrar não só a solução deste problema, mas também como utilizar a biblioteca Microsoft.Office.Interop.MSProject e suas classes para nos apoiar na solução. Nesta primeira parte fiz a explicação do problema. Ainda esta semana estarei colocando a solução. Qualquer dúvida ou sugestão não deixe de colocar comentários.

Até a próxima!

Sócrates de Halley

Retomada . . .

Olá!

Eu tinha a intenção de postar várias experiências minhas neste blog, com relação à desenvolvimento, mas o tempo é muito curto e acredito que também para os leitores, 24 horas em um dia é muito pouco..

Em fim.. irei tentar fazer com que seja frequente os posts. Todas as experiências sobre desenvolvimento e assuntos de tecnologia que eu achar viável e legal, eu postarei!

Abraço!
--
..

Sócrates de Halley

segunda-feira, 23 de agosto de 2010

quarta-feira, 4 de novembro de 2009

Eco Planet

Olá!
Com a intenção de Plantar árvores no mundo, o Google lançou um site de busca, com a mesma qualidade e tecnologia de busca da própria Google. Dependendo da quantidade de acessos feitos no site, 1 árvore é plantada. Verifiquem acessando o site!

O site é o http://www.eco4planet.com/pt/ , que por sinal é todo preto, o que significa até uma economia de energia (a economia é insignificante, mas se todos utilizarem este site em suas consultas a economia passa a ser maior em todo o mundo).

Quem puder, coloque em seu favoritos ou como primeira página em seu browser e ajude a plantar árvores no mundo!Não custa nada e o planeta agradece!

Sócrates de Halley

Begin.. {..

Olá!
Me chamo Sócrates, sou formado em Ciência da Computação pela FATEC/PE.
Atualmente trabalho como Analista de Sistemas na MV Sistemas e estou desenvolvendo com Flex e .NET para Web.
Irei neste Blog compartilhar algumas infomações e experiências no desenvolvimento de sistemas e também alguns tópicos sobre a área de Tecnologia e Informática em Geral.

Grande abraço.

Sócrates de Halley