Lost in the matrix

C/C++/C#/Java, Multithreading

Aujourd'hui, je suis tombé sur un problème avec le contrôle PropertyGrid, car je voulais éditer les propriétés d'un objet ainsi que ses sous-objets. Toutefois, j'ai eu quelques soucis, car la PropertyGrid ne permet pas (de base) d'éditer les sous-objets et ceux-ci apparaissent grisés.

Voici mon code:

using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication
{
    public partial class PropertyGridTest : Form
    {
        public PropertyGridTest()
        {
            InitializeComponent();
            this.propertyGrid1.SelectedObject = new Car();
        }
    }

    public class Driver
    {
        public string Name { get; set; }
        public bool HasDriverLicence { get; set; }
    }

    public class Car
    {
        public Car()
        {
            this.Driver = new Driver()
            {
                Name = "Johannes Fetz",
                HasDriverLicence = false
            };
            this.Color = Color.Red;
            this.Kind = "Sedan";
        }

        public string Kind { get; set; }
        public Color Color { get; set; }
        public Driver Driver { get; set; }
    }
}
qui donne le résultat suivant:
Après quelques recherches, j'ai trouvé comment résoudre ce problème. En effet, il suffit d'indiquer à la PropertyGrid que "Driver" est un objet "Expandable". Pour cela, il faut ajouter un using :
using System.ComponentModel;
et un attribut au dessus de la classe "Driver" :
[TypeConverterAttribute(typeof(ExpandableObjectConverter))]
public class Driver
{
Ce qui donne le résultat suivant:

A la place de :

ConnectionInfo crConnectionInfo = new ConnectionInfo();
crConnectionInfo.Type = ConnectionInfoType.SQL;
crConnectionInfo.ServerName = "ODBCsource";
crConnectionInfo.DatabaseName = "DataBase";
crConnectionInfo.UserID = "Login";
crConnectionInfo.Password = "Password";
foreach (CrystalDecisions.CrystalReports.Engine.Table tmpTable in doc.Database.Tables)
{
     tmpTable.LogOnInfo.ConnectionInfo = crConnectionInfo;
     tmpTable.ApplyLogOnInfo(tmpTable.LogOnInfo);
     tmpTable.Location = tmpTable.Name;
}
Il suffit de faire :
doc.DataSourceConnections[0].SetConnection("ODBCsource", "DataBase", "Login", "Password");
doc.Refresh();
Où la variable "doc" est du type "ReportDocument".
Attention j'ai pris l'index 0 dans "DataSourceConnections" à titre d'exemple.
La valeur peut changer en fonction du modèle.

Voici une petite astuce dont je ne vois pas l'utilité immédiate, mais qui peut intéresser certain.

public int this[string val1, string val2]
{
    set
    {
       Console.WriteLine("{0} - {1}", val1, val2);
    }
}

Aujourd'hui, j'ai eu un problème lors de l'impression d'un rapport avec le composant Crystal Reports.
Lors de l'appel de la méthode "ReportDocument.PrintToPrinter" une étrange erreur
"Win32Exception : The operation completed successfully" est apparue. Après une longue recherche,
je me suis aperçu, que le problème était en réalité lié au pilote (Driver) de mon imprimante sous
Windows 7 64 bits. En effet, le programme fonctionne très bien sous Windows Vista 64 bits
avec la même imprimante. De plus, un de mes collègue à strictement le même problème
avec la même configuration. Cependant, le problème est particulièrement vicieux, car j'arrive à imprimer
certains documents à partir de mon poste, avec le Bloc Note de Windows par exemple.
Quelle péripétie!

Je viens de trouver dans mon code pourquoi ma fenêtre de pré-visualisation Crystal Report
était très lente. J'ai supprimé la ligne ci-dessous dans la méthode "InitializeComponent" de
mon formulaire (.Designer.cs) :

this.crystalReportViewer1.SelectionFormula = "";
Cela m'a permis de gagner neuf secondes à l'affichage.

using System.Reflection;

public sealed class AssemblyInfo
{
    #region Ctors

    static AssemblyInfo()
    {
    }

    private AssemblyInfo()
    {
        PortableExecutableKinds peKind;
        ImageFileMachine machine;

        Assembly.GetCallingAssembly().ManifestModule.GetPEKind(out peKind, out machine);
        this.Kind = peKind;
        this.Machine = machine;
    }

    #endregion

    #region Public Methods

    public bool Is(PortableExecutableKinds kind)
    {
        return ((this.Kind & kind) != 0);
    }

