Au fil des flows

27fév/121

Lancer une instance d’application une seule fois sous Windows en C#

Posted by Fabien Arcellier

Dans certain cas, par exemple quand l'application est liée à une base de donnée fixe, on ne veut pas que l'utilisateur puisse lancer plusieurs instances de l'application.

Il existe de nombreuses solutions possibles. L'une des plus simple est de s'appuyer sur le mécanisme de Mutex.

Qu'est ce qu'un mutex ?

Un mutex est une primitive qui permet de synchroniser des processus entre eux. Il s'agit d'un objet du Kernel Windows qui est encapsulé par le frameworks dotNet au travers de la classe Mutex.

Un mutex peut etre identifié par une clée au niveau de l'OS. C'est sur ce principe qu'on s'appuiera.

L'idée est simple. La première application qui se lance crée le mutex avec comme clef, le nom de l'application et se déclare ainsi propriétaire. Si une seconde application se lance, le mutex existera déjà. Dans ce cas, on affiche un message d'erreur.

Voici un exemple de code permettant de faire ça :

namespace Eurosonic.ExempleApplication
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Windows.Forms;
    using Eurosonic.ExempleApplication.Properties;
    using System.Threading;

    public static class Program
    {
        static Mutex mutex;

        /// <summary>
        /// Point d'entrée principal de l'application.
        /// </summary>
        [STAThread]
        public static void Main()
        {
            // Chargement du gestionnaire d'erreur standart
            string application_name = Path.GetFileName(Application.ExecutablePath);
            try
            {
                Program.mutex = Mutex.OpenExisting(application_name);
                MessageBox.Show(String.Format("L'application {0} est déjà lancée.", application_name), "Avertissement", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch
            {
                Program.mutex = new Mutex(true, application_name);
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form_MdiMain());
            }
        }
    }
}

J'ai stocké le mutex en static sinon, ce code ne fonctionnait pas. Je pense que mon application déréférençait le mutex juste après l'avoir réservé. Le garbage collector faisant le reste. Cependant je n'ai pas poussé les investigations plus en avants.

Si vous avez une explication plus convaincante, je suis preneur. N'hésitez pas à poster un commentaire.

Les limites

Cette solution pour protéger une ressource est loin d'être parfaite. Dans notre cas nous l'avons simplement jugé suffisante. Cependant si vous lancez une instance de l'application depuis un autre ordinateur sur le meme fichier ressource au travers du réseau par exemple, celui ci ne sera pas protégé. La limite tient au fait que l'OS ne peut protéger que ce qu'il contrôle.

Remplis sous: Non classé 1 commentaire
18jan/125

InternalsVisibleTo, votre meilleur ami pour tester les methodes internes

Posted by Fabien Arcellier

J'arrive avec 4 ans de retard. Jusqu'à présent, j'ai toujours pu en C# tester plus ou moins le code de mes projets.
Seulement, pour me simplifier la vie, je déclarai la plupart de mes classes public. En terme d'encapsulation, vous en conviendrez c'est très moyen.

Aujourd'hui, j'ai rencontré le cas différent. L'assembly possède des classes qui ne doit pas être publique. Cependant je doit tout de même les tester.

Pour résoudre ce problème, j'ai découvert l'attribut InternalVisibleTo. Il permet de rendre accessible les methodes internes d'un assembly à un autre assembly. Ca ressemble beaucoup au mot clée friend en C++, qui s'applique lui aux classes. Pourquoi 4 ans de retard ? Tout simplement car cet attribut existe depuis dotNet 2.0.

Comment l'employer ?

En C#, vous pouvez placer ce fichier dans AssemblyInfo.cs. Voila ce que ça donne pour le programme ImageAnalyzer. Je veux que les methodes internes soient accessibles à l'assembly ImageAnalyzerTests.

[assembly: InternalsVisibleTo("ImageAnalyzerTests")]

Mais si quelqu'un appelle son assembly ImageAnalyzerTests ?

Effectivement, il pourra employer les methodes internes. Ue petite astuce. Vous pouvez executer vos tests unitaires en mode DEBUG.

Quand vous compilez en release, l'ouverture des methodes internes ne sera pas faite. Vous pouvez utiliser votre propre directive de compilation mais ça c'est une autre histoire.

#if DEBUG
    [assembly: InternalsVisibleTo("ImageAnalyzerTests")]
#endif

Voici 2 captures d'écran montrant les 2 scénarios.

InternalsVisibleTo Debug
InternalsVisibleTo Release

L'assembly de tests compilé ne peut plus atteindre les methodes de ImageAnalyzer compilé en Release

Autre point, vous pouvez aussi signer votre assembly et en tenir compte dans la declaration InternalsVisibleTo. Je vous renvoie à la MSDN pour plus de détails.

Remplis sous: Non classé 5 Commentaires
9jan/1215

Donner le focus a un controle PictureBox

Posted by Fabien Arcellier

Je developpai un controle de type PictureBox permettant de sélectionner une zone sur une image. Ce controle doit être utilisable directement dans le designer de visual studio. Il ne doit pas necessiter d'implémenter du code en plus dans la form qui l'héberge pour fonctionner.

Parmi les exigences que je m'etais fixé, il y'avait celle ci :

  • Si l'utilisateur appuie sur Escape, le programme annule la sélection en cours

Au moment de réaliser ce scénario, j'ai eu une petite surprise. Le controle PictureBox dont j'hérite n'est pas un controle focusable. Consequence facheuse, un controle qui ne prend pas le focus ne peut intercepter d'evenement clavier, même quand la propriete KeyPreview de l'objet Form parent est active.

Avec Winform, soit dit en passant, ce n'est pas le seul controle à etre ainsi. Voici une liste tiré de la MSDN sur les controles qui ne peuvent prendre le focus :

  • Panel
  • GroupBox
  • ProgressBar
  • Splitter
  • Label
  • LinkLabel (sans link actif)

Premier réflexe, faire une recherche sur Google à ce sujet (avec les mots PictureBox Winform Focus). J'ai trouvé 2 approches :

  • utiliser un bouton flat à la place d'une PictureBox
  • intercepter les evenements claviers sur la Form et les dispatcher

Comme vous vous en doutez, la deuxième solution n'est pas valable pour résoudre le problème fixée. Pour la première, elle est trop abracadabrante pour sembler être la bonne manière.

Rendre un controle Focusable

J'ai donc creusé un peu plus et j'ai trouvé la propriété ControlStyles qui permet de configurer le comportement d'un controle.

Pour lui permettre de pouvoir obtenir le focus, il suffit de lui affecter la propriété Selectable dans son constructeur :

this.SetStyle(ControlStyles.Selectable, true);

Ca ne suffit pas, à présent, il faut donner au controle le moyen de récupérer le focus. Votre controle même si il peut prendre le focus n'a aucun moyen de le faire.

J'ai trouvé la solution (qui est on ne peut plus evidente à la réflexion) sur le forum de StackOverFlow. Il suffit de demander le focus sur l'appel à la méthode OnMouseDown. C'est à dire lors d'un clic de souris sur votre image.

Voici le code qui résume l'approche :

using System;
using System.Drawing;
using System.Windows.Forms;
 
class SelectablePictureBox : PictureBox {
  public SelectablePictureBox() {
    this.SetStyle(ControlStyles.Selectable, true);
  }
  protected override void OnMouseDown(MouseEventArgs e) {
    this.Focus();
    base.OnMouseDown(e);
  }
}
Remplis sous: Non classé 15 Commentaires