Laden...

Zur Laufzeit geänderter Datakontext wird in View nicht aktualisiert

Erstellt von DeSharper vor 7 Jahren Letzter Beitrag vor 7 Jahren 2.387 Views
D
DeSharper Themenstarter:in
40 Beiträge seit 2016
vor 7 Jahren
Zur Laufzeit geänderter Datakontext wird in View nicht aktualisiert

Hallo zusammen,

ich versuche mich gerade mit WPF anzufreunden und das gestaltet sich etwas zäher als gedacht.
Ich möchte eigentlich im ersten Schritt nur eine bunte Kachel mit einer Zahl drauf, wobei sowohl Zahl als auch Farbe erst zur Laufzeit festgelegt werden sollen. Das Binding scheint prinzipiell zu funktionieren, aber es werden nur die Default-Werte bzw. die Werte angezeigt, die ich im Standard-Konstruktor setze, alles was sich danach ändert, wird in der View nicht angezeigt.

Dafür hab ich mir ein ViewModel erstellt, dass DependencyProperties für beides hat. (Farbe hab ich der Übersichtlichkeit halber hier weggelassen)


    public class HexagonViewModel : DependencyObject 
    {
        public static readonly DependencyProperty NumberProperty = DependencyProperty.Register("Number", typeof(int), typeof(HexagonViewModel), new PropertyMetadata(2));

        public int Number
        {
            get { return (int)this.GetValue(NumberProperty); }
            set
            {
                if( value >= 2 && value <= 12 )
                {
                    this.SetValue(NumberProperty, value);
                }
            }
        }

        public HexagonViewModel()
        {
            Number = 5;
        }

        public HexagonViewModel(int number)
        {
            this.Number = number;
        }
}

Im xaml-Code sieht das folgendermaßen aus:


        <TextBlock 
              VerticalAlignment="Center" HorizontalAlignment="Center" 
              Margin="0" TextWrapping="Wrap" FontFamily="Arial"  
              FontSize="20" Foreground="Red"
              Text="{Binding Source={StaticResource ViewModel}, Path=Number, Mode=TwoWay}">
        </TextBlock>

Und im Code behind weise ich den Datakontext zu.


        public Hexagon_simple()
        {
            InitializeComponent();

            this.DataContext = new HexagonViewModel(12); // Versuch 1
            ((HexagonViewModel)DataContext).Number = 9; // Versuch 2
        }

Mein Problem ist jetzt, dass im View immer nur die 5 angezeigt wird, obwohl ich beim Debuggen sehen kann, dass inzwischen eine andere Zahl drinsteht. Ich bin nach über einer Stunde googeln noch nicth wirklich weitergekommen und hab ehrlich gesagt keine Ahnung, wo mein Problem liegt.

Zusätzlich hätte ich noch eine Frage. Ich hab beim Lesen immer nur Parameterlose Konstruktoren bei ViewModels gesehen. Muss das so sein? Ist es eine doofe Idee auf diese Art, also über die Parameter des Konstruktors, die Eigenschaften des Viewmodels festzulegen? Und kann ich verhindern, dass der parameterlose Konstruktor aufgerufen wird und direkt meinen "echten" Konstruktor verwenden? Den andern hab ich ja nur, weil sonst der compiler meckert.

P
441 Beiträge seit 2014
vor 7 Jahren

Hi,

du benötigst in deinem ViewModel keine DepencyProperty und auch keine Vererbung auf das DepencyObject.
Um Änderungen im ViewModel an die View weiterzugeben, muss dein ViewModel INotifyPropertyChanged implementieren.

Ein Beispiel findest du hier: [Artikel] INotifyPropertyChanged implementieren

D
DeSharper Themenstarter:in
40 Beiträge seit 2016
vor 7 Jahren

Sorry, ich kappiers nicht. Ich hab das jetzt so abgeändert, aber ich hab keine Ahnung, was ich in "OnPropertyChanged" machen soll? Im ViewModel wurden die Werte ja geändert so wie gedacht (das hat ja auch mit den dependencyProperties funktionert). Ich müsste aber doch jetzt irgendwie der View Bescheid sagen, dass sich was geändert hat und ich das bitte auch angezeigt haben will. Wie und wo kann ich das machen, wenn ich doch das ViewModel die View gar nicth kennt? Ich dachte mit dem tollen WPF-Databinding passiert das automatisch. Ich kappier das alles nicht, vielleicht zieh ich aufs Land und lerne Kühe melken.

5.658 Beiträge seit 2006
vor 7 Jahren

Hi DeSharper,

in dem verlinkten Artikel ist doch alles erklärt. Du mußt bei einer Änderung des Eigenschaftswertes das PropertyChanged-Event auslösen. Über dieses Event wird dann die View automatisch über die Änderung benachrichtigt, und aktualisiert sich dann. An welcher Stelle man das Event wie auslöst, geht aus den Beispielen in dem Artikel oder auf MSDN hervor.

