I have a WPF application and am using MVVM pattern. On one of my User Controls, I have two PasswordBoxes to compare the user entered passwords. I am trying to implement a compare behavior whose result will determine if the submit button should be enabled or disabled in the ViewModel. I am kinda stuck.
EDIT:
This is not a duplicate question as #Dbl mentioned in a comment. The duplicate question mentioned in his comment is about how to compare two SecureString data types. My question is totally different. It is about how to compare two object values - does not matter if they are SecureString or not - in a XAML UserControl without breaking the MVVM pattern where a behavior attached to one element needs to know about the value of another element inside the behavior. Also, this behavior needs to be able to access the underlying ViewModel of the the element and update an INPC property in the ViewModel.
Here is my XAML (removed quite a bit of elements for brevity):
<UserControl
x:Class="DynaProPOS.WPF.Views.AppUser"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behavior="clr-namespace:DynaProPOS.WPF.Behaviors"
xmlns:custProps="clr-namespace:DynaProPOS.WPF.CustomProperties"
prism:ViewModelLocator.AutoWireViewModel="True"
Background="{DynamicResource BackgroundBrush}">
<Border Width="750" Height="260" BorderBrush="White" BorderThickness="2">
<Grid x:Name="grid" KeyboardNavigation.TabNavigation="Cycle" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto">
<PasswordBox TabIndex="3" Grid.Row="3" Grid.Column="1" Margin="2" x:Name="Password1" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<i:Interaction.Behaviors>
<behavior:PasswordBoxBindingBehavior Password="{Binding Password}" />
</i:Interaction.Behaviors>
</PasswordBox>
<PasswordBox TabIndex="4" Grid.Row="4" Grid.Column="1" Margin="2,18,2,4" x:Name="Password2" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<i:Interaction.Behaviors>
<behavior:ComparePasswordBehavior OriginalPassword="{Binding ElementName=Password1, Path=Password}"/>
</i:Interaction.Behaviors>
</PasswordBox>
<Grid Grid.Column="3" Grid.RowSpan="5" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="90*" />
</Grid.RowDefinitions>
</Grid>
<syncfusion:ButtonAdv TabIndex="6" x:Name="RegisterButton" Grid.Row="5" Grid.Column="4" Margin="5" HorizontalAlignment="Right" Label="Submit" VerticalAlignment="Center" />
</Grid>
</Border>
And Here is my ViewModel (again, remove lot of code for brevity).
public class AppUserViewModel : BindableBase
{
private bool isEnabled;
public AppUserViewModel(IAuthenticationService _authService)
{
authService = _authService;
RegisterCommand = new DelegateCommand( async () => await RegisterUserAsync() );
}
public bool IsEnabled
{
get { return isEnabled; }
set { SetProperty( ref isEnabled, value ); }
}
}
And finally, here is my Behavior class.
public class ComparePasswordBehavior : Behavior<PasswordBox>
{
protected override void OnAttached()
{
AssociatedObject.LostFocus += OnComparePasswordLostFocus;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.LostFocus -= OnComparePasswordLostFocus;
base.OnDetaching();
}
public static readonly DependencyProperty OriginalPasswordProperty =
DependencyProperty.Register("OriginalPassword", typeof(SecureString), typeof(ComparePasswordBehavior), new PropertyMetadata(null));
private static void OnComparePasswordLostFocus( object sender, RoutedEventArgs e )
{
PasswordBox pswdBox = sender as PasswordBox;
var behavior = Interaction.GetBehaviors(pswdBox).OfType<ComparePasswordBehavior>().FirstOrDefault();
if (behavior != null)
{
var binding = BindingOperations.GetBindingExpression( behavior, OriginalPasswordProperty);
PropertyInfo propInfo = binding.DataItem.GetType().GetProperty(binding.ParentBinding.Path.Path);
// at this point I am stumped. I don't seems to be able to
// retrieve the value from the original password box element.
// I am also not able to set the IsEnabled property of the ViewModel.
}
}
public SecureString OriginalPassword
{
get { return ( SecureString )GetValue( OriginalPasswordProperty ); }
set { SetValue( OriginalPasswordProperty, ( SecureString )value ); }
}
}
I have a dependency property defined in my behavior to hold the password value from the original password box. In the lostfocus event of my behavior, I need to compare the two passwords and set the IsEnabled Property of my ViewModel accordingly.
I need to do two things here. I need to retrieve the Password value from Password1 PasswordBox Element
I also need to set the IsEnabled Property of my ViewModel based on the password comparison result. Can somebody please help? I have been stuck here for a day now. Thanks.
The instance of ComparePasswordBehavior doesn't know anything about the instance of PasswordBoxBindingBehavior and vice versa. Besides, it is the resposibility of the view model to compare the passwords and set the IsEnabled property.
The behaviour should just transfer the password from the PasswordBox to the view model. You should store the SecureStrings in the view model and do the comparison in there.
Please refer to the following sample code.
Behavior:
public class PasswordBehavior : Behavior<PasswordBox>
{
protected override void OnAttached()
{
AssociatedObject.LostFocus += OnComparePasswordLostFocus;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.LostFocus -= OnComparePasswordLostFocus;
base.OnDetaching();
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(SecureString), typeof(PasswordBehavior), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
public SecureString Password
{
get { return (SecureString)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
private static void OnComparePasswordLostFocus(object sender, RoutedEventArgs e)
{
PasswordBox pswdBox = sender as PasswordBox;
PasswordBehavior behavior = Interaction.GetBehaviors(pswdBox).OfType<PasswordBehavior>().FirstOrDefault();
if (behavior != null)
{
behavior.Password = pswdBox.SecurePassword;
}
}
}
View Model:
public class AppUserViewModel : BindableBase
{
private bool isEnabled;
public bool IsEnabled
{
get { return isEnabled; }
set { SetProperty(ref isEnabled, value); }
}
private SecureString _password1;
public SecureString Password1
{
get { return _password1; }
set
{
if (SetProperty(ref _password1, value))
ComparePasswords();
}
}
private SecureString _password2;
public SecureString Password2
{
get { return _password2; }
set
{
if (SetProperty(ref _password2, value))
ComparePasswords();
}
}
private void ComparePasswords()
{
IsEnabled = (_password1 != null || _password2 != null)
&& SecureStringToString(_password1) == SecureStringToString(_password2);
}
private string SecureStringToString(SecureString value)
{
IntPtr valuePtr = IntPtr.Zero;
try
{
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
return Marshal.PtrToStringUni(valuePtr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
}
View:
<PasswordBox>
<i:Interaction.Behaviors>
<behavior:PasswordBehavior Password="{Binding Password1}" />
</i:Interaction.Behaviors>
</PasswordBox>
<PasswordBox>
<i:Interaction.Behaviors>
<behavior:PasswordBehavior Password="{Binding Password2}"/>
</i:Interaction.Behaviors>
</PasswordBox>
Related
I have a ListBox control with TypeUsers.When I select some record in Listbox and update Name in TextBox the Name property/textbox return always null. Never take value from TextBox, always null ?
Image description here
This is my code
<ListBox x:Name="LstTypeUsers"
Grid.Row="0" Grid.Column="4"
Width="220" Height="120"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ItemsSource="{Binding TypeUsers}"
DisplayMemberPath="Name">
</ListBox>
<TextBox
Grid.Row="0" Grid.Column="2"
x:Name="txtName"
HorizontalAlignment="Left" Height="23"
TextWrapping="Wrap"
VerticalAlignment="Top" Width="170"
Text="{Binding ElementName=LstTypeUsers, Path=SelectedItem.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
Validation.ErrorTemplate="{x:Null}"/>
<Button
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="100" Height="30"
Command="{Binding UpdateTypeUserCmd}"
Grid.ColumnSpan="3" Margin="20,90,0,0">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/Save.png" />
<TextBlock Width="55" Height="18" ><Run Text=" "/><Run Text="Update"/></TextBlock>
</StackPanel>
</Button>
EDIT
// Model class
public class UserType: INotifyPropertyChanged
{
[Key]
private int usertypeId;
public int UserTypeId
{
get
{
return this.usertypeId;
}
set
{
this.usertypeId = value;
OnPropertyChanged("UserTypeId");
}
}
[MaxLength(200)]
private string name;
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
OnPropertyChanged("Name");
}
}
[Required]
private bool status;
public bool Status
{
get
{
return this.status;
}
set
{
this.status = value;
OnPropertyChanged("Status");
}
}
public virtual ObservableCollection<User> User { get; private set; }
public UserType()
{
this.User = new ObservableCollection<User>();
}
}
// ViewModelBase class
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// UserTypeViewModel
public class UserTypeViewModel
private UserType _userType;
private ObservableCollection<UserType> _UserTypeList;
// Constructor
public UserTypeViewModel()
{
_userType = new UserType();
_UserTypeList = new ObservableCollection<UserType>(GetUserTypeAll());
}
public ObservableCollection<TypeUsers> TypeUsers
{
get
{
return _UserTypeList;
}
set
{
_UserTypeList = value;
//OnPropertyChanged("TypeUsers");
}
}
public string Name
{
get
{
return _userType.Name;
}
set
{
_userType.Name = value;
//OnPropertyChanged("Name");
}
}
Thank you.
Implement INotifyPropertyChanged interface in UserType class.
You're binding directly to the WPF control (ListBox) and not the ViewModel. I suggest you add a property in your ViewModel that will bind to the TextBox.Text property, once the data changes or the user had changed the value in the TextBox, then the data will update and be reflected in the UI.
Also, if I remember correctly, at launch, the SelectedItem property of the ListBox is null, so there might be a problem there too, but I'm not certain about that...
I have resolved my problem. I have also implemented INotifyPropertyChanged interface in Model class. I'm new in WPF MVVM and I read that this interface is implemented only in the ViewModel for connection with View. Now I have implemented in both classes and everything works great.
Thanks Everyone.
Please help, I was trying to do this small example.
My aim is to when I keep the checkbox ticked the app should show the the Ip address of the Host I enter. But The checkbox IsChecked property is never updated in the view model, Even it is been changed in the UI
My View `
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.Background>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.00" Color="LavenderBlush" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
<StackPanel Grid.Row="0" Margin="150,30,69,236" Grid.ColumnSpan="2">
<TextBox x:Name="inputBox" Text="{Binding TxtHostName, Mode=TwoWay}" Foreground="Azure" Background="YellowGreen" VerticalAlignment="Bottom" Height="45"/>
</StackPanel>
<Button Command="{Binding StartCommand }" Content="Get IP" HorizontalAlignment="Left" Margin="257,89,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.013,-0.273"/>
<TextBlock Text="{Binding IpAddress}" Background="BlueViolet" Margin="150,153,69,104" Grid.ColumnSpan="2" />
<Button Content="Close" Command="{Binding CloseCommand}" HorizontalAlignment="Left" Margin="257,250,0,0" VerticalAlignment="Top" Width="75"/>
<CheckBox Content="CheckBox" IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left" Margin="150,111,0,0" VerticalAlignment="Top"/>
</Grid>
`
My ViewModel:
public class ViewModel:INotifyPropertyChanged
{
#region INPC
public void RaisePropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private string txtHostName;
public string TxtHostName
{
get { return txtHostName; }
set { txtHostName = value;
RaisePropertyChanged("TxtHostName");
}
}
private string ipAddress;
public string IpAddress
{
get { return ipAddress; }
set { ipAddress = value;
RaisePropertyChanged("IpAddress");
}
}
private bool checkbox;
public bool CheckBox
{
get { return checkbox; }
set { checkbox = value;
RaisePropertyChanged("IsSelected");
}
}
public event EventHandler RequestClose;
protected void OnRequestClose()
{
if (RequestClose != null)
RequestClose(this, EventArgs.Empty);
}
private RelayCommand _StartCommand;
public ICommand StartCommand
{
get
{
if (this._StartCommand == null)
this._StartCommand = new RelayCommand(StartClick);
return this._StartCommand;
}
}
private RelayCommand _CloseCommand;
public ICommand CloseCommand
{
get
{
if(this._CloseCommand==null)
this._CloseCommand=new RelayCommand(CloseClick);
return this._CloseCommand;
}
}
private void CloseClick(object obj)
{
OnRequestClose();
}
private void StartClick(object obj)
{
if (checkbox)
{
string HostName = TxtHostName;
IPAddress[] ipaddress = Dns.GetHostAddresses(HostName);
foreach (IPAddress ipaddr in ipaddress)
{
IpAddress = ipaddr.ToString();
}
}
else
{
IpAddress = "Please tick the checkbox";
}
}
}
}
The RealyCommand is as it should be.
The CheckBox Property value never changes weather I change it in the UI or not.
Your raising your property changed event against IsSelected, but your bindable property is called Checkbox, rename Checkbox to IsSelected and update your private variable to something like isSelected.
In this case Id rename the variable to IsChecked or ComboBoxIsChecked.
I'm not sure if there is a copy-and-paste error but your View Model property is called Checkbox while you are raising the property changed event using the label IsSelected.
This and the error in the binding might be your problem. Based on your View Model the binding should be:-
<CheckBox Content="CheckBox" IsChecked="{Binding Checkbox, Mode=TwoWay}" HorizontalAlignment="Left" Margin="150,111,0,0" VerticalAlignment="Top"/>
Update: Recommendation if you are using C# 5.0 or above
To avoid typo's when creating setters and raising IPropertyNotifyChange events I would recommend using the CallerMemberName attribute as follows:-
public void RaisePropertyChanged([CallerMemberName] string propName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Then your setter in your example becomes:-
private bool checkbox;
public bool CheckBox
{
get { return checkbox; }
set { checkbox = value;
RaisePropertyChanged();
}
}
Meaning as you refactor your View Model then the compiler will insert the name of the calling property to ensure the label in the INotifyProertyChanged event matches your property name without you having to remember to manually update it yourself.
I am writing simple application,
The UI has two textboxes, for Username & Password and button to submit the information.
I wanted to use routed commands instead of buttonclick event.
Username should contain alphanumeric characters only, if user enter any other special characters, it should display a text saying invalid characters entered.
so I wanted to bind the visibility and content of that textblock based on the validation done on Username textbox field.
can any one help me on how to achieve this?
Below is code I have made but it is not working as expected. can any one help me where I am doing wrong?
Below my mainWindow.xaml
using System.Windows;
using System.Windows.Input;
using System.ComponentModel;
namespace ExcelUtility
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ViewModel viewModelObj = new ViewModel();
public MainWindow()
{
InitializeComponent();
}
void navigatePageExecuted(object target, ExecutedRoutedEventArgs e)
{
SubmitUserDetails(txtUserName.Text + ";" + txtPassword);
}
void navigatePageCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(txtUserName.Text))
{
viewModelObj.Username = txtUserName.Text;
}
e.CanExecute = viewModelObj.VaidUserName; }
private void SubmitUserDetails(string credentials)
{
this.Cursor = Cursors.Wait;
prgValidate.Visibility = Visibility.Visible;
MainGrid.IsEnabled = false;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync(credentials);
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
prgValidate.Visibility = Visibility.Collapsed;
string Result = (string)e.Result;
MessageBox.Show(Result); //Here I need to call some other functions based on return value for simplicity i have changed
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
string[] credentials = e.Argument.ToString().Split(';');
e.Result = viewModelObj.validateCredentials(credentials[0], credentials[1]);
}
}
}
This is my xaml
<Window x:Class="ExcelUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ExcelUtility"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:BoolToVisibleOrHidden x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:CommandsLibrary.navigatePageCommand}" Executed="navigatePageExecuted" CanExecute="navigatePageCanExecute"/>
</Window.CommandBindings>
<Grid Name="MainGrid">
<TextBlock Height="23" HorizontalAlignment="Left" Margin="40,44,0,0" Name="tbUserName" Text="Username" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="136,42,0,0" Name="txtUserName" VerticalAlignment="Top" Width="163" Text="{Binding Username, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Height="23" HorizontalAlignment="Left" Margin="138,19,0,0" Name="tbNotify" Text="{Binding Notification, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="161" Visibility="{Binding NotVaidUserName,Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="138,98,0,0" Name="txtPassword" VerticalAlignment="Top" Width="161" />
<TextBlock Height="23" HorizontalAlignment="Left" Margin="44,107,0,0" Name="tbPassword" Text="Password" VerticalAlignment="Top" Width="65" />
<Button Command="{x:Static local:CommandsLibrary.navigatePageCommand}" Content="Submit" Height="23" HorizontalAlignment="Left" Margin="172,167,0,0" Name="btnSubmit" VerticalAlignment="Top" Width="109" />
<ProgressBar Height="24" IsIndeterminate="True" Visibility="Collapsed" HorizontalAlignment="Left" Margin="52,232,0,0" Name="prgValidate" VerticalAlignment="Top" Width="257" />
</Grid>
This is my viewModel
using System;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows;
namespace ExcelUtility
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _notVaidUserName;
public bool NotVaidUserName
{
get { return _notVaidUserName; }
set
{
_notVaidUserName = value;
RaisePropertyChanged("NotVaidUserName");
}
}
private string notification;
public string Notification
{
get
{
return notification;
}
set
{
if (notification != value)
{
notification = value;
RaisePropertyChanged("Notification");
}
}
}
private string username;
public string Username
{
get
{
return username;
}
set
{
if (username != value)
{
username = value;
NotVaidUserName = VaidateUserName(username);
RaisePropertyChanged("Username");
}
}
}
public bool VaidateUserName(string strUsername)
{
bool bValidUserName = false;
if (!string.IsNullOrWhiteSpace(strUsername))
{
if (new Regex(#"^[a-zA-Z0-9]*$").IsMatch(strUsername))
{
bValidUserName = true;
if (strUsername.Length > 7)
{
Notification = "Max allowed key length is 6";
bValidUserName = false;
}
}
else
{
Notification = "No special characters allowed";
}
}
return bValidUserName;
}
public string validateCredentials(string Username, string Password)
{
return "Valid Credentials";
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
class BoolToVisibleOrHidden : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
object returnvalue = new object();
returnvalue = (bool)value ? Visibility.Visible : parameter != null ? Visibility.Collapsed : Visibility.Hidden;
return returnvalue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (Visibility)value == Visibility.Visible;
}
}
}
Many thanks in advance.
Durga
The reason your code is not working is that you have not set the data context for view. I like to set the data context in the xaml as it will give you auto-complete in VS for your binding instruction. On the root node, add the attribute
DataContext="{Binding RelativeSource={RelativeSource Self}}"
This will set the window itself as your data context, allowing your command to work. However, the binding expressions on the textboxes will fail. You have split your ViewModel logic over the view and the ViewModel.
I would move you code completely into the ViewModel and use
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
as the data context. Then the only code you have in the code behind of the view would be something along the lines of
namespace ExcelUtility
{
public partial class MainWindow : Window
{
private ViewModel viewModel;
public MainWindow()
{
InitializeComponent();
}
public ViewModel ViewModel { get { return viewModel ?? (viewModel = new ViewModel()); } }
}
}
Once your bindings are working, you will not have to set the UserName or Password (as you do in the navigatePageCanExecute and navigatePageExecuted methods) in the view model as the binding will set it for you.
I am not sure about your CommandsLibrary. You didn't include it in the example.
This should give you a start to figure out the rest.
I hope it helps
My WPF window binds directly to an Entity Framework data context (CollectionViewSource). Users begin editing immediately once a record is found. The moment the form is dirty I'd like to disable the Add button and enable the Save and Undo buttons. Is there a simple way to do this using binding or an event?
I'm not using MVVM. I use Entity Framework database first and the EF designer. I'm hoping to avoid adding code for every field. The database is quite large.
You can do this without events if you'd like. And you can leverage the power of DataBinding without going so far as MVVM. The example below demonstrates, in a very simple way, how you can accomplish this. If your entity classes don't already have an IsDirty property (it has been a while since I've used database-first EF), you could add the property with a partial class.
XAML:
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center" Text="Name:" Margin="10"/>
<TextBox x:Name="NameTextBox" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="10" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Save" HorizontalAlignment="Right" Margin="5" IsEnabled="{Binding IsDirty}" />
<Button Content="Cancel" HorizontalAlignment="Right" Margin="5" IsEnabled="{Binding IsDirty}" Click="Cancel_Click"/>
<Button Content="Add" HorizontalAlignment="Right" Margin="5" IsEnabled="{Binding IsClean}"/>
</StackPanel>
</Grid>
</Window>
A test Entity class:
public class Entity : INotifyPropertyChanged
{
private string _name;
private bool _isDirty = false;
public string Name
{
get { return _name; }
set
{
if(!IsDirty)
IsDirty = (value != _name);
_name = value;
RaisePropertyChanged("Name");
}
}
public bool IsDirty
{
get{ return _isDirty; }
set{
_isDirty = value;
RaisePropertyChanged("IsDirty");
RaisePropertyChanged("IsClean");
}
}
public bool IsClean
{
get { return !_isDirty; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the code behind for the window:
namespace Test
{
public partial class EditTesting : Window
{
private Entity _myEntity;
public EditTesting()
{
InitializeComponent();
_myEntity = new Entity();
this.DataContext = _myEntity;
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
_myEntity.Name = string.Empty;
_myEntity.IsDirty = false;
}
}
}
I have two radio buttons working as radioButton List in UI using MVVM. When the user control is loaded first time, one of the radio button is selected and the related controls are shown in UI... Now when I change the radio button, UI is not getting updated.
Below is the sample XAML:
<Label Grid.Column="0" Grid.Row="3" Content="Exchange Details:" Margin="3" VerticalContentAlignment="Center" Style="{StaticResource NormalLabelStyle}"></Label>
<Grid Grid.Column="1" Grid.Row="3" Width="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<RadioButton GroupName="rdoExchange" Content="Basic" IsChecked="{Binding Path=ExchangeDetailsBasic}" Grid.Column="0" VerticalContentAlignment="Center" VerticalAlignment="Center"></RadioButton>
<RadioButton GroupName="rdoExchange" Content="Advanced" IsChecked="{Binding Path=ExchangeDetailsAdvanced}" Grid.Column="2" VerticalContentAlignment="Center" VerticalAlignment="Center"></RadioButton
</Grid>
<Label Grid.Column="3" Grid.Row="0" Content="Number of Mailbox Profiles:" VerticalContentAlignment="Center" Style="{StaticResource NormalLabelStyle}" Visibility="{Binding Path=IsAdvanced}" ></Label>
<telerik:RadNumericUpDown Grid.Column="4" Grid.Row="0" Margin="3" Value="{Binding Path=NumberofMailboxProfiles}" IsInteger="True" Minimum="1" Maximum="4" HorizontalAlignment="Left" Visibility="{Binding Path=IsAdvanced}">< /telerik:RadNumericUpDown>
Below is my ViewModel code:
private enum ExchangeDetails{
Basic,
Advanced
}
private bool isBasicMode = true;
public bool ExchangeDetailsBasic {
get {
return this.isBasicMode;
}
set {
if (value) {
this.applicationSpecificRequirements[ExchangeDetailsKey] = ExchangeDetails.Basic.ToString();
if (!this.isBasicMode) {
this.CheckBasicOrAdvancedSelecteAndDisplayView();
}
}
}
}
public bool ExchangeDetailsAdvanced {
get {
return !this.isBasicMode;
}
set {
if (value) {
this.applicationSpecificRequirements[ExchangeDetailsKey] = ExchangeDetails.Advanced.ToString();
this.CheckBasicOrAdvancedSelecteAndDisplayView();
}
}
}
public Visibility IsAdvanced { get; private set; }
private void CheckBasicOrAdvancedSelecteAndDisplayView() {
this.isBasicMode = this.applicationSpecificRequirements.ContainsKey(ExchangeDetailsKey) ? (this.applicationSpecificRequirements[ExchangeDetailsKey].Equals(ExchangeDetails.Basic.ToString()) ? true : false) : true;
this.IsAdvanced = this.isBasicMode ? Visibility.Collapsed : Visibility.Visible;
}
Radio buttons, groups, and binding don't mix. This is, amazingly, by design.
There are three ways to change the value of a bound control in the UI. One is that the user can do it himself with a mouse click or keypress. The second is that code can change the value of the data source, and binding will update the value in the UI.
The third way is to set the value explicitly in code. If you do this, the binding on the control you've just set is disabled.
This is a little counter-intuitive. You'd expect the new value to get pushed to the data source. The design assumption is that if you wanted the value to get changed in the data source, you'd change it in the data source, and that your code is manipulating the UI because you don't want it to be bound anymore. This gives you a simple way of manually overriding binding - just set the value of the control in code - that doesn't compel you to find the Binding object and manipulate it explicitly. This makes a certain amount of sense. I guess.
But it creates problems with radio buttons. Because grouped radio buttons change each others' values in code. If you have three radio buttons in a group, and one gets checked, the radio button finds the other buttons in the group and unchecks them. You can see this if you look at the code in Reflector.
So what happens is exactly what you're observing: you click on radio buttons and binding gets disabled.
Here's what you do about it - and this actually makes a considerable amount of sense. Don't use groups. You can use radio buttons, but only for their visual style. Disregard their grouping functionality.
Instead, implement the logic that makes the bound boolean properties mutually exclusive in your view model, e.g.:
public bool Option1
{
set
{
_Option1 = value;
if (value)
{
Option2 = false;
Option3 = false;
}
OnPropertyChanged("Option1");
}
}
If you think about it, this logic really shouldn't be in the view anyway. Because it's logic, and that's what the view model is for. So while it's something of a pain, you can console yourself with the thought that architecturally it's the right thing to do.
I guess you are missing the implementation of INotifyPropertyChanged for the view model class. If you have used two way data binding and you are raising the property changed event when the selection changes everything should work fine. #Zamboni has explained it with the code example.
If you implement INotifyPropertyChanged in your view model and you set Binding Mode=TwoWay in your XAML, you can let the binding take care of the rest for you.
Here is sample using some of your code:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<RadioButton GroupName="rdoExchange" Content="Basic"
IsChecked="{Binding Path=ExchangeDetailsBasic, Mode=TwoWay}"
Grid.Column="0"
VerticalContentAlignment="Center"
VerticalAlignment="Center"/>
<RadioButton GroupName="rdoExchange" Content="Advanced"
IsChecked="{Binding Path=ExchangeDetailsAdvanced, Mode=TwoWay}"
Grid.Column="1"
VerticalContentAlignment="Center"
VerticalAlignment="Center"/>
<Label Grid.Column="0" Grid.Row="1" Grid.RowSpan="2"
Content="Number of Mailbox Profiles:"
VerticalContentAlignment="Center"
Visibility="{Binding Path=IsAdvanced, Mode=TwoWay}" />
</Grid>
Here is the ViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
}
private bool _isBasicMode = true;
public bool ExchangeDetailsBasic
{
get
{
return this._isBasicMode;
}
set
{
this._isBasicMode = value;
if (value)
{
ExchangeDetailsAdvanced = false;
IsAdvanced = Visibility.Collapsed;
}
this.OnPropertyChanged("ExchangeDetailsBasic");
}
}
private bool _isAdvancedMode = false;
public bool ExchangeDetailsAdvanced
{
get
{
return this._isAdvancedMode;
}
set
{
_isAdvancedMode = value;
if (value)
{
ExchangeDetailsBasic = false;
IsAdvanced = Visibility.Visible;
}
this.OnPropertyChanged("ExchangeDetailsAdvanced");
}
}
private Visibility _isAdvanced = Visibility.Collapsed;
public Visibility IsAdvanced
{
get
{
return _isAdvanced;
}
set
{
_isAdvanced = value;
this.OnPropertyChanged("IsAdvanced");
}
}
}
Here is the base class that implements INotifyPropertyChanged.
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Robert Rossney's answer is great, but I still think that radio buttons should behave like radio buttons and let the VM handle more important logic.
Here is my solution: an attached property that toggles the IsChecked property of all buttons in the same group. Works on my machine :-)
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace Elca.MvvmHelpers {
public class RadioButtonHelper : DependencyObject {
private static readonly Dictionary<string, List<RadioButton>> s_group2ButtonsMap = new Dictionary<string, List<RadioButton>>();
private static readonly List<RadioButton> s_knownButtons = new List<RadioButton>();
private static void OnRadioButtonChecked(object sender, RoutedEventArgs e) {
RadioButton rb = (RadioButton)sender;
UncheckOtherButtonsInGroup(rb);
}
public static bool? GetIsChecked(RadioButton d) {
return (bool?) d.GetValue(IsCheckedProperty);
}
public static void SetIsChecked(RadioButton d, bool? value) {
d.SetValue(IsCheckedProperty, value);
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked",
typeof(bool?),
typeof(RadioButtonHelper),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
IsCheckedChanged));
public static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var rb = d as RadioButton;
if (rb == null) {
throw new Exception("IsChecked attached property only works on a FrameworkElement type");
}
RememberRadioButton(rb);
if ((bool) e.NewValue) {
rb.IsChecked = true; // this triggers OnRadioButtonChecked => other buttons in the same group will be unchecked
}
}
private static void RememberRadioButton(RadioButton rb) {
var groupName = GetGroupName(rb);
// if this button is unknown, add it to the right list, based on its group name
if (s_knownButtons.Contains(rb)) {
return;
}
s_knownButtons.Add(rb);
List<RadioButton> existingButtons;
if (! s_group2ButtonsMap.TryGetValue(groupName, out existingButtons)) {
// unknown group
s_group2ButtonsMap[groupName] = new List<RadioButton> {rb};
RegisterButtonEvents(rb);
} else {
if (! existingButtons.Contains(rb)) {
existingButtons.Add(rb);
RegisterButtonEvents(rb);
}
}
}
private static void RegisterButtonEvents(RadioButton rb) {
rb.Unloaded += OnButtonUnloaded;
rb.Checked += OnRadioButtonChecked;
}
private static void OnButtonUnloaded(object sender, RoutedEventArgs e) {
RadioButton rb = (RadioButton) sender;
ForgetRadioButton(rb);
}
private static void ForgetRadioButton(RadioButton rb) {
List<RadioButton> existingButtons = s_group2ButtonsMap[GetGroupName(rb)];
existingButtons.Remove(rb);
s_knownButtons.Remove(rb);
UnregisterButtonEvents(rb);
}
private static void UnregisterButtonEvents(RadioButton rb) {
rb.Unloaded -= OnButtonUnloaded;
rb.Checked -= OnRadioButtonChecked;
}
private static void UncheckOtherButtonsInGroup(RadioButton rb) {
List<RadioButton> existingButtons = s_group2ButtonsMap[GetGroupName(rb)];
foreach (RadioButton other in existingButtons) {
if (other != rb) {
SetIsChecked(other, false);
}
}
SetIsChecked(rb, true);
}
private static string GetGroupName(RadioButton elt) {
string groupName = elt.GroupName;
if (String.IsNullOrEmpty(groupName)) {
groupName = "none"; // any value will do
}
return groupName;
}
}
}
In the view, for each button:
<RadioButton MvvmHelpers:RadioButtonHelper.IsChecked="{Binding IsExplicitFileSelected, Mode=TwoWay}">
...
</RadioButton>
The VM has a boolean property for each radio button. One must assign a value to each such property to start the listening process of the attached property.
All buttons without a group name are considered to be part of the same group.