2

I'm a little lost right now. I'm using the CommunityToolkit.Mvvm for MVVM in a small application. My UI consists of some settings and some parameters; I'm updating (performing calculations) once a property changes.

In this case I'd like to fire NotifyPropertyChangedFor as I do calculations with the focal length of a camera - but this gets never triggered.

So while things are working nicely with 'local' properties I'm having issues with objects that come in via a service (so iot injected) like the focal length.

Simplified example:

I have a data container like this:

public partial class Settings : ObservableObject
{
    private CameraSettings cameraSettings;

    public CameraSettings CameraSettings
    {
        get => cameraSettings ?? (cameraSettings = new CameraSettings());
        set => SetProperty(ref cameraSettings, value);
    }
}

And an object that is actually holding the values:

public partial class CameraSettings: ObservableObject
{
    private int focalLength = 15;

    public int FocalLength
    {
        get => focalLength;
        set => SetProperty(ref focalLength, value);
    }
}

And I have a viewmodel which is supposed to use this:

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ResultsEditorBox))]
private CameraSettings cameraSettingsContainer;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ResultsEditorBox))]
private string connectionType;

public SettingsViewModel(SettingsService settingsService)
{
    cameraSettingsContainer = settingsService.Settings.CameraSettings;
}

The ResultEditorBox is just another local property to update UI

public string ResultsEditorBox
{
    get
    {
        return "UI update done";
    }
}

While using any 'local' observed property like connectionType everything is working as expected and ResultsEditorBox gets called in the end.

If I do the same with my custom object cameraSettingsContainer I see that set => SetProperty(ref focalLength, value); gets called correctly; but the NotifyPropertyChangedFor doesn't trigger.

To my understanding this should work via the CommunityToolkit.Mvvm.

But why is NotifyPropertyChangedFor not working via the CameraSettings object?

Julian
  • 5,290
  • 1
  • 17
  • 40
Vegetico
  • 93
  • 8
  • What is `ResultsEditorBox`? It doesn't show up in your code at all. What exactly isn't working? Note that the MVVM Source Generators create properties that start with an uppercase letter, so `cameraSettingsContainer` becomes `CameraSettingsContainer`. The `[NotifyPropertyChangedFor()]` attribute is not used to notify UI elements about changes, it's used to call `OnPropertyChanged()` on related properties. Please include all relevant code and clarify the problem. – Julian Jun 05 '23 at 11:03
  • Property 'A' has field '_a', not 'a'. Follow the naming conventions. They are there for a reason. Or deal with bugs like that. – H.A.H. Jun 05 '23 at 11:26
  • @H.A.H. That's not a problem. The MVVM Source Generators explicitly support both notations, you can use `_a` or `a`, it results in the same property `A` being generated. – Julian Jun 05 '23 at 11:27
  • What you're trying to do here is not possible like that. The `[NotifyPropertyChangedFor()]` only applies to the `CameraSettingsContainer` property, but not its child properties. You'll need some other way to raise the `PropertyChanged` event for `ResultsEditorBox`. – Julian Jun 05 '23 at 11:29
  • 1
    Hi @Julian, thanks for the info. I thought I could daisy chain this via having both objects - ```Settings``` Container class as well as the ```CameraSettings``` class derive from ```ObservableObject``` and implement the properties accordingly. But if this doesn't work ok. My current workaround is to have all CameraSettings dublicated in the UI (around 10ish) - and then store the object once the configuration is done. – Vegetico Jun 05 '23 at 11:36
  • 1
    @H.A.H. Its a style question if you use ```_a``` or ```a```. In my case I'm continuing a existing code base - and would like to keep the style as it is. Otherwise its gets more messy - when mixing styles. By the way, the toolkit docu does it the same way: https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observableobject – Vegetico Jun 05 '23 at 11:37
  • @Vegetico It is not a style question. Programmers are used to write _ to see all object scoped member. Assigning such fields shows intent to modify them without calling any code. And not only that. Assigning fields does not produce any warnings. It will plain and simple not work. Very easy to miss while testing. There are more than one reason to start writing better. "All my code is written the wrong way" is not a valid reason not to change. – H.A.H. Jun 05 '23 at 11:44
  • @H.A.H. That's a matter of opinion. I'm not going to take a specific side here, it just doesn't matter from a technical point of view and certainly doesn't explain the observed behavior in this question. The Source Generators support both notations and the rest is opinion. – Julian Jun 05 '23 at 11:58
  • @Julian It is not "Opinion". It is called "Coding Conventions". Those specific coding conventions are called "Naming Conventions". Opinions you express. Conventions you follow. – H.A.H. Jun 05 '23 at 12:02
  • In C#, both are frequently used. Conventions are not laws or rules, they're just conventions. People agree on them to be able to mean the same thing when they talk about technical terms, but different groups or teams may follow different naming conventions. There is not one right way. Some conventions are just more or less common than others. And again, it doesn't matter in this context. – Julian Jun 05 '23 at 12:06
  • @Julian I am pretty sure he did not try to modify the private field. – H.A.H. Jun 05 '23 at 12:10
  • @H.A.H. I've used both the MVVMToolkit as well as MvvmLight by Laurent Bugnion a lot in the past - the second one being the inspiration for the first. I'm moderately sure that I've used the auto-generated Setters and Getters to trigger my UI update stuff and that I have generally understood the concept - although I never really looked into the analyzers and the code generated as it was never needed. It's also working in general - just not for my daisy-chained data objects. And Julian explained why. – Vegetico Jun 05 '23 at 12:33

1 Answers1

1

What you're trying to do here is not possible like that, because child property changes are not automatically propagated up to the highest level.

The [NotifyPropertyChangedFor()] attribute only applies to the CameraSettingsContainer property, but not its child properties. You'll need some other way to raise the PropertyChanged event for ResultsEditorBox.

One way to solve this would be to subscribe to the PropertyChanged event of the CameraSettingsContainer and then raise the notification in the event handler:

public SettingsViewModel(SettingsService settingsService)
{
    CameraSettingsContainer = settingsService.Settings.CameraSettings;
    CameraSettingsContainer.PropertyChanged += (s,e) =>
    {
        OnPropertyChanged(nameof(ResultsEditorBox));
    }
}

I wrote a blog series about MVVM Source Generators, which you may be interested in. It explains how they work and what happens under the hood.

Julian
  • 5,290
  • 1
  • 17
  • 40
  • Ok, so I need to implement the propagation manually. That works - brilliant. Thanks. I thought that happens automatically if all elements are derived correctly - and for whatever reason my head was stuck in that thinking. – Vegetico Jun 05 '23 at 13:05