Das DependencyObject ist bei der Datenbindung quasi die "Gegenseite", und daher benötigt man das nur, wenn man ein Control erstellen will, welches Databinding unterstützt.

Natürlich kannst du auch ein ViewModel ohne Parameterlosen Konstruktur erstellen. Wenn es dabei einen Compilerfehler gibt, dann poste mal die Fehlermeldung bzw. schau mal in [Hinweis] Syntaxfehler selbst lösen (Compilerfehlermeldungen).

Weeks of programming can save you hours of planning

D
DeSharper Themenstarter:in
40 Beiträge seit 2016
vor 7 Jahren

Ich hab den verlinkten Artikel gelesen, und das so umgesetzt. Das sah da alles nicht so kompliziert aus. Das Ergebnis bleibt das gleiche.


    public class HexagonViewModel : INotifyPropertyChanged
    {
        private int number;
        public int Number
        {
            get { return this.number; }
            set
            {
                if (this.number != value)
                {
                    if (value >= 2 && value <= 12)
                    {
                        this.number = value;
                        OnPropertyChanged("Number");
                    }
                }
            }
        }

        public HexagonViewModel()
        {
            Number = 5;
        }

        public HexagonViewModel(int number)
        {
            this.Number = number;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler tempHandler = PropertyChanged;
            if (tempHandler != null)
                tempHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Ist womöglich ne blöde Frage, aber warum löst die Tatsache, dass PropertyXY geändert wurde nicht von alleine ein PropertyChangedEvent aus?
Kann ich irgendwie prüfen, ob das Event richtig ausgelöst wird?

5.658 Beiträge seit 2006
vor 7 Jahren

Ich hab den verlinkten Artikel gelesen, und das so umgesetzt. Das sah da alles nicht so kompliziert aus. Das Ergebnis bleibt das gleiche.

Du bindest offenbar an die falsche Instanz des ViewModels. Versuch es mal so:

<TextBlock Text="{Binding Number}">
        </TextBlock>

Ist womöglich ne blöde Frage, aber warum löst die Tatsache, dass PropertyXY geändert wurde nicht von alleine ein PropertyChangedEvent aus?

Wie soll das Event denn ausgelöst werden, wenn du es nicht auslöst?

Kann ich irgendwie prüfen, ob das Event richtig ausgelöst wird?

[Artikel] Debugger: Wie verwende ich den von Visual Studio? und [Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio

Weeks of programming can save you hours of planning

D
985 Beiträge seit 2014
vor 7 Jahren

Diese StaticResource ist schon mal seltsam und deutet eher darauf, dass du dich mit einer statischen Instanz verbindest, die nichts mit dem DataContext zu tun hat. Also ersatzlos streichen.

Dann solltest du auch etwas sehen können (und der Compiler sollte auch nicht mehr meckern wegen dem parameterlosen Konstruktor)

P
441 Beiträge seit 2014
vor 7 Jahren

Zusätzlich hätte ich noch eine Frage. Ich hab beim Lesen immer nur Parameterlose Konstruktoren bei ViewModels gesehen. Muss das so sein? Ist es eine doofe Idee auf diese Art, also über die Parameter des Konstruktors, die Eigenschaften des Viewmodels festzulegen? Und kann ich verhindern, dass der parameterlose Konstruktor aufgerufen wird und direkt meinen "echten" Konstruktor verwenden? Den andern hab ich ja nur, weil sonst der compiler meckert.

Der parameterlose Konstruktor wird vom Visual Studio Designer benötigt. Auch, wenn du das ViewModel aus dem View instanziierst, was du zu machen scheinst, denn du verwendest beim Binding ein StaticResource (also eine in XAML, in einem übergeordneten Resource Dictionary instanziiertes Objekt; keine statische Implementierung).

Du scheinst zwei Instanzen deines ViewModels zu benutzen, einmal die StaticResource (s.o.) und einmal die Instanz aus der Code Behind.
Ich würde hier eher die Instanz aus der Code Behind entfernen und schauen, dass du diese "sauber" hälst, anstatt die aus dem ResourceDictionary.

D
DeSharper Themenstarter:in
40 Beiträge seit 2016
vor 7 Jahren

Die static ressource war tatsächlich das Problem. Das hatte ich kopflos aus irgendeinem Tutorial übernommen. Jetzt funktioniert es auch mit meinen ursprünglichen DependencyProperties. Danke!

P
441 Beiträge seit 2014
vor 7 Jahren

Dein Problem war, dass du zwei Unterschiedliche Instanzen deines Viewmodels verwendet hast.
Ich würde wetten, du hast irgendwo ein solches Statement:


<xyz.Ressources>
<namespace:HexagonViewModel x:Key="ViewModel" />
</xyz.Ressources>