Blog
Seite: 1
Tray Icon mit WPF
Es gibt mir zwei bekannte möglichkeiten ein Tray Icon mit WPF zu realisieren. Die erste ist man erstellt ein WPF Window und erstellt darin, das Tray Icon Objekt. Meiner Meinung nach ist das aber der falsche Weg. Darin wird nur das Tray Icon Objekt gehostet. Die Nachteile:
- Code ist nicht gut getrennt Tray Icon / Window
- Will man nur das Tray Icon zum Programmstart anzeigen muss das Fenster versteckt werden
Das ganze realisiert man so:
- Projekttyp "Neue WPF Anwendung" erstellen
- Das vorhandene Window kann man wie man will löschen oder stehen lassen, wenn man dieses später benötigt
- In der App.xaml entfernen wir den Eintrag StartupUri="Window1.xaml"
- Damit verhindern wir, dass beim Programmstart das Fenster Window1 erstellt und angezeigt wird.
- Für das TrayIcon verwenden wir das Objekt NotifyIcon welches sich in der Libary System.Windows.Forms befindet.
- Wir fügen also zu den Verweisen ->Rechter Mausklick -> Verweis hinzufügen -> System.Windows.Forms und System.Drawing hinzu damit wir auch das Icon setzen können
Ich habe eine kleine Tray Icon Klasse geschrieben, damit man schnell ein paar ContextMenu einträge zum Tray Icon hinzufügen kann. Dazu erstellen wir uns eine neue Klassen-Datei namens Tray.cs und fügen folgenden Code ein.
using System.Windows.Forms; using System; public class Tray { private NotifyIcon _notico; private bool _Animate = false; private ContextMenu _contextMenu = new ContextMenu(); public Tray() { Initialize(); } public Tray(System.Drawing.Icon pIcon) { Initialize(); _notico.Icon = pIcon; } /// <summary> /// Initialisiert das NotifyIcon /// </summary> private void Initialize() { // NotifyIcon erzeugen _notico = new NotifyIcon(); _notico.Visible = true; ContextMenu contextMenu = new ContextMenu(); // Kontextmenüeinträge erzeugen _notico.ContextMenu = _contextMenu; } public void CreateMenuItem(String pName) { MenuItem menuItem = new MenuItem(); menuItem = new MenuItem(); menuItem.Index = 1; menuItem.Name = pName; menuItem.Text = "&" + menuItem.Name; _contextMenu.MenuItems.Add(menuItem); } public void CreateMenuItem(String pName, bool pTrue) { MenuItem menuItem = new MenuItem(); menuItem.Index = 2; menuItem.Name = pName; menuItem.Text = "&" + menuItem.Name; menuItem.Click += (sender, e) => { MenuItem m = (MenuItem)sender; m.Checked = !m.Checked; }; menuItem.Checked = pTrue; _contextMenu.MenuItems.Add(menuItem); } public NotifyIcon NotifyIcon { get { return _notico; } } }
Jetzt öffnen wir die App.xaml.cs Datei und überschreiben die Methode OnStartUp().
Damit unser Programm nicht beendet wird, wenn man ein erstelltes Fenster schließt, müssen wir die Propertie ShutdownMode neu setzen.
In der MSDN steht dazu folgendes.
"Ruft die Bedingung ab, unter der die Shutdown-Methode aufgerufen wird, oder legt diese fest."
Wir setzen die Propertie deshalb auf den Wert OnExplicitShutdown.
Damit das Tray Icon im Tray überhaupt sichtbar ist, benötigen wir ein Icon. Man kann ein neues Icon direkt im Vs.net erstellen oder ein vorhandenes hinzufügen. Wie man das Icon ins Programm ladet bleibt jedem selber überlassen. Ich lade das Icon einfachhalts halber aus einer Datei.
Danach erstellen wir das Tray Objekt und übergen das Icon welches, wir aus der Datei geladen haben.
Nun können wir, auf die Tray Methoden zugreifen und beliebig viele ContextMenüs erstellen.
Über die Propertie NotifyIcon der Klasse Tray können wir auf die MenüItems zugreifen und deren verhalten bestimmen. Z.b, wenn man auf ein Menüitem Klickt, dass ein neues Fenster erstellt wird, oder dass die komplette Applikation mit samt Tray Icon geschlossen wird.
Das sieht dann so aus:
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Windows; using System.Drawing; namespace Tray_Icon_WPF { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { this.ShutdownMode = ShutdownMode.OnExplicitShutdown; //Icon laden Icon icon = Icon.ExtractAssociatedIcon(@"C:UsersmartinDocumentsVisual Studio 2008BlogTray Icon WPFTray Icon WPFIcon1.ico"); //trayicon erstellen und icon laden Tray tray = new Tray(icon); tray.CreateMenuItem("Fenster aufrufen"); tray.CreateMenuItem("Beenden"); //Menuitem Fenster aufrufen suchen, zugreifen Click Event anmelden und delegate setzen tray.NotifyIcon.ContextMenu.MenuItems.Find("Fenster aufrufen", true).First().Click += (sender, eargs) => { Window1 window = new Window1(); window.Show(); }; //Menuitem Beenden suchen, zugreifen Click Event anmelden und delegate setzen tray.NotifyIcon.ContextMenu.MenuItems.Find("Beenden", true).First().Click += (sender, eargs) => { //Icon aus tray löschen tray.NotifyIcon.Dispose(); Environment.Exit(0); }; base.OnStartup(e); } } }
Das Demo Projekt ist im Anhang. Wenn ihr die Demo ausführt, müsst ihr den Pfad zum Icon anpassen. Über Fragen, Anregungen, Kritik freu ich mich.
Links:
Guter Artikel über das Tray Icon, allerdings wird der ShutdownMode nicht erwähnt
http://www.codecomplete.de/blogs/xamlblog/archive/2008/12/15/wpfanwendung-mit-trayicon.aspx
So sollte es meiner Meinung nicht machen (Code im Window)
http://possemeeg.wordpress.com/2007/09/06/minimize-to-tray-icon-in-wpf/
Schlüsselwörter: WPF
zuletzt geändert: 14. Feber 2010 19:38
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen
Filter Data mit MVVM (Filtering ListView, ListBox mit MVVM)
In diesem Eintrag zeige ich, wie man in WPF Daten (IList<T>) mit dem MVVM Pattern filtern kann.
Ich habe darauf geachtet, dass man möglichst individuell bestimmen kann wie Daten gefiltert werden können. Natürlich gibt es noch andere Möglichkeiten Listen zu filtern (CollectionView) die ihre Vor- und Nachteile gegenüber dieser Methode haben. Seht selbst ob sie euch gefällt.
Im Prinzip ist dieser Lösungsvorschlag relativ einfach zu realisieren. Im ViewModel hat man wie üblich die Liste mit den Daten auf die eine ListBox, ListView... bindet. Wenn wir nun aber die Liste filtern wollen, benötigen wir zusätzlich eine weitere Liste in dem nur die gefilterten Daten gespeichert sind. Das Control zum Anzeigen der Daten bindet nun auf die Gefilterte Liste mit den Daten und nicht mehr auf die orginal Liste. Die gefilterte Liste macht folgendes.
Sie führt eine Linq Abfrage auf die Original Liste aus. Zu dieser Linq Abfrage fügt man sämtliche Bedinungen ein die dem Filter entsprechen. Schlussendlich wird aus der Linq Abfrage die gefilterte Liste erstellt, welche dann eine ListBox, ListView… anzeigt.
Um diese Vorgehensweise besser veranschaulichen zu können habe ich ein Beispiel erstellt. In diesem Beispiel sollen sämtliche Produkte eines Geschäftes angezeigt werden. Damit der Kunde eine bessere Übersicht über die Produkte hat, soll man einen Filter hinzufügen.
Einmal soll der Kunde nach Produkten suchen können und diese auch nach dem Preis und dem Attribut Ausverkauft filtern können.
Unser Produkt hat die Eigenschaften Name, Preis, Ausverkauft.
In Unserem ViewModel haben wir einmal die Liste mit den Orginal Daten (ProduktList) und eine Liste welche nur die Gefilterten Daten zurück gibt (FilteredList). Zusätzlich haben wir noch pro Filter eine Propertie die den Filter representiert. Also FilterAusverkauft, FilterName zum nach Produkten suchen zu können und FilterPreis um nur Produkte mit einem bestimmten Preis an zu zeigen.

Zuerst der XAML Code. Die Oberfläche zeigt einmal eine ListView an, in der die gefilterten Produkte je nach Filter Einstellung angezeigt werden. Die ListView bindet auf die Gefilterte Liste vom ViewModel. Zusätzlich hat der Benutzer die Möglickeit die Filter mittels TextBox, CheckBox zu setzen.
Wichtig ist, dass man bei den Filter Properties beim Binding ein UpdateSourceTrigger=PropertyChanged an gibt. Dann werden sämtliche aktualisierungen sofort weiter gegeben. Wenn jetzt jemand beim Filter Name einen Buchstaben eingibt, wird sofort die GefilterteListe erneuert.
Beim Propertie FilterName wird zusätzlich die Methode NotifyPropertyChanged("FilteredList") ausgeführt. Was zur Folge hat, dass die ListView sich nochmal die Daten von der Propertie FiltedList holt.
Dabei wird unsere GetFiltedList Methode aufgerufen. Dieser übergeben wir die orginal Liste. Diese führt die Linq Abfrage aus und erstellt uns eine neue gefilterte Liste.
Der Linq Abfrage übergeben wir unsere Bedingungs Methoden FilterNameMethode, FilterAusverkauftMethode, FilterPreisMethode. In dieser können wir selbst bestimmen wie wir was überprüfen. Schauen wir uns dazu die FilterNameMethode etwas genauer an.
Die Bedinungsmethode muss immer einen Boolean zurückgeben. Sinnvoll ist es auch, das gerade itterierende Item der Linq Abfrage der Methode zu übergen. Als zweiten Parameter übergeben wir die FilterName Propertie. Damit können wir die Linq Abfrage beeinflussen.
In der dritten if Bedinung wird überprüft ob pName (=FilterName) leer ist. Wir geben hier true zurück. Das Element soll also in die gefilterte Liste hinzugefügt werden, da der Filter nicht aktiv scheint. Erst wenn jemand in die Propertie Filtername bzw. in pName etwas hineinschreibt soll je nach dem, wenn der Anfangsbuchstabe passt, das gerade itterierende Item zur gefilterten Liste hinzugefügt werden.
Die Vorgangsweise bei den anderen Filter Methoden ist die gleiche. Statt einer String Überprüfung wird ein int oder bool Wert geprüft. Soll das Item in die gefilterte Liste aufgenommen werden gibt die Filter Methode true zurück andern falls false.
Will man ein Element Manipulieren, löschen, duplizieren etc. muss man die Änderungen immer an der Orginal Liste durchführen. Wichig dabei ist, dass nach der Änderung ein NotifyPropertyChanged("FilterList") ausgeführt wird, damit die FiltedList neu erstellt wird.
Ich denke, dass man dieses Filter Prinzip relativ einfach umsetzen kann. Bei unklarheiten, ergänzungen schreibt mir ein Comment. Das gesamte Projekt kann vom Anhang heruntergeladen werden.

Beispielprojekt Download:
)
<GroupBox Header="Filter" DockPanel.Dock="Top"> <StackPanel Orientation="Horizontal"> <!-- Hier kann man die Filter aktivieren --> <TextBlock Text="Name" VerticalAlignment="Top" /> <TextBox Text="{Binding Path=FilterName,UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="Preis" VerticalAlignment="Top" /> <TextBox Text="{Binding Path=FilterPreis,UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="Ausverkauft" VerticalAlignment="Top" /> <CheckBox IsChecked ="{Binding Path=FilterAusverkauft,UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> </GroupBox> <!-- Daten anzeigen --> <ListView ItemsSource="{Binding Path=FilteredList,UpdateSourceTrigger=PropertyChanged}" DockPanel.Dock="Top"> <ListView.View> <GridView> <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn> <GridViewColumn Header="Preis" DisplayMemberBinding="{Binding Preis}"></GridViewColumn> <GridViewColumn Header="Ausverkauft" DisplayMemberBinding="{Binding Ausverkauft}"></GridViewColumn> </GridView> </ListView.View> </ListView>
Wichtig ist, dass man bei den Filter Properties beim Binding ein UpdateSourceTrigger=PropertyChanged an gibt. Dann werden sämtliche aktualisierungen sofort weiter gegeben. Wenn jetzt jemand beim Filter Name einen Buchstaben eingibt, wird sofort die GefilterteListe erneuert.
public string FilterName { get { return _FilterName; } set { _FilterName = value; NotifyPropertyChanged("FilterName"); NotifyPropertyChanged("FilteredList"); } }
Beim Propertie FilterName wird zusätzlich die Methode NotifyPropertyChanged("FilteredList") ausgeführt. Was zur Folge hat, dass die ListView sich nochmal die Daten von der Propertie FiltedList holt.
public ObservableCollection<Produkt> FilteredList { get { return GetFilteredList(ProduktList); } }
Dabei wird unsere GetFiltedList Methode aufgerufen. Dieser übergeben wir die orginal Liste. Diese führt die Linq Abfrage aus und erstellt uns eine neue gefilterte Liste.
private ObservableCollection<Produkt> GetFilteredList(ObservableCollection<Produkt> pOrginalProduktList) { ObservableCollection<Produkt> filteredproduktlist = new ObservableCollection<Produkt>(); var x = from p in pOrginalProduktList where FilterNameMethod(p, this.FilterName) && FilterAusverkauftMethod(p, this.FilterAusverkauft) && FilterPreisMethod(p, this.FilterPreis) select p; foreach (var u in x) filteredproduktlist.Add(u); return filteredproduktlist; }
Der Linq Abfrage übergeben wir unsere Bedingungs Methoden FilterNameMethode, FilterAusverkauftMethode, FilterPreisMethode. In dieser können wir selbst bestimmen wie wir was überprüfen. Schauen wir uns dazu die FilterNameMethode etwas genauer an.
public static bool FilterNameMethod(Produkt pProdukt, string pName) { if (pProdukt == null) return false; if (pName == null) return false; if (pName.Equals(string.Empty)) return true; return (pProdukt.Name.ToUpper().StartsWith(pName.ToUpper())); }
Die Bedinungsmethode muss immer einen Boolean zurückgeben. Sinnvoll ist es auch, das gerade itterierende Item der Linq Abfrage der Methode zu übergen. Als zweiten Parameter übergeben wir die FilterName Propertie. Damit können wir die Linq Abfrage beeinflussen.
In der dritten if Bedinung wird überprüft ob pName (=FilterName) leer ist. Wir geben hier true zurück. Das Element soll also in die gefilterte Liste hinzugefügt werden, da der Filter nicht aktiv scheint. Erst wenn jemand in die Propertie Filtername bzw. in pName etwas hineinschreibt soll je nach dem, wenn der Anfangsbuchstabe passt, das gerade itterierende Item zur gefilterten Liste hinzugefügt werden.
Die Vorgangsweise bei den anderen Filter Methoden ist die gleiche. Statt einer String Überprüfung wird ein int oder bool Wert geprüft. Soll das Item in die gefilterte Liste aufgenommen werden gibt die Filter Methode true zurück andern falls false.
Will man ein Element Manipulieren, löschen, duplizieren etc. muss man die Änderungen immer an der Orginal Liste durchführen. Wichig dabei ist, dass nach der Änderung ein NotifyPropertyChanged("FilterList") ausgeführt wird, damit die FiltedList neu erstellt wird.
Ich denke, dass man dieses Filter Prinzip relativ einfach umsetzen kann. Bei unklarheiten, ergänzungen schreibt mir ein Comment. Das gesamte Projekt kann vom Anhang heruntergeladen werden.

Beispielprojekt Download:
Schlüsselwörter: WPF, XAML, LINQ
zuletzt geändert: 11. Dezember 2009 16:35
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen
ColorAnimation im XAML vs ColorAnimation im C# Code
Vorgeschichte:
CoffeClock ist ein Erinnerungsprogram. Wenn man gerade etwas auf dem Herd hat, und nebenbei am Computer sitzt kommt es schnell vor, dass man alles um sich herum vergisst. Bei mir ist das jedenfalls so. So kam es schon öfter vor, dass einige Leckerein verbrannten. Um dieses Problem vor zu beugen habe ich dieses Erinnerungsprogramm geschrieben.
Man gibt den Erinnerungstext und die Zeit ein, wann man an etwas Erinnert werden soll. Danach versteckt sich das Programm und wartet im Hintergrund bis die Zeit abgelaufen ist. Wenn die Zeit abgelaufen ist wird der Komplette Bilschirm schwarz und der Erinnerungstext wird weiß eingeblendet.
Wenn man die ESC Taste drückt, soll das Erinnerungsprogramm beendet werden und man kehrt zum Desktop zurück.
Die Animation:
Jetzt wär es cool wenn man das Zurückkehren zum Desktop mittels einer Animation machen könnte. Von Schwarz überlaufend in den Desktop (bzw. von Schwarz in Transparent).
Animationen habe ich bis jetzt immer im XAML erstellt. Also wollte ich das auch in diesem Fall so machen. Das hätte dann so ausgesehen:
<Window.Triggers> <EventTrigger RoutedEvent="Window.PreviewKeyDown"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetProperty="Background.Color" From="Black" To="Transparent" Duration="00:00:01.000" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Window.Triggers>
Tja, leider kam mir keine Idee, wie ich im XAML abfragen kann ob der Benutzer die ESC Taste gedrückt hat. Hinzu kommt, dass ich nach der Animation das Programm beenden muss. In XAML wäre das wahrscheinlich ebenso schwer bis gar unmöglich zu realisieren.
Also musste ich die Animation doch im C# Code erstellen.
Mein erster geh versuch sah so aus (der C# Animationscode sollte equivalent zum XAML Code sein):
ColorAnimation colorAnimation = new ColorAnimation(); colorAnimation.Duration = new Duration(new TimeSpan(0, 0, 10)); colorAnimation.From = Colors.Black; colorAnimation.To = Colors.Transparent; this.BeginAnimation(Window.BackgroundProperty, colorAnimation);Mit diesem Code erhielt ich die Exception:
"AnimationTimeline" vom Typ "System.Windows.Media.Animation.ColorAnimation" kann nicht zum Animieren der Background-Eigenschaft vom Typ "System.Windows.Media.Brush" verwendet werden.
Parametername: animation
Beim näheren betrachten sieht man auch, dass man im XAML als TargetPropertie Background.Color angegeben hat. Leider kann man das im C# Code so nicht setzen, da man mit Window.BackgroundProperty nur auf die Properties des DependencyProperties zugreifen kann.
Die Lösung ist eine Referenz auf den Background Brush. Das sieht dann so aus:
Brush backgroundBrush = this.Background; backgroundBrush.BeginAnimation(SolidColorBrush.ColorProperty, colorAnimation);Nach der Animation habe ich dann den Befehl this.Close() ausgeführt.
Jetzt stand ich vor dem nächsten Problem. Die Animation wird asynchron ausgeführt. Das heißt man sieht die Animation gar nicht, da sofort das Fenster geschlossen wird.
Zum glück hat die Klasse ColorAnimation ein Event Completed welches, wie der Name bereits verrät, gefeuert wird, wenn die Animation fertig ist.
Ich habe den Close() Befehl also in das Event Completed gesteckt. Nun wird, nach dem drücken der Taste ESC zuerst die Animation abgespielt und zu guter letzt das Programm beendet.
Der komplette und richtige Code sieht so aus:
this.PreviewKeyDown += (sender, e) => { if (e.Key.Equals(Key.Escape)) { ColorAnimation ani = new ColorAnimation(Colors.Transparent, new Duration(new TimeSpan(0, 0, 0, 1, 0))); ani.Completed += (Completedsender, Completede) => { this.Close(); }; SolidColorBrush newBrush = new SolidColorBrush(Colors.Black); this.Background = newBrush; newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani); } };Ein großes Dank geht an talla vom mycsharp Forum der mir bei meinen Animationsproblemen weiter geholfen hat.
Links:
http://msdn.microsoft.com/de-de/library/system.windows.media.animation.coloranimation.aspx
Das Erinnerungsprogramm CoffeeClock können Sie hier herunterladen ( benötigt .NET 3.5 7Kb groß):
Zum Download
Schlüsselwörter: XAML, WPF, Get
zuletzt geändert: 22. Oktober 2009 21:32
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen
MarkupExtension - Bilder aus Resource direkt ins XAML laden
Vor kurzem habe ich eine MarkupExtension geschrieben, die aus einer Resource-Datei (resx.) Bilder lädt. Eine MarkupExtension zu schreiben ist nicht schwer. Man erbt von der Abstrakten Klasse MarkupExtension und muss die zwei Konstruktoren (einmal mit Parameter) implementieren und die Methode ProvideValue überschreiben.
1.0 Einführung - Eine einfache MarkupExtension
Im zweiten Konstruktor der wie folgt aussieht....
public ImageResourceConverterExtension(string pkey) : this()
{
//Wert Propertie Key zuweisen
Key = pkey;
}
{
//Wert Propertie Key zuweisen
Key = pkey;
}
....steht im Parameter der MarkupExtension Key.
D.h. Wenn im XAML in der MarkupExtension STRING
<TextBlock Text="{GetCommon:MeineMarkupExtension STRING}"/>
steht, erhält man diesen Wert "STRING" über den Konstruktor Parameter pkey. Diesen kann man dann, interpretieren parsen und vieles mehr
.In unserem Beispiel geben wir den Key einfach wieder als string an die Propertie zurück an der die MarkupExtension angewandt wurde.
Die Funktion ProvideValue weist ein object der Propertie zu, an der die MarkupExtension gesetzt wurde. Z.B
public override object ProvideValue(IServiceProvider pserviceProvider)
{
//Propertie in der wir unseren Key "STRING" gespeichert haben
return Key;
}
{
//Propertie in der wir unseren Key "STRING" gespeichert haben
return Key;
}
Das heißt im TextBlock Text steht dann, wenn das Fenster geladen wurde, STRING.
1.1 ImageResourceConverter MarkupExtension
So und jetzt meine MarkupExtension die Bilder aus einer Resource.resx lädt.
Achtung: die MarkupExtension funktioniert nur mit PNG Bilder!
Verwendung wie folgt:
<Image Source="{GetCommon:ImageResourceConverter Get.Common.Resource.Image1}"/>
Syntax: Namespace kann auch der ProjektName sein.ResourceName.Name des Bildes auf die man zugreifen will.
Gibt man hinter dem Namespace.ResourceName.BildName kein @ an nimmt die MarkupExtension an, dass die Resource sich in der aktuellen Instanz befindet.
<Image Source="{GetCommon:ImageResourceConverter Get.Demo.Resource.Image1@Get.Demo.exe}" Width="20"/>
Syntax: Namespace.ResourceName.BildName@DLLName in der sich die Resource befindet
Die Dll wird im Verzeichnis Environment.CurrentDirectory gesucht.
Hier der Code der MarkupExtension:
[MarkupExtensionReturnType(typeof(ImageSource)), Localizability(LocalizationCategory.NeverLocalize)]
public class ImageResourceConverterExtension : MarkupExtension
{
#region Members
/// <summary>
/// Seperator zwischen Namespace und Resource
/// </summary>
private const char _point = '.';
/// <summary>
/// Seperator zwischen ProjektName.ResourceName.Name und DLLName
/// </summary>
private const char _ExternAssemblySpliter = '@';
#endregion
#region Konstruktor
/// <summary>
/// Konstruktor - Initialisiert die Properties
/// </summary>
public ImageResourceConverterExtension()
{
ImageName = string.Empty;
BaseName = string.Empty;
}
/// <summary>
/// Konstruktor
/// </summary>
/// <param name="pkey">Key der bei der MarkupExtension angegeben wurde.
/// Z.B. "Get.Demo.Resource.Image1@Get.Demo.exe"</param>
public ImageResourceConverterExtension(string pkey)
: this()
{
Key = pkey;
//Prüfen ob die Resource-Datei aus einer fremden Assembly geladen werden soll
if (!pkey.Contains(_ExternAssemblySpliter))
{
//Properties ImageName und BaseName setzen
SetImageNameAndBaseName(pkey);
}
else
{
//Fremde Assembly laden - Dateiname extrahieren
string filename = pkey.Split(_ExternAssemblySpliter).Last();
FileInfo fileInfo = new FileInfo(Environment.CurrentDirectory + "" + filename);
if (!fileInfo.Exists)
throw new FileNotFoundException();
//Assembly laden
Assembly assembly = Assembly.LoadFrom(fileInfo.ToString());
ResourceAssembly = assembly;
SetImageNameAndBaseName(pkey.Replace(_ExternAssemblySpliter.ToString()
+ filename, string.Empty));
}
}
#endregion
#region Funktionen
/// <summary>
/// Extrahiert die Werte aus dem übergeben Key und weist sie den jeweiligen Properties zu.
/// </summary>
/// <param name="pkey">MarkupExtension Key Z.B. "Get.Demo.Resource.Image1@Get.Demo.exe"</param>
private void SetImageNameAndBaseName(string pkey)
{
//Letzter Value ist der BildName
ImageName = pkey.Split(_point).Last();
//Der Rest besteht aus Namespace.ResourceName
BaseName = pkey.Replace(_point.ToString() + ImageName, string.Empty);
}
/// <summary>
/// Gibt ein ImageSource Objekt zurück an der die MarkupExtension gesetzt wurde.
/// </summary>
/// <param name="pserviceProvider">Objekt, das Dienste für die
/// Markuperweiterung bereitstellen kann.</param>
/// <returns>ImageSource Objekt</returns>
public override object ProvideValue(IServiceProvider pserviceProvider)
{
//Bild aus Resource laden
if (ResourceAssembly == null)
return GetBitmapImageFromResource(BaseName, ImageName, this.GetType().Assembly);
else
return GetBitmapImageFromResource(BaseName, ImageName, ResourceAssembly);
}
/// <summary>
/// Ladet mithilfe der übergebenen Informationen ein Bild aus der
/// Resource (.resx) und konvertiert es in ein ImageSource Objekt
/// </summary>
/// <param name="pBaseName"></param>
/// <param name="pImageName"></param>
/// <param name="pAssembly"></param>
/// <returns></returns>
public static ImageSource GetBitmapImageFromResource(string pBaseName,
string pImageName, Assembly pAssembly)
{
ResourceManager resourceManager = new ResourceManager(pBaseName, pAssembly);
//System.Drawing.Bitmap aus Resource holen
object image = resourceManager.GetObject(pImageName);
if (image == null) return null;
if (!image.GetType().Equals(typeof(Bitmap))) return null;
Bitmap bitmap = image as Bitmap;
//Bitmap in ImageSource konvertieren
BitmapImage bitmapImage = new BitmapImage();
MemoryStream stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Png);
//ImageSource aus Stream holen
//http://cornucopia30.blogspot.com/2007/08/wpf-point-image-to-embedded-resource.html
PngBitmapDecoder bitmapDecoder = new PngBitmapDecoder(stream,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
ImageSource imageSource = bitmapDecoder.Frames[0];
return imageSource;
}
#endregion Funktionen
#region Properties
/// <summary>
/// Gets or sets the resource key.
/// </summary>
/// <value>The key.</value>
[ConstructorArgument("pkey")]
public string Key { get; private set; }
/// <summary>
/// Ruft den Namespace bzw. Projekt-Namen + Resourcename ab oder legt diesen fest.
/// </summary>
public string BaseName { get; private set; }
/// <summary>
/// Ruft den Wert des Bildnamens ab oder legt diesen fest.
/// </summary>
public string ImageName { get; private set; }
/// <summary>
/// Ruft die Externe Assembly ab oder legt diese fest.
/// Diese Propertie ist nur gesetzt wenn man in der MarkupExtension
/// das @ mit dem DLLNamen festgelegt hat.
/// </summary>
public Assembly ResourceAssembly { get; private set; }
#endregion
}
public class ImageResourceConverterExtension : MarkupExtension
{
#region Members
/// <summary>
/// Seperator zwischen Namespace und Resource
/// </summary>
private const char _point = '.';
/// <summary>
/// Seperator zwischen ProjektName.ResourceName.Name und DLLName
/// </summary>
private const char _ExternAssemblySpliter = '@';
#endregion
#region Konstruktor
/// <summary>
/// Konstruktor - Initialisiert die Properties
/// </summary>
public ImageResourceConverterExtension()
{
ImageName = string.Empty;
BaseName = string.Empty;
}
/// <summary>
/// Konstruktor
/// </summary>
/// <param name="pkey">Key der bei der MarkupExtension angegeben wurde.
/// Z.B. "Get.Demo.Resource.Image1@Get.Demo.exe"</param>
public ImageResourceConverterExtension(string pkey)
: this()
{
Key = pkey;
//Prüfen ob die Resource-Datei aus einer fremden Assembly geladen werden soll
if (!pkey.Contains(_ExternAssemblySpliter))
{
//Properties ImageName und BaseName setzen
SetImageNameAndBaseName(pkey);
}
else
{
//Fremde Assembly laden - Dateiname extrahieren
string filename = pkey.Split(_ExternAssemblySpliter).Last();
FileInfo fileInfo = new FileInfo(Environment.CurrentDirectory + "" + filename);
if (!fileInfo.Exists)
throw new FileNotFoundException();
//Assembly laden
Assembly assembly = Assembly.LoadFrom(fileInfo.ToString());
ResourceAssembly = assembly;
SetImageNameAndBaseName(pkey.Replace(_ExternAssemblySpliter.ToString()
+ filename, string.Empty));
}
}
#endregion
#region Funktionen
/// <summary>
/// Extrahiert die Werte aus dem übergeben Key und weist sie den jeweiligen Properties zu.
/// </summary>
/// <param name="pkey">MarkupExtension Key Z.B. "Get.Demo.Resource.Image1@Get.Demo.exe"</param>
private void SetImageNameAndBaseName(string pkey)
{
//Letzter Value ist der BildName
ImageName = pkey.Split(_point).Last();
//Der Rest besteht aus Namespace.ResourceName
BaseName = pkey.Replace(_point.ToString() + ImageName, string.Empty);
}
/// <summary>
/// Gibt ein ImageSource Objekt zurück an der die MarkupExtension gesetzt wurde.
/// </summary>
/// <param name="pserviceProvider">Objekt, das Dienste für die
/// Markuperweiterung bereitstellen kann.</param>
/// <returns>ImageSource Objekt</returns>
public override object ProvideValue(IServiceProvider pserviceProvider)
{
//Bild aus Resource laden
if (ResourceAssembly == null)
return GetBitmapImageFromResource(BaseName, ImageName, this.GetType().Assembly);
else
return GetBitmapImageFromResource(BaseName, ImageName, ResourceAssembly);
}
/// <summary>
/// Ladet mithilfe der übergebenen Informationen ein Bild aus der
/// Resource (.resx) und konvertiert es in ein ImageSource Objekt
/// </summary>
/// <param name="pBaseName"></param>
/// <param name="pImageName"></param>
/// <param name="pAssembly"></param>
/// <returns></returns>
public static ImageSource GetBitmapImageFromResource(string pBaseName,
string pImageName, Assembly pAssembly)
{
ResourceManager resourceManager = new ResourceManager(pBaseName, pAssembly);
//System.Drawing.Bitmap aus Resource holen
object image = resourceManager.GetObject(pImageName);
if (image == null) return null;
if (!image.GetType().Equals(typeof(Bitmap))) return null;
Bitmap bitmap = image as Bitmap;
//Bitmap in ImageSource konvertieren
BitmapImage bitmapImage = new BitmapImage();
MemoryStream stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Png);
//ImageSource aus Stream holen
//http://cornucopia30.blogspot.com/2007/08/wpf-point-image-to-embedded-resource.html
PngBitmapDecoder bitmapDecoder = new PngBitmapDecoder(stream,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
ImageSource imageSource = bitmapDecoder.Frames[0];
return imageSource;
}
#endregion Funktionen
#region Properties
/// <summary>
/// Gets or sets the resource key.
/// </summary>
/// <value>The key.</value>
[ConstructorArgument("pkey")]
public string Key { get; private set; }
/// <summary>
/// Ruft den Namespace bzw. Projekt-Namen + Resourcename ab oder legt diesen fest.
/// </summary>
public string BaseName { get; private set; }
/// <summary>
/// Ruft den Wert des Bildnamens ab oder legt diesen fest.
/// </summary>
public string ImageName { get; private set; }
/// <summary>
/// Ruft die Externe Assembly ab oder legt diese fest.
/// Diese Propertie ist nur gesetzt wenn man in der MarkupExtension
/// das @ mit dem DLLNamen festgelegt hat.
/// </summary>
public Assembly ResourceAssembly { get; private set; }
#endregion
}
Hier eine kurze Beschreibung der MarkupExtension:
Im Key wird angegeben wo sich das Bild in der Resource befindet. Die Resource wird in der Funktion GetBitmapImageFromResource mit dem ResourceManager geladen.
Die Propertie Source der Image Klasse hat den Typ ImageSource. Deshalb muss man das geladene Bitmap in eine ImageSource umwandeln.
Anschließend wird die erzeugte ImageSource in der Funktion ProvideValue zurück gegeben. Diese MarkupExtension habe ich im schnell verfahren geschrieben. Das heißt, dass wichtige Überprüfungen fehlen. Auch habe ich das Ganze nur für .PNG Dateien getestet bzw. Programmiert. Ich überprüfe auch nicht ob man die MarkupExtension bei einem Nicht Image Objekt anwendet.
[Edit:]
Hier noch ein Artikel zur Konvertierung von Bildern (jpg, png, ...) in eine ImageSource.
Andere wichtige und gute MarkupExtensions:
http://tomlev2.wordpress.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/ Ermöglicht Keybindings ohne Codebehind durch Verwendung der MarkupExtension.
Schlüsselwörter: XAML, WPF
zuletzt geändert: 22. September 2009 19:50
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen
IsAsync - Meldung über Ladevorgang ausgeben
Vor einigen Tagen musste ich extrem viel Daten in einer ListView anzeigen. Ich habe an eine Propertie im ViewModel gebunden, welche die Daten für die ListView bereitstellten.
Das eigentliche Problem an der Sache war, dass sämtliche Daten im ViewModel über das Lazyloading Prinzip zu Verfügung gestellt wurden. Das heißt, obwohl das ViewModel initialisiert wurde, werden die Daten erst geladen wenn wann man diese benötigt, bzw. wenn man auf diese Bindet.
Die Properties generieren die Daten zur Laufzeit oder lesen große XML Dateien aus, was bis zu ca. 30 Sekunden in Anspruch nehmen kann. Während dem laden der XML Datei oder dem generieren der Daten friert die komplette Applikation ein und der Benutzer glaubt das Programm wäre abgestürtzt.
Zum Glück untersützt das Binding Asynchrones laden von Daten. Wenn man die Eigenschaft IsAsync auf True setzt wird das asynchrone laden der Daten aktiviert.
Jetzt ist allerdings die ListBox 30 Sekunden lang leer. Toll wäre wenn wir den Benutzer darauf hinweisen könnten, dass die Daten gerade geladen werden.
In einem TextBlock geben wir den Text "Loading" aus wenn die ListBox.ItemsSource = NULL ist. D.h. solange die ListBox keine Daten hat wird der Text Loading angezeigt. Hat die ListBox die Daten geladen ist die ItemsSource ungleich NULL. In diesem Fall blenden wir den TextBlock mit dem Text "Loading" aus.
Code mäßig sieht das so aus:
<TextBlock Width="100" DockPanel.Dock="Top">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="Loading"></Setter>
<Setter Property="Foreground" Value="Red"></Setter>
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Style.Triggers>
<!--TextBlock einblenden wenn die ListBox keine Daten hat-->
<DataTrigger Binding="{Binding ElementName=_ListBox,Path=ItemsSource}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!--
Asynchrones laden aktivieren über IsAsync
Items ist die Propertie die die Daten generiert und dafür ca. 30 Sekunden lang braucht
-->
<ListBox Name="_ListBox" ItemsSource="{Binding Path=Items,IsAsync=True}" DockPanel.Dock="Top" BorderBrush="Black" BorderThickness="1">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="ItemsSource" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="Loading"></Setter>
<Setter Property="Foreground" Value="Red"></Setter>
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Style.Triggers>
<!--TextBlock einblenden wenn die ListBox keine Daten hat-->
<DataTrigger Binding="{Binding ElementName=_ListBox,Path=ItemsSource}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!--
Asynchrones laden aktivieren über IsAsync
Items ist die Propertie die die Daten generiert und dafür ca. 30 Sekunden lang braucht
-->
<ListBox Name="_ListBox" ItemsSource="{Binding Path=Items,IsAsync=True}" DockPanel.Dock="Top" BorderBrush="Black" BorderThickness="1">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="ItemsSource" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
Den "Loading" Text kann man mit einer Animation etwas aufpepen.
<Style.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <BeginStoryboard> <Storyboard> <StringAnimationUsingKeyFrames Storyboard.TargetProperty="(TextBlock.Text)" Duration="0:0:3" RepeatBehavior="Forever"> <DiscreteStringKeyFrame Value="Loading" KeyTime="0:0:0" /> <DiscreteStringKeyFrame Value="Loading ." KeyTime="0:0:1" /> <DiscreteStringKeyFrame Value="Loading .." KeyTime="0:0:1.5" /> <DiscreteStringKeyFrame Value="Loading ..." KeyTime="0:0:2" /> </StringAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Style.Triggers>
Sieht so aus:
Ich habe ein Beispiel angefügt. Das Beispiel unterscheidet sich nur darin, dass der TextBlock als Style in einem Dictionary gespeichert ist.
Anhang:
Schlüsselwörter: XAML, WPF
zuletzt geändert: 20. Juli 2009 19:10
Link zu diesem Artikel: (in die Zwischenablage)
Kommentare: Anzeigen
Seite: 1
(c) 2003-2009 | Impressum| Empfehlenswerte Blog Einträge

Kommentare
keine Elemente zum AnzeigenKommentar schreiben: