Really simple WPF form data validation - how to? - c#

I'm having this really simple class, lets call it Customer.
It look like this:
namespace TestValidation
{
class Customer
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
if (String.IsNullOrEmpty(value))
{
throw new Exception("Customer name is mandatory.");
}
}
}
}
}
Now, I've created a basic form, where the user can add customers to the database. The form contain simple TextBox, bounded to the Name property of Customer, and an "Add" button.
The XAML code is:
<Window x:Class="TestValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestValidation"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Margin="119,86,107,194" Name="CustomerName"
Text="{Binding Path=Customer.Name,
ValidatesOnExceptions=True,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged,
NotifyOnValidationError=True}"
/>
<Button Content="Add" HorizontalAlignment="Left" Margin="204,176,0,0" VerticalAlignment="Top" Width="74"/>
</Grid>
</Window>
From the setter of the Name property, you can understand that the name is mandatory for me, so I want an validation event to rise if the Name TextBox left blank. By validation rules of WPF - once the user focus out of the textbox, and there's no value over there - it should change the border color to red. For some reason - this is not happening, and I don't have a clue why. What is wrong in my process?
Now, I've read so many good articles about Validation in WPF (like Enforcing Complex Business Data Rules with WPF, Data validation in WPF and Validation in Windows Presentation Foundation), but none of them helped me solving my problem.
Eventually, I want the form to look like the form in Brian Noyes excellent article over the first link (Don't have 10 credits, so I can't attach a photo... sorry).
I'll be grateful if someone can explain to me how it really works.
Important note - I'm working with .Net framework 4, so I need a solution that suits this version.

I would definitely recommend using IDataErrorInfo for WPF validation since WPF already understands how to use it, and its easy to implement.
To start with, add the interface to the class containing the data you want to validate. The required methods will probably look something like this:
public class Customer : IDataErrorInfo
{
...
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get { return null; }
}
string IDataErrorInfo.this[string columnName]
{
get
{
if (columnName == "Name")
{
// Validate property and return a string if there is an error
if (string.IsNullOrEmpty(Name))
return "Name is Required";
}
// If there's no error, null gets returned
return null;
}
}
#endregion
}
Next, you need to set ValidatesOnDataErrors=True in your TextBox binding so it runs the validation whenever the Name property changes:
<TextBox Text="{Binding Path=Customer.Name, ValidatesOnDataErrors=True}" ... />
And finally, create a Validation Template in your XAML to tell WPF how to draw a validation error. Here's the style/template I usually use:
<!-- ValidatingControl Style -->
<Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding
Path=(Validation.Errors)[0].ErrorContent,
RelativeSource={x:Static RelativeSource.Self}}" />
</Trigger>
</Style.Triggers>
</Style>
Also, be sure your Customer class implements INotifyPropertyChanged so it correctly responds to UI updates. I don't see that in your code, but often people leave that out for simplicity :)

You did not specify a validation rule. The validation rule would be invoked before the control is left and then can do whatever you want to validate the inputs.
A simple example - and I guess that's what you want to do - is provided here.

Use IDataErrorInfo for validation. this link will help you.

I think the issue might be that your class isn't implementing INotifyPropertyChanged, so isn't binding as you're expecting.
Implement the INotifyPropertyChanged interface, raise an event when the property changed and it should work.
See http://msdn.microsoft.com/en-us/library/ms743695(v=vs.110).aspx for a walkthrough.

<Binding Path=Name UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
http://msdn.microsoft.com/en-us/library/ms752347%28v=vs.110%29.aspx#what_is_data_binding
Please use this blog : prasadcsharp.blogspot.com

Here is something that worked fine with me. No lag or long coding but I used it on double values only. You may change it as you need.
private void search_box_TextChanged(object sender, TextChangedEventArgs e)
{
// box text and background to normal state if user types numbers
search_box.Foreground = Brushes.Black;
search_box.Background = Brushes.White;
if (search_id.IsSelected == true)
{
try
{
//convert while user is typing
if (string.IsNullOrEmpty(search_box.Text)==false)
Convert.ToDouble(search_box.Text);
search_error.Text = null;
}
//if user types a letter or a space or a symbol ====>
catch (Exception)
{
// user cant type any value other than numbers as exception prevents it and clears the box text value <======
search_box.Text = null;
search_box.Foreground = Brushes.White;
search_box.Background = Brushes.Red;
search_error.Text="id is numberic value";
}
}
}
Hope it helps.

