Search code examples
c#wpfxamldata-binding

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


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.


Solution

  • Thanks to a comment on the question:

    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 the code from the other answer.