Laden...

Tatsächlich gebundenes Element ungleich DataContext

Erstellt von tkrasinger vor 14 Jahren Letzter Beitrag vor 14 Jahren 933 Views
T
tkrasinger Themenstarter:in
574 Beiträge seit 2008
vor 14 Jahren
Tatsächlich gebundenes Element ungleich DataContext

Ich habe in einem Window eine TextBox: <TextBox Text="{Binding Id}" Name="txtId"/>

Im Code setze ich dann am Window: this.DataContext = CurrentObject;

Die Id wird richtig angezeigt.
Dann hab ich einen Button in dessen Code das CurrentObject mittels WCF Service gespeichert wird. Vom Service krieg ich das gespeicherte Objekt wieder zurück und ich setze dann den DataContext neu (objekt hat eine neue Id bekommen):

this.DataContext = savedObject;

Mein Problem ist jetzt, dass sich der Text in der TextBox aber nicht ändert.

Der Event "DataContextChanged" der TextBox wird aufgerufen. Genau einmal und zwar zu dem Zeitpunkt zu dem ich den DataContext am Window neu setze.
Die TextBox hat auch einen neuen DataContext, wenn ich mir allerdings über txtId.GetBindingExpression(TextBox.TextProperty) das Binding hole, stelle ich fest, dass sich das DataItem im Binding nicht verändert hat. Da ist immer noch mein altes Objekt drinnen.

Wenn ich nach dem Speichern den DataContext zuerst auf null setze, und dann erst das gespeicherte Objekt reinsetze, wird der neue Text angezeigt. Die Variante wär aber nur eine Notlösung.

Was kann da mit dem Binding schiefgelaufen sein? Hab überhaupt keinen Plan mehr wo ich mich reinhängen könnte, um festzustellen ob am Binding irgendwas verändert wird.

446 Beiträge seit 2004
vor 14 Jahren

Hallo,

ich glaube du solltest einen anderen Weg einschlagen.

Den DataContext solltest du während der Laufzeit nicht ändern müssen.

Außerdem kostet das setzen des DataContext bei größeren Objekten viel performance.

Du solltest folgende Schicht haben

View - ViewModel - WCF

Sprich, auf die Property auf die du bindest, sollte immer den aktuellen Wert des WCF Service zurück geben.

Dann must du auch nicht den DataContext neu setzen.

Schaut mal im IRC vorbei:
Server: https://libera.chat/ ##chsarp

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

hm, also nach sehr vielen Versuchen wo was schief läuft hab ichs jetzt endlich gefunden. Da ich mir denke, dass sowas vielleicht jemanden anderen auch mal passieren kann, da der Fehler wohl ein "Feature" (grml) der WPF ist hier mein Ergebnis:

Vereinfacht hatte ich folgendes:

Ein WCF Service stellt eine Methode OpenObject(id) zur Verfügung welche eine Instanz der Klasse A liefert. Diese hat die Eigenschaften "LastOpenTime" und "Id".
In der Service Methode wird ein Objekt der Klasse A instanziert und die Eigenschaft "LastOpenTime " immer mit der aktuellen Zeit befüllt.

In einem WPF-Control gibts einen Button um ein Objekt mit einer ID vom Service zu holen, die gelieferte Instanz von A wird dann dem DataContext des Controls zugewiesen. 2 Textboxen mit entsprechenden Bindings zeigen die Eigenschaften Id und LastOpenTime an.

Wenn man also immer wieder auf den Button clickt, sollte sich immer die Anzeige in jener TextBox ändern, in der "LastOpenTime" angezeigt wird.

Das war bei mir aber nicht der Fall. Die Textbox hat immer nur nach dem ersten WCF-Aufruf etwas angezeigt, dann hat sich die Anzeige nicht mehr verändert.

Nun, warum?

Neben der Klasse A gabs auch noch die Klassen B und C die nicht abgeleitet aber relativ ähnlich sind. Auch B und C hatten eine Eigenschaft "Id". Aufgrund einer gewissen BusinessLogik hätte es sein können, dass Ids über die Klassen hinweg (keine Identity!) gleich sind. Dass also z.B A.Id gleich B.Id ist. Also hab ich in der Klasse A die Methode .Equals() überschrieben und geprüft ob die ID des zu vergleichenden Objekts mit der this.Id übereinstimmt.

Anscheinend macht die WPF beim DataBinding aber folgendes, beim Aufruf der Zeile:

this.ControlToDisplayA.DataContext = openedA;

Hier geht WPF her und macht quasi einen "Nichts unnötig machen"-Check und vergleicht ob alt und neu im DataContext das selbe wären. Dieser Vergleich geschieht nun aber über Equals()-Methode und nicht über ==.
Da sich aber beim Holen der Instanz von A nie die ID verändert hat, sondern immer nur LastOpenTime, hat die WPF immer gemeint, das Objekt, das ich neu in den DataContext setzen will, ist das selbe was vorher drinnen war (laut Equals()-Methode) und ändert daher nichts*.

Die überschriebene Equals()-Methode einmal rausgenommen und schon funktioniert alles wie gewünscht.

*Nichts ist leider nicht ganz richtig, da sich der DataContext sehr wohl geändert hat, am Control und auch auf den TextBoxen die die Werte anzeigen. Was sich nicht verändert hat, war die interne DataItem/Source-Eigeschaft die im Binding der TextBoxen gespeichert war.
Dadurch hatte ich die Situation, dass die TextBox eigentlich den richtigen DataContext hatte und immer das richtige Objekt bekam, aber immer nur das inital eingesetzte Objekt angezeigt hat.

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

Noch einfacher:

<Window x:Class="ID75930.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBox Text="{Binding Guid}" Name="txtGuid"></TextBox>
        <TextBox Text="{Binding Id}"></TextBox>
        <TextBox Text="{Binding ElementName=txtGuid, Path = DataContext}"></TextBox>
        <Button Name="btnCreateNew" Click="btnCreateNew_Click">Create New</Button>
        <Button Name="btnSwitch" Click="btnSwitch_Click">Switch Type</Button>
    </StackPanel>
</Window>

using System;
using System.Windows;

namespace ID75930
{
    public partial class Window1 : Window
    {
        private bool _showA;

        public Window1()
        {
            InitializeComponent();
        }

        void btnSwitch_Click(object sender, RoutedEventArgs e)
        {
            _showA = !_showA;
        }

        void btnCreateNew_Click(object sender, RoutedEventArgs e)
        {
            if (_showA)
                this.DataContext = new A();
            else
                this.DataContext = new B();
        }
    }

    public class A
    {
        public A()
        {
            this.Id = 1;
            this.Guid = Guid.NewGuid();
        }

        public int Id { get; set; }

        public Guid Guid { get; set; }

        public override bool Equals(object obj)
        {
            if (obj is A)
                return ((A)obj).Id == this.Id;
            else if (obj is B)
                return ((B)obj).Id == this.Id;
            else
                return false;
        }
        public override string ToString()
        {
            return "A:" + this.Id + ":" + this.Guid.ToString();
        }
    }

    public class B
    {
        public B()
        {
            this.Id = 2;
            this.Guid = Guid.NewGuid();
        }

        public int Id { get; set; }

        public Guid Guid { get; set; }

        public override string ToString()
        {
            return "B:" + this.Id + ":" + this.Guid.ToString();
        }
    }
}