Home > Blockchain >  Xaml bindings don't update (and everything seems like it should work)
Xaml bindings don't update (and everything seems like it should work)

Time:07-21

I've been working on a unique kind of project for a while now and recently I've written a custom "binding system"(for external code) which works great, however today I needed to get some MVVM style bindings to work(for internal UI). After an entire day of googling and trying different things, I still don't have an answer or working code. I'm almost at a point where I'll just add "normal" bindings to my existing binding system and call it a day.

anyway... my question...

I'm trying to make a one-way binding from a ViewModel class to a UI element. There is are some "rules" I have to conform to though (like all (public) properties MUST be in a static class). At design-time everything works and VS can resolve the bindings (if the datacontext is set in xaml, NOT in cs). The bindings even update once to their default value at startup, but NOT when the property source changed.


TLDR; read the bold text :)


Code:

[Public static class] here the property is set by external code at runtime

public static class StaticClass
{
    public static string ExampleProperty
    {
        get
        {
            return ViewModel.Instance.ExampleProperty;
        }
        set
        {
            if (ViewModel.Instance.ExampleProperty != value) ViewModel.Instance.ExampleProperty = value;
        }
    }
}

[ViewModel] a singleton class that holds the non-static backing field for the static properties in the class above and implements INotifyPropertyChanged

internal class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private static ViewModel _instance = null;
    internal static ViewModel Instance
    {
        get
        {
            if (_instance is null) _instance = new();
            return _instance;
        }
    }

    private static string _exampleProperty { get; set; } = "Pls work"; 

    public string ExampleProperty
    {
        get
        {
            return _exampleProperty;
        }
        set
        {
            _exampleProperty = value;
            OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (propertyName is not null) PropertyChanged?.Invoke(null, new(propertyName));
    }
}

[Xaml example binding]

<Button Content="{Binding ExampleProperty, UpdateSourceTrigger=PropertyChanged}" Click="Button_Click"/>

[MainWindow.cs] obviously a test project so this just changes the ExampleProperty to a random number on the Button.Click event

public partial class MainWindow : Window
{
    Random random = new();

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = ViewModel.Instance;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Button btn = (Button)sender;
        StaticClass.ExampleProperty = random.Next(0, 69).ToString();
    }
}

so what am I doing wrong? Any help is greatly appreciated.

CodePudding user response:

The expression

DataContext = new ViewModel();

assigns a different instance of the ViewModel class to the DataContext than the one returned from ViewModel.Instance, so that

StaticClass.ExampleProperty = random.Next(0, 69).ToString();

does not affect the DataContext. You should have assigned

DataContext = ViewModel.Instance;

Your ViewModel class does however not implement the singleton pattern correctly - it would have to avoid the creation of more than one instance, e.g. by declaring a private constructor.


The view does not need to use the singleton aspect of the view model at all. In order to access the current view model instance, cast the DataContext:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Button btn = (Button)sender;
    ViewModel vm = (ViewModel)btn.DataContext;
    vm.ExampleProperty = random.Next(0, 69).ToString();
}

CodePudding user response:

Clemens... legend...

Anyway for anyone too lazy to read all the above (hi future people with the same problem).

Thanks to Clemens' comment:

Also be aware that it is possible to bind directly to static properties of a static class, even with change notification, thus eliminating the need for a singleton. See e.g. here: stackoverflow.com/a/41823852/1136211 

(and answer) I've had success with both a static and a non-static binding (FINALLY...).

[For binding UI to a static class]

[The static class:]

public static class StaticClass
{
    public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;

    #region Properties

    public static string _exampleProperty = "Default value";
    public static string ExampleProperty
    {
        get
        {
            return _exampleProperty;
        }
        set
        {
            if (_exampleProperty != value)
            {
                _exampleProperty = value;
                OnStaticPropertyChanged();
            }
        }
    }

    #endregion

    private static void OnStaticPropertyChanged([CallerMemberName]string propertyName = null)
    {
        StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
    }
}

[How to bind to UI:]

<TextBlock Text="{Binding Path=(local:StaticClass.ExampleProperty)}"/>

[How to set the property:]

StaticClass.ExampleProperty = "New value that automatically updates the UI :)";

[For binding UI to a NON-static class]

Use my original code and apply clemens' fixes (properly... unlike my sleepy self) :)

I hope this helped, thanks again for everyone who answered!

  • Related