WPF RadioButton Change Not Updating UI MVVM - wpf

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.

Related

WPF ListView Select and get Item Focus automatically

I'm don't know how to get focused item automatically change within a ListView.
I would like the focused item in the view to automatically change when I change the "IsSelected" property to an other element in the databinded list:
When an item is modified by PC/SC card reader (see this as input), the next element should be focused like this:
I would like to stay in MVVM and therefor not having View referenced in the ViewModel. Below is my current code.
Model : The main purpose is to extend a DTO with an IsSelected property and implementing INotifyPropertyChanged
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsSelected;
private DtoReader _dtoReader;
public SmartDeviceModel(DtoReader _reader)
{
_dtoReader = _reader;
}
public string DisplayName => _dtoReader.DisplayName;
public string Uid
{
get
{
return _dtoReader.Uid;
}
set
{
_dtoReader.Uid = value;
OnPropertyChanged("Uid");
}
}
public long RadioId
{
get
{
return _dtoReader.RadioId : _dtoMarker.RadioId;
}
set
{
_dtoReader.RadioId = value;
OnPropertyChanged("RadioId");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
ViewModel received events of a PC/SC card reader to pair data from RFID chip with current selected item. When RFID chip is removed from PC/SC Reader, the next element is well selected but got not focused.
public class ScanDeviceViewModel : BaseViewModel
{
public BindingList<SmartDeviceModel> ReaderList { get; }
public int SelectedReaderIndex;
private ITagReaderInput _rfidReader;
public ScanDeviceViewModel()
{
//Get Data listener for RFID Tag
_rfidReader = new IdentivTagReader.IdentivTagReader();
// Data Source of DTO
SiteInteractor siteInterractor = new SiteInteractor();
// List used for DataBinding
ReaderList = new BindingList<SmartDeviceModel>();
foreach (DtoReader m in SiteInteractor.GetReaders().OrderBy(x => x.DisplayName))
{
ReaderList.Add(new SmartDeviceModel(m));
}
if (ReaderList.Count() > 0)
{
for (var i = 0; i < ReaderList.Count(); i++)
{
if (String.IsNullOrEmpty(ReaderList[i].Uid))
{
SelectedReaderIndex = i;
ReaderList[i].IsSelected = true;
break;
}
}
}
_rfidReader.LabelDetected += RfidTagDetected;
_rfidReader.LabelRemoved += RfidRemoved;
}
private void RfidTagDetected(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex] != null && string.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid))
{
ReaderList[SelectedReaderIndex].IsSelected = true;
ReaderList[SelectedReaderIndex].Uid = tag.Uid;
ReaderList[SelectedReaderIndex].RadioId = tag.RadioId;
}
}
private void RfidRemoved(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex].Uid == tag.Uid)
{
ReaderList[SelectedReaderIndex].IsSelected = false;
while (ReaderList.Count >= SelectedReaderIndex + 1)
{
SelectedReaderIndex++;
if (String.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid)){
ReaderList[SelectedReaderIndex].IsSelected = true;
break;
}
}
}
}
}
View I'm using a "Setter" using databinding to my model property "IsSelected" as suggested here but I most missed something else I don't understand yet.
<ListView ItemsSource="{Binding ReaderList}"
Margin="5" x:Name="listViewReader" SelectionMode="Single"
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderBrush" Value="LightGray" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Viewbox Grid.Row ="0" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Bottom" MaxHeight="90">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="{Binding DisplayName}" />
<DockPanel Grid.Row="1">
<Label Content="UID"/>
<Label Content="{Binding Uid}"/>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="1">
<Label Content="RadioID" />
<Label Content="{Binding RadioId}"/>
</DockPanel>
</Grid>
</Viewbox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I tried several approach like this answer, although item is well selected, it is not focused.
I finally figure it out. Below is my current working code.
In the Model I have just changed the flag IsSelected to IsCurrent to avoid confusion with ListViewItem built-in property but it might just be an implementation detail.
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsCurrent;
[...]
}
The BindingList in ViewModel is mostly the same as in OP:
public class ScanDeviceViewModel : INotifyPropertyChanged
{
public BindingList<SmartDeviceModel> ReaderList { get; internal set; }
[...]
}
NB : BindingList seems to reduce OnNotifyPropertyChange need but other Type of List should work with a tiny bit of extra code. I also noticed BindingList might not be suited for huge list scenario.
The View is then using the above ViewModel as DataContext and therefore Binding ItemSource to the BindingList. The ListViewItem Style Setter is then using the IsCurrent Property from the Model.
<ListView ItemsSource="{Binding ReaderList}"
SelectionMode="Single"
SelectionChanged="OnListViewSelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsCurrent}" />
</Style>
</ListView.ItemContainerStyle>
[...]
And finally this piece of View Code behind below is mainly to simulate the focus as per user input, otherwise the elemant get selected but not focused and might be outside the visible item scope :
private void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView listView = e.Source as ListView;
if (listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) is FrameworkElement container)
{
container.Focus();
}
}
According to MVVM you can implement custom Interaction Behavior:
Import to XAML: xmlns:b="http://schemas.microsoft.com/xaml/behaviors" (if you are using .NET Core 3.1 - 5)
Add to content-body:
<ListView ...>
<b:Interaction.Behaviors>
<local:AutoScrollToLastItemBehavior />
</b:Interaction.Behaviors>
</ListView>
Finally add the next class:
public sealed class AutoScrollToLastItemBehavior : Microsoft.Xaml.Behaviors.Behavior<ListView>
{
// Need to track whether we've attached to the collection changed event
bool _collectionChangedSubscribed = false;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += SelectionChanged;
// The ItemSource of the listView will not be set yet,
// so get a method that we can hook up to later
AssociatedObject.DataContextChanged += DataContextChanged;
}
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView();
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ScrollIntoView();
}
private void DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The ObservableCollection implements the INotifyCollectionChanged interface
// However, if this is bound to something that doesn't then just don't hook the event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && !_collectionChangedSubscribed)
{
// The data context has been changed, so now hook
// into the collection changed event
collection.CollectionChanged += CollectionChanged;
_collectionChangedSubscribed = true;
}
}
private void ScrollIntoView()
{
int count = AssociatedObject.Items.Count;
if (count > 0)
{
var last = AssociatedObject.Items[count - 1];
AssociatedObject.ScrollIntoView(last);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= SelectionChanged;
AssociatedObject.DataContextChanged -= DataContextChanged;
// Detach from the collection changed event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && _collectionChangedSubscribed)
{
collection.CollectionChanged -= CollectionChanged;
_collectionChangedSubscribed = false;
}
}
}