    public bool Is(ImageFileMachine machine)
    {
        return ((this.Machine & machine) != 0);
    }

    #endregion

    #region Properties

    public static AssemblyInfo Instance
    {
        get
        {
            return (AssemblyInfo._instance);
        }
    }

    public PortableExecutableKinds Kind { get; private set; }

    public ImageFileMachine Machine { get; private set; }

    #endregion

    #region Fields

    private static readonly AssemblyInfo _instance = new AssemblyInfo();

    #endregion
}
Exemple d'utilisation:
if (AssemblyInfo.Instance.Is(PortableExecutableKinds.Required32Bit))
{
}






/// <summary>
/// Différence entre deux dates en minute.
/// </summary>
/// <param name="start">Date de début</param>
/// <param name="end">Date de fin</param>
/// <returns>La différence en minute</returns>
public static int GetMinuteDiff(DateTime start, DateTime end)
{
   if (start > end)
      return (-((int)start.Subtract(end).TotalMinutes));
   return ((int)end.Subtract(start).TotalMinutes);
}

La première fois que l'on utilise le "CrystalReportViewer",
la fenêtre peut mettre plus d'une minute à se lancer.
Par conséquent j'ai cherché un patch pour résoudre ce problème.
Le patch ci-dessous est particulièrement moche, mais il a le mérite de fonctionner.
____________________________________________________________________

ReportDocument doc = new ReportDocument();
doc.Load("report.rpt");

#region Crystal Report Viewer Patch

doc.ExportToStream(CrystalDecisions.Shared.ExportFormatType.Text).Close();

#endregion

CrystalReportViewer1.ReportSource = doc;

Pour notre démonstration, nous allons créer un plugin qui permet
d'ouvrir un onglet contenant un UserControl de votre choix.
Cette démonstration sera
réalisée en C# sous visual studio 2008.

Grace à cet exemple vous pourrez étendre de façon exponentielle
les possibilités de Visual Studio.

1) Ouvrez Visual Studio et créez un nouveau projet.

2) Sélectionnez “Complément Visual Studio

3) Cliquez sur “Suivant”

4) Sélectionnez “C#” et cliquez sur “Suivant”

5) Sélectionnez “Microsoft Visual Studio 200X” et
cliquez sur “Suivant”

6) Entrez les informations sur votre plugin et
cliquez sur “Suivant”.

7) Selectionnez les élements comme ci-dessous et
cliquez sur “Suivant”

8) Cliquez sur “Suivant”

9) Cliquez sur “Suivant”

10) Ouvrez le fichier “Connect.cs”

Ce fichier contient une classe “Connect” qui est le point
d’entrer de notre plugin. Attention, c’est du C#.Net 1.0.
Par conséquent le code généré est plutôt moche.

Cette classe contient 7 méthodes:

Methode Action
OnConnection Appelée lors du chargement du plugin.
OnDisconnection Appelée lors du déchargement du plugin.
OnAddInsUpdate Appelée lorsque des plugins on été chargés ou déchargés
OnStartupComplete Appelée lorsque le Visual est en cours de lancement
OnBeginShutdown Appelée lorsque le Visual est en cours de fermeture
QueryStatus Appelée lorsque le paramétrage d’une commande a changé
Exec Appelée lors de l’exécution d’une commande dans le plugin


Pour l’instant, nous n’allons pas modifier ce fichier.

Remarque : un plugin est en réalité qu’une bibliothèque
de classes compilée (MyFirstAddIn.dll dans notre cas)

11) Allez dans les propriétés du projet.

Notre plugin est destiné à être utilisé avec Visual Studio. C’est-à-dire
que pour déboguer notre plugin, nous allons lancer une instance de
Visual Studio qui sera l’hôte de notre application.
Ainsi, nous allons indiquer à Visual Studio qu’il doit lancer une instance
de
Visual “devenv.exe” en mode Debug.

12) Dans l'onglet “Déboguer” Sélectionnez “Démarrer le programme externe”
dans “Action de démarrage”.

Assurez-vous que le chemin vers le programme “devenv.exe” est
correct. Normalement les paramètres par défaut sont corrects.

13) Lancez l’application en mode “Debug”.

Comme nous pouvons le voir Visual va lancer une instance Visual.
Nous pouvons aussi remarquer qu’un nouveau bouton “"MyFirstAddIn”
est apparu dans “Outils”. Si nous cliquons dessus, il se passe
bien entendu rien du tout, car nous n’avons rien implémenté dans
le fichier “Connect.cs”.

