Raising Property Changed for property outside of the ViewModel

666 Views Asked by At

Using MvvmCross, fwiw I have a ViewModel with several properties created primarily for ease XAML binding purposes. For example:

public int HomeScore
{
   get { return Contest.Team[HomeID].Score; }
}

HomeScore is bound to a TextBlock in the XAML view. Contest is a singleton class that contains a dictionary Team of two teams, HomeID representing one of the Keys. The value is a class of TeamStats, that contains a property integer Score. The current dilemma / challenge is when another method updates the Score, how should that notification get passed on to the ViewModel and subsequently the View to show the updated score in the display. I've played around with the MvvmCross SetProperty and RaisePropertyChanged at various levels but all to no avail.

1

There are 1 best solutions below

4
lidqy On

If the Team's "Score" property itself publishes/raises PropertyChanged, you need to listen to it and on any change raise PropertyChanged for "HomeScore".

Contest.Team[HomeID].PropertyChanged += PropagateHomeScore;

private void PropagateHomeScore (object sender, PropertyChangedEventArgs args)    { 
   if (e.PropertyName == "Score") {
      RaisePropertyChanged (nameof(HomeScore))
    }
}

By the way, if you discard the convenience wrapper "HomeScore" and put the property path directly in XAML, you don't have to do anything.

WPF would bind the complete path including the change listeners automagically. Afaik it can handle the dictionary indexer.

XAML

<TextBlock Text="{Binding Contest.Team[HomeID].Score}" />

(HomeID should likely be replaced by its actual value).

** Update:

Demo for Binding to a dictionary of a static class**

XAML Window1.xaml

<Window x:Class="WpfApp1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApp1"
    Title=""
    Width="700"
    Height="220">
    <StackPanel HorizontalAlignment="Center">
        <StackPanel.Resources>
            <Style x:Key="Style1" TargetType="Control">
                <Setter Property="FontSize" Value="20" />
                <Setter Property="FontWeight" Value="Bold" />
                <Setter Property="Padding" Value="20" />
                <Setter Property="VerticalAlignment" Value="Center" />
            </Style>
        </StackPanel.Resources>
        <Label Style="{StaticResource Style1}">Dictionary-Binding + INotifyPropertyChanged Demo</Label>

        <StackPanel HorizontalAlignment="Center"
            Orientation="Horizontal">

            <Button Margin="10"
                Click="ButtonBase_OnClick"
                Content="Increment:"
                Style="{StaticResource Style1}" />
            <TextBox Foreground="Magenta"
                IsReadOnly="True"
                Style="{StaticResource Style1}"
                Text="{Binding Source={x:Static local:Contest.Team}, Path=[1].Score, Mode=OneWay}" />

        </StackPanel>
    </StackPanel>
</Window>

CS: Window1.xaml.cs Code behind + VM

namespace WpfApp1 {

    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows;

    public partial class Window1 {
        public Window1() => InitializeComponent();
        private void ButtonBase_OnClick(object sender, RoutedEventArgs e) => Contest.Team[1].Score++;
    }
    public static class Contest {
        public static Dictionary<int, ScoreObject> Team { get; } = new() {
                                 { 1, new ScoreObject { Score = 10 } },
                                 { 2, new ScoreObject { Score = 20 } },
                                 { 3, new ScoreObject { Score = 30 } },
                                 { 4, new ScoreObject { Score = 40 } },
        };
    }
    public class ScoreObject : INotifyPropertyChanged {
        private int _score;
        public int Score {
            get => _score;
            set {
                if (_score != value) {
                    _score = value;
                    OnPropertyChanged(nameof(Score));
                }
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}