Benutzer mit den meisten Antworten
Databinding zwischen UserControl und MainWindow

Frage
-
Hallo zusammen,
ich habe ein UserControl mit einer Textbox
<UserControl x:Class="Test.MyUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:Test="clr-namespace:Test" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <TextBox Text="{Binding Path=MyText}"/> </Grid> </UserControl>
und mein MainWindow mit einer String-Property:
public partial class MainWindow : Window { public string MyText { get; set; } public MainWindow() { InitializeComponent(); MyText = "Hallo"; } }
Wie bekomme ich nun die Textproperty aus dem MainWindow an mein Databinding im UserControl gebindet? Gibt es da ein Stichwort / eine Anleitung bzw. Beispiel wie man so etwas macht?
Danke für die Hilfe!
Antworten
-
Hi,
hier mal eine Demo:UserControl XAML_
<UserControl x:Class="WpfApplication1.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfApplication1" mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="200" DataContext="{Binding RelativeSource={RelativeSource Self}}"> <Grid> <TextBox Text="{Binding Path=MyText}"/> </Grid> </UserControl>
UserContol CodeBehind
using System.Windows; using System.Windows.Controls; namespace WpfApplication1 { public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); } public string MyText { get { return (string)GetValue(MyTextProperty); } set { SetValue(MyTextProperty, value); } } public static readonly DependencyProperty MyTextProperty = DependencyProperty.Register("MyText", typeof(string), typeof(UserControl1), new PropertyMetadata("<leer>")); } }
XAML MainWindow:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApplication1" mc:Ignorable="d" Title="MainWindow" Height="300" Width="300" DataContext="{Binding RelativeSource={RelativeSource Self}}"> <StackPanel> <TextBox Name="tb" Text="{Binding Info}"/> <local:UserControl1 MyText="{Binding ElementName=tb, Path=Text}"/> </StackPanel> </Window>
MainWindow CodeBehind:
using System.Windows; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } public string Info { get; set; } = "<keine Info>"; } }
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut- Als Antwort markiert Kampino89 Samstag, 27. Februar 2016 23:57
Alle Antworten
-
Hallo,
da fehlt noch so einiges damit eine Datenbindung funktioniert. Ich weiß nicht was du bereits kennst, darum hier eine kurze Liste mit Stichworten:
- INotifyPropertyChanged implementieren um PropertyChanged im Setter von MyText auszulösen
(Wobei das bei Dependency Properties nicht notwendig ist, lediglich bei "normalen" Eigenschaften) - DataContext des Windows festlegen damit auch die Eigenschaft gefunden wird. Bzw. DataContext des UserControls auf das Window legen um die Bindung direkt zu ermöglichen.
- Richtiges ViewModel erstellen? Stichwort MVVM
- Trennung der einzelnen Elemente. Statt den DataContext von außen zuzuweisen und innen darauf zu vertrauen dass die Eigenschaften alle da sind, solltest du Dependency Properties im UserControl erstellen an die dann von außen MyText- und im UserControl selbst die TextBox gebunden wird.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets- Als Antwort vorgeschlagen Tom Lambert (Koopakiller)Moderator Sonntag, 28. Februar 2016 01:47
- Bearbeitet Tom Lambert (Koopakiller)Moderator Samstag, 20. Juli 2019 15:12 Korrektur, siehe Antwort
- INotifyPropertyChanged implementieren um PropertyChanged im Setter von MyText auszulösen
-
Hi,
alles, was Tom geschrieben hat, ist zu machen.Das Wichtigste und Komplizierteste ist aber die Eigenschaft (Property MyText) in Deinem UserControl. Damit diese Bindung innerhalb des UserControls funktioniert, muss das UserControl auch einen DataContext haben, der auf das Objekt verweist, in welchem sich die Eigenschaft MyText befindet, z.B. auf sich selbst. Damit Änderungen des Inhaltes von MyText auch in der Oberfläche sichtbar werden, muss in der Eigenschaft NotifyPropertyChanges ausgelöst werden.
Wenn diese Eigenschaft (MyText) als Eigenschaft des UserControls auch von außen (z.B. MainWindow) gebunden werden soll, dann muss MyText als DependencyProptery im UserControl definiert sein.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hallo,
vielen Dank für die Antwort. Ich habe bisher schon etwas mit Databindings gearbeitet und ein paar Grundstrukturen sind auch soweit verständlich. Die INotifyPropertyChanged-Schnittstelle habe ich vergessen. Die kommt auf jeden Fall noch rein.
In erster Linie wollte ich in einem kleinen Projekt probieren wie ich eine Datenbindung zwischen UserControl und MainWindow hin bekomme.
Die Stichwörter "DependencyProperty" und "MVVM" sagen mir nichts. Was ist das? Gibt es da ein gutes Tutorial um sich was anzulesen (habe das Stichwort "DependencyProperty" immer nur in Kombination mit großen Codes gesehen, aber nie wirklich als Erklärung).- Bearbeitet Kampino89 Samstag, 27. Februar 2016 18:15
-
Hi,
hier mal eine Demo:UserControl XAML_
<UserControl x:Class="WpfApplication1.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfApplication1" mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="200" DataContext="{Binding RelativeSource={RelativeSource Self}}"> <Grid> <TextBox Text="{Binding Path=MyText}"/> </Grid> </UserControl>
UserContol CodeBehind
using System.Windows; using System.Windows.Controls; namespace WpfApplication1 { public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); } public string MyText { get { return (string)GetValue(MyTextProperty); } set { SetValue(MyTextProperty, value); } } public static readonly DependencyProperty MyTextProperty = DependencyProperty.Register("MyText", typeof(string), typeof(UserControl1), new PropertyMetadata("<leer>")); } }
XAML MainWindow:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApplication1" mc:Ignorable="d" Title="MainWindow" Height="300" Width="300" DataContext="{Binding RelativeSource={RelativeSource Self}}"> <StackPanel> <TextBox Name="tb" Text="{Binding Info}"/> <local:UserControl1 MyText="{Binding ElementName=tb, Path=Text}"/> </StackPanel> </Window>
MainWindow CodeBehind:
using System.Windows; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } public string Info { get; set; } = "<keine Info>"; } }
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut- Als Antwort markiert Kampino89 Samstag, 27. Februar 2016 23:57
-
Hallo Peter,
dank dir für das Beispiel. Ich habe direkt noch die ein oder andere Frage (rein zum Verständnis).
Du legst den Datenkontext der Fenster mitDataContext="{Binding RelativeSource={RelativeSource Self}}
auf das Fenster selber. Wenn ich das richtig verstehe ist das das Äquivalent zu
this.DataContext = this;
Liege ich damit richtig?
In MainWindow.xaml legst du die Text-Property der Textbox "tb" mittels Binding auf "Info". Für das UserControl greifst du anschließend auf die Textbox zu und holst dir die Text-Property aus dem Control um den Inhalt an "MyText" zu binden. Wenn ich nun die Textbox weg lassen möchte und direkt schreibe
<Test:MyUserControl MyText="{Binding Info}"/>
erscheint die Ausgabe "<leer>" in der Textbox. Kannst du mir erklären wieso?
Nochmals vielen Dank :)
-
Hallo Kampino,
zu deinen ersten Fragen:
Eine DependencyProperty ist sozusagen eine von WPF verwaltete Eigenschaft. Du kannst sie ganz normal setzen, aber der Wert wird nicht in einer normalen Variable (Feld) sondern in einem großes Dictionary gespeichert. Darauf haben praktisch alle Controls in WPF zugriff und darüber werden Datenbindungen und Animationen erstellt.
MVVM ist eine Technik um die Daten, die Logik und die UI zu trennen. MVVM steht für Model-View-ViewModel
Deine Annahme über this.DataContext=this; und der XAML-Version ist richtig.
DependencyProperty's (DP) haben einen Standardwert der verwendet wird. Den hat Peter in seiner Deklaration der DP angegeben.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets -
Hallo Peter,
dank dir für das Beispiel. Ich habe direkt noch die ein oder andere Frage (rein zum Verständnis).
Du legst den Datenkontext der Fenster mitDataContext="{Binding RelativeSource={RelativeSource Self}}
auf das Fenster selber. Wenn ich das richtig verstehe ist das das Äquivalent zu
this.DataContext = this;
Liege ich damit richtig?
In MainWindow.xaml legst du die Text-Property der Textbox "tb" mittels Binding auf "Info". Für das UserControl greifst du anschließend auf die Textbox zu und holst dir die Text-Property aus dem Control um den Inhalt an "MyText" zu binden. Wenn ich nun die Textbox weg lassen möchte und direkt schreibe
<Test:MyUserControl MyText="{Binding Info}"/>
erscheint die Ausgabe "<leer>" in der Textbox. Kannst du mir erklären wieso?
Nochmals vielen Dank :)
Habs schon gefunden.
Der "Trick" ist das MainWindow als Element anzuegeben:<Test:MyUserControl MyText="{Binding ElementName=MyMainWindow, Path=Text}"/>
Damit wäre alles erledigt :)
-
Du legst den Datenkontext der Fenster mit
DataContext="{Binding RelativeSource={RelativeSource Self}}
auf das Fenster selber. Wenn ich das richtig verstehe ist das das Äquivalent zu
this.DataContext = this;
Liege ich damit richtig?
Hi,
das Ergebnis (Wirkungsweise) ist identisch. Mit dem Self wird der DataContext auf die Instanz (this) gelegt.Akademisch betrachtet ist es aber eine unterschiedlich. Wenn Du wirklich Code vom Design trennen willst, stört die Zuweisung im Code. Designer der Oberfläche und Entwickler der Geschäftslogik einigen sich vorher, wie aus der Oberfläche die Objekte der Geschäftslogik zu erreichen sind. Wenn sich beide geeinigt haben, dass sich die Geschäftslogik im Codebehind befindet (noch kein MVVM), dann behält der Designer die Hoheit über sein Design und verweist mit Self auf den Codebehind. Mit der Zuweisung von this im Code mischt sich der Entwickler der Geschäftslogik in die Arbeit des Designers ein. Beim Redesign des Projektes kann das zu erhöhtem Aufwand führen, wenn beispielsweise die Geschäftslogik aus dem CodeBehind in separate Objekte ausgelagert wird, wie das im MVVM üblich ist.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Der "Trick" ist das MainWindow als Element anzuegeben:
<Test:MyUserControl MyText="{Binding ElementName=MyMainWindow, Path=Text}"/>
Damit wäre alles erledigt :)
Hi,
das ist kein Trick, sondern ein Lösungsweg, der aus meiner Sicht nicht gut ist.Bei einer Bindung benötigt man ein Objekt, an das zu binden ist, und eine Eigenschaft, in der sich die darzustellen Werte befinden. In Deinem dargestellten "Trick" benennst Du das Fenster (-Objekt) und nutzt aus diesem Objekt eine Eigenschaft, die sich im CodeBehind befindet.
Für einen später möglichen Übergang zu MVVM sollte man schon früh eine getrennte Zuweisung vorsehen. Als Objekt wird später eine Instanz des ViewModels angeben (z.B. über den DataContext) und als Pfad nur die konkrete aus dem ViewModel zu bindende Eigenschaft. Damit kann nur mit Nennung des Verweises im DataContext auf die Geschäftslogik im ViewModel umgeschaltet werden.
Es gibt natürlich auch Fälle, wo man das anders machen kann bzw. muss.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hallo zusammen,
danke für die Erläuterungen und die Hinweise.
Ich habe mal versucht diese Anmerkungen umzusetzen und mir aus diesem Grund mal ein Beispiel angeschaut (http://openbook.rheinwerk-verlag.de/visual_csharp_2012/1997_28_005.html).
Auf mein Projekt übertragen habe ich nun folgendes:Model:
namespace MVVM_Example { public class Image { /// <summary> /// /// </summary> public string ImagePath { get; set; } /// <summary> /// Konstruktor /// </summary> public Image() { ImagePath = "Bilder\\Bild.bmp"; } } }
View:
public partial class MainWindow : Window { public ViewModel Model { get; set; } public MainWindow() { InitializeComponent(); DataContext = new ViewModel(); } }
Und mein ViewModel:
public class ViewModel : INotifyPropertyChanged { /// <summary> /// Eventhandler /// </summary> public event PropertyChangedEventHandler PropertyChanged; private Image ImageModel; /// <summary> /// Property /// </summary> private BitmapImage _ImageView; public BitmapImage ImageView { get { return _ImageView; } set { if (_ImageView == value) { return; } _ImageView = value; OnPropertyChanged("ImageView"); } } /// <summary> /// Konstruktor /// </summary> public ViewModel() { ImageModel = new Image(); ImageView = new BitmapImage(new Uri(ImageModel.ImagePath, UriKind.Relative)); } /// <summary> /// /// </summary> /// <param name="propertyname"></param> protected internal void OnPropertyChanged(string propertyname) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyname)); } } }
Dazu dann meinen XAML-Code im MainWindow:
<Grid> <Main:ImageUserControl MyImage="{Binding ImageView}"/> </Grid>
Und mein UserControl:
public partial class ImageUserControl : UserControl { public static readonly DependencyProperty MyImageProperty = DependencyProperty.Register("MyImage", typeof(ImageSource), typeof(ImageUserControl)); public ImageSource MyImage { get { return (ImageSource)GetValue(MyImageProperty); } set { SetValue(MyImageProperty, value); } }
public ImageUserControl() { InitializeComponent(); } }
<UserControl x:Class="MVVM_Example.ImageUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MVVM_Example" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" DataContext="{Binding RelativeSource={RelativeSource Self}}"> <Grid> <Image Source="{Binding Path=MyImage}" /> </Grid> </UserControl>
Wenn ich das nun richtig umgesetzt habe, habe ich das Model mit meinen Daten (sprich in dem Falle nur einen Pfad zu einem Bild), mein View, welches beschreibt wie alles auszusehen hat (MainWindow mit UserControl) und dann das ViewModel, welches die Daten in MainWindow darstellt (also den Pfad in ein BitmapImage umwandeln).
Nur leider sehe ich das Bild nicht und VS gibt die MeldungSystem.Windows.Data Error: 40 : BindingExpression path error: 'ImageView' property not found on 'object' ''ImageUserControl' (Name='')'. BindingExpression:Path=ImageView; DataItem='ImageUserControl' (Name=''); target element is 'ImageUserControl' (Name=''); target property is 'MyImage' (type 'ImageSource')
aus. Wenn ich, wie in Peters Beispiel, schreibe
<Grid> <Image Name="Image" Source="{Binding ImageView}"/> <Main:ImageUserControl MyImage="{Binding ElementName=Image, Path=Source}"/> </Grid>
bekomme ich das Bild angezeigt. Wo steckt da noch der Denkfehler? -
Hallo,
nochmal kurz etwas zu MVVM und deiner Umsetzung. Wie Peter schon schrieb ist es zwar von der Funktion her egal ob man im XAML oder im C# Code den DataContext zuweist; sollte die Zuweisung aber im XAML statt finden ist es sauberer getrennt.
Weiterhin hast du im Codebehind des Windows eine Eigenschaft Model vom Typ ViewModel. Das passt so nicht, das Model ist nicht das ViewModel. Das ist zwar nur eine Frage der Benennung, ist aber trotzdem nicht sonderlich schön.
Wenn ich, wie in Peters Beispiel, schreibe
<Grid> <Image Name="Image" Source="{Binding ImageView}"/> <Main:ImageUserControl MyImage="{Binding ElementName=Image, Path=Source}"/> </Grid>
bekomme ich das Bild angezeigt. Wo steckt da noch der Denkfehler?Die ImageView-Eigenschaft ist Teil des ViewModels was dein UserControl nicht kennt. Dieses kennt nur sich selbst als DataContext und versucht auf die ImageView-Eigenschaft des UserControls zuzugreifen, die nicht existiert.
Durch Angabe des ElementName und einem "Proxy"-Control schafft man es aus dem DataContext des UserControls heraus zu kommen. Du könntest hier aber auch mittels RelativeSource arbeiten um auf den DataContext des MainWindows zu kommen.<Main:ImageUserControl MyImage="{Binding ImageView, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
So kann man sich das zusätzlche Control im Window sparen.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets- Bearbeitet Tom Lambert (Koopakiller)Moderator Sonntag, 28. Februar 2016 15:39
-
Hallo Tom,
ja du hast recht. Die Namensgebung war wirklich ein bisschen unglücklich gewählt. Das das Model und das ViewModel zwei unterschiedliche Konstruke sind hatte ich aber berücksichtigt. Ich habe es bisher so verstanden, dass das ViewModel die Schnittstelle zwischen den Models und dem View ist und damit die Quelle für die Datenbindungen darstellt.
Dein Codeschnipsel mit der RelativeSource funktioniert nicht. Es tritt der FehlerSystem.Windows.Data Error: 40 : BindingExpression path error: 'ImageView' property not found on 'object' ''MainWindow' (Name='MyMainWindow')'. BindingExpression:Path=ImageView; DataItem='MainWindow' (Name='MyMainWindow'); target element is 'ImageUserControl' (Name=''); target property is 'MyImage' (type 'ImageSource')
auf.
-
Hallo,
stimmt, das ViewModel ist so eine Art Vermittler.
Zu dem RelaiveSource: Das Beispiel enthält einen kleinen Fehler. RelativeSource ermittelt ein Element nach den angegebenen Suchmustern - hier das Window. Wenn du nun auf ein Element aus dem DataContext zugreifen willst, musst du auch angeben dass im DataContext gesucht werden muss:
<Main:ImageUserControl MyImage="{Binding DataContext.ImageView, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
Wenn du dir übrigens mal die Fehlermeldung ansiehst, dort steht dass die ImageView-Eigenschaft in einem Objekt vom Typ MainWindow nicht gefunden wurde. Mit etwas Erfahrung kann man aus den Meldungen ganz gut ablesen was man falsch gemacht hat.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets -
Ok.
Wenn ich das nun richtig verstehe bedeutet das soviel wie, dass von dem aktuellen Datenkontext(den ich ja auf ein Objekt der Klasse "ViewModel" lege, die Eigenschaft "ImageView" unter "MyImage" gebunden wird.
Mit der Angabe "AnchestorType={x:Type Window}" wird im Endeffekt mitgeteilt, dass die Basis für die relative Zuweisung das Window ist.
Sehe ich das soweit richtig?
- Bearbeitet Kampino89 Montag, 29. Februar 2016 12:37
-
Hast Recht, habe damals wohl übersehen, dass es eine DP ist. Habe meine Antwort dahingehend ergänzt
Viele Grüße, Tom Lambert - MVP, MCC und MSP
Wozu Antworten markieren und Posts bewerten? Klicke hier
Nützliche Links: .NET Quellcode | C#/VB.NET Konverter | GitHub Forum Samples | Account bestätigen (Verify Your Account)
Ich: Webseite | Facebook | Twitter | Code Snippets | GitHub