14) Ajoutez un nouveau UserControl.

Nous avons vu que le plugin est parfaitement chargé par Visual Studio.
Il faut maintenant créer le Contrôle utilisateur(UserControl)
pour notre démonstration.

15) Entrez un nom puis cliquez sur “Ajouter”.

Pour notre exemple, j’ai appelé le UserControl “MyUserControl”

16) Ajoutez les éléments que vous desirez dans ce UserControl.

Dans notre exemple j’ai ajouté une PictureBox.

17) Implémentez le fichier “Connect.cs”

Nous sommes maintenant en mesure d’implémenter notre plugin.
Pour des raisons pratiques, je vais seulement indiquer les lignes à rajouter dans ce fichier afin
de ne pas nuire à la visibilité de ce tutoriel (le code complet est disponible en bas du tutoriel).

A) Ajoutez un “using” dans l’entête du fichier pour indiquer que nous allons utiliser les “Windows Forms”:

using System.Windows.Forms;

B) Ajoutez deux attributs privés dans la classe:

// Instance de notre UserControl
private UserControl _UserControl;
// Instance sur la fenêtre qui contient notre UserControl
private Window _toolWindow;

C) Ajoutez à la fin de la méthode “OnConnection” le code ci-dessous:

else if (connectMode != ext_ConnectMode.ext_cm_UISetup)
{
try
{
object obj = null;// Instance du contrôle renvoyé par CreateToolWindow2
Windows2 windows2 = this._applicationObject.Windows as Windows2;
this._toolWindow = windows2.CreateToolWindow2(this._addInInstance,
Assembly.GetExecutingAssembly().Location,
"MyFirstAddIn.MyUserControl", // Nom du contrôle
"MyFirstAddIn", // Nom du plugin
//
Identificateur unique pour la nouvelle fenêtre.
"{84A3675C-CDA0-4c8b-858F-8F00BEACF199}",
ref obj);
this._UserControl = obj as UserControl;
this._toolWindow.Linkable = false;
this._toolWindow.IsFloating = false;
}
catch (Exception) // A implémenter
{
}
}

D) Ajoutez dans la méthode “Exec” le code ci-dessous
juste avant le “handled = true;”:

this._toolWindow.Visible = true;

E) Compilez, exécutez, puis cliquez dans le bouton “MyFirstAddIn”
dans “Outils”.

Remarque: ne lancez jamais le plugin dans l’instance du Visual que vous
utilisez pour développer le plugin, sinon vous devrez redémarrer Visual.

Notre plugin est maintenant fonctionnel. Il ne reste qu’à implémenter
notre UserControl. Vous pouvez ainsi réaliser une multitude d’outil
pour améliorer la productivité dans votre organisation comme:

- Un client VIM intégré dans visual.
- Un client de messagerie instantané.
- Des outils de diagnostiques.
- Des “Profiler” de code.
- Un gestionnaire de documentation colaboratif.

Afin de gagner du temps, vous pouvez assigner un raccourcis clavier à
votre commande. Pour cela il suffit d’aller dans “Outils” puis “Options”.

Sélectionnez “Environnement –> Clavier”. Faites une recherche afin de retrouver
la commande. Dans notre cas la commande s’appelle “MyFirstAddIn.
Connect.MyFirstAddIn”. Il ne vous reste plus qu’à assigner un raccourcis.

18) Déployment du plugin.

Pour Déployer le plugin, il suffit de fournir la bibliothèque de classe
qui se trouve dans le répertoire de sortie du projet (Dans notre cas
“MyFirstAddIn.dll” ) Ainsi qu’un fichier XML avec l’extension “.AddIn”.

Ce fichier XML est visible dans l’explorateur de la solution.
Dans notre cas il s’appelle “MyFirstAddIn.AddIn”. Il suffit de le copier
dans le répertoire “Addins” de l’utilisateur voir ci-dessous:

Remarque: Un fichier est déjà existant dans votre répertoire.
Il s’agit du fichier utiliser par Visual pour déboguer votre application.

Attention, la seule réstriction c’est que la valeur contenue dans la
balise “Assembly” soit correct. La valeur doit pointer sur le chemin du
plugin que vous deployer. Ainsi le choix de l’emplacement du plugin
est à votre discretion.

Merci d’avoir suivi ce tutoriel.

