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
CodePudding user response:
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:
UserController
needs to implementINotifyPropertyChanged
if it doesn't already viaBindableBase
For
UserController.CurrentUser
, instead of a simpleget; 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));
}
}
- In your view model, instead of a
CurrentUser
property, expose aUserController
property:
public UserController UserController => UserController.Instance;
- 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).
CodePudding user response:
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.