-2

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.

galla153
  • 5
  • 3
  • Out of curiosity, why didn't you set that the Singleton instance named `Instance` to the DataContext? – emoacht Jul 20 '22 at 04:25
  • You may want to take a look at [Why is Singleton considered an anti-pattern?](https://stackoverflow.com/q/12755539/1136211) – Clemens Jul 20 '22 at 05:42
  • As a note, setting `UpdateSourceTrigger=PropertyChanged` on the Content Binding has no effect. UpdateSourceTrigger only affects TwoWay or OneWayToSource Bindings. – Clemens Jul 20 '22 at 05:54
  • 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: https://stackoverflow.com/a/41823852/1136211 – Clemens Jul 20 '22 at 05:59
  • @emoacht Because i'm stupid and i was testing some stuff in the hopes of not having to make a stack overflow post :), so I didn't notice when copy pasting the code (so I did have it set to ```ViewModel.Instance``` originally) I'll update the code – galla153 Jul 20 '22 at 20:22

2 Answers2

1

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();
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Thanks for the answer(s) I already learned more about bindings than I did from the MSDN documentation :). It still doesn't work though. I set ```MainWindow.DataContext``` to ```new ViewModel()``` (and removed the nonsense singleton garbage), implemented your ```Button_Click``` method and removed the ```UpdateSourceTrigger=PropertyChanged```. What am I doing wrong or is my VS just cursed. I'll go take a shower now and freshen up a bit, I've been working on this project for almost 24h straight now. Maybe I'll get a brainwave in the shower, and then I'll try your solution from scratch. XD – galla153 Jul 20 '22 at 20:44
  • Be aware that `_exampleProperty` should not be static. Declare it as a field like this: `private string _exampleProperty = "Pls work";`. Also note that `if (propertyName is not null)` in OnPropertyChanged is wrong. Passing `null` as property name is a valid use case. – Clemens Jul 20 '22 at 21:21
0

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.

Clemens
  • 123,504
  • 12
  • 155
  • 268
galla153
  • 5
  • 3