Reputation: 134
I am having trouble updating a ShellView TextBlock with the FirstName property of the LoggedInUserProfile which is created as a Singleton after the user has logged in.
I have a UserProfileView and this binds and updates OK, but the ShellView (which contains the UserProfileView) does not. If I put breakpoints in I can see the LoggedInUserProfile has got correct data.
This is my first WPF app and I have spent a week running myself in circles and trying to figure out what I am doing wrong, but to know avail, hence I am reaching out for guidance.
I dont know but I suspect I am not handling an event properly, not binding correctly or have done something wrong with DI.
Below I have provided the code from what I think are the main components.
What I want to have happen is the First Name of the logged in user is displayed inthe TextBlock of the ShellView after the user has Logged in as well as in the UserProfileView.
Any help you can offer would be appreciated to point me in the right direction. I have include what I think are the main components below.
ShellViewModel
using Caliburn.Micro;
using CRMDesktopUI.EventModels;
using CRMDesktopUI.Library.Models;
using System.Threading;
using System.Threading.Tasks;
namespace CRMDesktopUI.ViewModels
{
public class ShellViewModel:Conductor<object>, IHandle<LogOnEvent>
{
private IEventAggregator _events;
private SimpleContainer _container;
private LoginViewModel _loginVM;
private UserProfileViewModel _userProfileVM;
private ILoggedInUserModel _loggedInUserModel;
public ShellViewModel(LoginViewModel loginVM, IEventAggregator events,ILoggedInUserModel loggedInUserModel, UserProfileViewModel userProfileVM,SimpleContainer container)
{
_events = events;
_loginVM = loginVM;
_userProfileVM = userProfileVM;
_container = container;
_loggedInUserModel = loggedInUserModel;
_events.SubscribeOnUIThread(this);
ActivateItemAsync(_loginVM);
}
Task IHandle<LogOnEvent>.HandleAsync(LogOnEvent message,CancellationToken cancellationToken)
{
_loginVM = _container.GetInstance<LoginViewModel>();
ActivateItemAsync(_userProfileVM);
return Task.CompletedTask;
}
public string FirstName
{
get //This gets called before log in screen activated
{
if(_loggedInUserModel == null)
{
return "Not logged in";
}
else
{
return _loggedInUserModel.FirstName;
}
}
set //Set never gets called
{
_loggedInUserModel.FirstName = value;
NotifyOfPropertyChange(() => FirstName);
}
}
}
}
ShellView.xaml
<Window x:Class="CRMDesktopUI.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CRMDesktopUI.Views"
xmlns:viewmodels="clr-namespace:CRMDesktopUI.ViewModels"
mc:Ignorable="d"
Width="1250" Height="600"
Background="#36393F"
ResizeMode="CanResizeWithGrip"
AllowsTransparency="True"
WindowStyle="None">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.ColumnSpan="2"
Background="#252525"
MouseDown="Border_MouseDown">
<Grid HorizontalAlignment="Stretch">
<Label Content="Test App"
Foreground="Gray"
FontWeight="SemiBold"
FontFamily="/Fonts/#Poppins"/>
<StackPanel HorizontalAlignment="Right"
Orientation="Horizontal">
<Button Width="20" Height="20"
Content="🗕"
Background="Transparent"
BorderThickness="0"
Foreground="Gray"
FontWeight="Bold"
Margin=" 0 0 0 3"
Click="MinimiseButton_Click"/>
<Button Width="20" Height="20"
Content="â–¡"
Background="Transparent"
BorderThickness="0"
Foreground="Gray"
FontWeight="Bold"
Click="MaximiseButton_Click"/>
<Button Width="20" Height="20"
Content="✕"
Background="Transparent"
BorderThickness="0"
Foreground="Gray"
FontWeight="Bold"
Click="CloseButton_Click"/>
</StackPanel>
</Grid>
</Border>
<Grid Background="#2F3136"
Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<Label Content="Contacts"
VerticalAlignment="Center"
FontWeight="Medium"
Foreground="Gray"
Margin="8 0 0 0" />
<StackPanel Grid.Row="2"
Orientation="Horizontal"
Background="#292B2f">
<Border CornerRadius="25"
Width="30"
Height="30"
Background="#3bff6f"
Margin="18 0 10 0" />
<DockPanel VerticalAlignment="Center">
<TextBlock Text="First Name:"
Foreground="White"
FontWeight="Light"/>
<TextBlock Text="{Binding FirstName}" <== This does not update the textblock
TextAlignment="Right"
Foreground="White"
FontWeight="SemiBold"
Margin="5 0 10 0" />
</DockPanel>
</StackPanel>
</Grid>
<Grid Grid.Column="1" Grid.Row="1">
<ContentControl x:Name="ActiveItem"
Margin="20" />
</Grid>
</Grid>
</Window>
LoggedInUserModel
namespace CRMDesktopUI.Library.Models
{
public class LoggedInUserModel:ILoggedInUserModel
{
public string Token { get; set; }
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
}
}
LoginViewModel
using Caliburn.Micro;
using CRMDesktopUI.EventModels;
using CRMDesktopUI.Library.Api;
using System;
using System.Threading.Tasks;
namespace CRMDesktopUI.ViewModels
{
public class LoginViewModel:Screen
{
private string _username;
private string _password;
private IAPIHelper _apiHelper;
private string _errormessage;
private IEventAggregator _events;
public LoginViewModel(IAPIHelper apiHelper,IEventAggregator events)
{
_apiHelper = apiHelper;
_events = events;
}
public string ErrorMessage {
get {
return _errormessage;
}
set {
_errormessage = value;
NotifyOfPropertyChange(() => IsErrorVisible);
NotifyOfPropertyChange(() => ErrorMessage);
}
}
public string UserName {
get {
return _username;
}
set {
_username = value;
NotifyOfPropertyChange(() => UserName);
NotifyOfPropertyChange(() => CanLogIn);
}
}
public string Password {
get {
return _password;
}
set {
_password = value;
NotifyOfPropertyChange(() => Password);
NotifyOfPropertyChange(() => CanLogIn);
}
}
public bool CanLogIn {
get {
bool output = false;
if(UserName?.Length > 0 && Password?.Length > 0)
{
output = true;
}
return output;
}
}
public bool IsErrorVisible {
get {
bool output = false;
if(ErrorMessage?.Length > 0)
{
output = true;
}
return output;
}
}
public async Task LogIn()
{
try
{
ErrorMessage = "";
var result = await _apiHelper.Authenticate(UserName,Password);
//get more information about the logged in user
await _apiHelper.GetLogedInUserInfo(result.Access_Token);
await _events.PublishOnUIThreadAsync(new LogOnEvent());
}
catch(Exception ex)
{
ErrorMessage = ex.Message;
}
}
}
}
UserProfileViewModel
using Caliburn.Micro;
using CRMDesktopUI.Library.Models;
namespace CRMDesktopUI.ViewModels
{
public class UserProfileViewModel : Screen
{
ILoggedInUserModel _loggedInUserModel;
public UserProfileViewModel(ILoggedInUserModel loggedInUserModel)
{
_loggedInUserModel = loggedInUserModel;
}
public string FirstName
{
get //This gets called after user logs in
{
return _loggedInUserModel.FirstName;
}
set //This never gets called
{
_loggedInUserModel.FirstName = value;
NotifyOfPropertyChange(() => FirstName);
}
}
}
}
UserProfileView
<UserControl x:Class="CRMDesktopUI.Views.UserProfileView"
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:CRMDesktopUI.Views"
xmlns:viewmodels="clr-namespace:CRMDesktopUI.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:UserProfileViewModel}"
mc:Ignorable="d"
Width="800" Height="450">
<StackPanel>
<TextBlock Text="User Profile"
Foreground="White"
FontSize="28"
HorizontalAlignment="Left"
Margin="0 0 0 20"/>
<StackPanel Orientation="Horizontal">
<Border Width="800"
Height="200">
<Border.Background>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,2">
<GradientStop Color="#5bc3ff"
Offset="0.0"/>
<GradientStop Color="#3aa0ff"
Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<Border.Clip>
<RectangleGeometry RadiusX="10"
RadiusY="10"
Rect="0 0 800 200"/>
</Border.Clip>
<Grid>
<StackPanel>
<TextBlock Text="{Binding FirstName}"
Foreground="White"
FontSize="28"
Margin="20 10 10 0"/>
</StackPanel>
<Image Width="150"
Height="180"
Source="/Images/822739_user_512x512.png"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,0,-39,-31"
RenderTransformOrigin="0.804,0.953">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>
</Border>
</StackPanel>
</StackPanel>
</UserControl>
HelperClass
using CRMDesktopUI.Library.Models;
using CRMDesktopUI.Models;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace CRMDesktopUI.Library.Api
{
public class APIHelper:IAPIHelper
{
private HttpClient apiClient;
private ILoggedInUserModel _loggedInUser;
public APIHelper(ILoggedInUserModel loggedInUser)
{
InitialiseClient();
_loggedInUser = loggedInUser;
}
private void InitialiseClient()
{
string api = ConfigurationManager.AppSettings["api"];
apiClient = new HttpClient();
apiClient.BaseAddress = new Uri(api);
apiClient.DefaultRequestHeaders.Accept.Clear();
apiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<AuthenticatedUser> Authenticate(string username,string password)
{
var data = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password", password)
});
using(var response = await apiClient.PostAsync("/Token",data))
{
if(response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<AuthenticatedUser>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
public async Task GetLogedInUserInfo(string token)
{
apiClient.DefaultRequestHeaders.Clear();
apiClient.DefaultRequestHeaders.Accept.Clear();
apiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
apiClient.DefaultRequestHeaders.Add("Authorization",$"Bearer {token}");
using(var response = await apiClient.GetAsync("/Api/User"))
{
if(response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<LoggedInUserModel>();
_loggedInUser.Token = token.ToString();
_loggedInUser.Id = result.Id;
_loggedInUser.FirstName = result.FirstName;
_loggedInUser.LastName = result.LastName;
_loggedInUser.EmailAddress = result.EmailAddress;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
}
Bootstrapper
using Caliburn.Micro;
using CRMDesktopUI.Helpers;
using CRMDesktopUI.Library.Api;
using CRMDesktopUI.Library.Models;
using CRMDesktopUI.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace CRMDesktopUI
{
public class Bootstrapper:BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
ConventionManager.AddElementConvention<PasswordBox>(
PasswordBoxHelper.BoundPasswordProperty,
"Password",
"PasswordChanged");
}
protected override void Configure()
{
_container.Instance(_container);
_container
.Singleton<IWindowManager,WindowManager>()
.Singleton<IEventAggregator,EventAggregator>()
.Singleton<ILoggedInUserModel,LoggedInUserModel>()
.Singleton<IAPIHelper, APIHelper>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(viewModelType,viewModelType.ToString(),viewModelType));
}
protected override void OnStartup(object sender,StartupEventArgs e)
{
DisplayRootViewFor<MainViewModel>();
}
protected override object GetInstance(Type service,string key)
{
return _container.GetInstance(service,key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
protected override IEnumerable<Assembly> SelectAssemblies()
{
return new[] { Assembly.GetExecutingAssembly() };
}
}
}
Upvotes: 0
Views: 65
Reputation: 18155
Once you have successfully logged, you need to Notify that the FirstName property has changed.
Task IHandle<LogOnEvent>.HandleAsync(LogOnEvent message,CancellationToken cancellationToken)
{
_loginVM = _container.GetInstance<LoginViewModel>(); // Not sure why you do this.
ActivateItemAsync(_userProfileVM);
// Since User has logged now, you need to notify change in FirstName
NotifyOfPropertyChange(nameof(FirstName));
return Task.CompletedTask;
}
This would ensure that the ShellView knows that the FirstName property has changed.
Alternatively you could subscribe to the PropertyNotifyChanges of LoginViewModel and filter out the changes for FirstName, however, since you only need the first name after login is successful, the LogOnEvent might be more suitable place.
Also note that you could make the FirstName property readonly as it is most likely not to be edited by the view.
public string FirstName => _loggedInUserModel.FirstName;
Upvotes: 1