1) when you use exceptions for validation, i reccomand to throw the exception before assigning the value to the property backing field, so you refuse it and your data-object (the Customer object in this case) will contain only valid data:
using System;
namespace TestValidation
{
public class Customer
{
private string _name;
public string Name
{
get => this._name;
set
{
if (String.IsNullOrEmpty(value))
throw new ArgumentException("Customer name is mandatory.", nameof(Name));
_name = value;
}
}
}
}
2) By default, WPF data-binding engine ignores the exceptions that are raised in the setter procedure of the data objetc. You correctly set the ValidatesOnExceptions to true in order to instruct the data binding system to react on Exceptions. But, you set the UpdateSourceTrigger on PropertyChanged, thus the update of the property (Name) of the Source object (Customer) is triggered only when the Target property (Text) of the Target element (TextBox) is changed. If you start with an empty TextBox and just tab into it and than tab again away, the Text property has not been changed, so the updating of the Source property (Name) will no be triggered (this will happen even with LostFocus as the UpdateSourceTrigger mode). You can correct this just initializig the Text property to null or String.Empty in the costructor or in the Loaded event handler. This way, the textbox will appear with a red border as soon as the window is rendered. If you set UpdateSourceTrigger to LostFocus (that is the default for TextBox's Text property), the TextBox will appear initially without error, but if you tab in and out, it will be highlighted with the expected red border.
Note: all this works because the Text property of the TextBox use TwoWay as the default binding mode, data can go from target to source.
using System.Windows;
namespace TestValidation
{
public partial class MainWindow: System.Windows.Window
{
public CustomerTest()
{
InitializeComponent();
}
private void window_Loaded(object sender, RoutedEventArgs e)
{
this.txtCustomerName.Text = null;
}
}
}
<Window x:Class="TestValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ValidationTests"
Title="MainWindow" Height="350" Width="525"
Loaded="window_Loaded">
<Window.Resources>
<local:Customer x:Key="customer" />
</Window.Resources>
<Grid DataContext="{StaticResource customer}">
<TextBox Margin="119,86,107,194"
x:Name="txtCustomerName" x:FieldModifier="protected"
Text="{Binding Path=Name,
ValidatesOnExceptions=True,
UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Add" HorizontalAlignment="Center" Margin="204,176,0,0" VerticalAlignment="Top" Width="74"/>
</Grid>
</Window>
3) In this case, the INotifyPropertyChanged is not required, since you are just interested in changing the value of the Source property (Name) by the interaction of the user in the TextBox, you are not modifying the Name property with other C# code. The INotifyPropertyChanged is implemented to notify the WPF data-binding system about changes in the data objetcs, so that WPF can update the data in user interface (update the Target when the Source is changed due to code procedures).

You didn't implement INotifyPropertyChanged.
Also keep your attention to IDataErrorInfo and INotifyDataErrorInfo.. which are using in case if you want to move validation logic out of setters.
Also need admit that in modern app better move validation logic in separate type. (see fluentValidation)
using System;
namespace TestValidation
{
public class Customer : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => this._name;
set
{
if(_name == value) return;
if (String.IsNullOrEmpty(value))
throw new ArgumentException("Customer name is mandatory.", nameof(Name));
_name = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
// TODO: Impelemnt interface INotifyPropertyChanged
// Create the OnPropertyChanged method to raise the event
// The calling member's name will be used as the parameter.
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion
}
}

Related

WPF databinding not working despite the target datacontext bound to the correct source memory address

