Messaging problem using MVVM-Light - wpf

Initially I wanted to pass an "ObservableCollection< customClass>" between two VMs , but even simple messaging isn't working for me.
MainViewModel
private void openNewSpecialCustomer()
{
GalaSoft.MvvmLight.Messaging.Messenger.Default.Send("Musaab");
Console.WriteLine("send done");
AddNewSpecialCustomer a = new AddNewSpecialCustomer();
_dialogService.showDialoge(a);
}
AddNewSpecialCustomerViewModel
public AddNewSpecialCustomerViewModel()
{
GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<string>(this, doSomething);
Console.WriteLine("Should now Receive");
validProperties = new Dictionary<string, bool>();
validProperties.Add("specialCustomerName",false);
validProperties.Add("tel", false);
allPropertiesValid = false;
}
public void doSomething(string s)
{
Console.WriteLine("Should be received");
specialCustomerName = s;
Console.WriteLine("s value " + s);
}
public String specialCustomerName
{
get { return _specialCustomerName; }
set
{
if (_specialCustomerName != value)
{
_specialCustomerName = value;
OnPropertyChanged("specialCustomerName");
}
}
}
now XAML for AddNewSpecialCustomer
<Window FlowDirection="RightToLeft" x:Class="GlassStore.AddNewSpecialCustomer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:GlassStore.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AddNewSpecialCustomer" Height="318" Width="458">
<Window.DataContext>
<local:AddNewSpecialCustomerViewModel/>
</Window.DataContext>
<Grid Background="{DynamicResource NormalBrush}">
<Button Command="{Binding Save}" Content="موافق" Height="29" HorizontalAlignment="Left" Margin="31,218,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<Label Content="إسم العميل" Height="27" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" Width="120" />
<TextBox Text="{Binding specialCustomerName,Mode=TwoWay,ValidatesOnDataErrors=True,ValidatesOnExceptions=True,UpdateSourceTrigger=PropertyChanged}" Height="27" HorizontalAlignment="Left" Margin="155,12,0,0" Name="textBox1" VerticalAlignment="Top" Width="210" />
<Label Content="المنطقة/المكان" Height="27" HorizontalAlignment="Left" Margin="12,67,0,0" Name="label2" VerticalAlignment="Top" Width="120" />
<TextBox Text="{Binding region}" Height="27" HorizontalAlignment="Left" Margin="155,67,0,0" Name="textBox2" VerticalAlignment="Top" Width="210" />
<TextBox Text="{Binding tel,ValidatesOnDataErrors=True,ValidatesOnExceptions=True,UpdateSourceTrigger=PropertyChanged}" Height="27" HorizontalAlignment="Left" Margin="155,119,0,0" Name="textBox3" VerticalAlignment="Top" Width="210" />
<Label Content="رقم الهاتف " Height="27" HorizontalAlignment="Left" Margin="12,119,0,0" Name="label3" VerticalAlignment="Top" Width="120" />
<Button Content="إلغاء" Height="29" HorizontalAlignment="Left" Margin="143,218,0,0" Name="button2" VerticalAlignment="Top" Width="75" />
<Label Content="" Height="29" HorizontalAlignment="Left" Margin="12,177,0,0" Name="label4" VerticalAlignment="Top" Width="412" />
</Grid>
you can see that there is textBox, with a Text property bounded to specialCustomerName Property , which I'm trying to change it via the messenger , the data-bind mode is TwoWay, so I expect my textBox to have my name on it when loaded , (I'm sending my name via the messenger) which is not the case, I hope this may be more clear code
thanks in advance

Is your second window constructor called after the first one ?
You should first register and only then send the message. Previously sent messages are not received. Sorry for being C.O. but this kind of mistake is rather possible :)

