Posts mit dem Label Windows Forms werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Windows Forms werden angezeigt. Alle Posts anzeigen

Dienstag, 27. Oktober 2009

Dot Net (C#): lokalisiertes PropertyGrid ; lokalisierte Attribute

In der heutigen Zeit wird eine Anwendung meistens für mehrere Sprachen realisiert. Mit Microsoft .NET ist es kein Problem, Windows-Anwendungen zu lokalisieren. Doch immer wieder kommt die Frage auf, wie man Attribute, insbesondere solche für das PropertyGrid (Description, DisplayText, Category), lokalisieren kann.

Da meine Internetrecherche zu diesem Thema kaum brauchbare Beispiele hervorbrachte, möchte ich an einem Minimalbeispiel darauf eingehen:

Als erstes brauchen wir eine Klasse, die lokalisiert werden soll, nehmen wir in diesem Fall eine "Person":

namespace LocalizeableDescriptionPropertyGrid
{
public class Person
{
// Properties
public String FirstName { get; set; }
public String LastName { get; set; }

// ctor
public Person(string fName, string lName)
{
this.FirstName = fName;
this.LastName = lName;
}

// other methods ...
}
}

Normalerweise würde man die Klasse nun wir folgt mit Attributen versehen:
[DescriptionAttribute("Der Vorname dieser Person")]
public String FirstName { get; set; }

[DescriptionAttribute("Der Nachname dieser Person")]
public String LastName { get; set; }

Diese Vorgehensweise hat jedoch den Nachteil, dass die Attribute nicht lokalisierbar sind. Leider gibts es in .NET keine Möglichkeit, solche Attribute problemlos zu lokalisieren, da deren Information von der Laufzeitumgebung einmal eingelesen wird (per Reflection) und danach nicht mehr aktualisiert. Mit einem kleinen Trick kann man jedoch zumindest dafür sorgen, dass genau in diesem Momemt die Attribute der Sprache angepasst werden. Dazu ist es als erstes notwendig, die lokalisierten Texte wie gewohnt in einer beliebigen Resource-Datei abzulegen.



Als nächstes muss eine Klasse her, die es einem Attribut ermöglicht, seine Informationen aus einer Resource-Datei zu laden. Ich zeige das Verfahren am Beispiel des Description-Attributes, es kann alternativ mit dem Category- und DisplayText-Attribut verfahren werden:

Lediglich ein privates Feld (oder Property) sowie das Überschreiben des Get-Property sind notwenidg.
public class LocalizedDescriptionAttribute : DescriptionAttribute
{
/// <summary>
/// Contains the name of the resource-string
/// </summary>
private string rDescription;

/// <summary>
/// Creates a new LocalizedDescription Attribute instance
/// giving it the name of the resource-string
/// </summary>
/// <param name="description"></param>
public LocalizedDescriptionAttribute(string description)
{
this.rDescription = description;
}

/// <summary>
/// (Overridden) Get: fetching the description during runtime
/// from the Resources (with respect to the current culture)
/// </summary>
public override string Description
{
get
{
return Properties.Resources.ResourceManager.GetString(
this.rDescription,
Thread.CurrentThread.CurrentCulture
);
}
}
}

Als nächstes ist es notwendig, die Klasse Person abzuändern und das neue Attribut einzusetzen:
namespace LocalizeableDescriptionPropertyGrid
{
public class Person
{
// Properties
[LocalizedDescriptionAttribute("PersonFirstName")]
public String FirstName { get; set; }
[LocalizedDescriptionAttribute("PersonLastName")]
public String LastName { get; set; }

// ctor
public Person(string fName, string lName)
{
this.FirstName = fName;
this.LastName = lName;
}

// other methods ...
}
}


Eine fertige Klasse könnte wie folgt aussehen:
namespace LocalizeableDescriptionPropertyGrid
{
public class Person
{
// Properties
[LocalizedCategoryAttribute("PersonCatData"),
LocalizedDescriptionAttribute("PersonFirstNameDescription"),
LocalitedDisplayTextAttribute("PersonFirstNameDisplayText")]
public String FirstName { get; set; }

[LocalizedCategoryAttribute("PersonCatData"),
LocalizedDescriptionAttribute("PersonLastNameDescription"),
LocalitedDisplayTextAttribute("PersonLasttNameDisplayText")]
public String LastName { get; set; }

// other properties

// ctor
public Person(string fName, string lName)
{
this.FirstName = fName;
this.LastName = lName;
}

// other methods ...
}
}


Das gesamte Beispiel könnt ihr hier runterladen: localizablepropgridcode.rar (14,6 KB)

Mittwoch, 30. Juli 2008

Dot Net (C#): Screenshot von einem Control erzeugen

Folgende zwei Methoden sind nötig, um einen Screenshot von einem Control zu erzeugen und diesen in eine Datei zu speichern:

   1:  public static Bitmap takeScreenshot(Control pControl)
   2:  {
   3:      Bitmap bmp = new Bitmap(pControl.Width, pControl.Height);
   4:      pControl.DrawToBitmap(bmp, Rectangle.FromLTRB(0, 0, pControl.Width, pControl.Height));
   5:      return bmp;
   6:  }
   7:   
   8:  public static Boolean saveScreenshotToFile(Control pControl, String pFilename)
   9:  {
  10:      try
  11:      {
  12:          takeScreenshot(pControl).Save(pFilename);
  13:          return true;
  14:      }
  15:      catch (Exception)
  16:      {
  17:          return false;
  18:      }
  19:  }

Die erste Methode nimmt einen Screenshot auf und speichert diesen in einem Bitmap-Objekt. Die zweite Methode kann genutzt werden, um den Screenshot direkt in eine Datei zu speichern.

Dienstag, 8. April 2008

Dot Net (C#): Invoke()-Methode für MultiThread-Applikationen mit WindowsForms

Um ein Control auf einer WindowsForms von einem anderen Thread aus zu verändern, muss die Invoke()-Methode der Form oder des Controls verwendet werden. Ein Control darf nur von dem Thread geändert werden, von dem es erzeugt wurde!

Folgendes Minimal-Beispiel (unvollständig) zeigt einen Anwendungsfall, in dem ein Thread seinen Status an eine Form berichtet:

Die Klasse MathClass erzeugt Thread-Objekte, deren Methode CalcBackground() in einem Thread ausgefürt wird.Aus dieser Methode heraus soll die GUI über den aktuellen Thread-Status informiert werden:

   1:   
   2:      public class MathClass
   3:      {
   4:   
   5:          private MainForm myForm;
   6:          private int iNumber;
   7:   
   8:          public Thread Initialize(MainForm myform, int number)
   9:          {
  10:              this.myForm = myform;
  11:              this.iNumber = number;
  12:              return new Thread(new ThreadStart(this.CalcBackground));
  13:          }
  14:   
  15:          delegate void UpdateUI(int number, int state);
  16:          delegate void Finish(int number);
  17:   
  18:          public void CalcBackground()
  19:          {
  20:              UpdateUI updateUI = new UpdateUI(this.myForm.updateUIProc);
  21:              Finish finish = new Finish(this.myForm.FinishThread);
  22:   
  23:              try
  24:              {
  25:                  // do something
  26:                  // ...
  27:                  // report status to form:
  28:                  object[] param = new object[2] { this.iNumber, i };
  29:                  myForm.Invoke(updateUI, param); // call myForm.updateUIProc(iNumber, i)
  30:              }
  31:              catch (ThreadAbortException)
  32:              {
  33:                  // clean up
  34:              }
  35:              catch (Exception)
  36:              {
  37:                  // catch any other exception
  38:              }
  39:              finally
  40:              {
  41:                  try
  42:                  {
  43:                      // call myForm.FinishThread(iNumber)
  44:                      myForm.Invoke(finish, new object[] { this.iNumber }); 
  45:                  }
  46:                  catch (ObjectDisposedException e)
  47:                  {
  48:                  }
  49:                  catch (Exception ee)
  50:                  {
  51:                  }
  52:              }
  53:          }
  54:   
  55:      }

Die Mainform erzeugt in der Methode startThreads() zwei neue Threads. Die Methoden updateUIProc() sowie FinishThread() werden per Invoke()-Methode von Thread aus gestartet und laufen selbst im Thread der Mainform:
   1:      public partial class MainForm : Form
   2:      {
   3:   
   4:          public MainForm()
   5:          {
   6:              InitializeComponent();
   7:          }
   8:   
   9:          private void Form1_FormClosing(object sender, FormClosingEventArgs e)
  10:          {
  11:              // kill threads before form closes
  12:              if (anyThreadAlive)
  13:              {
  14:                  killThreads();
  15:              }
  16:          }
  17:   
  18:          public void updateUIProc(int number, int val)
  19:          {
  20:              this.textBox1.AppendText(
  21:                  string.Format("thread {0} has done {1} % of its job", number, val));
  22:          }
  23:   
  24:          public void FinishThread(int number)
  25:          {
  26:              this.textBox1.AppendText(
  27:                  string.Format("thread {0} has finished", number));
  28:          }
  29:   
  30:          private void startThreads()
  31:          {
  32:              Thread th1 = new MathClass().Initialize(this, 0);
  33:              Thread th2 = new MathClass().Initialize(this, 1);
  34:              th1.Start();
  35:              th2.Start();
  36:          }
  37:      }

Es ist nicht erlaubt, das Control Mainform.textBox1 direkt aus dem Thread zu verändern.Ein Control darf nur von dem Thread verändert werden, von dem es erstellt wurde.Um trotzdem ein Control aus einem Thread heraus zu verändern, benutzt man die Methode Invoke() und übergibt dieser ein delegate (Funktionszeiger) sowie ein Parameterarray. Die eigentliche Methode (z.B. updateUIProc() oder FinishThread()) läuft anschließend (in diesem Fall) im Thread der MainForm. Dies hat ebenfalls zur Folge, dass keine der beiden Funktionen parallel mehrmals ausgeführt werden kann.