Changer les Forms par défaut VSTO - Outlook

by Nicolas Calvi 30. juin 2008 17:26
Il est possible avec VSTO de surcharger les formulaires par défauts d'Outlook, comme le formulaire d'ajout d'un contact par exemple. Pour cela il y a deux méthodes, l'un avec l'assistant (via Form Region) de Visual Studio, l'autre avec du code en interceptant l'ouverture de celle-ci. Dans le premier cas on surcharge ou on remplace définitivement un formulaire, mais si l'on veut par exemple ajouter un second répertoire contact dans Outlook et qu'en fonction du répertoire, afficher un formulaire différent, comme le formulaire par défaut pour le premier répertoire et un formulaire personnalisé sur le second, et bien avec la première méthode ce n'est pas possible.
Vient alors la seconde méthode qui consiste à surcharger le contrôleur d'instanciation (en fait une liste) des formulaires, nommé Inspectors sur l'objet d'application. Pour information un Inspector est un modèle de formulaire affichable dans Outlook pour la gestion d'un type d'objet précis (Appointement, Contact, ...) Quand, par exemple, on ajoute un nouveau contact, un nouvel Inspector est créé, dans le cas d'un contact c'est l'Inspector de type ContactItem qui est instancié. Si l'on veut donc substituer ce formulaire par notre propre WinForm, il faut d'abord s'abonner sur l'événement NewInspector du contrôleur pour qu'il nous informe de la création d'un nouvel Inspector.
public class ThisAddIn
{
  private Outlook.Inspectors m_oInspectors = null;

  private void ThisAddIn_Startup(object sender, System.EventArgs e)
  {
    this.m_oInspectors = this.Application.Inspectors;
    this.m_oInspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(this.Inspectors_NewInspector);
  }

  private void Inspectors_NewInspector(Outlook.Inspector p_oInspector)
  {
  }
}
Une fois abonné on peut donc intercepter les ouvertures et les empêcher en s'abonnant sur l'événement Open du nouvel Inspector. Dans cet événement on empêchera son ouverture avec la propriété Cancel de l'argument et ensuite on ouvrira notre propre WinForm.
private void Inspectors_NewInspector(Outlook.Inspector p_oInspector)
{
  // On vérifie que c'est le bon objet que l'on veut intercepter 
  if ( oItem is Outlook.ContactItem )
  {
    // On s'abonne pour pouvoir empêcher l'ouverture
    ((Outlook.ContactItem)oItem).Open += new Microsoft.Office.Interop.Outlook.ItemEvents_10_OpenEventHandler(InspectorItem_Open);

    // Affichage de notre propore WinForm
    using ( MaForm oForm = new MaForm )
    {
      oForm.ShowModal();
    }
  }
}

// Fonction qui va empêcher l'Inspector Contact de s'ouvrir
private void InspectorItem_Open(ref bool Cancel)
{
  Cancel = true;
}
Nous pouvons aussi effectuer cette opération seulement sur certaine actions, comme pour l'ajout d'un contact sur un répertoire que nous avons créé. Pour cela il suffit de vérifier si nous somme bien sur le bon répertoire et de traiter l'action si c'est le cas.
private void Inspectors_NewInspector(Outlook.Inspector p_oInspector)
{
  // On vérifie que c'est le bon objet que l'on veut intercepter 
  // Si c'est le bon on vérifie le nom du répertoire, si il est bon on substitue, sinon on laisse le formulaire par défaut
  if ( oItem is Outlook.ContactItem && this.Application.ActiveExplorer().CurrentFolder.Name == "Mon répertoire contact" )
  {
    // On s'abonne pour pouvoir empêcher l'ouverture
    ((Outlook.ContactItem)oItem).Open += new Microsoft.Office.Interop.Outlook.ItemEvents_10_OpenEventHandler(InspectorItem_Open);

    // Affichage de notre propore WinForm
    using ( MaForm oForm = new MaForm )
    {
      oForm.ShowModal();
    }
  }
}

// Fonction qui va empêcher l'Inspector Contact de s'ouvrir
private void InspectorItem_Open(ref bool Cancel)
{
  Cancel = true;
}
Voilà comment gérer de façon précise l'ouverture des formulaires sous Outlook.

Astuce pour les événements COM en VSTO

by Nicolas Calvi 18. juin 2008 22:54

Si vous développez sous VSTO, il y a une petite chose à savoir qui peut éviter bien des heures de prise de tête. En effet si on se branche sur un événement provenant d'un objet obtenu par la couche d'Interop Office, il faut systématiquement garder en référence membre l'objet sur lequel vous vous êtes branché.