As both the sending of the message and the receiving of the message happens in constructors it could lead to a race condition. Register for the receiving of the message in the constructor, but send the message at a later point in time, e.g. in the load or a command handler.
Edit:
Could not reproduce the behaviour, here is the code I used to test this:
ViewLocator:
public class ViewModelLocator
{
private static MainViewModel _main;
public ViewModelLocator() {
CreateMain();
}
public static MainViewModel MainStatic {
get {
if (_main == null) {
CreateMain();
}
return _main;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main {
get {
return MainStatic;
}
}
public static void ClearMain() {
if (_main != null) {
_main.Cleanup();
_main = null;
}
}
public static void CreateMain() {
if (_main == null) {
_main = new MainViewModel();
}
}
#region [SecondViewModel]
private static SecondViewModel _secondViewModel;
public static SecondViewModel SecondViewModelStatic {
get {
if (_secondViewModel == null) {
CreateSecondViewModel();
}
return _secondViewModel;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public SecondViewModel SecondViewModel {
get {
return SecondViewModelStatic;
}
}
public static void ClearSecondViewModel() {
if (_secondViewModel != null) {
_secondViewModel.Cleanup();
_secondViewModel = null;
}
}
public static void CreateSecondViewModel() {
if (_secondViewModel == null) {
_secondViewModel = new SecondViewModel();
}
}
#endregion
public static void Cleanup() {
ClearMain();
ClearSecondViewModel();
}
}
MainViewModel
public class MainViewModel : ViewModelBase
{
public MainViewModel() {
if (IsInDesignMode) {
// Code runs in Blend --> create design time data.
} else {
// Code runs "for real"
}
Messenger.Default.Send("Initializer - does not show becaus of race condition!");
}
public string Welcome {
get {
return "Welcome to MVVM Light";
}
}
#region [TestCommand]
private RelayCommand _cmdTest;
public RelayCommand TestCommand {
get {
return _cmdTest ?? (
_cmdTest = new RelayCommand(
() => {
// Execute delegate
Messenger.Default.Send("Hello!");
}
)
);
}
}
#endregion
public override void Cleanup() {
// Clean up if needed
base.Cleanup();
}
}
SecondViewModel
public SecondViewModel() {
Messenger.Default.Register<string>(this, (s) => this.DoSomething(s));
if (IsInDesignMode) {
// Code runs in Blend --> create design time data.
} else {
// Code runs "for real": Connect to service, etc...
}
}
#region [Message]
public const string MessagePropertyName = "Message";
private string _message = default(string);
public string Message {
get {
return _message;
}
set {
if (_message == value) {
return;
}
_message = value;
RaisePropertyChanged(MessagePropertyName);
}
}
#endregion
public void DoSomething(string s) {
this.Message = s;
}
public override void Cleanup() {
base.Cleanup();
}
}
MainWindow XAML
<Window x:Class="MvvmLightTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="300"
Width="300"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<StackPanel>
<StackPanel>
<TextBlock FontSize="36"
FontWeight="Bold"
Foreground="Purple"
Text="{Binding Welcome}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<Button Content="click to send message" Margin="0,40,0,0" Command="{Binding TestCommand}" />
</StackPanel>
<StackPanel DataContext="{Binding SecondViewModel, Source={StaticResource Locator}}" Margin="0,40,0,0">
<TextBlock Text="{Binding Message, TargetNullValue='--'}" FontWeight="Bold" HorizontalAlignment="Center"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>

Related

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() };
}
}
}
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;

Login Window in WPF Prism

