0

We are migrating our WPF project from the MVVMLight library to the Microsoft CommunityToolkit library.

We went ahead following the shared Microsoft migration documentation and updated all our (about 1200) commands accordingly.

When we got the build afterwards, we noticed that the events were not triggered correctly in the project and the commands were not working.

However, the SetProperty() method does not run my RelayCommands.

Below we have resolved this issue for just one command of our user model. There are more then 100+ Models and 1200ish commands exist.

BaseViewModel.cs

public class BaseViewModel : ObservableObject, IDisposable
    {
        protected bool _disposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual void Dispose(bool disposing)
        {
            if (_disposed.Equals(false))
            {
                if (disposing)
                {

                }
                _disposed = true;
            }
        }

        private bool _isSaved;
        public bool IsSaved
        {
            get { return _isSaved; }
            set
            {
                SetProperty(ref _isSaved, value, nameof(_isSaved));
            }
        }
    }

LoginViewModel

public class LoginViewModel : BaseViewModel
{       
        public LoginViewModel()
        {
            User = new UserModel();
            User.PropertyChanged += User_PropertyChanged; // WHAT WE NEWLY ADDED
            LoginCommand = new RelayCommand<Window>(OnLoginCommandExecuted, CanLoginCommandExecute);
            CancelCommand = new RelayCommand<Window>(OnCancelCommandExecuted, CanCancelCommandExecute);
        }

        private void User_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (LoginCommand is not null) LoginCommand.NotifyCanExecuteChanged(); // We should notify it manually..
        }

        private UserModel _user;
        public UserModel User
        {
            get { return _user; }
            set
            {
                SetProperty(ref _user, value, nameof(_user));
            }
        }

        public RelayCommand<Window>? LoginCommand { get; private set; }
        public async void OnLoginCommandExecuted(Window window)
        {       
        //DO STUFF
    }
        public bool CanLoginCommandExecute(Window window)
        {
                //DO STUFF
        }
        
}

LoginWindow.xaml

<Window
    x:Class="ProjectName.LoginWindow"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    x:Name="loginWindow">   
    <Button x:Name="btnLogin" Command="{Binding LoginCommand}" CommandParameter="{Binding ElementName=loginWindow}" />              
</Window>

Is there any way to trigger NotifyCanExecuteChanged manager for all commands in active models?

What can i do in this situation? Does the following implementation need to be applied for all models and commands? Is this an expected situation? Thanks and happy codings.

EDIT: I changed whole project with annotations.

LoginViewModel

[ObservableProperty] 
[NotifyCanExecuteChangedFor(nameof(LoginCommandExecutedCommand))]
private UserModel _user = new();

[RelayCommand(CanExecute = nameof(CanLoginCommandExecute))]
private async void OnLoginCommandExecuted(Window window)
{
   //DO STUFF
}

 private bool CanLoginCommandExecute(Window window)
        {
            // DO STUFF LOGIC
        }

UserModel

        [ObservableProperty]
        private string _username;
        partial void OnUsernameChanged(string value)
        {
            XtraMessageBox.Show("I HAVE CHANGED - USERNAME");
            IsDirty = true;
        }

        [ObservableProperty]
        private string _password;
        partial void OnPasswordChanged(string value)
        {
            XtraMessageBox.Show("I HAVE CHANGED - PASSWORD");
            IsDirty = true;
        }

It fires the changes but notifying commands. Cause UserModel not changing. UserModels properties are changed.

That we can see model properties are changed. And LoginViewModel's User Model updated. But it is not executing my commands. Because its only GET'ing my UserModel, not setting. Autogenerated code only notify when UserModel sets. What should i do?

Unseen
  • 13
  • 5
  • 3
    `nameof(_isSaved)` should certainly be `nameof(IsSaved)`. Or you just write `SetProperty(ref _isSaved, value);` – Clemens Feb 16 '23 at 14:18
  • I recreated your example and it seems to work fine. Regarding executing commands in active model, I think you should look into CommandManager class (however not sure about that) – Demon Feb 16 '23 at 15:01

1 Answers1

1

There are several things you could change.

In a partial class viewmodel, you can do:

    [ObservableProperty]
    [AlsoNotifyChangeFor(nameof(FullName))]
    [AlsoNotifyCanExecuteFor(nameof(SaveCommand))]
    private bool _isSaved = true;


    partial void OnIsSavedChanged(bool newValue)
    {
        // Called when IsSaved property changes
    }

The boiler plate code for the IsSaved property and change notification is created in a partial class by the code generator. That will reduce the risk of breaking change notification (like you have by using the field rather than property) because you just have the private member to define.

You may add partial onchanged and onchanging methods which will be called when the matching property name changes or is about to change.

When IsSaved changes, the attribute will raise canexecutechanged for the command.

A relaycommand has an explicit notifycanexecutechanged method

https://learn.microsoft.com/en-us/dotnet/api/microsoft.toolkit.mvvm.input.relaycommand.notifycanexecutechanged?view=win-comm-toolkit-dotnet-7.1

If none of the attributes suit you could just do:

  CommandManager.InvalidateRequerySuggested()

Or you could iterate through relaycommands in a viewmodel and call notifycanexecutechanged on them.

Note also though that the source code for mvvmlight is available.

I've used it in one .net6 project.

Andy
  • 11,864
  • 2
  • 17
  • 20
  • Neither of them worked for me. But when I called NotifyCanExecuteChanged by calling and iterating all commands it worked. But I need a more generic structure. Also annotation values have been updated for your information. Thank you. – Unseen Feb 20 '23 at 11:47
  • Hello @Andy again, I have a ModelBase derived from Observable Object. And I have a UserModel derived from this model. My UserModel has 4 properties. I initialize this UserModel in my ViewModel constructor. Then I change these 4 properties, but the change not notify my button commands because the UserModel does not change even though the data is updated. How can I trigger commands after model changes ? Thanks. – Unseen Feb 23 '23 at 14:47
  • If you're changing properties then you can decorate them [AlsoNotifyCanExecuteFor(nameof(SaveCommand))] – Andy Feb 23 '23 at 15:15
  • Hey again @andy i updated my question after annotations implement. That sets UserModel properties but only getting usermodel. How can i notify this on base classes. Isnt this package support base class observations. Thank you. – Unseen Feb 24 '23 at 06:53
  • A viewmodel adapts data to a view, That's it's reason for existence. They are VIEW models, not models. So if it suits your view better for LoginViewModel to have username and password properties then that's where they go. Not in some other class that does not have a command depends on knowing when they have changed. Alternatively you would need to create some rather more complicated mechanism with a delegate or something. – Andy Feb 24 '23 at 08:38