get-the-solution

ItemsSource zu UserControl hinzufügen

By
on Regards: .NET Framework; 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 übergeben.

Meine Idee ist, eine DependencyProperty namens ItemsSource zu erstellen. So kann man die Daten bequem im XAML Code dem UserControl “übergeben”. Man setzt dieses Propertie damit das UserControl die Daten verarbeiten und anzeigen kann. Warum die Propertie ItemsSource heißt, hat einen bestimmten Grund.

Eine ListBox, ListView… wird z.B. auch über das Property ItemsSource mit Daten gefüllt. Somit würden wir uns in gewisserweise an die Namenskonventionen von Microsoft halten. Jeder Dritte der unser UserControl verwendet, weiß sofort was er mit diesem Propertie machen kann oder sollte. Eine Propertie besteht aus einem Modifizierer DatenTyp und Name. Somit weiß der Anwender auch, mit welchem DatenTyp das UserControl arbeiten kann.

Theoretisch könnte man auch mit dem setzten des DataContext die Daten des UserControls anzeigen lassen. Allerdings ist der DataContext vom Typ object und der Anwender der das UserControl verwendet weiß nicht, welchen DatenTyp das UserControl erwartet.

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 immer ein Interface zum Einsatz kommen.

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 Window verwendet welches sich in einem DataTemplate oder in einem anderem verschachtelt Element befindet. Wenn wir z.B. an unser 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

Das erstellen einer ItemsSource für das UserControl welches eine Liste von Objekten anzeigen soll, ist dabei wesentlich einfacher als das erstellen einer ItemsSource für ein UserControl welches ein ViewModel oder sonst einen spezifischen DatenType verwendet.

Dann muss man nämlich ein Interface für den DatenTyp bzw. das Objekt erstellen und sich als nächstes überlegen, ob das Binding mittels RelativeSource oder das sonst übliche Binden auf den DataContext verwendet wird um das Vorhaben zu realisieren.

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. Damit man im XAML beim übergeben der Daten nicht den Tag angeben muss. 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. Das wars.

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.

Verwandte Themen:

UserControl in UserControl erbt DataContext http://msdn.microsoft.com/de-de/library/system.windows.controls.usercontrol.aspx http://www.wpftutorial.net/CustomVsUserControl.html

Beispiel Projekt herunterladen