I'm implementing a login window before my shell runs. How will I run the code and say that after authentication in the login window, proceed with the execution of shell application.
Here is my initial code:
LoginViewModel.cs
public event EventHandler LoginCompleted;
private void RaiseLoginCompletedEvent()
{
LoginCompleted?.Invoke(this, EventArgs.Empty);
}
Bootstraper.cs from the Shell
My problem here is that I cannot instantiate my LoginModel since the constructor of my Model has a parameter to use my Services Interface.
Is there any solution for this? Thanks!
New up an instance of a class that implements the ILoginAuth interface yourself:
var loginVM = new LoginViewModel(new LoginAuth());
...or let the container do it for you:
var loginVM = new LoginViewModel(Container.Resolve<ILoginAuth>());
For the latter option to work, you must register your type mappings by overriding the RegisterTypes method:
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<ILoginAuth, LoginAuth>();
}
Another method of doing this:
ILoginAuth auth = CommonServiceLocator.ServiceLocator.Current.GetInstance<ILoginAuth>();
This way you don't have to include ILoginAuth in your constructor. Also, you can do this from any where!
<pre>
/*Brian code modified */
public interface ICloseWindow {
Action Close {
get;
set;
}
}
public partial class LoginWindow: Window {
public LoginWindow(ILoginViewModel viewModel) {
InitializeComponent();
DataContext = viewModel;
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e) {
if (DataContext is ICloseWindow vm) {
vm.Close += () =>{
DialogResult = true;
};
}
}
private void Close_Window(object sender, RoutedEventArgs e) {
DialogResult = false;
}
}
/*App.xaml.cs*/
protected override void InitializeShell(Window shell) {
Window login = Container.Resolve < LoginWindow > ();
var result = login.ShowDialog();
if (!result.Value) {
Application.Current.Shutdown();
}
else {
base.InitializeShell(shell);
}
}
public class LoginViewModel: BindableBase,
ILoginViewModel,
ICloseWindow {
private string _userName;
private string _password;
private bool _isAuthenticated;
private ICommand _authenticateUserCommand;
private IEventAggregator _eventAggregator;
public Action Close {
get;
set;
}
public ICommand AuthenticateUserCommand {
get =>_authenticateUserCommand;
set =>_authenticateUserCommand = value;
}
public LoginViewModel(IEventAggregator eventAggregator) {
AuthenticateUserCommand = new DelegateCommand < object > (AuthenticateUser);
_eventAggregator = eventAggregator;
}
private void AuthenticateUser(object parameter) {
var passwordBox = parameter as PasswordBox;
var password = passwordBox.Password;
if (password == "password") {
_isAuthenticated = true;
_eventAggregator.GetEvent < MessageSentEvent > ().Publish("Login Data");
}
if (_isAuthenticated) {
Close ? .Invoke();
}
}
public string UserName {
get =>_userName;
set =>_userName = value;
}
public string Password {
get {
return _password;
}
set {
if (_password != value) {
_password = value;
RaisePropertyChanged(nameof(Password));
}
}
}
}
<Window x:Class="YourApplication.Views.LoginWindow"
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:YourApplication"
mc:Ignorable="d"
Title="LoginWindow"
Height="450"
Width="800"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
x:Name="WindowBorder"
RenderOptions.BitmapScalingMode="HighQuality">
<Grid SnapsToDevicePixels="True"
UseLayoutRounding="True"
TextOptions.TextFormattingMode="Display"
TextOptions.TextRenderingMode="ClearType">
<DockPanel>
<DockPanel DockPanel.Dock="Left"
Width="400"
LastChildFill="True">
<Canvas>
<Canvas.Background>
<ImageBrush ImageSource="pack://application:,,,/YourApplication;component/Images/Splash.png" />
</Canvas.Background>
<TextBlock Margin="4,0,0,4"
VerticalAlignment="Center"
Foreground="#FFAAAAAA"
TextWrapping="NoWrap"
Text="Library Management System"
FontSize="16"
Background="Transparent"
DockPanel.Dock="Top" />
</Canvas>
</DockPanel>
<DockPanel LastChildFill="True">
<Button x:Name="PART_CLOSE"
DockPanel.Dock="Top"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="5"
Height="20"
Width="20"
Style="{DynamicResource MetroWindowButtonStyle}"
Click="Close_Window">
<Path Data="F1M54.0573,47.8776L38.1771,31.9974 54.0547,16.1198C55.7604,14.4141 55.7604,11.6511 54.0573,9.94531 52.3516,8.23962 49.5859,8.23962 47.8802,9.94531L32.0026,25.8229 16.1224,9.94531C14.4167,8.23962 11.6511,8.23962 9.94794,9.94531 8.24219,11.6511 8.24219,14.4141 9.94794,16.1198L25.8255,32 9.94794,47.8776C8.24219,49.5834 8.24219,52.3477 9.94794,54.0534 11.6511,55.7572 14.4167,55.7585 16.1224,54.0534L32.0026,38.1745 47.8802,54.0534C49.5859,55.7585 52.3516,55.7572 54.0573,54.0534 55.7604,52.3477 55.763,49.5834 54.0573,47.8776z"
Stretch="Uniform"
Fill="#FFAAAAAA"
Width="10"
Margin="0,0,0,0"></Path>
</Button>
<StackPanel>
<PasswordBox x:Name="txtPassword"
Margin="2,20,10,10"
Height="22"
Width="100" />
<Button Width="100"
Height="22"
Margin="10,150,10,10"
Content="Login"
Command="{Binding AuthenticateUserCommand}"
CommandParameter="{Binding ElementName=txtPassword}" />
</StackPanel>
</DockPanel>
</DockPanel>
</Grid>
</Window>
</pre>