Dans le prochain article, nous exploiterons un peu plus les possibilités
de Visual Studio (Lancement de compilation, automatisation de tâches,
parsing de code).

Code source du tutoriel : ICI



/// <summary>
/// String2Stream
/// </summary>
/// <param name="str">la chaine</param>
/// <returns>le flux</returns>
public static Stream String2Stream(string str)
{
MemoryStream memStream = new MemoryStream();
byte[] data = Encoding.Unicode.GetBytes(str);
memStream.Write(data, 0, data.Length);
return (memStream as Stream);
}


/// <summary>
/// CSVsplitter
/// </summary>
/// <param name="str">la chaine</param>
/// <param name="delimiter">le séparateur</param>
/// <param name="hasQuote">utilise les quotes</param>
/// <returns>tableau de chaines</returns>
public static string[] CSVsplitter(string str, char delimiter, bool hasQuote)
{
if (!hasQuote) // Si il n'y a pas de "quote"
return (str.Split(delimiter));
// On remplace le séparateur pour éviter les conflits
// avec l'expression régulière
str = str.Replace(delimiter, '¤');
string[] tab = (new Regex('¤' + "(?=(?:[^\"]*\"\"[^\"]*\"\")*(?![^\"]+\"\"))",
RegexOptions.IgnorePatternWhitespace)).Split(str);
// On retire les "quotes"
for (int i = 0; i < tab.Length; ++i)
tab[i] = (new StringBuilder(tab[i], 1,
tab[i].Length - 2, tab[i].Length - 2)).ToString();
return (tab);
}


1 - Ajoutez les dépendances dans votre "setup"





2 - Ajoutez le module Crystal Reports.




3 - Sélectionnez le bon module, ici : "
CrystalReports11_5_NET_2005.msm"



4 - Modifiez les propriétés du module Crystal.




5 - Sélectionnez "MergeModuleProperties"




6 - Entrez votre code de licence pour le déploiement.


Il est agréable qu'une application mobile fonctionne de la même
façon sur windows mobile et windows CE. Sous windows mobile,
lorsqu'une application est lancée et que l'on tente de la relancer,
on récupère simplement le "focus" sur l'application existante.
Contrairement à Windows CE qui autorise les instances multiples.
Afin d'homogénéiser le comportement sur ces deux plateformes,
un code très simple permet de résoudre ce problème.

(le code compatible Windows mobile >= 3.0)

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
using MobileTools;

namespace MobileTools
{
static class SingleInstanceApplication
{
[DllImport("coredll.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("coredll.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("coredll.dll", SetLastError = true)]
private static extern IntPtr CreateMutex(IntPtr Attr, bool Own, string Name);

[DllImport("coredll.dll", SetLastError = true)]
private static extern bool ReleaseMutex(IntPtr hMutex);

public static void Run(Form frm)
{
SingleInstanceApplication.Run(frm, frm.Text);
}

public static void Run(Form frm, string caption)
{
// On créer un mutex avec un nom qui nous sert de référence.
IntPtr MtxHandle = CreateMutex(IntPtr.Zero,
true, Assembly.GetExecutingAssembly().GetName().Name);
// Si le mutex existe déjà on récupère le focus sur
// l'application existante par son nom (caption)
if (Marshal.GetLastWin32Error() == SingleInstanceApplication.ALREADY_EXISTS)
SetForegroundWindow(FindWindow(null, caption));
else
Application.Run(frm); // Sinon on lance la forme
// On libère le mutex
ReleaseMutex(MtxHandle);
}

private const int ALREADY_EXISTS = 183;
}
}

//Reference: Microsoft.WindowsCE.Forms

using Microsoft.WindowsCE.Forms;

///


/// Renvoie: WinCEGeneric, Smartphone ou PocketPC
///
public static WinCEPlatform GetPlatformTarget()
{
   return (SystemSettings.Platform);
}

using System.Reflection;
using System.IO;

namespace Tools
{
public static class PDATool
{
#region Property

public static string ApplicationDirectory
{
get
{
if (PDATool._Path == null) // On calcul la première fois le chemin.
PDATool._Path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().
GetName().CodeBase);
return (PDATool._Path);
}
}

#endregion

#region Attribut

// On conserve une le chemin pour éviter de le recalculer à chaque fois.
private static string _Path = null;

#endregion
}
}

using System;
using System.CodeDom.Compiler;
using System.Diagnostics;

