get-the-solution


Page 1 of 212

MarkupExtension – Bilder aus Resource direkt ins XAML laden

By
on Regards: .NET Framework; Archive; C#; XAML;

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 zwei Konstruktoren und die Methode ProvideValue implementieren.

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;
}

….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;
}

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.

Verwendung wie folgt:

<Image Source="{GetCommon:ImageResourceConverter Get.Common.Resource.Image1}"/>

Syntax: 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
    }

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.

Hier noch ein Artikel zur Konvertierung von Bildern (jpg, png, …) in eine ImageSource.


Page 1 of 212

Überschriebener Default Style in BasedOn angeben

By
on Regards: .NET Framework; Archive; XAML;

Wenn man auf einen vorhandenen Style aufbauen will, verwendet man im XAML das Schlüsselwort BasedOn= gibt eine StaticResource oder DynamicResource, je nach bedarf, mit dem Namen des zu erbenden Style an.

Was tut man aber, wenn der Style keinen Key hat. Das kann sein, wenn man den Default Style eines Controls wie zum Beispiel eines Buttons überschreiben will. So wird der Style automatisch von allen Buttons angenommen.

Wenn man nun zu diesem Default Style einen mod. Style schreiben will, kann man beim BasedOn keinen Namen angeben, weil eben der Default Style keinen Namen hat.

Wenn man in das BasedOn folgendes schreibt:

	...BasedOn={x:Type GroupBox}

erhält man eine Exception. Die Lösung geht aber in diese Richtung die folgende wäre:

	...BasedOn={StaticResource {x:Type GroupBox}}


Page 1 of 212

ListBox – SelectedItem Zusatzinformationen anzeigen

By
on Regards: Archive; C#; XAML;

Manchmal ist es praktisch in einer ListBox beim SelectedItem zusätzlich noch mehr Informationen anzuzeigen.

Hier ein Beispiel:

public class Class
{
     public string Artikelname { get; set; }
     public string Foto { get; set; }
}

Es soll nur die Property Artikelname in der ListBox angezeigt werden. Wurde ein Item “Selected” wäre es toll, wenn zusätzlich noch das Foto vom Artikel eingeblendet wird. Im DataTemplate habe ich zwei TextBlöcke die den Inhalt vom Artikelname und vom Foto anzeigen. Der TextBlock der das Foto representiert wird ausgeblendet. Erst wenn das Item “Selected” ist, soll der TextBlock eingeblendet werden. Das Problem im DataTemplate von der Class ist, dass wir im aktuellen DataContext nicht abfragen können, ob das aktuell gewählte Item Selected ist. Der DataContext enthält nämlich die Klasse Class (Zugegebenermaßen der Name ist nicht gut gewählt). Wir benötigen aber den DataContext vom ListBoxItem, da es dort eine Property für IsSelected gibt.

Über die RelativSource kann man auf das ListBoxItem zugreifen, und somit die Property IsSelected abfragen.
Per DataTrigger schalten wir, dann die Visibility.

<DataTemplate DataType="{x:Type local:Class}">
            <StackPanel Orientation="Vertical" >
                <TextBlock Text="{Binding Artikelname}"></TextBlock>
                <TextBlock x:Name="Foto" Text="{Binding Foto}" Visibility="Collapsed"></TextBlock>
            </StackPanel>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="True">
                    <Setter TargetName="Foto" Property="Visibility" Value="Visible"></Setter>
                </DataTrigger>
            </DataTemplate.Triggers>
</DataTemplate>

Das ging relativ einfach. Ein anderer Lösungsweg wäre, die Zusatzinformationen über den ItemTemplateSelector an zu zeigen. Wie man die Zusatzinformationen über den ItemTemplateSelector anzeigt wird hier erklärt.

http://www.developingfor.net/net/dynamically-switch-wpf-datatemplate.html


Page 1 of 212

ItemsSource zu UserControl hinzufügen

By
on Regards: .NET Framework; Archive; C#; XAML;

1. ItemsSource

wenn man ein (User)Control erstellt, benötigt dieses meistens Daten die es verarbeiten bzw. anzeigen soll. Dabei stellt sich die Frage, wie man dem UserControl die zu verarbeitenden Daten übergeben soll. Soll man dies über den DataContext, über eine Methode, oder über eine Propertie realisieren?

