cramar
cramar

Reputation: 134

How to update a TextBlock in ShellView when property changes in Model

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

Answers (1)

Anu Viswan
Anu Viswan

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

Related Questions