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

Nenhum comentário:

Postar um comentário