Bei den Standard Controls passiert das setzen der Daten über eine DependencyProperty. Für ein UserControl schien mir der Name ItemsSource für die DependencyProperty am geeignetsten. Mit einer DependencyProperty (abkürzung DP) kann man die Daten, nicht nur über den Code-Behind, sondern bequem im XAML dem UserControl “übergeben”. Man setzt dieses Propertie damit das UserControl die Daten verarbeiten und anzeigen kann.

Theoretisch könnte man auch mit dem setzten des DataContext die Daten dem UserControls übergeben. Allerdings ist der DataContext vom Typ object und der Anwender der das UserControl verwendet weiß nicht, welchen DatenTyp das UserControl erwartet. Außerdem sollten wir es vermeiden uns auf Dinge von “außen” zu verlassen.

1.1 Richtiger DatenTyp für die Propertie ItemsSource wählen

Wenn man meint, dass man bei der Wahl des DatenTyps bei der ItemsSource nichts besonderes beachten muss, dann ist man etwas zu voreilig. Das UserControl sollte wenn möglich mit so vielen DatenTyp wie möglich arbeiten können. Das beste Beispiel dazu ist die ListBox oder die ListView. Sie unterstützen Listen, Arrays eben alle DatenTypen welche das Interface IEnumerable implementieren. Deshalb sollte als DatenTyp für die Propertie ItemsSource ein möglichst flexibler DatenTyp zum Einsatz kommen. Das kann man z.B. mit Interfaces erreichen.

1.2 ItemsSource – Daten für die Anzeige vorbereiten

Die ItemsSource im UserControl kann die übergebenen Daten als DataContext setzten. Das erleichter das Binden im UserControl. Besser wäre aber, wenn im UserControl das Binding immer mittels RelativeSource gesetzt wird. Was für einen Grund das hat, wird im nächsten Abschnitt erklärt.

Das UserControl hat eine DependencyProperty ItemsSource. Das UserControl setzt den DataContext auf sich selber. Somit kann man im XAML einfach auf die ItemsSource binden.

<TextBlock Text="{Binding Path=ItemsSource.Text}"></TextBlock>

Folgendes Problem kann auftreten. Das UserControl wird in einem DataTemplate oder in einem anderem verschachtelt Element verwendet. Wenn wir z.B. an unserem UserControl irgend ein Binding setzen kommt es zu einem Binding Error. Aber warum?

Wir gehen davon aus, dass das UserControl genau den gleichen DataContext wie das Window besitzt. Sämtliche Standard UserControls, wie zum Beispiel TextBox’es, ListViews, erben den DataContext vom Übergeordneten Control. Weil aber das UserControl den DataContext auf sich selber setzt, haben wir einen anderen DataContext wie beim Window.

Diese Problem kann man mithilfe der RelativeSource umgehen. Hätte das Binding wie folgt ausgeshene, wäre es egal, welchen DataContext das UserControl besitzt.

<TextBlock Text="{Binding Path=ItemsSource.Text,RelativeSource=
	        {RelativeSource Mode=FindAncestor,AncestorType={x:Type local:UserControlName}}}"></TextBlock>

Dennoch gibt es Gegebenheit in dem ein Binding ohne RelativeSource berechtigt ist.

1.3 UserControl mit der ItemSource vom Type IEnumerable

Im folgenden Beispiel wird eine ItemsSource für ein UserControl erstellt, welches eine Liste von Objekten anzeigt. Die ItemsSource ist eine DependencyProperty und verwendet als DatenTyp das Interface IEnumerable. Somit wird gewährleistet, dass man die Objekte in einer Liste, Array usw. übergeben kann.

Im Beispiel Projekt habe ich eine Solution angelegt, welches ein ClassControl Projekt (beinhaltet UserControl und ObjektTyp) und ein WPF Window Projekt enthält um das UserControl zu testen.

Das UserControl zeigt eine Liste vom Objekttyp Class an. Zugegebenermaßen – der Name ist unpassend gewählt. Der ObjektTyp Class sieht wie folgt aus:

        [assembly: XmlnsDefinition("http://schemas.get.com/winfx/2009/xaml", "ClassControl")]
	namespace ClassControl
	{
	    public class Class
	    {
	        public Class()
	        {
	            StringList = new ObservableCollection<string>();
	        }
	        public string Artikelname { get; set; }
	        public string Foto { get; set; }
	        public ObservableCollection<string> StringList { get; set; }
	    }
	 
	}

