Laden...

Binding einer Eigenschaft an DependencyProperty eines UserControls

Erstellt von tkrasinger vor 14 Jahren Letzter Beitrag vor 14 Jahren 3.395 Views
T
tkrasinger Themenstarter:in
574 Beiträge seit 2008
vor 14 Jahren
Binding einer Eigenschaft an DependencyProperty eines UserControls

(Ich weiß es ist etwas lang)
Ich binde eine Eigenschaft einer Klasse an eine DependencyProperty eines UserControls. Das funktioniert einwandfrei. Wenn ich allerdings nun auf dem HauptWindow den kompleten DataContext ändere, wird die Property, die an die DP vom UserControl gebunden ist, nicht neu gebunden und im Label des UserControls bleibt der "alte" Wert stehen. Wie kann ich erreichen, dass ich die Änderung auch im UserControl mitbekomme bzw. warum wird das Binding am UserControl nicht aktualisiet?

<Window x:Class="WPFTest.ChildBindingTest"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:c="clr-namespace:WPFTest"
    Title="ChildBindingTest" SizeToContent="WidthAndHeight">
    <Grid>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <Label>Name:</Label><Label Content="{Binding Name}"></Label>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label>Child Name:</Label><Label Content="{Binding Child.Name}"></Label>
            </StackPanel>
            <c:TestControl Value="{Binding Child}"/>
            <Button Name="btnChangeContext" Click="btnChangeContext_Click">Change DataContext</Button>
        </StackPanel>
    </Grid>