how to add property to Window in xaml

I'm learning MVVM by write an litle app with Login function. In View layer, I have a LoginWindow with some binding like this:
<TextBox x:Name="tbxUsername" Grid.Row="0" Grid.Column="1" Width="150" Height="22" Margin="15,10,5,10"
Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}" />
<PasswordBox View:PasswordHelper.Attach="True" View:PasswordHelper.Password="{Binding Path=Password, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
x:Name="pwdPassword" Grid.Row="1" Grid.Column="1" Width="150" Height="22" Margin="15,10,5,10" />
The problem is i want to implement a binding like this:
<Window.Authenticated={Binding Path=Authenticated, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, NotifyOnTargetUpdated=True} TargetUpdated="authenticated_TargetUpdated"/>
Authenticated is a bool value which will changed in my viewmodel.
Is there an way for me?
Edit for #lain:
Here my LoginWindow.xaml (style and layout removed).
<Window x:Class="ATCheck_View.LoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:View="clr-namespace:ATCheck_View"
xmlns:ViewModel="clr-namespace:ATCheck_ViewModel;assembly=ATCheck_ViewModel"
Title="Login"
WindowStartupLocation="CenterScreen"
ResizeMode="CanMinimize"
SizeToContent="WidthAndHeight"
>
<Window.DataContext>
<ViewModel:LoginViewModel />
</Window.DataContext>
<Grid>
<TextBox x:Name="tbxUsername" Grid.Row="0" Grid.Column="1"
Width="150" Height="22" Margin="15,10,5,10" Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged, TargetNullValue='atcheck', NotifyOnTargetUpdated=True}"/>
<PasswordBox View:PasswordHelper.Attach="True" View:PasswordHelper.Password="{Binding Path=Password, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, TargetNullValue='123456'}" x:Name="pwdPassword" Grid.Row="1" Grid.Column="1"
Width="150" Height="22" Margin="15,10,5,10" />
<Button x:Name="btnLogin" Width="65" Height="20" Margin="5,15,10,12"
Command="{Binding LoginCommand}"
CommandParameter="">
<TextBlock VerticalAlignment="Center">Login</TextBlock>
</Button>
<Button x:Name="btnCancel" Width="60" Height="20" Margin="5,15,5,12" Click="btnCancel_Click">
<TextBlock VerticalAlignment="Center">Cancel</TextBlock>
</Button>
</Grid>
</Window>
LoginViewModel:
public class LoginViewModel: ViewModelBase
{
private string _username;
private string _password;
private bool _authenticated = false;
public string Username
{
get
{
return _username;
}
set
{
_username = value;
RaisePropertyChangedEvent("Username");
}
}
public string Password
{
get
{
return _password;
}
set
{
_password = value;
RaisePropertyChangedEvent("Password");
}
}
public bool Authenticated
{
get
{
return _authenticated;
}
private set
{
_authenticated = value;
RaisePropertyChangedEvent("Authenticated");
}
}
public ICommand LoginCommand
{
get
{
return new RelayCommand<string>(Login);
}
}
private void Login(string p)
{
Authenticated = true;
Console.WriteLine("Infomation:");
Console.WriteLine(Authenticated);
Console.WriteLine(Username);
Console.WriteLine(Password);
}
}
ViewModelBase implement INotifyPropertyChanged and RelayCommand that I folow John Smith's article.
#nit: I tried as your lead, propdp, rebuild, and type Authenticated folow "Window" tag, but nothing happened when I press commbo Ctrl + Space.
What all i want to do is an messagebox that will show when Authenticated change from False to True:
private bool _authenticated = false;
public bool Authenticated
{
get
{
return _authenticated;
}
set
{
if (value == true)
{
MessageBox.Show("Logged!");
}
}
}
public LoginWindow()
{
InitializeComponent();
LoginViewModel myViewModel = (LoginViewModel)this.DataContext;
myViewModel.PropertyChanged += myViewModel_PropertyChanged;
}
void myViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Authenticated")
{
Authenticated = ((LoginViewModel)sender).Authenticated;
}
}
You will have to add DependencyProperty to your Window class like below:
public static readonly DependencyProperty AuthenticatedProperty =
DependencyProperty.Register( "Authenticated", typeof(bool),
typeof(YOURWINDOWCLASS), new FrameworkPropertyMetadata(false));
// .NET Property wrapper
public bool Authenticated
{
get { return (bool)GetValue(AuthenticatedProperty ); }
set { SetValue(AuthenticatedProperty , value); }
}
Then you can bind
<Window Authenticated={Binding Path=Authenticated, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, NotifyOnTargetUpdated=True} TargetUpdated="authenticated_TargetUpdated"/>