Comparing two Passwords in WPF MVVM

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>

What event or binding will allow me to disable the Add record button the moment edits begin?

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

DependencyProperty.RegisterAttached and Multiple Instances

I'm working on a WPF MVVM application. I'm looking to databind a WebBrowser control to a view model which is in turn bound to a Tab. Following the advice in this article, I created a static helper class consisting of a static DependancyProperty:
public static class WebBrowserHelper
{
public static readonly DependencyProperty BodyProperty =
DependencyProperty.RegisterAttached("Body", typeof(string), typeof(WebBrowserHelper), new PropertyMetadata(OnBodyChanged));
public static string GetBody(DependencyObject dependencyObject)
{
return (string)dependencyObject.GetValue(BodyProperty);
}
public static void SetBody(DependencyObject dependencyObject, string body)
{
dependencyObject.SetValue(BodyProperty, body);
}
private static void OnBodyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string newValue = (string)e.NewValue;
var webBrowser = (WebBrowser)d;
webBrowser.NavigateToString(newValue);
}
}
XAML Binding WebBrowser to DependancyProperty:
<WebBrowser Grid.Column="2" HorizontalAlignment="Center" src:WebBrowserHelper.Body="{Binding HTMLBody}" VerticalAlignment="Center" Height="Auto" Width="Auto" />
ViewModel that bound to ItemsSource of Tab Control:
public class SomeVM : ViewModelBase, INotifyPropertyChanged
{
private string _htmlBody;
private SomeView _myView = new SomeView();
public SomeVM (string tabName)
{
TabName = tabName;
string contentsAsHTML = do_a_whole_bunch_of_stuff_to_generate_an_HTML_string();
HTMLBody = contentsAsHTML;
}
public string HTMLBody
{
get { return _htmlBody; }
set
{
if (_htmlBody != value)
{
_htmlBody = value;
RaisePropertyChanged("HTMLBody");
}
}
}
public SomeView View
{
get {return _myView;}
set { }
}
public string TabName { get; set; }
}
MainViewModel, Creating the Tab collection:
private ObservableCollection<SomeVM> _tabs;
public ObservableCollection<SomeVM> Tabs
{
get
{
if (_tabs== null)
{
_tabs= new ObservableCollection<SomeVM>();
_tabs.Add(new SomeVM("Tab 1"));
_tabs.Add(new SomeVM("Tab 2"));
_tabs.Add(new SomeVM("Tab 3"));
}
return _tabs;
}
}
MainWindow.xaml setting up the Tab Binding:
<TabControl ItemsSource="{Binding Tabs, Source={StaticResource vm}}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding View}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
My problem is that "OnBodyChanged" is fired multiple times on ever tab change. The HTML takes a few seconds to load, and I would rather it only loads when the property is actually modified in the viewmodel.
EDIT
Here's the smallest sample project that recreates my problem.
Your problem is not relevant to attached properties or MVVM.
In fact, the real problem is that TabControl destroy and recreate its child every time you change the selected tab. That would explain why the handler is invoked more than once. The VisualTree only contains the selected Tab.
If you can try with another control, you will see there are no errors.
For solving this issue, I will redirect you to this post.