</Window>
public partial class ChildBindingTest : Window
    {
        public ChildBindingTest()
        {
            InitializeComponent();
            BindableClass obj = new BindableClass() { Name = "Person 1", Child = new BindableClass() { Name = "Child Person 1" } };
            this.DataContext = obj;
        }

        private void btnChangeContext_Click(object sender, RoutedEventArgs e)
        {
            BindableClass obj = null;
            if (((BindableClass)this.DataContext).Name == "Person 2")
                 obj = new BindableClass() { Name = "Person 1", Child = new BindableClass() { Name = "Child Person 1" } };
            else
                obj = new BindableClass() { Name = "Person 2", Child = new BindableClass() { Name = "Child Person 2" } };
            this.DataContext = obj;
        }
    }

    public class BindableClass : INotifyPropertyChanged
    {
        private BindableClass _Child;
        public BindableClass Child {
            get { return _Child; }
            set { _Child = value; OnPropertyChanged("Child"); }
        } 

        private string _Name;
        public string Name {
            get { return _Name; }
            set { _Name = value; OnPropertyChanged("Name"); }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propName) {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
<UserControl x:Class="WPFTest.TestControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Background="LightBlue" Orientation="Horizontal">
        <Label>Child Control:</Label><Label Content="{Binding Name}"></Label>
    </StackPanel>
</UserControl>
    public partial class TestControl : UserControl
    {
        public TestControl() {
            InitializeComponent();
        }

        public BindableClass Value {
            get { return (BindableClass)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public void SetValue(BindableClass value) {
            this.DataContext = value;
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", 
            typeof(BindableClass), 
            typeof(TestControl), 
            new UIPropertyMetadata(null, ValueChanged));

        private static void ValueChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != null) {
                TestControl owner = (TestControl)element;
                owner.SetValue(e.NewValue as BindableClass);
            }
        }
    }

PS:
Wechsle ich den Code im btnClick auf:

if (((BindableClass)this.DataContext).Name == "Person 1")
                ((BindableClass)this.DataContext).Child.Name = "Person 3";

dann geht die Änderung auch bis ins UserControl durch.

U
1.578 Beiträge seit 2009
vor 14 Jahren

hast du beim ValueChanged mal n breakpoint gesetzt und geschaut ob auch das neue DataContext ankommt ?
wenn ja wirds vermutlich so sein das das datacontext zwar gesetzt wird - aber die ui einfach nicht aktualisiert wird

T
tkrasinger Themenstarter:in
574 Beiträge seit 2008
vor 14 Jahren

hast du beim ValueChanged mal n breakpoint gesetzt und geschaut ob auch das neue DataContext ankommt ?
wenn ja wirds vermutlich so sein das das datacontext zwar gesetzt wird - aber die ui einfach nicht aktualisiert wird

Hab ich gemacht, ich komm beim Button Click weder in ValueChanged, noch in den setter von Value. Ich hab auch einen Breakpoint in den getter der Eigenschaft "Child" reingesetzt. Dort komm ich beim Button Click nur einmal rein. Da vermute ich, dass das aber wohl vom Binding "Child.Name" kommt.
Für mich scheint es so, als würde das Binding überhaupt gar nicht mehr angegriffen werden. Wenn ich die Form lade, komm ich nämlich 3 mal in "Child" vorbei (bei Child.Name, für Child selbst und im TestControl bei SetValue).

U
1.578 Beiträge seit 2009
vor 14 Jahren

ich seh keine position wo du "Value =" von deinem TestControl aufrufst ?!
wo setzt du den datacontext des controls neu ?

T
tkrasinger Themenstarter:in
574 Beiträge seit 2008
vor 14 Jahren

In:

<c:TestControl Value="{Binding Child}"/>

U
1.578 Beiträge seit 2009
vor 14 Jahren

ah ok - nun seh ich klar
ich vermute das das UserContol noch separat inforiert werden muss

das UserControl hat zwar das PropertyChanged von child schon geholt - nur du erstellst ja jedesmal ein neues objekt - dann muss er das propertychanged auch neu abonieren

hier mal ein vorschlag wenn ich darf:


public partial class ChildBindingTest : Window, INotifyPropertyChanged
{
    public ChildBindingTest()
    {
        InitializeComponent();
        Data = new BindableClass() { Name = "Person 1", Child = new BindableClass() { Name = "Child Person 1" } };
        
    }

    private void btnChangeContext_Click(object sender, RoutedEventArgs e)
    {
        if (Data.Name == "Person 2")
            Data = new BindableClass() { Name = "Person 1", Child = new BindableClass() { Name = "Child Person 1" } };
        else
            Data = new BindableClass() { Name = "Person 2", Child = new BindableClass() { Name = "Child Person 2" } };
    }

    public BindableClass Data
    {
        get { return _data; }
        set
        {
            _data = value;
            OnPropertyChanged("Data");
        }
    }
    private BindableClass _data;

    private void OnPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }    
    public event PropertyChangedEventHandler PropertyChanged;
}


<Window x:Class="WPFTest.ChildBindingTest"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:c="clr-namespace:WPFTest"
    Title="ChildBindingTest" SizeToContent="WidthAndHeight"
    DataContext="{Binding Data}"> <!-- -->
    <Grid>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <Label>Name:</Label><Label Content="{Binding Name}"></Label>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label>Child Name:</Label><Label Content="{Binding Child.Name}"></Label>
            </StackPanel>
            <c:TestControl Value="{Binding Data}"/> <!-- -->
            <Button Name="btnChangeContext" Click="btnChangeContext_Click">Change DataContext</Button>
        </StackPanel>
    </Grid>
</Window>

die idee dahinter ist das man das was man binden laesst auch ueber aenderung des objektes informiert
im code brauchst du dann nur Data aktualisieren

(btw - DataContext ist ein dependency property - dh du brauchst "Value" gar nicht sondern kannst direkt DataContext binden - genau wie in mein beispiel beim hauptfenster)

T
tkrasinger Themenstarter:in
574 Beiträge seit 2008
vor 14 Jahren

hm, hab deinen Code ausprobiert, da erscheint überhaupt nix, stattdessen seh ich im output:

System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=Data; DataItem=null; target element is 'ChildBindingTest2' (Name=''); target property is 'DataContext' (type 'Object')

(btw - DataContext ist ein dependency property - dh du brauchst "Value" gar nicht sondern kannst direkt DataContext binden - genau wie in mein beispiel beim hauptfenster)

stimmt wenn ich <c:TestControl DataContext="{Binding Child}" ../> schreibe funktionierts.

Hab jetzt mal wieder ein bissl rumprobiert und folgendes festgestellt. Sobald ich im TestControl den eigenen DataContext umstel, bekomm ich vom parent keine Änderungen mehr mit.