Navigation between view using MVVM in WPF

Im new to WPF and MVVM.
Im trying to create Login window using MVVM and i succeeded to create.
here is the Login.xmal code.
<Button x:Name="btnLogin" Content="Login" HorizontalAlignment="Left" Margin="51,0,0,10"
VerticalAlignment="Bottom" Width="124" Height="57" Grid.Column="1"
CommandParameter="{Binding ElementName=txtPassword}"
Command="{Binding LoginCommand}"
>
</Button>
<Button x:Name="btnClose" Content="Close" HorizontalAlignment="Left" Margin="180,0,0,10"
VerticalAlignment="Bottom" Width="124" Height="57" Grid.Column="1" Command="{Binding ExitCommand}">
</Button>
<Label Content="User Name" Margin="10,74,0,0" VerticalAlignment="Top" Height="49"
VerticalContentAlignment="Center" Grid.Column="1" HorizontalAlignment="Left" Width="130"/>
<TextBox x:Name="txtUserName" HorizontalAlignment="Right" Height="49" Margin="0,74,10,0"
TextWrapping="Wrap" VerticalAlignment="Top" Width="185"
VerticalContentAlignment="Center" Grid.Column="1" FontSize="18">
<TextBox.Text>
<Binding Path="Username" Mode="OneWayToSource">
<Binding.ValidationRules>
<ExceptionValidationRule></ExceptionValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Content="Password" Margin="10,128,0,0" VerticalAlignment="Top" Height="49"
VerticalContentAlignment="Center" Grid.Column="1" HorizontalAlignment="Left" Width="130"/>
<PasswordBox x:Name="txtPassword" HorizontalAlignment="Right"
Height="49" Margin="0,128,10,0"
VerticalAlignment="Top" Width="185"
VerticalContentAlignment="Center" Grid.Column="1" FontSize="18">
</PasswordBox>
after this i have created the viewModeBase.cs class in which i implemented INotifyPropertyChanged and this included in LoginViewModel.cs...
here is LoginViewModel.cs code
public class LoginViewModel : ViewModelBase
{
private string m_username;
public string Username
{
get { return m_username; }
set
{
m_username = value;
OnPropertyChanged("Username");
}
}
private string m_password;
public string Password
{
get { return m_password; }
set
{
m_password = value;
OnPropertyChanged("Password");
}
}
private DelegateCommand exitCommand;
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand =new DelegateCommand(Exit);
}
return exitCommand;
}
}
private void Exit()
{
Application.Current.Shutdown();
}
public LoginViewModel()
{
}
private DelegateCommand<object> loginCommand;
public ICommand LoginCommand
{
get
{
if (loginCommand == null)
{
loginCommand = new DelegateCommand<object>(Login);
}
return loginCommand;
}
}
public void Login(object pPasswordBox)
{
try
{
if (string.IsNullOrEmpty(Username))
{
MessageBox.Show("Username cannot be blank.");
return;
}
if (string.IsNullOrEmpty(((PasswordBox)pPasswordBox).Password))
{
MessageBox.Show("Password cannot be blank.");
return;
}
dlUsers odlUsers = new dlUsers();
bool lResult = odlUsers.UserAuthentication(clsGymManagment.ConnectionString, Username,
((((PasswordBox)pPasswordBox).Password)));
if (lResult)
{
///TODO: Need code to Hide Login Window and Open New XAML.....
}
else
{
MessageBox.Show("Username/Password is wrong.");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
As i want to Hide LOGIN.XAML file and open UI.XAML file.. (UI.XAML you can consider any XAML window.)...
also it would be help full if you could assist me to navigation between Usercontrol on UI.XAML
You need to control the login window from a separate block of code, for instance App.xaml.cs. Set app.xaml to call code rather than show a window.
Have App_Startup create LoginViewModel, new up a form, set the data context of the form to your ViewModel and show the it.
Updates to the form will update the ViewModel, when it closes it will return control to your calling code.
Login.xaml.cs
private void btnOk_Click(object sender, RoutedEventArgs e)
{
if (anything incorrect)
{
MessageBox.Show("Enter a username and password");
}
else
DialogResult = true;
}
App.xaml.cs
Login.DataContext = LoginViewModel;
if (Login.ShowDialog() ?? false)
{
//Check the LoginViewModel for a correct password.
}
Fortunately the ability to hide and display different controls as you move through different pages inside an application is already written for you. See http://msdn.microsoft.com/en-us/library/ms750478.aspx.
Navigation Window is really powerful and can quite easily be skinned to provide very completely different looks too. See http://alski.net/post/2012/01/13/WPF-Wizards-part-2-Glass.aspx

data grid binding in silverlight

I have a simple class abc
class abc
{
public string a { get; set; }
public string b { get; set; }
public string c { get; set; }
public abc(string d, string e, string f)
{
a = d;
b = e;
c = f;
}
}
public MainPage()
{
InitializeComponent();
abc obj = new abc("abc1", "abc2", "abc3");
LayoutRoot.DataContext = obj;
}
and a grid which contain three textbox 1 2 3 I am trying to bind these 3 properties of a class to a grid usercontrol.
<Grid x:Name="LayoutRoot" Background="White">
<TextBox Height="27" HorizontalAlignment="Left" Margin="125,86,0,0" Name="textBox1" Text="{Binding Path= a}" VerticalAlignment="Top" Width="120" />
<TextBox Height="25" HorizontalAlignment="Left" Margin="21,192,0,83" Name="textBox2" Text="{Binding Path= b}" Width="120" />
<TextBox Height="25" HorizontalAlignment="Left" Margin="250,192,0,0" Name="textBox3" Text="{Binding Path= c}" VerticalAlignment="Top" Width="120" />
</Grid>
it doesn't show any error but it does not show any output to a screen,what specific problem it creating?
Try do not use "Path= " (with space) in the binding expression. Try use:
Text="{Binding a}"
"Path" is present hiddenly in binding expressions. You need read some resources about bindings.
First your type 'abc' should implement INotifyPropertyChanged.
public class abc : INotifyPropertyChanged
{
...
}
Then you need to raise the INotifyPropertyChanged.PropertyChanged event
private void RaiseProperty(string propertyName)
{
var handle = PropertyChanged;
if(handle != null)
{
handle(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _a;
public string a { get{ return _a;} set{ _a = value; RaiseProperty("a"); } }
....
This should work as you need to a mechanism to notify the Binding if you are using CLR proprties; and that mechanism is provided by INotifyPropertyChanged interface

Resources