How to bind a ListBox to Properties in a Class?

I have one main outstanding issue, I know now how to databind to lists and individual items, but there is one more problem I have with this, I need to bind a listbox to some properties that are in a Class.
For Example I have two Properties that are bound in some XAML in a class called Display:
Public Property ShowEventStart() As Visibility
Public Property ShowEventEnd() As Visibility
I can use these in my XAML but I want them to be in a ListBox/Drop-down List, how can I have my properties show in this list and be able to change their values, does it have to be a List to be in a List?
I just want to be able to modify these properties from a Drop-down list to toggle the Visibility of the ShowEventStart and ShowEventEnd Property Values using the Checkboxes in the Drop-down List.
Plus this must be a Silverlight 3.0 solution, I cannot figure out how to have something that can be bound in the XAML which is not a list and then bound it as a list to modify these items!
I just need a list of Checkboxes which alter the values of the Class Properties such as ShowEventStart and ShowEventEnd, there are other properties but this will be a start.
You can create a PropertyWrapper class and in you window code behind expose a property that returns a List<PropertyWrapper> and bind your ListBox.ItemsSource to it.
public class PropertyWrapper
{
private readonly object target;
private readonly PropertyInfo property;
public PropertyWrapper(object target, PropertyInfo property)
{
this.target = target;
this.property = property;
}
public bool Value
{
get
{
return (bool) property.GetValue(target, null);
}
set
{
property.SetValue(target, value, null);
}
}
public PropertyInfo Property
{
get
{
return this.property;
}
}
}
your window code behind:
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
properties = new List<PropertyWrapper>
{
new PropertyWrapper(this, typeof(Window1).GetProperty("A")),
new PropertyWrapper(this, typeof(Window1).GetProperty("B")),
};
this.DataContext = this;
}
private List<PropertyWrapper> properties;
public List<PropertyWrapper> Properties
{
get { return properties; }
}
private bool a;
private bool b = true;
public bool A
{
get
{
return a;
}
set
{
if (value != a)
{
a = value;
NotifyPropertyChange("A");
}
}
}
public bool B
{
get
{
return b;
}
set
{
if (value != b)
{
b = value;
NotifyPropertyChange("B");
}
}
}
protected void NotifyPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(
this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
your window markup:
<ListBox ItemsSource="{Binding Path=Properties}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Property.Name}"/>
<CheckBox IsChecked="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Hope this helps
I tried to mock up something similar. See how this works for you and let me know if I misunderstood the question.
From MainPage.xaml:
<StackPanel Orientation="Horizontal">
<ListBox SelectedItem="{Binding ShowControls, Mode=TwoWay}" x:Name="VisibilityList"/>
<Button Content="Test" Visibility="{Binding ShowControls}"/>
<CheckBox Content="Test 2" Visibility="{Binding ShowControls}"/>
</StackPanel>
The code behind MainPage.xaml.cs:
public partial class MainPage : UserControl
{
VisibilityData visibilityData = new VisibilityData();
public MainPage()
{
InitializeComponent();
VisibilityList.Items.Add(Visibility.Visible);
VisibilityList.Items.Add(Visibility.Collapsed);
this.DataContext = visibilityData;
}
}
And the data class:
public class VisibilityData : INotifyPropertyChanged
{
private Visibility showControls = Visibility.Visible;
public Visibility ShowControls
{
get { return showControls; }
set
{
showControls = value;
OnPropertyChanged("ShowControls");
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
When you run that code you should get a ListBox with the options Visible and Collapsed and when you choose an option you should see the visibility of the button and checkbox is changed to reflect your choice. Let me know if that's not what you were looking for.
After thinking about this the solution occured to me, which is to construct the List in XAML then using a Converter on the Checkboxes to convert their Boolean IsChecked to the Property Visibility Property in the Class.
You can create a list such as:
<ComboBox Canvas.Left="6" Canvas.Top="69" Width="274" Height="25" Name="Display">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Display.EventStart,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Event Start"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Display.EventEnd,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Event End"/>
</StackPanel>
</ComboBox>
I can then Bind to the Two Properties I want (this example from the actual application).

Resources