The scenario is very simple here. I'm trying to bind a textbox to a property of a class at runtime:
tb.displayValue.DataContext = p.GetValue(currentNode, null);
xaml for the textbox:
<TextBox Name="displayValue" Grid.Column="1"
Style="{StaticResource propertyTextBoxStyle}"
Text="{Binding Path=DataContext,
RelativeSource={RelativeSource Self},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}">
</TextBox>
When the application starts, the textbox does get populated with the correct information so the source to target binding is working fine. However, when I try and edit a value in the textbox and then switch focus, the changes are not reflected in the actual data structure. The value would stay on the UI, but as soon as I try to reload the UI from the data structure again it defaults back to the original value.
I suspect the binding is not working correctly at first, but after checking the memory address of tb.displayValue.DataContext and comparing it to the actual memory address of the data structure it's an identical match.
INotifyPropertyChanged has been implemented and I have added the OnPropertyChanged call to every setter. After spending two days trying to debug this issue I think I'm really running out of options here so any suggestion would be appreciated.
The simplest two-way binding works this way: you set the DataContext on your Window to a new instance of your MainWindowViewModel class which implements INotifyPropertyChanged, and you set the binding path on your TextBox to the name of the public property on your ViewModel you want to bind to.
I'm trying to show how you need a public property with a get and set to bind to, and how to properly set the DataContext for your window so that all of the controls within it are able to bind to the public properties available on it.
I've never heard of setting the DataContext of a TextBox directly to the return value from a method before, and it just seems wrong, so maybe you are not going about it the right way, and hopefully this helps you see how it can work.
MainWindow.cs
<Window x:Class="DemoWPFApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:DemoWPFApp1.ViewModels"
Height="300" Width="460" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<TextBox Name="displayValue" Text="{Binding Path=BoundProperty,
Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
</TextBox>
</Window>
MainWindowViewModel.cs
namespace DemoWPFApp1.ViewModels
{
public class MainWindowViewModel : BaseViewModel
{
private string m_boundProperty;
public string BoundProperty
{
get
{
return m_boundProperty;
}
set
{
m_boundProperty = value; OnPropertyChanged();
}
}
public MainWindowViewModel()
{
BoundProperty = "Some value.";
}
}
}
BaseViewModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace DemoWPFApp1.ViewModels
{
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null)
{
var e = PropertyChanged;
if (e != null && propName != null)
{
e.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
}

Why aren't my data-bound user settings saving?

I have the following:
MainWindow:
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestApp"
xmlns:settings="clr-namespace:TestApp.Settings"
mc:Ignorable="d" Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl>
<ItemsControl.ItemsSource>
<Binding>
<Binding.Source>
<CollectionViewSource Source="{Binding Source={x:Static settings:CustomSettings.Default}, Path=coll}" />
</Binding.Source>
</Binding>
</ItemsControl.ItemsSource>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Name}" />
<Button Grid.Column="1" Click="Button_Click" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Code-Behind:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace TestApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
if (Settings.CustomSettings.Default.coll == null)
{
Settings.CustomSettings.Default.coll = new ObservableCollection<BasicClass>();
Settings.CustomSettings.Default.coll.Add(new BasicClass("String1"));
Settings.CustomSettings.Default.coll.Add(new BasicClass("String2"));
Settings.CustomSettings.Default.coll.Add(new BasicClass("String3"));
}
Settings.CustomSettings.Default.Save();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Settings.CustomSettings.Default.Save();
foreach (BasicClass item in Settings.CustomSettings.Default.coll)
{
MessageBox.Show(item.Name);
}
}
}
public class BasicClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
public BasicClass() { }
public BasicClass(string Name)
{
this.Name = Name;
}
}
}
Settings:
using System.Collections.ObjectModel;
using System.Configuration;
using System.Diagnostics;
namespace TestApp.Settings
{
internal sealed partial class CustomSettings : ApplicationSettingsBase
{
private static CustomSettings defaultInstance = ((CustomSettings)(Synchronized(new CustomSettings())));
public static CustomSettings Default
{
get
{
return defaultInstance;
}
}
[UserScopedSetting()]
[DebuggerNonUserCode()]
public ObservableCollection<BasicClass> coll
{
get
{
return ((ObservableCollection<BasicClass>)(this["coll"]));
}
set
{
this["coll"] = value;
}
}
}
}
How it works:
The app present three controls consisting of a TextBox and a Button. These are part of an ItemsControl whose source is bound to a user setting 'coll', of type ObservableCollection<BasicClass>. BasicClass has one property, 'Name', which appears in the TextBox via data-binding.
Expected behaviour:
I change the text in a TextBox, then click the corresponding Button. This would then save the new value in 'coll', and then present a MessageBox sequence demonstrating that this has indeed been changed. I restart the app, and my value is showing the newly saved value.
Actual behaviour:
I change the text, I click the Button, the MessageBox sequence shows me that the value is now stored in the user settings (and should therefore have been saved). However, when I restart the app, I see the original value, not the saved one.
An anomaly(?):
If I click the button twice instead of once (going through the MessageBox sequence twice), when I restart the value has now been successfully saved.
EDIT (original answer below):
While I suspect implementing IBindableComponent on a subclass of ObservableCollection might work, I don't recommend it. If you're just storing strings, System.Collections.Specialized.StringCollection might help you.
But in general, I don't think it makes much sense to update your application settings each time something changes. Instead, load them during application startup into your view model (e.g. an ObservableCollection) and move them back to the application settings when closing. This way, the values only need to be deserialized and serialized once.
The effect you mention in your comment about setting the value to itself works (it seems) because the list is re-serialized when you set it. It appears, ApplicationSettingsBase is storing a serialized copy of every value you provide, and therefore can't react to changes in your original object. When you provide the value again, it overwrites its copy with a serialized version of the new state of your object. However, if you serialize the list every time a user makes a change, it will impact your app's performance, once the list gets longer.
This might be interesting to you as well:
How to store int[] array in application Settings
And it seems unnecessary to subclass ApplicationSettingsBase yourself, see https://social.msdn.microsoft.com/Forums/en-US/4e299ed8-8e3a-408e-b900-eb6738fe0775/persist-and-restore-application-state?forum=wpf
ORIGINAL:
I'm not familiar with the ApplicationSettingsBase, but perhaps this helps https://msdn.microsoft.com/en-in/library/8eyb2ct1(en-us).aspx:
You can only bind an application setting to a component that supports the IBindableComponent interface. Also, the component must implement a change event for a specific bound property, or notify application settings that the property has changed through the INotifyPropertyChanged interface. If the component does not implement IBindableComponent and you are binding through Visual Studio, the bound properties will be set the first time, but will not update. If the component implements IBindableComponent but does not support property change notifications, the binding will not update in the settings file when the property is changed.
It seems like it has to do with the fact that only serialized versions of your settings are stored and therefore changes within the list are not recognized (because the reference itself does not change). Note that when you replace the list entirely upon clicking a button, the new list's values are stored.