Imaginons la situation suivante, la création d'un bouton sur une Toolbar, il est tentant comme en Winform de créer une variable locale pour la création du bouton et de lui affecter l'événement dans la foulé, comme le code ci-dessous :

private void CreerToolbarEtBouton()
{
  // Création de la barre
  Office.CommandBar oBar = null;

  oBar  = this.Application.ActiveExplorer().CommandBars.Add(MTSInterface._CustomMenuBarName, Office.MsoBarPosition.msoBarTop, Missing.Value, true);
  oBar .Visible = true;

  // Création bouton
  Office.CommandBarButton oButton = null;

  oButton = (Office.CommandBarButton) oBar .Controls.Add(Office.MsoControlType.msoControlButton, Missing.Value, Missing.Value, Missing.Value, Missing.Value);
  oButton.Style = Office.MsoButtonStyle.msoButtonCaption;
  oButton.Caption = "Mon bouton";
  oButton.Visible = true;
  oButton.Click += new Office._CommandBarButtonEvents_ClickEventHandler(this.ButtonOptionsEvent);
}

Jusque là rien de bien méchant, cependant le problème avec l'Interop c'est qu'à la fin de la vie d'un objet .Net et ce même si il existe encore du coté COM, tous les abonnements à des événements fait depuis la partie .Net sont supprimés. Du coup dans le cadre de notre bouton, il ne réagira pas au Click.

Vous l'aurez donc compris, pour pouvoir conserver les abonnements il faut systématiquement conserver une référence à l'objet COM dans la partie .Net, la plupart du temps mettre cette référence dans une variable membre suffit amplement. Il faut donc déplacer la déclaration de notre oBouton au niveau de la classe pour résoudre le problème.

J'attire aussi l'attention sur une subtilité au niveau de l'objet Inspectors qui est déclaré au niveau de l'objet Application qui se trouve dans l'Addin. Prenons cet exemple :

this.Application.Inspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(this.Inspectors_NewInspector);

Dans cette exemple on peut penser à juste titre qu'il n'y aura pas de problème, car l'événement est bien rattaché à une variable toujours disponible du coté .Net, mais en réalité il n'en est rien. Le désabonnement se fera comme sur l'exemple de notre bouton déclaré localement. Il faut donc extraire l'Inspectors en variable membre pour contourner le problème. Il faut donc déclarer une variable membre et écrire ceci :

private Outlook.Inspectors m_oInspectors;

// ...
// Plus loin dans le code

this.m_oInspectors = this.Application.Inspectors;
this.m_oInspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(this.Inspectors_NewInspector);

Sachant cela, vous n'aurez plus de surprise avec la gestion des événements qui viennent de l'Interop.

Traitement fenêtre d'attente modale sans Thread

by Nicolas Calvi 17. juin 2008 13:08
Parfois on aimerait bien afficher une fenêtre d'attente pendant un long traitement et que celle-ci soit affichée dans le contexte de notre application, le plus souvent en mode modale. Cependant si on exécute ce code :
using ( MaFormAttente oForm = new MaFormAttente() )
{
  // On affiche la fenêtre d'attente
  oForm.Show();
  // On effectue le traitement qui justifie la mise en attente
  MaFonctionDeTraitement();
}
La fenêtre d'attente va bien s'afficher mais elle n'est pas liée à la Form d'avant, elle n'est pas modale. Cela veut dire que l'utilisateur peut cliquer ailleurs et ainsi masquer la fenêtre d'attente. Ceci n'est pas très ergonomique. Si on affiche la fenêtre d'attente en mode modale :
using ( MaFormAttente oForm = new MaFormAttente() )
{
  // On affiche la fenêtre d'attente
  oForm.ShowModal(this);
  // On effectue le traitement qui justifie la mise en attente 
  MaFonctionDeTraitement(); 
}
Cela pose problème car l'exécution va s'arrêter sur le ShowModal et on doit attendre la fermeture de la fenêtre pour reprendre le traitement, or ce n'est pas ce que nous voulons. Ce qui serait intéressant, c'est de pouvoir invoquer le ShowModal et faire exécuter le contenu de MaFonctionDeTraitement dans celle-ci. Dans ce cas nous aurions bien la fenêtre d'attente en mode modale et l'exécution du traitement, tout cela sans utiliser de Thread.

La solution que je vous propose est de créer une méthode anonyme et l'exécuter dans la fenêtre modale, à la fin de l'exécution nous pourrions récupérer un résultat de la méthode anonyme. Voici donc comment faire :

Première étape : Créer un délégué pour l'exécution

