I need some help with binding. Twoway mode doesn't work at all.
I fill my window with data passed to the constructor and it's working fine.
The problem is that I can't rewrite some data imputed in the window control even if I use Twoway binding.
Below is how I open window
var tempInregistrare = BaseConnection.GetInregistrareById(itemSelected.InregistrareId.ToString());
var tichet = new TichetView(new InregistrareModel {
Id =tempInregistrare.Id,
NumeFurnizor = tempInregistrare.NumeFurnizor,
IdFurnizor = tempInregistrare.IdFurnizor,
NumeProdus = tempInregistrare.NumeProdus......
ticket.Show();
Here is window constructor with DataContext set to self.
public TichetView(InregistrareModel inregistrare)
{
InitializeComponent();
InregistrareModel = inregistrare;
DataContext = this;
grdButtonsPrint.Visibility = Visibility.Visible;
}
public InregistrareModel InregistrareModel
{
get => inregistrareModel;
set
{
if (value != inregistrareModel)
{
inregistrareModel = value;
NotifyPropertyChanged();
}
}
}
public class InregistrareModel
{
public int Id { get; set; }
public int IdProdus { get; set; }
public int IdFurnizor { get; set; }
public string NumeProdus { get; set; }
public string NumeFurnizor { get; set; }
public string NrAuto { get; set; }
public string NumeSofer { get; set; }
public double CantitateInitiala { get; set; }
public double CantitateIesire { get; set; }
public double Umiditate { get; set; }
public double CantitateScazuta { get; set; }
public double CantitateMarfa { get; set; }
public DateTime DataIntrare { get; set; }
public DateTime DataIesire { get; set; }
public FurnizorModel Furnizor { get; set; }
public int NIR { get; set; }
}
And here is Xaml of window
<TextBlock Grid.Row="2" Grid.Column="2" VerticalAlignment="Center">Intrare</TextBlock>
<TextBox Grid.Row="2" Grid.Column="3" Text="{Binding InregistrareModel.CantitateInitiala}"/>
<TextBlock Grid.Row="4" Grid.Column="2" VerticalAlignment="Center">Umiditatea (%)</TextBlock>
<TextBox x:Name="txtUmiditate" Grid.Row="4" Grid.Column="3" IsReadOnly="False" Text="{Binding InregistrareModel.Umiditate, Mode=TwoWay}"/>
<TextBlock Grid.Row="5" Grid.Column="2" VerticalAlignment="Center">Cantitatea scazuta</TextBlock>
<TextBox x:Name="txtCantitateScazuta" Grid.Row="5" Grid.Column="3" IsReadOnly="False" Text="{Binding InregistrareModel.CantitateScazuta, Mode=TwoWay}"/>
<TextBlock Grid.Row="6" Grid.Column="2" VerticalAlignment="Center">Iesire</TextBlock>
<TextBox Grid.Row="6" Grid.Column="3" Text="{Binding InregistrareModel.CantitateIesire}"/>
<TextBlock Grid.Row="7" Grid.Column="2" VerticalAlignment="Center">Dată iesire</TextBlock>
<TextBox Grid.Row="7" Grid.Column="3" Text="{Binding InregistrareModel.DataIesire,StringFormat='{}{0:HH:HH dd/M/yyyy}'}"/>
<TextBlock Grid.Row="10" Grid.Column="2" VerticalAlignment="Center">Net</TextBlock>
<TextBox Grid.Row="10" Grid.Column="3" Text="{Binding InregistrareModel.CantitateMarfa}"/>
All text boxes are filled with data expect the second and third that I fill by hand.
The goal that I can't achieve right now is to take data from 1st textbox(InregistrareModel.CantitateInitiala) to type in 3rd(txtCantitateScazuta) some data and show the result in the last textbox so after I update my database with this data.
I totally agree with #Peter Boone.
You have a lot of gross architectural mistakes in the Solution and for good reason it needs to be completely redone.
But if you do not have such an opportunity, try to implement this option.
For fields (TextBox, TextBlock) that change, declare properties in the Window.
In the body of these properties, implement communication with the parent container (InregistrareModel) and with other properties.
Let's say you have a CantitateInitiala property that affects the value of the CantitateScazuta property.
public InregistrareModel InregistrareModel
{
get => inregistrareModel;
set
{
if (value != inregistrareModel)
{
inregistrareModel = value;
NotifyPropertyChanged();
CantitateInitiala = InregistrareModel.CantitateInitiala;
}
}
public double CantitateInitiala
{
get => InregistrareModel.CantitateInitiala;
set
{
if (value != InregistrareModel.CantitateInitiala)
{
InregistrareModel.CantitateInitiala = value;
NotifyPropertyChanged();
// Calculation of the CantitateScazuta value
double cantSc = CantitateInitiala / 123.45;
CantitateScazuta = cantSc;
}
}
}
public double CantitateScazuta
{
get => InregistrareModel.CantitateScazuta;
set
{
if (value != InregistrareModel.CantitateScazuta)
{
InregistrareModel.CantitateScazuta = value;
NotifyPropertyChanged();
}
}
}
In XAML, change the bindings for these fields:
<TextBlock Grid.Row="2" Grid.Column="2" VerticalAlignment="Center">Intrare</TextBlock>
<TextBox Grid.Row="2" Grid.Column="3" Text="{Binding CantitateInitiala}"/>
<TextBlock Grid.Row="4" Grid.Column="2" VerticalAlignment="Center">Umiditatea (%)</TextBlock>
<TextBox x:Name="txtUmiditate" Grid.Row="4" Grid.Column="3" IsReadOnly="False" Text="{Binding InregistrareModel.Umiditate, Mode=TwoWay}"/>
<TextBlock Grid.Row="5" Grid.Column="2" VerticalAlignment="Center">Cantitatea scazuta</TextBlock>
<TextBox x:Name="txtCantitateScazuta" Grid.Row="5" Grid.Column="3" IsReadOnly="False" Text="{Binding CantitateScazuta, Mode=TwoWay}"/>
Но нужно тщательно продумать алгоритм зависимостей свойств в том случае, если они имеют циклическую зависимость.
То есть не только изменение CantitateInitiala влияет на значение CantitateScazuta, но также существует обратная зависимость CantitateInitiala от изменения CantitateScazuta.
You should make the ViewModel a separate file/class. Let's call it TichetViewModel. This class should have the InregistrareModel. Something like this:
public class TichetViewModel : ObservableObject
{
private InregistrareModel _InregistrareModel;
public InregistrareModel InregistrareModel
{
get { return _InregistrareModel; }
set
{
if (value != _InregistrareModel)
{
_InregistrareModel = value;
NotifyPropertyChanged();
}
}
}
public TichetViewModel()
{
InregistrareModel = new InregistrareModel();
}
}
Then set the DataContext of TichetView in either you code behind or in xaml.
Code behind:
TichetView.xaml.cs
public TichetView()
{
InitializeComponent();
DataContext = new TichetViewModel();
}
or in xaml
TichetView.xaml
<Window.DataContext>
<local:TichetViewModel />
</Window.DataContext>
I like doing this in xaml because the Intellisense in Visual Studio picks this up and autocompletes based on the classes.
Implement INotifyPropertyChanged on the properties of the InregistrareModel. Like this:
public class InregistrareModel : ObservableObject
{
private int _Id;
public int Id
{
get { return _Id; }
set
{
if (value != _Id)
{
_Id = value;
NotifyPropertyChanged();
}
}
}
private string _NumeProdus;
public string NumeProdus
{
get { return _NumeProdus; }
set
{
if (value != _NumeProdus)
{
_NumeProdus = value;
NotifyPropertyChanged();
}
}
}
}
I have the following "element":
public class ReportElementViewModel
{
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public bool IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked != value)
{
_isChecked = value;
RaisePropertyChanged("IsChecked");
}
}
}
}
My ViewModel contains many ReportElementViewModels:
public abstract class ReportViewModel<TPrimaryModel> : SharedViewModel
where TPrimaryModel : Model, new()
{
public ObservableCollection<ReportElementViewModel> ReportElementViewModels
{
get { return _reportElementViewModels; }
set
{
if (_reportElementViewModels != value)
{
_reportElementViewModels = value;
RaisePropertyChanged("ReportElementViewModels");
}
}
}
}
I removed the members to reduce code complexity but they are implemented correctly.
In my view I want to show all ReportElementViewModels by showing their Name and IsChecked (checkbox).
I thought the ItemsControl would be the right "tool", however it doesn't work (nothing is shown):
<ItemsControl ItemsSource="{Binding ReportElemetViewModels}" Height="auto" VerticalAlignment="Top" Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="269"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You have made a spelling mistake in your Binding.
ItemsSource="{Binding ReportElemetViewModels}"
Should be:
ItemsSource="{Binding ReportElementViewModels}"
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
}
}
I have a TextBox, its Text property is bound to a property of type double named Grade. I also have a CheckBox, when the CheckBox is checked I want the Grade to take an auto calculated value (i.e. automatically set to MaxScore/Count of questions). If the CheckBox is not checked then I want to set and change the Grade Manually. My question is how can I implement this?
<TextBox Height="23"
Visibility="{Binding Path=Visible2, Converter={StaticResource boolToVis}, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding Path=Grade,UpdateSourceTrigger =PropertyChanged,Mode=TwoWay}"
HorizontalAlignment="Left" Margin="376,453,0,0"
Name="textBox3" VerticalAlignment="Top" Width="120" />
and i bind this to:
public double Grade
{
get
{
return grade;
}
set
{
grade = value;
OnPropertyChanged("Grade");
foreach (ExaminationQuestion exaq in
this.Examination.ExaminationQuestions)
{
if (exaq.Question.Guid == SelectedQuestionDropList.Guid)
{
exaq.Grade = value;
}
}
}
}
Thanks
in your VM, have another public Property Auto and make sure your Checkbox binds to it and so does your TextBox (so that it becomes readonly/disabled/hidden). And my default AutoCheck will be true
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label Content="Grade" />
<TextBox Height="23" Width="120"
Text="{Binding Path=GradeDisplay}"
IsEnabled="{Binding EnableBox}"
/>
<Label Content="Auto?" />
<CheckBox IsChecked="{Binding IsAuto}" />
</StackPanel>
And for the View Model
public bool EnableBox { get; set; }
public string GradeDisplay
{
get
{
if (EnableBox)
return Grade.ToString();
else
return "AUTO";
}
set
{
double result;
if (double.TryParse(value, out result))
Grade = result;
NotifyPropertyChanged("GradeDisplay");
}
}
private bool _IsAuto;
public bool IsAuto
{
get
{
return _IsAuto;
}
set
{
_IsAuto = value;
EnableBox = !value;
NotifyPropertyChanged("GradeDisplay");
NotifyPropertyChanged("EnableBox");
}
}
private double _Grade;
private double Grade
{
set
{
_Grade = value;
}
get
{
if (IsAuto)
{
// CODE TO GET AUTO GRADE
return 0.0;
}
else
{ // RETURN MANUALLY SET GRADE
return _Grade;
}
}
}
// CTOR
public MainWindowViewModel()
{
IsAuto = false;
}