Getting a user variable, setting it's value, and displaying it on another screen

I am new to WPF, C# and xaml. I'd describe my knowledge as poor, but growing.
I have a very simple application. What I am trying to accomplish is the following:
I have a textbox, and I'm asking the user of the application to enter an email address. I'd like to store that variable somewhere, where I can write it to the screen later. So:
1. Get the email address (User hits a 'Next' button)
2. "Thanks an email will be set to " "when the utility is finished."
I'm using Blend with SketchFlow. I created a singleton class that allows me to store the variable.
namespace Mysillyapplication
{
public class ApplicationParameters
{
private static ApplicationParameters _instance;
private string _emailAddress;
public static ApplicationParameters Instance
{
get
{
if (_instance == null)
{
_instance = new ApplicationParameters();
}
return _instance;
}
}
public string EmailAddress
{
get { return _emailAddress; }
set
{
_emailAddress = value;
}
}
}
The code for the page that gets the email address:
namespace Mysillyapplication
{
/// <summary>
/// Interaction logic for BasicParameters.xaml
/// </summary>
public partial class BasicParameters : UserControl
{
public BasicParameters()
{
this.InitializeComponent();
}
public string EmailAddress
{
get
{
return ApplicationParameters.Instance.EmailAddress;
}
set
{
ApplicationParameters.Instance.EmailAddress = value;
}
}
}
}
In my xaml I have the following lines:
<TextBox x:Name="email" HorizontalAlignment="Left" Height="23" Margin="32,65,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="291"/>
AND
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction TargetName="MySillyapplication_BasicParameters_Name" PropertyName="EmailAddress" Value="{Binding Text, ElementName=email}" />
<pi:NavigateToScreenAction TargetScreen="DBSyncScreens.BeginSync"/>
</i:EventTrigger>
AND Finally, I want to display it out on another page:
In that page's CS I have the following lines:
public string EmailAddress
{
get
{
return ApplicationParameters.Instance.EmailAddress;
}
set
{
ApplicationParameters.Instance.EmailAddress = value;
}
}
And in the XAML:
<TextBlock HorizontalAlignment="Left" Height="25" Margin="10,35,0,0" Style="{DynamicResource BasicTextBlock-Sketch}"
VerticalAlignment="Top" Width="605" >
<Run Text="A log of the sync will be sent to "/>
<Run Text="{Binding EmailAddress, Mode=TwoWay}"/>
<Run Text=". Click Next to begin the sync process."/>
</TextBlock>
is wrong and doesn't work.
I could be doing this the wrong way. Anyone have any ideas on how to easily make that user variable email address easy to handle. I feel like what I'm trying to accomplish is very easy, yet I'm having so much difficulty with it.
get a variable, store it, access it on other pages.
I wrote about this a bit in my blog post What is this "DataContext" you speak of?
To summarize, WPF applications have two layers: the UI layer and the data layer.
The data layer is the DataContext, and bindings are used in the UI layer to access data from the data later.
When you write <TextBox Text="{Binding EmailAddress}" />, you are telling WPF to look in the data layer (DataContext) behind the TextBox and bind to the EmailAddress property.
The DataContext is inherited down the visual tree, so typically you will only see the DataContext set once, such as
public SomeConstructor()
{
this.InitializeComponent();
this.DataContext = ApplicationParameters.Instance;
}
It should be noted that you can also specify other binding properties to access values from somewhere other than the current DataContext, such as ElementName or Source.
For example, since you are binding to a Singleton property, you could use the following syntax for your binding:
Text="{Binding Path=EmailAddress,
Source={x:Static my:ApplicationParameters.Instance}}"
This would set the source of your binding to your singleton object, so it would bind to ApplicationParameters.Instance.EmailAddress
For this to work, you'll also have to specify the "my" namespace, like this:
<Window ... xmlns:my="clr-namespace:MyNamespace" />
But to make a long story short, the reason why your binding won't work is because the DataContext behind your TextBlock is not ApplicationParameters.Instance, so it finds no EmailAddress property to bind to :)
It is because you haven't set your DataContext on your second page so it can't find EmailAddress.
In your page constructor try adding:
DataContext = this;
In the BasicParameters constructor, add the following code:
public BasicParameters()
{
this.InitializeComponent();
this.DataContext = ApplicationParameters.Instance;
}
HTH!