Informationen zum Attribut XmlnsDefinition siehe hier. Das UserControl hat eine DependencyPropertie mit dem DatenTyp IEnumerable. Als ContentProperty wird die Propertie ItemsSource angegeben. So muss man im XAML nicht den Tag <ItemsSource> angeben, wenn man die Daten setzen will. Mehr dazu nach dem CodeBehind.

UserControl1.xaml.cs:

        using System.Collections;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Markup;
	 
	[assembly: XmlnsDefinition("http://schemas.get.com/winfx/2009/xaml", "ClassControl")]
	namespace ClassControl
	{
	    /// <summary>
	    /// Interaction logic for UserControl1.xaml
	    /// http://msdn.microsoft.com/de-de/library/system.windows.markup.contentpropertyattribute.aspx
	    /// </summary>
	    [ContentProperty("ItemsSource")]
	    public partial class UserControl1 : UserControl
	    {
	        public UserControl1()
	        {
	            InitializeComponent();
	        }
	        public IEnumerable ItemsSource
	        {
	            get { return (IEnumerable)GetValue(_ItemsSourceProperty); }
	            set { SetValue(_ItemsSourceProperty, value); }
	        }
	 
	        // IEnumerable als Type verwenden - 
                // damit werden die verschiedensten Typen unterstützt wie List<Class>, Array<Class> ...
	        public static readonly DependencyProperty _ItemsSourceProperty =
	            DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new UIPropertyMetadata(null));
	    }
	}

Das Binding wird mittels RelativeSource umgesetzt.
UserControl1.xaml:

           <UserControl
	    x:Class="ClassControl.UserControl1"
	    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	    xmlns:local="clr-namespace:ClassControl"
	    Background="Red">
	    <UserControl.Resources>
	        <DataTemplate DataType="{x:Type local:Class}">
	            <StackPanel>
	                <Border BorderBrush="Black" BorderThickness="1">
	                    <TextBlock Text="{Binding Path=Artikelname}"></TextBlock>
	                </Border>
	                <Border BorderBrush="Black" BorderThickness="1">
	                    <TextBlock Text="{Binding Path=Foto}"></TextBlock>
	                </Border>
	            </StackPanel>
	        </DataTemplate>
	    </UserControl.Resources>
	    <StackPanel Orientation="Vertical">
	        <!-- Wir verwenden den DataContext vom UserControl nicht;
	        Mit RelativeSource funktioniert das Binding immer, auch wenn der falsche DataContext gesetzt ist. -->
	        <ItemsControl VerticalAlignment="Top" HorizontalAlignment="Left" Margin="2" ItemsSource="{Binding Path=ItemsSource,
	            RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:UserControl1}}}" />
	    </StackPanel>
	</UserControl>

Und so bindet man das UserControl im Window ein:

                <!--UserControl mit ItemsSource vom Typ IEnumerable-->
	        <controls:UserControl1 Name="_ClassControl">
	          <!-- Weil das UserControl das Attribut [ContentProperty("ItemsSource")] besitzt,
	            müssen wir den Array nicht implizit der ItemsSource zuweisen z.B. so
	 
	            <controls:UserControl1.ItemsSource>
	
	                <x:Array Type="{x:Type controls:Class}">
	                    <controls:Class Artikelname = "Amazon Buch" Foto = "Buch.jpg"></controls:Class>
	                </x:Array>
	            </controls:UserControl1.ItemsSource>
	            -->
	            <x:Array Type="{x:Type controls:Class}">
	                <controls:Class Artikelname = "Amazon Buch" Foto = "Buch.jpg"></controls:Class>
	                <controls:Class Artikelname = "C#" Foto = "Foto.jpg"></controls:Class>
	                <controls:Class Artikelname = "Lebenslauf Buch" Foto = "Me.jpg"></controls:Class>
	            </x:Array>
	        </controls:UserControl1>

Da das UserControl das ContentPropertie Attribut angegeben hat, muss man nicht zwingend den ItemsSource Tag angeben.

Im Prinzip gibt es bei dieser Thematik nichts kompliziertes, man sollte jedoch die kleinen aber feinen Details genug Beachtung schenken, damit man sich später änderungen am UserControl ersparen kann.

Im Beispiel Projekt wird eine WPF-Benutzersteuerelementbibliothek verwendet. Es sollte beachtet werden, dass ein UserControl keine ControlTemplates unterstützt. Die alternative dazu wäre ein Benutzerdefiniertes WPF-Steuerelementbibliothek Projekt.

 


Page 1 of 212