简体   繁体   中英

Share data between ViewModels and Update View

i just need i tiny help because my second view is not updating itself.

Is just have two views and the corresponding viewmodels. in ViewA i press a butten that sets the CurrentUser Property in my UserController (Or CurrentContext)

the class implements the prism BindableBase (witch includes INotiefyPropertyChanged) Also Fody and PropertyChanged/Fody is installed and setup correctly.

 public class UserController : BindableBase
{

    private static UserController instance;

    public User CurrentUser { get; set; }


    public static UserController Instance
    {
        get
        {
            if (instance == null)
                instance = new UserController();
            return instance;
        }
    }
    private UserController()
    {
    }

}

Set the CurrentUser in ViewModel A:

 private void ShowDetails(User user)
    {
        UserController.Instance.CurrentUser = user;
    }

Try to Display the User in ViewModel B:

public User CurrentUser => UserController.Instance.CurrentUser; 

ViewB:

<Page.DataContext>
    <viewModels:UserInfoPageViewModel />
</Page.DataContext>

<Grid Background="Black">
    <StackPanel>
        <TextBox Text="{Binding CurrentUser.UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>
</Grid>

what am i not seeing why the ViewB is not updating?

Basic:

MainViewModel -->  UserController --> UserDetailViewModel

Where you have this

public User CurrentUser =>

That's a one time binding as it stands.

You would have to raise property change event in code with "CurrentUser"

Edit:

I think you could reduce your problems by using a different approach with your User object.

Rather than implementing a singleton pattern using a static, it would be more usual to have dependency injection in any substantial sized project. Pretty much a given in commercial apps where automated testing such as TDD is kind of expected.

The usual pattern for an app would be you can initially do nothing at all until you log in. You input name an password and are authenticated. Your user is looked up in the database. Authorisation is somehow obtained for what you can do in the app.

Then you're on to using the app. The ui is built out and you can do stuff.

If you switch user then you tear everything down and start again. It is quite common just to have to log out and close the app. Start it up again. Because with most PCs when User 1 stops using it he logs out of windows. User B comes along and logs in. The app is closed down inbetween. With that in mind I'm assuming you would want to dispose existing viewmodels if a user could switch user.

I put some code together which, just uses mainviewmodel to instantiate two viewmodels passing in user. We can imagine resolving our viewmodels out a dependency injection container rather than directly out that viewmodel.

I also used the mvvm community toolkit for this. Partly because I prefer it but also because I think it's clearer to any reader that some code generation is going on and where it is. The attributes drive property code generation with inpc implementation in a partial class.

I could have added another control to take focus but as it is this only has one textbox so I have UpdateSourceTrigger=PropertyChanged. The entire purpose is to demonstrate the notification working with an instance class so the textblocks changing as I type is a plus.

    <Window.DataContext>
            <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
         <StackPanel>
            <TextBlock Text="Input:"/>
            <TextBox Text="{Binding TheUser.UserName, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBlock Text="First:"/>
            <ContentPresenter Content="{Binding FirstViewModel}"/>
            <TextBlock Text="Second:"/>
            <ContentPresenter Content="{Binding SecondViewModel}"/>
        </StackPanel>
    </Grid>
</Window>

User

public partial class User : ObservableObject
{
    [ObservableProperty]
    private string userName = string.Empty;
}

As I mentioned, making User a single instance of an instance class is implemented in a quick and dirty way here. A singleton lifespan declaration in a DI container would be the proper way to do this.

public partial class MainWindowViewModel : ObservableObject
{

    [ObservableProperty]
    private User theUser = new User();

    [ObservableProperty]
    public object firstViewModel;

    [ObservableProperty]
    public object secondViewModel;

    public MainWindowViewModel()
    {
        firstViewModel = new AViewModel(theUser);
        secondViewModel = new BViewModel(theUser);
    }
}

All three viewmodels therefore have the one instance of User.

Both usercontrols only have a textblock in them

  <TextBlock Text="{Binding TheUser.UserName}"/>

Both viewmodels are very similar but there is no raising of property changed going on in them. No attribute.

public partial class AViewModel : ObservableObject
{
    public User TheUser { get; set; }

    public AViewModel(User user)
    {
        TheUser = user;
    }
}

Bviewmodel is almost exactly the same.

When I spin this up and type in the textbox, the change is seen in the textblocks immediately.

在此处输入图像描述

Not sure how far you'll want to go with changes but maybe this is something to consider or will help someone else comes across this question later.

The short answer to "why" is because when UserController.CurrentUser is changed it's not telling anyone. Normally if you want to notify other elements of a property change you need to use the INotifyPropertyChanged interface and invoke the PropertyChanged event in the setter of the property.

Your pattern is a little more complex though because you're not even binding to the property that's being changed, instead you're binding to a view model property which fetches its value from UserController . So not only does UserController not tell anyone when CurrentUser has changed, but your view model likewise doesn't know when this has happened and can't notify its binding targets either.

There are always multiple ways to do something like this, but I'd approach it this way:

  1. UserController needs to implement INotifyPropertyChanged if it doesn't already via BindableBase

  2. For UserController.CurrentUser , instead of a simple get; set; get; set; , you will need to rewrite it as follows:

private User _currentUser;
public User CurrentUser 
{ 
    get => _currentUser;
    set
    {
        _currentUser = value;
        NotifyPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentUser));
    }
}
  1. In your view model, instead of a CurrentUser property, expose a UserController property:
public UserController UserController => UserController.Instance;
  1. In XAML:
    <StackPanel>
        <TextBox Text="{Binding UserController.CurrentUser.UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>

What this does is treat UserController as the source of truth for CurrentUser , which it is, rather than your view model. It works because UserController (apparently) will never change during the life of the app so you don't have to worry about that segment of the binding path, while UserController.CurrentUser 's setter will take care of notifying WPF to update the binding when that property changes.

By contrast your view model's CurrentUser property is what I would call a derived property; these are tricky to bind to because even if UserController properly gave a property change notification for CurrentUser , you would still need some way of notifying every view model when UserController.CurrentUser has changed, and then they would have to in turn notify everyone that their own, derived CurrentUser property had changed. Basically you would need two layers of binding, which is not easy to do properly (and can lead to memory leaks if you're not careful).

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