Loading XAML to C# for Data Binding

Ok, we are trying out XAML for our GUI now (and learning as we go)...I have been able to do the data binding without a problem in XAML and C# independent of one another, but now comes the time I need to pass values back and forth and I'm a bit lost. When I compile and try to navigate to the page, it is throwing a XamlParseException: Specified class name doesn't match actual root instance type. Remove Class directive or provide an instance via XamlObjectWriterSettings.RootObjectInstance. Line 5 position 2.
Any help or a gentle shove in the right direction is greatly appreciated :)
Here's where I am:
namespace TheAirline.GraphicsModel.PageModel.PageFinancesModel
{
/// <summary>
/// Interaction logic for PageFinances.xaml
/// </summary>
public partial class PageFinances : Page
{
private Airline Airline;
public PageFinances(Airline airline)
{
InitializeComponent();
this.Language = XmlLanguage.GetLanguage(new CultureInfo(AppSettings.GetInstance().getLanguage().CultureInfo, true).IetfLanguageTag);
this.Airline = airline;
Page page = null;
//loading the XAML
using (FileStream fs = new FileStream("TheAirline\\GraphicsModel\\PageModel \\PageFinancesModel\\PageFinances.xaml", FileMode.Open, FileAccess.Read))
{
page = (Page)XamlReader.Load(fs);
}
//finding XAML element and trying to set the value to a variable
string airlineCash = GameObject.GetInstance().HumanAirline.Money.ToString();
TextBox cashValue = (TextBox)page.FindName("cashValue");
cashValue.DataContext = airlineCash;
}
}
}
And the first few lines of the XAML:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:AirlineModel="clr-namespace:TheAirline.Model.AirlineModel"
mc:Ignorable="d"
x:Class="TheAirline.GraphicsModel.PageModel.PageFinancesModel.PageFinances"
xmlns:c="clr-namespace:TheAirline.GraphicsModel.Converters"
...>
</Page>
Bindings in XAML are resolved against the object that is assigned to the DataContext property of any given XAML element. The value of that property (as well as many other properties) Is Inherited in any given Visual Tree from parent elements to child elements.
for instance, given this class:
public namespace MyNamespace
{
public class ViewModel
{
public string Name {get;set;}
public bool IsActive {get;set;}
}
}
and this XAML:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace"
FontSize="20">
<Window.DataContext>
<local:ViewModel>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Path=Name}"/>
<CheckBox IsChecked="{Binding Path=IsActive}"/>
<StackPanel>
</Window>
All four objects defined in XAML, the Window, the StackPanel, the TextBox, and the CheckBox, will have a FontSize of 20, and the instance of the ViewModel class assigned to their DataContext property. Therefore all bindings (Except bindings with a specified ElementName, RelativeSource, or Source) will be resolved against that instance.
It would be exactly the same if the property was assigned in code instead of in XAML:
public MyWindow() //Window Constructor
{
InitializeComponent();
this.DataContext = new ViewModel(); //Note that keyword "this" is redundant, I just explicity put it there for clarity.
}
Because of this, there is no need to set the DataContext property to each element explicitly, as the framework is already taking care of that.
Also, notice that in XAML, most built-in Markup Extensions have a default constructor convention that allows you to abbreviate their usage. In the case of the Binding Markup Extension, the default constructor has the Path property, therefore this:
<TextBox Text="{Binding Path=Name}"/>
is exactly the same as this:
<TextBox Text="{Binding Name}"/>
Now, for property changes in the underlying DataContext to be automatically passed from the binding source (ViewModel) to the binding target (XAML-defined objects), the source object must implement the System.ComponentModel.INotifyPropertyChanged interface and raise the PropertyChanged event every time a property changes.
Therefore, in order to support Two-Way Binding, the example class should look like this:
public namespace MyNamespace
{
public class ViewModel: INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private bool _isActive;
public bool IsActive
{
get
{
return _isActive;
}
set
{
_isActive = value;
NotifyPropertyChanged("IsActive");
}
}
}
public void NotifyPropertyChanged (string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
Notice that the ViewModel class has no dependency or direct reference to any of the XAML-defined objects, but still it contains the Values of the properties that will appear in the UI. This allows for a complete decoupling between UI and application logic/data known as the MVVM Pattern. I strongly suggest you research on that topic if you expect to be successful in programming in C# + XAML, because it is a radical mindshift when compared to other, traditional UI paradigms.
For example, something like this is not recommended in XAML-based applications:
if (myWindow.CheckBox1.IsChecked)
//Do Something
because that would mean that you're coupling the application logic and making it dependant on the state of UI elements, which is precisely what you need to avoid.
Notice that all the links and all the concepts referenced in this answer pertain to WPF, but are also applicable to Silverlight and WinRT. Since you did not specify which of the three XAML-based frameworks you're using, I posted the WPF ones, which is what I'm most familiar with.

ValidationRules not removing error when value is set back to valid value

INTRODUCTION
I have created a DecimalTextBox UserControl that houses some decimal validation I need done, so that I dont need to recreate the validation each time, and can just use the UserControl instead. This validation has properties that need to be bound to, and so I have created DependencyProperties so I can bind to them, according to this article by Josh Smith.
THE PROBLEM
The control's validation is behaving strangely. When I type an erroneous value into the TextBox, it shows up as an error. However when I try to change the value back in the code the value shown in the textbox remains unchanged.
Here are the steps I perform that cause this error (in this example 1 is an invalid value):
Load the form and the default value is 0.
Enter 1 into the textbox (and the textbox goes red due to the validation result being and error)
In the code I set the property bound to the textbox to 0
The form still displays 1 in a red textbox
CODE EXAMPLE
I prepaired an example demonstrating the problem, which can be downloaded here.
I'll post some of the code here, if you want more let me know.
ValidationTestControl's XAML
<UserControl x:Class="WPFTestProject.ValidationTestControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:WPFTestProject"
x:Name="ValidationTest"
Height="50" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="Type 'Banana' here: "></TextBlock>
<TextBox MinWidth="100">
<TextBox.Text>
<Binding ElementName="ValidationTest" Path="Text" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" ValidatesOnDataErrors="True" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<v:NotBananaValidationRule>
<v:NotBananaValidationRule.NotWhatBinding>
<v:NotBananaBinding x:Name="NotBananaValidationBinding"></v:NotBananaBinding>
</v:NotBananaValidationRule.NotWhatBinding>
</v:NotBananaValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text=" (the text will give error when = 'Banana')"></TextBlock>
</StackPanel>
</Grid>
ValidationTestControls Code Behind
(yes I know not very MVVM but I felt it was ok for this stand alone control)
public partial class ValidationTestControl : UserControl
{
public ValidationTestControl()
{
InitializeComponent();
Banana = "Banana";
Binding BananaBinding = new Binding("Banana");
BananaBinding.Source = this;
NotBananaValidationBinding.SetBinding(NotBananaBinding.NotWhatProperty, BananaBinding);
}
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(ValidationTestControl), new PropertyMetadata());
public static DependencyProperty BananaProperty = DependencyProperty.Register("Banana", typeof(string), typeof(ValidationTestControl), new PropertyMetadata());
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public string Banana
{
get
{
return (string)GetValue(BananaProperty);
}
set
{
SetValue(BananaProperty, value);
}
}
}
ValidationRule and FrameWorkElement created for binding
public class NotBananaValidationRule:ValidationRule
{
private NotBananaBinding _notWhatBinding;
public NotBananaBinding NotWhatBinding
{
get { return _notWhatBinding; }
set { _notWhatBinding = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string what = value.ToString();
if(what == _notWhatBinding.NotWhat||string.IsNullOrEmpty(what))
return new ValidationResult(false,
"Please enter a string that is not " + _notWhatBinding.NotWhat);
else
return new ValidationResult(true, null);
}
}
public class NotBananaBinding : FrameworkElement
{
public static readonly DependencyProperty NotWhatProperty = DependencyProperty.Register(
"NotWhat", typeof(string), typeof(NotBananaBinding), new UIPropertyMetadata());
public string NotWhat
{
get { return (string)GetValue(NotWhatProperty); }
set { SetValue(NotWhatProperty, value); }
}
public NotBananaBinding() { }
}
Basically what this code does is check if you have typed "Banana" and then returns a validation error. The control exposes dependency properties because I want to be able to bind to them when I use the control. The FrameworkElement NotBananaBinding lets me create dependency properties (because it is a DependencyObject so i can bind stuff for the validation. The ValidationRule has a NotBananaBinding property that stores the dependency property and uses it in the validate method.
I know my property names are kinda crappy, sorry. The thing is that the example does a good job of displaying the error. In my haste to make an example I didn't name the variables very well. If you find the code crappy please download the sample here.
WHAT I'VE FIGURED OUT SO FAR
Basically this problem seems to be caused by the fact that I am not actually changing the value.
Even if I call OnPropertyChanged on the property, because the value is not different it doesn't try and reevaluate the Validation.
I can obviously change the value to some arbitrary Valid value and then change it to the one I want it to be and it will work, but I was hoping there is some way to get call validation manually, to reevaluate the value and then change it etc. The changing it away and back is kinda messy.
CONCLUSION
Am I doing something wrong (perhaps something about the way I implemented the validation and binding from Josh Smiths post)
Is this just a c# bug, or is the behavior intended? If so then why?
Are there any elegant ways to fix it?
u_u
The validation prevents the Text property to be set. Put a break point on the setter and you will see that it will not break when you type the last 'a'. If you type Bananan and hit Backspace and it errors, push the button and it will work. The validation makes sure that there can be no invalid value in your property. So if you save it to let's say a database while in error, it won't save an invalid value.

Categories