J'ai choisi de créer un délégué afin d'envoyer le traitement à la fenêtre modale. Ce délégué permet d'avoir une valeur retour à la fin de l'exécution de la tâche. On le type en Object comme ça on peut renvoyer tout type d'objet.

public delegate object MonDelegueModal();

Deuxième étape : Créer une fenêtre d'attente

Il faut maintenant définir la fenêtre modale qui va accueillir le traitement et mettre en attente l'utilisateur. Pour cela, nous ajoutons une Form à notre projet. Dans cette Form nous allons définir deux variables membres, une pour stocker la référence de la méthode anonyme à exécuter et une autre pour stocker le retour de la méthode. Nous allons aussi redéfinir le constructeur afin de pouvoir passer la méthode anonyme et ajouter un accesseur pour récupérer le retour de la méthode anonyme. Ensuite sur l'événement Shown nous allons invoquer notre traitement, ce qui laisse le temps à la fenêtre de s'instancier. Voici le code correspondant :

public class MaFormAttente: Form
{
  private object m_oResultat;
  private MonDelegueModal m_oMonTraitement;

  public MaFormAttente(MonDelegueModal p_oTraitement)
  {
    this.m_oResultat = null;
    this.m_oMonTraitement = p_oTraitement;
  }

  private void MaFormAttente_Shown(object sender, EventArgs e)
  {
    this.m_oResultat = this.m_oMonTraitemen();
    this.Close();
  }

  public object Resultat
  {
    get { return(this.m_oResultat); }
  }
}
Ainsi la fenêtre va pouvoir s'ouvrir en modale et lancer l'exécution de notre méthode anonyme. Une fois le traitement terminée, la fenêtre se ferme et on récupère le résultat avec l'accesseur Resultat.

Troisième étape : Appel de notre fenêtre avec une méthode anonyme

Il ne reste plus qu'a appeler notre fenêtre dans notre code. Pour cela il suffit de définir une méthode anonyme sur le modèle MonDelegueModal, d'afficher la fenêtre d'attente et de récupérer le retour.

string sTexte = "Appel méthode anonyme en modale";

// Création de la méthode anonyme
MonDelegueModal oTraitement = new MonDelegueModal(delegate
{
  MessageBox.Show(sTexte);
  return ("Terminée");
});

// Affichage de la fenêtre d'attente qui va exécuter la méthode anonyme
using ( MaFormAttente oWait = new MaFormAttente(oTraitement) )
{
  // Ouverture modale de la fenêtre
  oWait.ShowDialog(this);
  
  // Récupération du retour du traitement
  MessageBox.Show((string)oWait.Resultat);
}
Voilà ! Votre traitement est bien exécuté et une fenêtre d'attente modale est affichée. N'hésitez pas à me poser des questions.

XNA Game Studio 3.0 CTP

by Nicolas Calvi 17. juin 2008 10:57

Bonjour,

L'équipe XNA vient de rendre disponible une version "Community Technical Preview" de la version 3.0 de ce framework. Dans les changements apportés par cette version on trouve notamment :

  • Le support du framework 3.5.
  • L'intégration d'XNA dans Visual Studio 2008 (et seulement VS2008)
  • Le support du developpement de jeux sur Zune.
  • Le content pipeline pourra accepter directement les fichiers son (wav, wma, mp3)
  • Support pour Direct3D des Shaders Matériel grâce aux fichiers .fbx

La release finale devrait sortir normalement courant de l'été 2008. Vous pouvez trouver la CTP ici.

Bienvenue sur le Black Blog

by Nicolas Calvi 17. juin 2008 10:45

Voilà c'est fait, je me suis lancé dans l'aventure du blog à orientation technique. Je ne prétend pas apporter des choses nouvelles, mais je vais plutôt essayer de transmettre les solutions aux problèmes que j'ai rencontrés. En effet, bien souvent quand je rencontre un problème lors d'un développement, je ne trouve pas toujours sur internet une explication claire. Je vais donc m'efforcer de vous donner une solution claire et précise sur ces petits problèmes de programmation du quotidien.

Je ferai aussi une remontée des informations sur les releases et autres produits disponibles sur la toile que je suis. Bien sûr je ne serai pas le premier à le faire remonter, mais bon je serai une partie du relai. De temps en temps je ferai aussi des focus sur des blogs que j'apprécie ou des articles que je trouve intéressants, le but étant de vous faire partager les informations que je juge utiles. 

J'espère que ce blog apportera un petit gravillon à la blogosphère et que ceux qui me liront pourront en retirer des informations pertinentes. 


Autres

MVP

Hardware Interaction Design & Developpement

Posts récents