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;
Related
I am an experienced WinForms developer, relatively new to WPF. I have a large WinForms application that uses a couple different base classes to represent dialog boxes. One such example is AbstractOkCancelDialog. That class contains a panel at the bottom of a dialog, with an Ok and Cancel button on the right side of the panel. I'm trying to determine the best way to handle this, as I realize that WPF doesn't provide visual inheritance.
I don't want to have to create OK and Cancel buttons, and place them, for every dialog in the application.
I have read that the way to do this in WPF is with user controls. I can envision creating a user control with OK and Cancel buttons on it. But I don't want to have to manually place that user control on hundreds of dialogs in my application. I'd really like to have something like this:
public AbstractOkCancelDialog = class(Window)
{
protected AbstractOkCancelDialogViewModel _ViewModel;
// AbstractOkCancelDialogViewModel would have commands for OK and Cancel.
// Every dialog would inherit from AbstractOkCancelDialog, and would use
// a viewmodel that inherits from AbstractOkCancelDialogViewModel. In
// this way, all view models would automatically be connected to the OK
// and Cancel commands.
}
I've seen some discussion online about how to create the base class. Those discussions explain how there can't be a xaml file associated with the dialog base class, and I understand that restriction. I just can't figure out how to automatically place the user control with the OK and Cancel buttons.
I'm hoping that someone can point me to a sample solution that shows this kind of structure. Thank you in advance!
Write one dialog class. It's a subclass of Window. It has XAML:
<Window
...blah blah blah...
Title="{Binding Title}"
>
<StackPanel MinWidth="300">
<!-- This is how you place content within content in WPF -->
<ContentControl
Content="{Binding}"
Margin="2"
/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="2,20,2,2">
<Button
Margin="2"
MinWidth="60"
DockPanel.Dock="Right"
Content="OK"
Click="OK_Click"
IsDefault="True"
/>
<Button
Margin="2"
MinWidth="60"
DockPanel.Dock="Right"
Content="Cancel"
IsCancel="True"
Click="Cancel_Click"
/>
</StackPanel>
</StackPanel>
</Window>
You can fancy that up endlessly, but this is a decent minimum to give you arbitrary content above a row of right-aligned buttons. Adding more buttons as needed could involve either templating that portion of the window as well, or creating them with an ItemsControl (I've done that in our production code), or a few other options.
Usage:
var vm = new SomeDialogViewModel();
var dlg = new MyDialog { DataContext = vm };
For each dialog viewmodel, consumers must define an implicit datatemplate which provides UI for that viewmodel.
I would suggest writing a dialog viewmodel interface which the consumer is expected to implement.
public interface IDialogViewModel
{
String Title { get; set; }
void OnOK();
// Let them "cancel the cancel" if they like.
bool OnCancel();
}
The window can check if its DataContext implements that interface, and act accordingly. If you like, it could require that interface and throw an exception of it isn't implemented, or it could just talk to it only if it's there. If they don't implement it but they still have a Title property, the binding to Title will still work. Bindings are "duck typed".
Naturally, you can write an OKCancelDialogViewModel or a SelectStringFromListViewModel, write corresponding DataTemplates that implement their UIs, and write nice clean static methods which show them:
public static class Dialogs
{
public static TOption Select<TOption>(IEnumerable<TOption> options, string prompt,
string title = "Select Option") where TOption : class
{
// Viewmodel isn't generic because that breaks implicit datatemplating.
// That's OK because XAML uses duck typing anyhow.
var vm = new SelectOptionDialogViewModel
{
Title = title,
Prompt = prompt,
Options = options
};
if ((bool)new Dialog { DataContext = vm }.ShowDialog())
{
return vm.SelectedOption as TOption;
}
return null;
}
// We have to call the value-type overload by a different name because overloads can't be
// distinguished when the only distinction is a type constraint.
public static TOption? SelectValue<TOption>(IEnumerable<TOption> options, string prompt,
string title = "Select Option") where TOption : struct
{
var vm = new SelectOptionDialogViewModel
{
Title = title,
Prompt = prompt,
// Need to box these explicitly
Options = options.Select(opt => (object)opt)
};
if ((bool)new Dialog { DataContext = vm }.ShowDialog())
{
return (TOption)vm.SelectedOption;
}
return null;
}
}
Here's a viewmodel datatemplate for the above selection dialog:
<Application.Resources>
<DataTemplate DataType="{x:Type local:SelectOptionDialogViewModel}">
<StackPanel>
<TextBlock
TextWrapping="WrapWithOverflow"
Text="{Binding Prompt}"
/>
<ListBox
ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption}"
MouseDoubleClick="ListBox_MouseDoubleClick"
/>
</StackPanel>
</DataTemplate>
</Application.Resources>
App.xaml.cs
private void ListBox_MouseDoubleClick(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
((sender as FrameworkElement).DataContext as IDialogViewModel).DialogResult = true;
}
var a = Dialogs.Select(new String[] { "Bob", "Fred", "Ginger", "Mary Anne" },
"Select a dance partner:");
var b = Dialogs.SelectValue(Enum.GetValues(typeof(Options)).Cast<Options>(),
"Select an enum value:");
Here an example of how to use a custom AlertDialog
UserControl
<UserControl x:Class="Library.Views.AlertMessageDialogView"
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:p="clr-namespace:Library.Properties"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
FlowDirection = "{Binding WindowFlowDirection, Mode=TwoWay}">
<Grid Background="{DynamicResource WindowBackgroundBrush}">
<Canvas HorizontalAlignment="Left" Height="145" VerticalAlignment="Top" Width="385">
<Label HorizontalAlignment="Left" Height="57" VerticalAlignment="Top" Width="365" Canvas.Left="10" Canvas.Top="10" FontSize="14" >
<TextBlock x:Name="txtVocabAnglais" TextWrapping="Wrap" Text="{Binding Message, Mode=TwoWay}" Width="365" Height="57" />
</Label>
<Button x:Name="cmdCancel" Content="{x:Static p:Resources.AlertMessageDialogViewcmdCancel}" Height="30" Canvas.Left="163" Canvas.Top="72" Width="71" Command = "{Binding CancelCommand}" CommandParameter = "null" IsDefault="True"/>
</Canvas>
</Grid>
</UserControl>
ViewModel Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows;
using System.ComponentModel;
using System.Windows.Controls;
namespace Library.ViewModel
{
public class AlertMessageDialogViewModel : BindableBaseViewModel
{
public event EventHandler CloseWindowEvent;
private string _title;
private string _message;
public BaseCommand<string> YesCommand { get; private set; }
public BaseCommand<string> CancelCommand { get; private set; }
private WinformsNameSpace.FlowDirection _windowFlowDirection;
public AlertMessageDialogViewModel()
{
CancelCommand = new BaseCommand<string>(cmdCancelBtnClick);
WindowFlowDirection = CustomFuncVar.WindowFlowDirection;
}
public WinformsNameSpace.FlowDirection WindowFlowDirection
{
get
{
return _windowFlowDirection;
}
set
{
_windowFlowDirection = value;
OnPropertyChanged("WindowFlowDirection");
}
}
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public string Title
{
get
{
return _title;
}
set
{
_title = value;
}
}
private void cmdCancelBtnClick(string paramerter)
{
if (CloseWindowEvent != null)
CloseWindowEvent(this, null);
}
}
}
DialogMessage Class
using System;
using System.Windows;
using System.Collections.Generic;
namespace Library.Helpers
{
public static class DialogMessage
{
public static void AlertMessage(string message, string title, Window OwnerWindowView)
{
try
{
//Affichage de méssage de succès enregistrement
AlertMessageDialogViewModel alertDialogVM = new AlertMessageDialogViewModel();
alertDialogVM.Message = message;
alertDialogVM.Title = title;
// Auto Generation Window
FrameworkElement view = LpgetCustomUI.AutoGetViewFromName("AlertMessageDialogView");
view.DataContext = alertDialogVM;
Dictionary<string, object> localVarWindowProperty = new Dictionary<string, object>();
localVarWindowProperty = LpgetCustomUI.GetWindowPropretyType_400x145(Properties.Resources.ApplicationTitle);
CummonUIWindowContainer alertDialogView = new CummonUIWindowContainer(view, null, false, localVarWindowProperty);
//End Auto Generation Window
// Attachement de l'évènement de fermture de View au modèle
alertDialogVM.CloseWindowEvent += new EventHandler(alertDialogView.fnCloseWindowEvent);
if (OwnerWindowView!=null)
{
alertDialogView.Owner = OwnerWindowView;
}
else
{
alertDialogView.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
alertDialogView.ShowDialog();
}
catch (Exception ex)
{
}
}
}
}
CummonUIWindowContainer Class
namespace CummonUILibrary.CummonUIHelpers
{
public class CummonUIWindowContainer : Window
{
public event RoutedEventHandler CmbRootEvtLanguageChange;
private FrameworkElement currentView;
private ContentControl _contentcontainer;
public CummonUIWindowContainer(string usercontrolName)
{
Contentcontainer = new ContentControl();
currentView = new FrameworkElement();
}
public CummonUIWindowContainer()
{
Contentcontainer = new ContentControl();
currentView = new FrameworkElement();
}
public CummonUIWindowContainer(FrameworkElement view, object model, bool setDataContextToView, Dictionary<string, object> WindowPropertyList)
{
Contentcontainer = new ContentControl();
Contentcontainer.Name = "ContentControl";
SetWindowProperty(view, model, setDataContextToView, WindowPropertyList);
}
public void SetWindowProperty(FrameworkElement view, object model, bool setDataContextToView, Dictionary<string, object> WindowPropertyList)
{
try
{
LinearGradientBrush brush = new LinearGradientBrush();
GradientStop gradientStop1 = new GradientStop();
gradientStop1.Offset = 0;
gradientStop1.Color = Colors.Yellow;
brush.GradientStops.Add(gradientStop1);
GradientStop gradientStop2 = new GradientStop();
gradientStop2.Offset = 0.5;
gradientStop2.Color = Colors.Indigo;
brush.GradientStops.Add(gradientStop2);
GradientStop gradientStop3 = new GradientStop();
gradientStop3.Offset = 1;
gradientStop3.Color = Colors.Yellow;
brush.GradientStops.Add(gradientStop3);
this.Background = brush;
CurrentView = view;
Type elementType = this.GetType();
ICollection<string> WindowPropertyListNames = WindowPropertyList.Keys;
foreach (string propertyName in WindowPropertyListNames)
{
PropertyInfo property = elementType.GetProperty(propertyName);
property.SetValue(this, WindowPropertyList[propertyName]);
}
if (setDataContextToView == true & model != null)
{
CurrentView.DataContext = model;
}
if (CurrentView != null)
{
Contentcontainer.Content = CurrentView;
}
//Contentcontainer.Margin = new Thickness(0,0, 0, 0);
IAddChild container=this;
container.AddChild(Contentcontainer);
}
catch (Exception ex)
{
}
}
public void fnCloseWindowEvent(object sender, EventArgs e)
{
this.Close();
}
public ContentControl Contentcontainer
{
get
{
return _contentcontainer;
}
set
{
_contentcontainer = value;
}
}
public FrameworkElement CurrentView
{
get
{
return currentView;
}
set
{
if (this.currentView != value)
{
currentView = value;
//RaisePropertyChanged("CurrentView");
}
}
}
private void cmbLanguage_SelectionChanged(object sender, RoutedEventArgs e)
{
//CmbRootEvtLanguageChange(sender, e);
}
}
}
How to use the Class
DialogMessage.AlertMessage("My Custom Message", "My Custom Title Message");
Thats how i would do it
Create an abstract base class for your dialog and changing the corresponding ControlTemplate
AbstractOkCancelDialog
public abstract class AbstractOkCancelDialog : Window
{
public static readonly DependencyProperty CancelCommandParameterProperty =
DependencyProperty.Register(
"CancelCommandParameter",
typeof(object),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((object) null));
public static readonly DependencyProperty CancelCommandProperty =
DependencyProperty.Register(
"CancelCommand",
typeof(ICommand),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((ICommand) null));
public static readonly DependencyProperty OkCommandParameterProperty =
DependencyProperty.Register(
"OkCommandParameter",
typeof(object),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((object) null));
public static readonly DependencyProperty OkCommandProperty =
DependencyProperty.Register(
"OkCommand",
typeof(ICommand),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((ICommand) null));
static AbstractOkCancelDialog()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AbstractOkCancelDialog), new
FrameworkPropertyMetadata(typeof(AbstractOkCancelDialog)));
}
public ICommand CancelCommand
{
get => (ICommand) GetValue(CancelCommandProperty);
set => SetValue(CancelCommandProperty, value);
}
public object CancelCommandParameter
{
get => GetValue(CancelCommandParameterProperty);
set => SetValue(CancelCommandParameterProperty, value);
}
public ICommand OkCommand
{
get => (ICommand) GetValue(OkCommandProperty);
set => SetValue(OkCommandProperty, value);
}
public object OkCommandParameter
{
get => GetValue(OkCommandParameterProperty);
set => SetValue(OkCommandParameterProperty, value);
}
}
Style
Put in Generic.xaml[?]
<Style
BasedOn="{StaticResource {x:Type Window}}"
TargetType="{x:Type local:AbstractOkCancelDialog}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AbstractOkCancelDialog}">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="1"
Margin="5"
Command="{TemplateBinding OkCommand}"
CommandParameter="{TemplateBinding OkCommandParameter}"
Content="Ok"
DockPanel.Dock="Right" />
<Button
Grid.Column="2"
Margin="5"
Command="{TemplateBinding CancelCommand}"
CommandParameter="{TemplateBinding CancelCommandParameter}"
Content="Cancel"
DockPanel.Dock="Right" />
</Grid>
</Grid>
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now you can create your individual dialogs like you would create any other window
Brief example:
TestDialog.xaml
<local:AbstractOkCancelDialog
x:Class="WpfApp.TestDialog"
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:local="clr-namespace:WpfApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TestDialog"
Width="800"
Height="450"
OkCommand="{x:Static local:Commands.OkWindowCommand}"
OkCommandParameter="{Binding RelativeSource={RelativeSource Self}}"
CancelCommand="{x:Static local:Commands.CancelWindowCommand}"
CancelCommandParameter="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<Grid>
<!-- Content -->
</Grid>
</local:AbstractOkCancelDialog>
TestDialog.xaml.cs
public partial class TestDialog : AbstractOkCancelDialog
{
...
}
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>
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"/>
I have a class as follows:
public class Guardian : ModelBase, IDataErrorInfo
{
internal Guardian()
{
}
[Required]
[StringLength(50)]
[Display(Name = "Guardian's First Name")]
public string FirstName
{
get { return GetValue(() => FirstName); }
set { SetValue(() => FirstName, value); }
}
[Required]
[StringLength(50)]
[Display(Name = "Guardian's Last Name")]
public string LastName
{
get { return GetValue(() => LastName); }
set { SetValue(() => LastName, value); }
}
[USPhoneNumber]
[Display(Name = "Home Phone Number")]
public string HomePhone
{
get { return GetValue(() => HomePhone); }
set { SetValue(() => HomePhone, value.NormalizeNANPPhoneNumber()); }
}
[USPhoneNumber]
[Display(Name = "Personal Cell")]
public string PersonalCell
{
get { return GetValue(() => PersonalCell); }
set { SetValue(() => PersonalCell, value.NormalizeNANPPhoneNumber()); }
}
[Required]
[StringLength(100)]
[Display(Name = "Address")]
public string Address1
{
get { return GetValue(() => Address1); }
set { SetValue(() => Address1, value); }
}
[StringLength(100)]
public string Address2
{
get { return GetValue(() => Address2); }
set { SetValue(() => Address2, value); }
}
[Required]
[StringLength(100)]
public string City
{
get { return GetValue(() => City); }
set { SetValue(() => City, value); }
}
[Required]
[StringLength(100)]
public string State
{
get { return GetValue(() => State); }
set { SetValue(() => State, value); }
}
[Required]
[StringLength(20)]
[USPostalCode]
[Display(Name = "ZIP Code")]
public string Zip
{
get { return GetValue(() => Zip); }
set { SetValue(() => Zip, value); }
}
[Required]
[Display(Name = "Relationship to Children")]
public FamilyRole Relationship
{
get { return GetValue(() => Relationship); }
set { SetValue(() => Relationship, value); }
}
internal bool IsEmpty()
{
return
string.IsNullOrWhiteSpace(FirstName)
&& string.IsNullOrWhiteSpace(LastName)
&& string.IsNullOrWhiteSpace(HomePhone)
&& string.IsNullOrWhiteSpace(PersonalCell)
&& string.IsNullOrWhiteSpace(Address1)
&& string.IsNullOrWhiteSpace(Address2)
&& string.IsNullOrWhiteSpace(City)
&& string.IsNullOrWhiteSpace(State)
&& string.IsNullOrWhiteSpace(Zip)
&& Relationship == null
;
}
/// <summary>
/// Provides support for cross-cutting concerns without having to write
/// an attribute in Silverlight.
/// When time allows, convert to an Attribute. The code produced then
/// can be reused in other projects.
/// </summary>
/// <param name="listToAddTo"></param>
private void CustomValidation(List<ValidationResult> listToAddTo)
{
if (listToAddTo == null)
throw new ArgumentNullException("listToAddTo");
if (string.IsNullOrWhiteSpace(HomePhone) && string.IsNullOrWhiteSpace(PersonalCell))
listToAddTo.Add(new ValidationResult("At least one phone number must be filled in.", new string[] { "HomePhone" }));
}
#region IDataErrorInfo Members
public string Error
{
get
{
List<ValidationResult> results = new List<ValidationResult>();
this.IsValidObject(results);
CustomValidation(results);
if (results.Count > 0)
return results[0].ErrorMessage;
else
return null;
}
}
public string this[string columnName]
{
get
{
List<ValidationResult> results = new List<ValidationResult>();
this.IsValidObject(results);
CustomValidation(results);
var resultByColumn = results.Where(r => r.MemberNames.Contains(columnName)).ToList();
if (resultByColumn.Count > 0)
return resultByColumn[0].ErrorMessage;
else
return null;
}
}
#endregion
}
I implement IDataErrorInfo for this class. Everything works fine, my problem is really an annoyance, but one big enough that the Guy Who Pays The Bills says needs to be fixed. I have a separate void that does extra validation, which is called by the IDataErrorInfo members. It checks to see if at least one phone number is filled in.
An instance of this class is on my model, called CurrentGuardian, and the model is the DataContext for the following popup:
<controls:ChildWindow xmlns:my="clr-namespace:Microsoft.Windows.Controls"
x:Class="Tracktion.Controls.CheckInWindows.AddGuardian"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Width="575" Height="326"
Title="Add Parent/Guardian" HasCloseButton="False"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<controls:ChildWindow.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontSize" Value="12" />
</Style>
</controls:ChildWindow.Resources>
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition Height="44" />
<RowDefinition Height="215*" />
<RowDefinition Height="45" />
</Grid.RowDefinitions>
<TextBlock Height="23" Name="textBlock1" Text="Please fill out the form below. Fields marked with an asterisk are required." VerticalAlignment="Top" TextAlignment="Center" />
<TextBlock Height="23" Margin="0,21,0,0" Name="textBlock2" Text="When done, click Add Another Guardian or Continue Adding Children below." VerticalAlignment="Top" TextAlignment="Center" />
<sdk:Label Grid.Row="1" Height="22" HorizontalAlignment="Left" Margin="0,11,0,0" Name="label1" VerticalAlignment="Top" Width="142" Content="* Guardian's First Name:" />
<sdk:Label Content="* Guardian's Last Name:" Height="22" HorizontalAlignment="Left" Margin="0,46,0,0" Name="label2" VerticalAlignment="Top" Width="142" Grid.Row="1" />
<sdk:Label Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="0,81,0,0" Name="label3" VerticalAlignment="Top" Width="142" Content="* Home Phone:" />
<sdk:Label Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="0,116,0,0" Name="label4" VerticalAlignment="Top" Width="120" Content="* Personal Cell:" />
<sdk:Label Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="302,11,0,0" Name="label5" VerticalAlignment="Top" Width="76" Content="* Address:" />
<sdk:Label Content="* What is your relationship to the child or children?" Height="23" HorizontalAlignment="Left" Margin="0,155,0,0" Name="label6" VerticalAlignment="Top" Width="360" Grid.Row="1" />
<TextBox Text="{Binding Path=CurrentGuardian.FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="148,5,0,0" Name="textBox1" VerticalAlignment="Top" Width="135" />
<TextBox Text="{Binding Path=CurrentGuardian.LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="148,40,0,0" Name="textBox2" VerticalAlignment="Top" Width="135" />
<TextBox Text="{Binding Path=CurrentGuardian.HomePhone, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="148,75,0,0" Name="txtHomePhone" VerticalAlignment="Top" Width="135" LostFocus="PhoneNumber_LostFocus" />
<TextBox Text="{Binding Path=CurrentGuardian.PersonalCell, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="148,110,0,0" Name="txtCellPhone" VerticalAlignment="Top" Width="135" LostFocus="PhoneNumber_LostFocus" />
<my:WatermarkedTextBox Text="{Binding Path=CurrentGuardian.Address1, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="366,5,0,0" x:Name="textBox5" VerticalAlignment="Top" Width="184" Watermark="Line 1" />
<my:WatermarkedTextBox Text="{Binding Path=CurrentGuardian.Address2, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="366,40,0,0" x:Name="textBox6" VerticalAlignment="Top" Width="184" Watermark="Line 2" />
<my:WatermarkedTextBox Text="{Binding Path=CurrentGuardian.City, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="366,75,0,0" x:Name="textBox7" VerticalAlignment="Top" Width="184" Watermark="City" />
<ComboBox ItemsSource="{Binding Path=States}" DisplayMemberPath="Abbreviation" SelectedValuePath="Abbreviation" SelectedValue="{Binding Path=CurrentGuardian.State, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="366,110,0,0" Name="comboBox1" VerticalAlignment="Top" Width="88" />
<my:WatermarkedTextBox Text="{Binding Path=CurrentGuardian.Zip, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="460,110,0,0" x:Name="textBox8" VerticalAlignment="Top" Width="90" Watermark="ZIP" />
<ComboBox DisplayMemberPath="Name" Height="28" HorizontalAlignment="Left" ItemsSource="{Binding Path=Relationships}" Margin="302,149,0,0" Name="comboBox2" SelectedItem="{Binding Path=CurrentGuardian.Relationship, Mode=TwoWay, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="249" Grid.Row="1" />
<Button Content="Cancel" Grid.Row="2" Height="37" HorizontalAlignment="Left" Margin="0,8,0,0" Name="btnCancel" VerticalAlignment="Top" Width="93" Style="{StaticResource RedButton}" Click="btnCancel_Click" />
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
<Button Content="Add Another Guardian" Height="37" Margin="5,8,0,0" Name="btnAddGuardian" VerticalAlignment="Top" Style="{StaticResource OrangeButton}" HorizontalAlignment="Right" Width="159" Click="btnAddGuardian_Click" />
<Button Content="Continue" Height="37" Margin="5,8,0,0" Name="btnContinue" VerticalAlignment="Top" Style="{StaticResource GreenButton}" HorizontalAlignment="Right" Padding="20,0" Click="btnContinue_Click" />
<!-- TODO: Visibility set when accessing this screen through check in screen. -->
<Button Content="Check In" Margin="5,8,0,0" Name="btnCheckIn" Visibility="Collapsed" Style="{StaticResource GreenButton}" Click="btnCheckIn_Click" />
</StackPanel>
</Grid>
One phone number is required. You can enter both, but at least one is required. When the form binds to the empty CurrentGuardian, the first phone number field, Home Phone, is highlighted in red. It stays red after focusing and blurring that field, when both numbers have no data. If I enter a phone number, the field turns to black. Deleting the number turns it red. So far, so good - this is expected behavior. Now, if I do not enter a number for Home Phone, but then I enter a phone number for personal cell, when I tab off Personal Cell the Home Phone number stays highlighted in red until I tab to it. Once I tab to it, the red outline disappears. How do I make the field validate? I currently have the following code in the blur event for both fields:
private void PhoneNumber_LostFocus(object sender, RoutedEventArgs e)
{
KioskCheckIn2 model = (KioskCheckIn2)this.DataContext;
model.CurrentGuardian.IsValidObject(); // revalidate
txtHomePhone.GetBindingExpression(TextBox.TextProperty).UpdateSource();
txtCellPhone.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
I have the ValidationResult for checking both numbers only returning the HomePhone field as the offending control since I could not figure out how to force re-validation of the controls so both red outlines would disappear.
Thanks in advance!
Try using the DataForm not inside a ChildWindow. This is known to be buggy. You can also try to apply these fixes. Maybe things have improved for Silverlight 5, I haven't checked that yet.
Generally, the DataForm control is in the Toolkit's "Preview" quality band and the ChildWindow isn't perfect either, so you can expect bugs in certain scenarios. You also have the source code though to make further fixes. ;)
Implementing INotifyDataErrorInfo, rather than IDataErrorInfo, seemed to do the trick. IDataErrorInfo is nice but lacks the ability to let the UI know when other properties have changed besides the current one. I tried allowing both implementations but it got a bit weird, so I changed it so it only implemented INotifyDataErrorInfo.
public class Guardian : ModelBase, /*IDataErrorInfo,*/ INotifyDataErrorInfo
{
internal Guardian()
{
}
[Required]
[StringLength(50)]
[Display(Name = "Guardian's First Name")]
public string FirstName
{
get { return GetValue(() => FirstName); }
set { SetValue(() => FirstName, value); }
}
[Required]
[StringLength(50)]
[Display(Name = "Guardian's Last Name")]
public string LastName
{
get { return GetValue(() => LastName); }
set { SetValue(() => LastName, value); }
}
[USPhoneNumber]
[Display(Name = "Home Phone Number")]
public string HomePhone
{
get { return GetValue(() => HomePhone); }
set { SetValue(() => HomePhone, value.NormalizeNANPPhoneNumber()); }
}
[USPhoneNumber]
[Display(Name = "Personal Cell")]
public string PersonalCell
{
get { return GetValue(() => PersonalCell); }
set { SetValue(() => PersonalCell, value.NormalizeNANPPhoneNumber()); }
}
[Required]
[StringLength(100)]
[Display(Name = "Address")]
public string Address1
{
get { return GetValue(() => Address1); }
set { SetValue(() => Address1, value); }
}
[StringLength(100)]
public string Address2
{
get { return GetValue(() => Address2); }
set { SetValue(() => Address2, value); }
}
[Required]
[StringLength(100)]
public string City
{
get { return GetValue(() => City); }
set { SetValue(() => City, value); }
}
[Required]
[StringLength(100)]
public string State
{
get { return GetValue(() => State); }
set { SetValue(() => State, value); }
}
[Required]
[StringLength(20)]
[USPostalCode]
[Display(Name = "ZIP Code")]
public string Zip
{
get { return GetValue(() => Zip); }
set { SetValue(() => Zip, value); }
}
[Required]
[Display(Name = "Relationship to Children")]
public FamilyRole Relationship
{
get { return GetValue(() => Relationship); }
set { SetValue(() => Relationship, value); }
}
internal bool IsEmpty()
{
return
string.IsNullOrWhiteSpace(FirstName)
&& string.IsNullOrWhiteSpace(LastName)
&& string.IsNullOrWhiteSpace(HomePhone)
&& string.IsNullOrWhiteSpace(PersonalCell)
&& string.IsNullOrWhiteSpace(Address1)
&& string.IsNullOrWhiteSpace(Address2)
&& string.IsNullOrWhiteSpace(City)
&& string.IsNullOrWhiteSpace(State)
&& string.IsNullOrWhiteSpace(Zip)
&& Relationship == null
;
}
protected override void PropertyHasChanged(string propertyName)
{
base.PropertyHasChanged(propertyName);
if (ErrorsChanged != null)
this.GetType().GetProperties().ToList().ForEach(p => ErrorsChanged(this, new DataErrorsChangedEventArgs(p.Name)));
}
/// <summary>
/// Provides support for cross-cutting concerns without having to write
/// an attribute in Silverlight.
/// When time allows, convert to an Attribute. The code produced then
/// can be reused in other projects.
/// </summary>
/// <param name="listToAddTo"></param>
private void CustomValidation(List<ValidationResult> listToAddTo)
{
if (listToAddTo == null)
throw new ArgumentNullException("listToAddTo");
if (string.IsNullOrWhiteSpace(HomePhone) && string.IsNullOrWhiteSpace(PersonalCell))
listToAddTo.Add(new ValidationResult("At least one phone number must be filled in.", new string[] { "HomePhone", "PersonalCell" }));
}
List<ValidationResult> getErrorList()
{
List<ValidationResult> results = new List<ValidationResult>();
this.IsValidObject(results);
CustomValidation(results);
return results;
}
/*
#region IDataErrorInfo Members
public string Error
{
get
{
List<ValidationResult> results = getErrorList();
if (results.Count > 0)
return results[0].ErrorMessage;
else
return null;
}
}
public string this[string columnName]
{
get
{
List<ValidationResult> results = getErrorList();
var resultByColumn = results.Where(r => r.MemberNames.Contains(columnName)).ToList();
if (resultByColumn.Count > 0)
return resultByColumn[0].ErrorMessage;
else
return null;
}
}
#endregion
*/
#region INotifyDataErrorInfo Members
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
List<ValidationResult> results = getErrorList();
return results.Where(e => e.MemberNames.Contains(propertyName));
}
public bool HasErrors
{
get
{
List<ValidationResult> results = getErrorList();
return results.Count > 0;
}
}
#endregion
}
Now, when I first go to the screen, both phone fields are highlighted. Entering a valid phone number in one field makes both phone number fields pass validation and the red line surrounding each disappears. Note that I put the second field back in under CustomValidation,
listToAddTo.Add(new ValidationResult("At least one phone number must be filled in.", new string[] { "HomePhone", "PersonalCell" }));
...so that both properties can show the red lines around them if the input is not in a correct format.
Here's some code I made to handle validation for both IDataErrorInfo and INotifyDataErrorInfo. You'll have to rework it a bit to fit your base class (if you have one) but I hope this helps someone out:
namespace CLARIA.Infrastructure
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
/// <summary>
/// Builds upon ModelBase with built-in validation.
/// </summary>
public abstract class ValidatableModelBase : ModelBase,
#if SILVERLIGHT
INotifyDataErrorInfo
#else
IDataErrorInfo
#endif
{
private List<ValidationResult> GetErrorList()
{
List<ValidationResult> results = new List<ValidationResult>();
this.IsValidObject(results);
CustomValidation(results);
return results;
}
/// <summary>
/// Allows the derived class to override and add custom validation.
/// The validation results generated from this method should be added
/// to the collection <see cref="addResultsToThisList"/>.
/// </summary>
/// <param name="addResultsToThisList"></param>
protected virtual void CustomValidation(List<ValidationResult> addResultsToThisList) {}
#if SILVERLIGHT
#region INotifyDataErrorInfo Members
protected override void PropertyHasChanged(string propertyName)
{
base.PropertyHasChanged(propertyName);
// Force re-validation of every property.
if (ErrorsChanged != null)
this.GetType().GetProperties().ToList().ForEach(p => ErrorsChanged(this, new DataErrorsChangedEventArgs(p.Name)));
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
List<ValidationResult> results = GetErrorList();
return results.Where(e => e.MemberNames.Contains(propertyName));
}
public bool HasErrors
{
get
{
List<ValidationResult> results = GetErrorList();
return results.Count > 0;
}
}
#endregion
#else
#region IDataErrorInfo Members
public string Error
{
get
{
List<ValidationResult> results = GetErrorList();
if (results.Count > 0)
return results[0].ErrorMessage;
else
return null;
}
}
public string this[string columnName]
{
get
{
List<ValidationResult> results = GetErrorList();
var resultByColumn = results.Where(r => r.MemberNames.Contains(columnName)).ToList();
if (resultByColumn.Count > 0)
return resultByColumn[0].ErrorMessage;
else
return null;
}
}
#endregion
#endif
}
}
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>