简体   繁体   中英

Update property of a model in User Control1 when this property is modified in User Control2 in WPF using MVVM

I have a main window that hosts 2 user controls using ContentControl. Inside main window view model the Person model is instantiated and passed to both user controls with DI. My requirement is if the property First Name is edited in User Control 2 then this need to be reflected back in User Control 1 First Name property. Code is attached.

ShellViewModel (Main Window)

public class ShellViewModel : Screen
    {
        public IPersonModel _personModel { get; }
        private IPeopleService _peopleService;
        
        public UserControl1ViewModel UserControl1VM { get; set; }
        public UserControl2ViewModel UserControl2VM { get; set; }
        
        public ShellViewModel(IPersonModel personModel, IPeopleService peopleService)
        {
            _personModel = personModel;
            _peopleService = peopleService;
            _personModel = _peopleService.GetPersonInfo(1);

            UserControl1VM = new UserControl1ViewModel(_personModel);
            UserControl2VM = new UserControl2ViewModel(_personModel);
        }


        
    }

public class UserControl1ViewModel : PropertyChangedBase
    {
        private IPersonModel Model;
        public UserControl1ViewModel(IPersonModel model)
        {
           Model = model;
        }

        public string FirstName
        {
            get
            {
                return Model.FirstName;
            }
            set
            {
                NotifyOfPropertyChange(() => FirstName);
                NotifyOfPropertyChange(() => FullName);
            }
        }


        public string LastName
        {
            get { return Model.LastName; }
            set
            {
                
                NotifyOfPropertyChange(() => LastName);
                NotifyOfPropertyChange(() => FullName);
            }
        }


        public string FullName => $"{FirstName} {LastName}";
    }

public class UserControl2ViewModel : PropertyChangedBase
    {
        private IPersonModel _model;
        public UserControl2ViewModel(IPersonModel model)
        {
            _model = model;
        }

        public string FirstName
        {
            get
            {
                return _model.FirstName;
            }
            set
            {
                _model.FirstName = value;
                NotifyOfPropertyChange(() => FirstName);
                //NotifyOfPropertyChange(() => FullName);
            }
        }


        public string LastName
        {
            get { return _model.LastName; }
            set
            {
                _model.LastName = value;
                NotifyOfPropertyChange(() => LastName);
                //NotifyOfPropertyChange(() => FullName);
            }
        }
    }

UserControl2 view:

UserControl x:Class="UI.Views.UserControl2View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:UI.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="400">
    <Grid>
        <StackPanel Margin="12">
            <TextBox Text="{Binding FirstName, Mode=TwoWay}" Margin="5"/>
            <TextBox Text="{Binding LastName, Mode=TwoWay}" Margin="5"/>
            <TextBlock Text="{Binding FirstName, Mode=OneWay}" Margin="5"/>
        </StackPanel>
    </Grid>
</UserControl>

UserControl1 View:

<UserControl x:Class="UI.Views.UserControl1View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:UI.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="350">
    <Grid>
        <StackPanel Margin="10">
            <TextBlock Text="{Binding FirstName}" Margin="5"/>
            <TextBlock Text="{Binding LastName}" Margin="5"/>
            <TextBlock Text="{Binding FullName}" Margin="5"/>
        </StackPanel>
    </Grid>
</UserControl>

You need to make use of EventAggregator pattern implementation in Caliburn Micro to notify instances of another ViewModel which doesn't have a direct reference in the current ViewModel.

To do so, you need to begin by defining a Message Type, which could be used to uniquely identify the particular change.

public class NotifyPersonModelChangeMessage
{
   public IPersonModel PersonModel { get; set; }
}

The next step involves publishing a message of type NotifyPersonModelChangeMessage from the UserControl2ViewModel when the desired property changes in it.

For example,

public class UserControl2ViewModel : PropertyChangedBase
    {
        private IPersonModel Model;
        private IEventAggregator _eventAggregator;
        public UserControl2ViewModel(IPersonModel model,IEventAggregator eventAggregator)
        {
            Model = model;
            _eventAggregator = eventAggregator;
            _eventAggregator.SubscribeOnUIThread(this);
        }

        public string FirstName
        {
            get
            {
                return Model.FirstName;
            }
            set
            {
                Model.FirstName = value;
                NotifyOfPropertyChange(nameof(FirstName));
                NotifyOfPropertyChange(nameof(FullName));
                NotifyNameChange();
            }
        }

        private void NotifyNameChange()
        {
            _eventAggregator.PublishOnUIThreadAsync(new NotifyPersonModelChangeMessage { PersonModel = Model });
        }
}

This would publish a message to the Event Aggregator implementation. The next step would be to ensure the UserControl1ViewModel subscribes to the message and makes the changes accordingly. This could be done using implementation of IHandle<T> interface of Caliburn Micro

public class UserControl1ViewModel : PropertyChangedBase, IHandle<NotifyPersonModelChangeMessage>
{
   private IPersonModel _model;

   public UserControl1ViewModel(IPersonModel model, IEventAggregator eventAggregator)
   {
      _model = model;
      eventAggregator.SubscribeOnUIThread(this);
   }

   public Task HandleAsync(NotifyPersonModelChangeMessage message, CancellationToken cancellationToken)
   {
       _model = message.PersonModel;
       NotifyOfPropertyChange(nameof(FirstName));
       NotifyOfPropertyChange(nameof(LastName));
       return Task.CompletedTask;
   }

   // rest of the class
}

HandleAsync method would be invoked each time a message of type NotifyPersonModelChangeMessage is published to the EventAggregator since we have subscribed to the EventAggregator and is listening for the particular message type.

You could read more on EventAggregator in the official documentation of Caliburn Micro

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM