dynamic binding wpf - wpf

Hi all this is my first question :)
This exemple tested on winform application and wpf application and the problem with binding on WPF
winform all works fine with ICustomTypeDescriptor and grid draw only columns added to Dictionary Properties (Name Age) and Male excluded
WPF all properties of the class person drawed on grid (Name Age Male)
any idea about this situation or interfaces equivalent of ICustomTypeDescriptor in wpf ?
<Grid>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Margin="90,30,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="325" />
</Grid>
List<Person> persons = new List<Person>();
persons.Add(new Person("Aymane", 30));
persons.Add(new Person("Raouia", 30));
grid.ItemsSource = persons; //wpf
grid.DataSource = persons; //winform
public class Person : ICustomTypeDescriptor
{
Dictionary<string, object> Properties = new Dictionary<string, object>();
public Person()
{
Properties.Add("Name", null);
Properties.Add("Age", null);
}
public Person(string name, object value)
: base()
{
Male = true;
Name = name;
Age = value;
}
public bool Male { get; set; }
public object Age { get { return Properties["Age"]; } set { Properties["Age"] = value; } }
public object Name { get { return Properties["Name"]; } set { Properties["Name"] = value; } }
#region ICustomTypeDescriptor Members
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
string ICustomTypeDescriptor.GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
string ICustomTypeDescriptor.GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(attributes, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return ((ICustomTypeDescriptor)this).GetEvents(null);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<PropertyDescriptor> props = new List<PropertyDescriptor>();
props.Add(new PersonPropertyDescriptor("Name", attributes));
props.Add(new PersonPropertyDescriptor("Age", attributes));
return new PropertyDescriptorCollection(props.ToArray());
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
class PersonPropertyDescriptor : PropertyDescriptor
{
public PersonPropertyDescriptor(string name, Attribute[] attrs)
: base(name, attrs)
{
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return typeof(Person); }
}
public override object GetValue(object component)
{
return ((Person)component).Properties[Name];
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(object); }
}
public override void ResetValue(object component)
{
((Person)component).Properties[Name] = null;
}
public override void SetValue(object component, object value)
{
((Person)component).Properties[Name] = value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
}

To gain control over the column generation handle the AutoGeneratingColumn event, have you can suppress the generation of a column by seting e.Cancel = true;
In your case:
private void DataGridAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dataGrid = sender as DataGrid;
if (dataGrid != null)
{
ICustomTypeDescriptor typeDescriptor =
dataGrid.Items[0] as ICustomTypeDescriptor;
if (typeDescriptor != null)
{
var props = typeDescriptor.GetProperties();
if (!props.Contains((PropertyDescriptor)e.PropertyDescriptor))
{
e.Cancel = true;
}
}
}
}
With the DataGrid definition of:
<DataGrid
AutoGenerateColumns="True"
Height="311"
HorizontalAlignment="Left"
Name="dataGrid1"
VerticalAlignment="Top"
Width="509"
AutoGeneratingColumn="DataGridAutoGeneratingColumn">
Gives the desired result.

Here the correct implementation of ICustomTypeDescriptor & ITypedList
namespace CustomTypeDescriptor
{
class Row : ICustomTypeDescriptor { }
class RowsCollection : List<Row>, ITypedList { }
class Table : IListSource, IEnumerable<Row>, IEnumerator<Row>
{
RowsCollection Rows { get; set; }
}
}

Related

MVVM Datagrid Binding SelectedItem not updating