public class MicroCompiler
{
public MicroCompiler()
{
this.Language = "CSharp";
this.WarningLevel = 1;
this.OutputPath = "./~TMP.exe";
}

public bool Compile()
{
this.Result = string.Empty;
this.KillCompiledProgram();
using (CodeDomProvider codeProvider = CodeDomProvider.CreateProvider(this.Language))
{
// Paramètres de la compilation
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = true;
parameters.GenerateInMemory = false;
parameters.WarningLevel = this.WarningLevel;
parameters.OutputAssembly = this.OutputPath;
// on compile
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters,
new string[] { this.Source });
// est ce qu'il y a des erreurs ?
if (results.Errors.Count > 0)
{
foreach (CompilerError CompErr in results.Errors)
this.Result += "Line: " + CompErr.Line + " : " + CompErr.ErrorNumber +
" => " + CompErr.ErrorText + Environment.NewLine;
return (false);
}
}
// on créer le processus pour éxecuter le code
this.CompiledProgram = new Process();
this.CompiledProgram.StartInfo.FileName = this.OutputPath;
return (true);
}

public bool KillCompiledProgram()
{
if (this.CompiledProgram == null || this.CompiledProgram.HasExited)
return (false);
this.CompiledProgram.Kill();
return (true);
}

public bool StartCompiledProgram()
{
if (this.CompiledProgram == null)
return (false);
return (this.CompiledProgram.Start());
}

#region Properties

// Niveau d'avertissement.
public int WarningLevel { get; set; }

// Emplacement du programme généré
public string OutputPath { get; set; }

// Résultat de la compilation
public string Result { get; private set; }

// Le code source
public string Source { get; set; }

// Langage de la source (ex: CSharp)
public string Language { get; set; }

// Instance du programme en exécution.
public Process CompiledProgram { get; private set; }

#endregion
}

namespace Test
{
class Program
{
static void Main(string[] args)
{
MicroCompiler mc = new MicroCompiler();
mc.Source = "mettre le code C# ici!";
if (mc.Compile())
mc.StartCompiledProgram();
}
}
}

   Il arrive quelquefois que nous devions utiliser des composants
dont certaines parties sont devenues obsolètes avec le temps.
Malheureusement, il est parfois impossible (contrainte de temps
et/ou d'argent) de refaire le code en intégralité. Ainsi, la
plupart du temps nous en-capsulons les anciens composants dans
de nouveaux.

Seulement comment interdire l'accès aux propriétés obsolètes
de la classe de base et les remplacer par de nouvelles ?

En réalité c'est assez simple. Cette problématique a été résolue
Dans la version 2 du framework .Net. Il suffit d'utiliser l'attribut
"Obsolete" et de "override" certaines propriétés.

___________________________________________________________________

using System;

namespace Test
{
class A
{
// Rem: la class A peut toujours utiliser cette propriété.
public bool OldProperty { get; set; }
}

class B : A
{
// On indique au compilateur que la propriété est obsolète
[Obsolete("utilisez NewProperty à la place.", true)]
public new bool OldProperty { get; set; } // on "override" la propriété

public bool NewProperty { get; set; }
}

class Program
{
static void Main(string[] args)
{
B test = new B();
test.NewProperty = true; // OK
test.OldProperty = true; // Provoque une erreur à la compilation
}
}
}

Voici une méthode pour envoyer un évènement "KeyPress" à un
contrôle C#. Elle ne nécessite pas d'avoir le "focus" sur
l'élément visé.
---------------------------------------------------------------
using System;
using System.Windows.Forms;
using System.Reflection;

namespace Test
{
public sealed class KeyEventSender
{
private KeyEventSender()
{
}

// WM_CHAR est définit dans l'API de windows
// notification d'un événement clavier
internal static readonly int WM_CHAR = 258;

public static void Send(Control control, char c)
{
Message m = new Message();
m.HWnd = (IntPtr)control.Handle; // "Handle" du contrôle.
m.Msg = KeyEventSender.WM_CHAR; // type de l'évènement windows
m.LParam = IntPtr.Zero; // pas d'option
m.WParam = (IntPtr)c; // valeur de caractère
// on récupère la définition de la méthode par réflexion
// car la méthode n'est pas publique
MethodInfo dynMethod = control.GetType().GetMethod("ProcessKeyEventArgs",
BindingFlags.NonPublic BindingFlags.Instance);
// on invoque la méthode.
dynMethod.Invoke(control, new object[] { m });
}

public static void Send(Control control, KeyPressEventArgs e)
{
KeyEventSender.Send(control, e.KeyChar);
}
}
}