I'm new to WPF and MVVM and i've an applicaton that uses Entity Framework to connect to database and a datagrid to show the users of the application.
The users CRUD operations are made in a separate window and not in the datagrid.
My problems are related with the update of datagrid.
The insert operation is ok but the update is not.
View 1 (Users List):
<DataGrid Grid.Row="1"
ItemsSource="{Binding Users, Mode=TwoWay}"
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False">
</DataGrid>
ViewModel :
class UserListViewModel: NotificationClass
{
UserDBContext _db = null;
public UserListViewModel()
{
_db = new UserDBContext();
Users = new ObservableCollection<User>(_db.User.ToList());
SelectedUser = Users.FirstOrDefault();
}
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
_users = value;
OnProprtyChanged();
}
}
private User _selectedUser;
public User SelectedUser
{
get
{
return _selectedUser;
}
set
{
_selectedUser = value;
OnProprtyChanged();
}
}
public RelayCommand Edit
{
get
{
return new RelayCommand(EditUser, true);
}
}
private void EditUser()
{
try
{
UserView view = new UserView();
view.DataContext = SelectedUser;
view.ShowDialog();
if (view.DialogResult.HasValue && view.DialogResult.Value)
{
if (SelectedUser.Id > 0){
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
updatedUser.Username = SelectedUser.Username; //this doesn't do nothing, object is already with the new username ?!
}
_db.SaveChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
after _db.SaveChanges(), datagrid should not be updated ?
Model:
class UserDBContext: DbContext
{
public UserDBContext() : base("name=DefaultConnection")
{
}
public DbSet<User> User { get; set; }
}
View 2 (User detail)
public partial class UserView : Window
{
public UserView()
{
InitializeComponent();
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
User object
class User: NotificationClass
{
public int Id { get; set; }
public string Username { get; set; }
public string CreatedBy { get; set; }
public DateTime? CreatedOn { get; set; }
}
NotificationClass
public class NotificationClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnProprtyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
if i close and open view 1, the new username is updated..
could someone help ? thanks
Just implementing INotifyPropertyChanged isn't enough, you have to explicitly invoke PropertyChanged (or in your case OnPropertyChanged) when a property changed.
See also https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
You can do it like so
class User : NotificationClass
{
private int _id;
private string _username;
private string _createdBy;
private DateTime? _createdOn;
public int Id
{
get => _id;
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string Username
{
get => _username;
set
{
if (value == _username) return;
_username = value;
OnPropertyChanged();
}
}
public string CreatedBy
{
get => _createdBy;
set
{
if (value == _createdBy) return;
_createdBy = value;
OnPropertyChanged();
}
}
public DateTime? CreatedOn
{
get => _createdOn;
set
{
if (value.Equals(_createdOn)) return;
_createdOn = value;
OnPropertyChanged();
}
}
}
it worked ! many thanks #nosale !
what about the change made to SelectedUser being reflected in my context ?
if i do this :
SelectedUser.Username = "test";
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
i was thinking that SelectedUser object has the "test" username and updatedUser has the old username, but not .. updatedUser already have "test"

WPF binding CombobBoxItem base on property

My View Model class:
class Student : INotifyPropertyChanged
{
private string name;
private bool isVisible;
public event PropertyChangedEventHandler PropertyChanged;
public string PersonName
{
get { return name; }
set
{
name = value;
OnPropertyChanged("PersonName");
}
}
public bool IsVisible
{
get { return isVisible; }
set
{
isVisible = value;
OnPropertyChanged("IsVisible");
}
}
}
My Students collection that store all my objects:
public ObservableCollection<Student> Students { get; set; }
XAML:
<ComboBox x:Name="cbStudents"
ItemsSource="{Binding Students}"
SelectionChanged="cbInterfaces_SelectionChanged"/>
So in some point i want to disappear several Students from my ComboBox so i just change IsVisible value to False.
Any idea how to do that using XAML ?
You can have your Students collection return only visible students.
//All students (visible and invisible)
ObservableCollection<Students> _AllStudents = GetAllStudentsFromDataSource();
//only visible students
ObservableCollection<Students> _VisibleStudents = new ObservableCollection<Students>();
foreach(var _s in _AllStudents.Where(x => x.IsVisible)){
_VisibleStudents.Add(_s);
}
//your property
public ObservableCollection<Student> Students { get{ return _VisibleStudents; } }
In the case of your check box toggling the visibility of students, your checkbox can be bound to a command like this:
<Checkbox IsChecked="{Binding IsCheckboxChecked}" Command={Binding ToggleStudents}" />
And your view model has an extra control for the checkbox toggle and the command:
bool _IsCheckboxChecked = false;
public bool IsCheckboxChecked {
get { return _IsCheckboxChecked;}
set {
if(_IsCheckboxChecked != value)
{
_IsCheckboxChecked = value;
}
}
}
public ICommand ToggleStudents
{
get;
internal set;
}
private void ToggleStudentsCommand()
{
ToggleStudents = new RelayCommand(ToggleStudentsExecute);
}
public void ToggleStudentsExecute()
{
_VisibleStudents.Clear();
if(_IsCheckboxChecked){
foreach(var _s in _AllStudents.Where(x => x.IsVisible)){
_VisibleStudents.Add(_s);
}
}
else
{
foreach(var _s in _AllStudents.Where(x => x.IsVisible == false)){
_VisibleStudents.Add(_s);
}
}
OnPropertyChanged("Students");
}
Your xaml doesn't need to change.

Telerik RadPropertyGrid Content of CollectionEditorPicker

as the topic suggests I wan't to modify the Content of the CollectionEditorPicker. This control is used to open the floating Window for the List of nested Properties.
Unfortunally the RadPropertyGrid don't show any Information about the collection in the Field.
How can I set some value in there? For example a placeholder like "Click here to open the collection" or "xx Items" or "Item 1, Item 2, Item 3..." so see some preview or Information about the field.
I've tried it with a template Selector, but if I'm doing so, the opened Popup is not resizable anymore. Also it looses some Information which are in the default CollectionEditorPicker.
Can you help me?
Below a minimal working Example.
The XAML:
<Window x:Class="TelerikPropertyGridTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:model="clr-namespace:TelerikPropertyGridTest.Model"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<model:TemplateSelector x:Key="RadPropertyListTemplateSelector">
<!-- Not Working -->
<model:TemplateSelector.CollectionsDataTemplate>
<DataTemplate>
<telerik:RadDropDownButton Content="Test">
<telerik:RadDropDownButton.DropDownContent>
<telerik:CollectionEditor telerik:AutoBindBehavior.UpdateBindingOnElementLoaded="Source"
></telerik:CollectionEditor>
</telerik:RadDropDownButton.DropDownContent>
</telerik:RadDropDownButton>
</DataTemplate>
</model:TemplateSelector.CollectionsDataTemplate>
<model:TemplateSelector.FloatNumberTemplate>
<DataTemplate>
<telerik:RadNumericUpDown telerik:AutoBindBehavior.UpdateBindingOnElementLoaded="Value" />
</DataTemplate>
</model:TemplateSelector.FloatNumberTemplate>
<model:TemplateSelector.IntNumberTemplate>
<DataTemplate>
<telerik:RadNumericUpDown telerik:AutoBindBehavior.UpdateBindingOnElementLoaded="Value"
NumberDecimalDigits="0" />
</DataTemplate>
</model:TemplateSelector.IntNumberTemplate>
</model:TemplateSelector>
</Grid.Resources>
<telerik:RadPropertyGrid Item="{Binding ObjectToBind}"
AutoGeneratingPropertyDefinition="RadPropertyGrid_OnAutoGeneratingPropertyDefinition"
EditorTemplateSelector="{StaticResource RadPropertyListTemplateSelector}">
</telerik:RadPropertyGrid>
</Grid>
</Window>
The ViewModel (generates a Random Object for testing)
public class MainWindowViewModel : BindableBase
{
private readonly Random _random = new Random();
private IExampleInterface _objectToBind;
public MainWindowViewModel()
{
this.ObjectToBind = new ExampleImplementation
{
SomeBooleanValue = this._random.Next() % 2 == 1,
SomeDateValue = this.RandomDay(),
SomeIntValue = this._random.Next(),
SomeString = Guid.NewGuid().ToString(),
SubClasses = new List<IExampleInterface>
{
new ExampleImplementation
{
SomeBooleanValue = this._random.Next() % 2 == 1,
SomeDateValue = this.RandomDay(),
SomeIntValue = this._random.Next(),
SomeString = Guid.NewGuid().ToString(),
SubClasses = new List<IExampleInterface>
{
new ExampleImplementation
{
SomeBooleanValue =
this._random.Next() % 2 == 1,
SomeDateValue = this.RandomDay(),
SomeIntValue = this._random.Next(),
SomeString = Guid.NewGuid().ToString()
}
}
}
}
};
}
public IExampleInterface ObjectToBind
{
get { return this._objectToBind; }
set
{
if (this._objectToBind != value)
{
this._objectToBind = value;
this.OnPropertyChanged("ObjectToBind");
}
}
}
private DateTime RandomDay()
{
var start = new DateTime(1995, 1, 1);
var range = (DateTime.Today - start).Days;
return start.AddDays(this._random.Next(range));
}
}
The IExampleInterface (should be later on a real Interface):
public interface IExampleInterface
{
string SomeString { get; set; }
int SomeIntValue { get; set; }
double SomeDouble { get; set; }
IList<IExampleInterface> SubClasses { get; set; }
IList<IExampleInterface> SubClasses2 { get; set; }
bool SomeBooleanValue { get; set; }
DateTime SomeDateValue { get; set; }
SomeEnum SomeEnumValue { get; set; }
}
The ExampleImplementation (should have later on a Real Implementation with additional Properties).
public class ExampleImplementation : BindableBase, IExampleInterface
{
private bool _someBooleanValue;
private DateTime _someDateValue;
private double _someDouble;
private SomeEnum _someEnumValue;
private int _someIntValue;
private string _someString;
private ObservableCollection<IExampleInterface> _subClasses;
private ObservableCollection<IExampleInterface> _subClasses2;
public bool SomeBooleanValue
{
get { return this._someBooleanValue; }
set
{
if (this._someBooleanValue != value)
{
this._someBooleanValue = value;
this.OnPropertyChanged("SomeBooleanValue");
}
}
}
public DateTime SomeDateValue
{
get { return this._someDateValue; }
set
{
if (this._someDateValue != value)
{
this._someDateValue = value;
this.OnPropertyChanged("SomeDateValue");
}
}
}
public double SomeDouble
{
get { return this._someDouble; }
set
{
if (Math.Abs(this._someDouble - value) > 0.01)
{
this._someDouble = value;
this.OnPropertyChanged("SomeDouble");
}
}
}
public SomeEnum SomeEnumValue
{
get { return this._someEnumValue; }
set
{
if (this._someEnumValue != value)
{
this._someEnumValue = value;
this.OnPropertyChanged("SomeEnumValue");
}
}
}
public int SomeIntValue
{
get { return this._someIntValue; }
set
{
if (this._someIntValue != value)
{
this._someIntValue = value;
this.OnPropertyChanged("SomeIntValue");
}
}
}
[Display(Name = #"TestString", GroupName = #"TestGroup", Description = #"TestDescription")]
public string SomeString
{
get { return this._someString; }
set
{
if (this._someString != value)
{
this._someString = value;
this.OnPropertyChanged("SomeString");
}
}
}
[Display(Name = #"Some Subclasses")]
public IList<IExampleInterface> SubClasses
{
get { return this._subClasses; }
set
{
if (!Equals(this._subClasses, value))
{
this._subClasses = new ObservableCollection<IExampleInterface>(value);
this.OnPropertyChanged("SubClasses");
}
}
}
public IList<IExampleInterface> SubClasses2
{
get { return this._subClasses2; }
set
{
if (!Equals(this._subClasses2, value))
{
this._subClasses2 = new ObservableCollection<IExampleInterface>(value);
this.OnPropertyChanged("SubClasses2");
}
}
}
}
And finally the TemplateSelector
public class TemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var def = item as PropertyDefinition;
if (def == null || def.SourceProperty == null)
{
return base.SelectTemplate(item, container);
}
if (typeof (IEnumerable).IsAssignableFrom(def.SourceProperty.PropertyType) && typeof(string) != def.SourceProperty.PropertyType)
{
return this.CollectionsDataTemplate;
}
if (typeof (double).IsAssignableFrom(def.SourceProperty.PropertyType))
{
return this.FloatNumberTemplate;
}
if (typeof (int).IsAssignableFrom(def.SourceProperty.PropertyType))
{
return this.IntNumberTemplate;
}
return base.SelectTemplate(item, container);
}
public DataTemplate CollectionsDataTemplate { get; set; }
public DataTemplate FloatNumberTemplate { get; set; }
public DataTemplate IntNumberTemplate { get; set; }
}
This is what I expect
The optimal solution would be to get detailed Information in the TextBlock, like Item 1, item 2 etc.
Thank you.
// Edit:
I've figured out the NullReferenceException and got a Demo to work, so that I can modify the text. But the popup is different to the default. Have you an idea to fix it?
I've updated the text and the example.
After wasting a few hours now I figured out a solution to realize this.
I've added a custom behavior to the Collection template. This behavior sets the Header of the CollectionEditor as soon as it's loaded or updated.
Below you can see my modifications:
The Template:
<model:TemplateSelector.CollectionsDataTemplate>
<DataTemplate>
<telerik:RadDropDownButton Content="Click to edit the collection">
<telerik:RadDropDownButton.DropDownContent>
<telerik:CollectionEditor telerik:AutoBindBehavior.UpdateBindingOnElementLoaded="Source"
ResizeGripperVisibility="Visible">
<i:Interaction.Behaviors>
<model:CollectionEditorBehavior />
</i:Interaction.Behaviors>
</telerik:CollectionEditor>
</telerik:RadDropDownButton.DropDownContent>
</telerik:RadDropDownButton>
</DataTemplate>
</model:TemplateSelector.CollectionsDataTemplate>
The behavior:
internal class CollectionEditorBehavior : Behavior<CollectionEditor>
{
protected override void OnAttached()
{
this.AssociatedObject.SourceUpdated += (sender, args) => this.PrepareHeader();
this.AssociatedObject.DataContextChanged += (sender, args) => this.PrepareHeader();
this.AssociatedObject.Loaded += (sender, args) => this.PrepareHeader();
}
private void PrepareHeader()
{
if (this.AssociatedObject == null)
{
// Error Case
return;
}
if (this.AssociatedObject.CollectionView == null ||
this.AssociatedObject.CollectionView.SourceCollection == null)
{
// Source not set
this.AssociatedObject.Header = "Collection";
return;
}
// Get the property from the DataContext to retrieve HeaderInformation
var propInfo = this.AssociatedObject.DataContext
.GetType()
.GetProperties()
.FirstOrDefault(
propertyInfo =>
Equals(propertyInfo.GetValue(this.AssociatedObject.DataContext),
this.AssociatedObject.CollectionView.SourceCollection));
if (propInfo == null)
{
// We didn't got the property Information, using default value
this.AssociatedObject.Header = "Collection";
return;
}
// Getting the DisplayName Attribute
var attr = Attribute.GetCustomAttribute(propInfo,
typeof (DisplayNameAttribute)) as DisplayNameAttribute;
if (attr != null)
{
// We have a DisplayName attribute
this.AssociatedObject.Header = attr.DisplayName;
return;
}
// Alternative: Get the Display Attribute
var attr2 = Attribute.GetCustomAttribute(propInfo,
typeof (DisplayAttribute)) as DisplayAttribute;
if (attr2 != null)
{
// We have the Display Attribute
this.AssociatedObject.Header = attr2.Name;
return;
}
// We have no DisplayAttribute and no DisplayName attribute, set it to the PropertyName
this.AssociatedObject.Header = propInfo.Name;
}
}

filter wpf datagrid values from a textbox

I have a textbox and a Datagrid. The datagrid has two columns name and Email address. I want to Filter the datagrid values with the value in the textbox.
You can use a ICollectionView for the DataGrid ItemSource then you can apply a Filter predicate and refesh the list when needed.
Here is a very quick example.
Xaml:
<Window x:Class="WpfApplication10.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="188" Width="288" Name="UI" >
<StackPanel DataContext="{Binding ElementName=UI}">
<TextBox Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" />
<DataGrid ItemsSource="{Binding DataGridCollection}" />
</StackPanel>
</Window>
Code:
namespace WpfApplication10
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ICollectionView _dataGridCollection;
private string _filterString;
public MainWindow()
{
InitializeComponent();
DataGridCollection = CollectionViewSource.GetDefaultView(TestData);
DataGridCollection.Filter = new Predicate<object>(Filter);
}
public ICollectionView DataGridCollection
{
get { return _dataGridCollection; }
set { _dataGridCollection = value; NotifyPropertyChanged("DataGridCollection"); }
}
public string FilterString
{
get { return _filterString; }
set
{
_filterString = value;
NotifyPropertyChanged("FilterString");
FilterCollection();
}
}
private void FilterCollection()
{
if (_dataGridCollection != null)
{
_dataGridCollection.Refresh();
}
}
public bool Filter(object obj)
{
var data = obj as TestClass;
if (data != null)
{
if (!string.IsNullOrEmpty(_filterString))
{
return data.Name.Contains(_filterString) || data.Email.Contains(_filterString);
}
return true;
}
return false;
}
public IEnumerable<TestClass> TestData
{
get
{
yield return new TestClass { Name = "1", Email = "1#test.com" };
yield return new TestClass { Name = "2", Email = "2#test.com" };
yield return new TestClass { Name = "3", Email = "3#test.com" };
yield return new TestClass { Name = "4", Email = "4#test.com" };
yield return new TestClass { Name = "5", Email = "5#test.com" };
yield return new TestClass { Name = "6", Email = "6#test.com" };
yield return new TestClass { Name = "7", Email = "7#test.com" };
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class TestClass
{
public string Name { get; set; }
public string Email { get; set; }
}
}
Result:

Force validation rules for controls bound on unselected tabItems in WPF

In my canonical example, I have two tabs the first with a button and the second with a text box that is bound to a validation rule. When the button is clicked an action occurs that should cause the validation to fail and therefore the Validation.Error event to fire. However, the event will only fire when I click the second tab.
The reason this is so important to me is I have a form with complex validation that occurs across multiple tabs and I want to highlight those tabs containing errors in some - and I especially want to display the errors when the form first loads.
I've already used a technique to force the validation to fire when the form loads but I just don't know why when the forms loaded it doesn't fire when the button is clicked.
The XAML for my test case :
<Window x:Class="WpfApplication1.TabsDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Tabs" Height="300" Width="300" Validation.Error="Window_Error">
<TabControl Grid.Row="0">
<TabItem>
<TabItem.Header>First</TabItem.Header>
<StackPanel Margin="5">
<Button Click="Button_Click">Clear the second textbox</Button>
</StackPanel>
</TabItem>
<TabItem>
<TabItem.Header>MyDataItem</TabItem.Header>
<TextBox>
<TextBox.Text>
<local:ValidationBinding Path="MyDataItem" UpdateSourceTrigger="LostFocus">
<local:ValidationBinding.ValidationRules>
<local:ValidateText />
</local:ValidationBinding.ValidationRules>
</local:ValidationBinding>
</TextBox.Text>
</TextBox>
</TabItem>
</TabControl>
</Window>
My code behind :
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class TabsDemo : Window
{
public TabsDemo()
{
InitializeComponent();
DataContext = new MyViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(DataContext as MyViewModel).MyDataItem = String.Empty;
}
private void Window_Error(object sender, ValidationErrorEventArgs e)
{
MessageBox.Show("Validation Error : " + e.Error.RuleInError);
}
}
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _myDataItem = "Default Value";
public string MyDataItem
{
get { return _myDataItem; }
set
{
if (_myDataItem != value)
{
_myDataItem = value;
NotifyPropertyChanged(new PropertyChangedEventArgs("MyDataItem"));
}
}
}
private void NotifyPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
PropertyChanged(this, args);
}
}
}
And for completeness here's the validation binding markup extension :
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace WpfApplication1
{
public class ValidationBinding : MarkupExtension
{
private readonly Binding _binding = new Binding();
private DependencyObject _dependencyObject;
private DependencyProperty _dependencyProperty;
public ValidationBinding()
{
_binding.ValidatesOnDataErrors = true;
_binding.ValidatesOnExceptions = true;
_binding.NotifyOnValidationError = true;
}
public ValidationBinding(string path)
{
_binding.Path = new PropertyPath(path);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var valueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
_dependencyObject = valueTarget.TargetObject as DependencyObject;
_dependencyProperty = valueTarget.TargetProperty as DependencyProperty;
var element = _dependencyObject as FrameworkElement;
if (element != null)
{
if (element.IsLoaded)
ForceValidation();
else
element.Loaded += (sender, args) => ForceValidation();
}
else
{
ForceValidation();
}
Debug.WriteLine("MarkupExtension.ProvideValue called for element " + element.Name);
return _binding.ProvideValue(serviceProvider);
}
private void ForceValidation()
{
BindingOperations.GetBindingExpression(_dependencyObject, _dependencyProperty).UpdateSource();
}
public object FallbackValue
{
get { return _binding.FallbackValue; }
set { _binding.FallbackValue = value; }
}
public string StringFormat
{
get { return _binding.StringFormat; }
set { _binding.StringFormat = value; }
}
public object TargetNullValue
{
get { return _binding.TargetNullValue; }
set { _binding.TargetNullValue = value; }
}
public string BindingGroupName
{
get { return _binding.BindingGroupName; }
set { _binding.BindingGroupName = value; }
}
public Collection<ValidationRule> ValidationRules
{
get { return _binding.ValidationRules; }
}
public bool ValidatesOnExceptions
{
get { return _binding.ValidatesOnExceptions; }
set { _binding.ValidatesOnExceptions = value; }
}
public bool ValidatesOnDataErrors
{
get { return _binding.ValidatesOnDataErrors; }
set { _binding.ValidatesOnDataErrors = value; }
}
public PropertyPath Path
{
get { return _binding.Path; }
set { _binding.Path = value; }
}
public string XPath
{
get { return _binding.XPath; }
set { _binding.XPath = value; }
}
public BindingMode Mode
{
get { return _binding.Mode; }
set { _binding.Mode = value; }
}
public UpdateSourceTrigger UpdateSourceTrigger
{
get { return _binding.UpdateSourceTrigger; }
set { _binding.UpdateSourceTrigger = value; }
}
public bool NotifyOnSourceUpdated
{
get { return _binding.NotifyOnSourceUpdated; }
set { _binding.NotifyOnSourceUpdated = value; }
}
public bool NotifyOnTargetUpdated
{
get { return _binding.NotifyOnTargetUpdated; }
set { _binding.NotifyOnTargetUpdated = value; }
}
public bool NotifyOnValidationError
{
get { return _binding.NotifyOnValidationError; }
set { _binding.NotifyOnValidationError = value; }
}
public IValueConverter Converter
{
get { return _binding.Converter; }
set { _binding.Converter = value; }
}
public object ConverterParameter
{
get { return _binding.ConverterParameter; }
set { _binding.ConverterParameter = value; }
}
public CultureInfo ConverterCulture
{
get { return _binding.ConverterCulture; }
set { _binding.ConverterCulture = value; }
}
public object Source
{
get { return _binding.Source; }
set { _binding.Source = value; }
}
public RelativeSource RelativeSource
{
get { return _binding.RelativeSource; }
set { _binding.RelativeSource = value; }
}
public string ElementName
{
get { return _binding.ElementName; }
set { _binding.ElementName = value; }
}
public bool IsAsync
{
get { return _binding.IsAsync; }
set { _binding.IsAsync = value; }
}
public object AsyncState
{
get { return _binding.AsyncState; }
set { _binding.AsyncState = value; }
}
public bool BindsDirectlyToSource
{
get { return _binding.BindsDirectlyToSource; }
set { _binding.BindsDirectlyToSource = value; }
}
public UpdateSourceExceptionFilterCallback UpdateSourceExceptionFilter
{
get { return _binding.UpdateSourceExceptionFilter; }
set { _binding.UpdateSourceExceptionFilter = value; }
}
}
}

Resources