ComboBox binding to ObservableCollection is not working - wpf

I am new to WPF. In existing application,Combo box is not getting binding values from ObservableCollection. I have a class ShipmentItem. I need to bind combo box with WeightUnit field.
below is the code:
public partial class ShipmentItem : DataEntity {
private int piecesField;
private float weightField;
private System.Nullable<float> widthField;
private System.Nullable<float> lengthField;
private System.Nullable<float> heightField;
private string descriptionField;
private WeightUnit weightUnitField;
private LengthUnit lengthUnitField;
public int Pieces {
get {
return this.piecesField;
}
set {
this.piecesField = value;
this.RaisePropertyChanged("Pieces");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=1)]
public float Weight {
get {
return this.weightField;
}
set {
this.weightField = value;
this.RaisePropertyChanged("Weight");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=2)]
public System.Nullable<float> Width {
get {
return this.widthField;
}
set {
this.widthField = value;
this.RaisePropertyChanged("Width");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=3)]
public System.Nullable<float> Length {
get {
return this.lengthField;
}
set {
this.lengthField = value;
this.RaisePropertyChanged("Length");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=4)]
public System.Nullable<float> Height {
get {
return this.heightField;
}
set {
this.heightField = value;
this.RaisePropertyChanged("Height");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=5)]
public string Description {
get {
return this.descriptionField;
}
set {
this.descriptionField = value;
this.RaisePropertyChanged("Description");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=6)]
public WeightUnit WeightUnit {
get {
return this.weightUnitField;
}
set {
this.weightUnitField = value;
this.RaisePropertyChanged("WeightUnit");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=7)]
public LengthUnit LengthUnit {
get {
return this.lengthUnitField;
}
set {
this.lengthUnitField = value;
this.RaisePropertyChanged("LengthUnit");
}
}}
Here is the observable collection :
public ObservableCollection<ShipmentItem> ShipmentItemCollection
{
get { return shipmentItemCollection; }
set { shipmentItemCollection = (ObservableCollection<ShipmentItem>)value; }
}
shipmentItemCollection.Add(new ShipmentItem()
{
Weight = 0,
Pieces = 0,
WeightUnit = WeightUnit.Pounds,
Description = string.Empty,
Length = 0,
Width = 0,
Height = 0,
LengthUnit = LengthUnit.Inches,
Skidded = false,
Stackable = false,
Nmfc = string.Empty,
FreightClass = string.Empty,
DeliveryStop = 0
});
shipmentItemList.ItemsSource = shipmentItemCollection;
shipmentItemList.DataContext = ShipmentItemCollection;
ShipmentItemList is Listview, which has text box and combo box.Text box are getting their values from the binding path except ComboBox.And this is the XAML code for combo box.
<ComboBox Name ="cmbWeightUnits"
SelectionChanged="cmbWeightUnits_SelectionChanged"
PreviewKeyDown="check_PreviewKeyDown"
ItemsSource="{Binding Path= ShipmentItemCollection}"
DisplayMemberPath="{Binding Path=WeightUnit}">
</ComboBox>
Any help would be appreciated.

View
<ComboBox ItemsSource="{Binding ShipmentItemCollection}"
DisplayMemberPath="{Binding Path=WeightUnit}">
</ComboBox>
In the View class, set the DataContext to ViewModel
ViewModel
private ObservableCollection<ShipmentItem> _shipmentItemCollection;
public ObservableCollection<ShipmentItem> ShipmentItemCollection
{
get { return _shipmentItemCollection; }
set { _shipmentItemCollection = value; }
}
continued (in a constructor or some method)
ShipmentItemCollection.Add(new ShipmentItem()
{
Weight = 0,
Pieces = 0,
WeightUnit = WeightUnit.Pounds,
Description = string.Empty,
Length = 0,
Width = 0,
Height = 0,
LengthUnit = LengthUnit.Inches,
Skidded = false,
Stackable = false,
Nmfc = string.Empty,
FreightClass = string.Empty,
DeliveryStop = 0
});

Related

WPF Binding in columns not working

I have a list ob object like this:
public class Device : ObjectBase
{
private int _DeviceNbr;
public int DeviceNbr
{
get { return _DeviceNbr; }
set { _DeviceNbr = value; }
}
private string _DeviceName;
public string DeviceName
{
get { return _DeviceName; }
set { _DeviceName = value; OnPropertyChanged(); }
}
private ObservableCollection<State> _DeviceStates;
public ObservableCollection<State> DeviceStates
{
get { return _DeviceStates; }
set { _DeviceStates = value; OnPropertyChanged(); }
}
}
public class State: ObjectBase
{
public int StateNbr { get; set; }
private string _stateType;
public string StateType
{
get { return _stateType; }
set { _stateType = value; }
}
private int _value;
public int Value
{
get { return _value; }
set { _value = value; OnPropertyChanged(); }
}
}
which I need to bind to a Datagrid.
My approach is to create a customDataGrid which looks like this:
public class CustomGrid : DataGrid
{
public ObservableCollection<ColumnConfig> ColumnConfigs
{
get { return GetValue(ColumnConfigsProperty) as ObservableCollection<ColumnConfig>; }
set { SetValue(ColumnConfigsProperty, value); }
}
public static readonly DependencyProperty ColumnConfigsProperty =
DependencyProperty.Register("ColumnConfigs", typeof(ObservableCollection<ColumnConfig>), typeof(CustomGrid), new PropertyMetadata(new PropertyChangedCallback(OnColumnsChanged)));
static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = d as CustomGrid;
dataGrid.Columns.Clear();
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "Nbr", Binding = new Binding("DeviceNbr") });
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "Device", Binding = new Binding("DeviceName") });
foreach (var columnConfig in dataGrid.ColumnConfigs.Where(c => c.IsVisible))
{
var column = new DataGridTextColumn()
{
Header = columnConfig.ColumnHeader,
Binding = new Binding("DeviceStates")
{
ConverterParameter = columnConfig.ColumnName,
Converter = new DeviceStateConverter(),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode =BindingMode.TwoWay
}
};
dataGrid.Columns.Add(column);
}
}
}
public class DeviceStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ObservableCollection<State> DeviceStates && parameter != null)
{
var DeviceState = DeviceStates.FirstOrDefault(s => s.StateType == parameter.ToString());
if (DeviceState != null)
return DeviceState.Value;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The ViewModel looks like this:
public class MainViewModel : ObjectBase
{
private ObservableCollection<Device> _Devices;
public ObservableCollection<Device> Devices
{
get { return _Devices; }
set { _Devices = value; OnPropertyChanged(); }
}
private ObservableCollection<ColumnConfig> _columnConfigs;
public ObservableCollection<ColumnConfig> ColumnConfigs
{
get { return _columnConfigs; }
set { _columnConfigs = value; OnPropertyChanged(); }
}
public MainViewModel()
{
Devices = new ObservableCollection<Device>();
_columnConfigs = new ObservableCollection<ColumnConfig>()
{
new ColumnConfig(){ ColumnHeader = "On", ColumnName = "On", ColumnWidth= 100, IsVisible= true},
new ColumnConfig(){ ColumnHeader = "Off", ColumnName = "Off", ColumnWidth= 100, IsVisible= true}
};
for ( int i = 0; i <= 100; i++)
{
_Devices.Add(new Device()
{
DeviceNbr = i,
DeviceName = "Device " + i.ToString(),
DeviceStates = new ObservableCollection<State>()
{
new State() { StateType = "On", Value= i},
new State() { StateType = "Off", Value= i+1}
}
});
}
OnPropertyChanged("ColumnConfigs");
OnPropertyChanged("Devices");
}
public void TestStateChange ()
{
Devices[2].DeviceName = "Device X";
Devices[2].DeviceStates[0].Value = 5;
// OnPropertyChanged("Devices");
}
}
And the XAML like this:
<local:CustomGrid
AutoGenerateColumns="False"
ColumnConfigs="{Binding ColumnConfigs}"
ItemsSource="{Binding Devices, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Here is the result:
Application
Now the problem is that the binding for the Devicesstates does not work.
In the ViewModel is a method called "TestStateChange" where tried to change that state. The state in the DeviceStatesCollection changed like expected but it doesn't reflect to the view. Can someone please provide me some help?
UPDATE
The binding works but PropertyChanged Event when changing the value of a state does not fire.
public void TestStateChange()
{
foreach (var device in Devices)
{
foreach (var state in device.DeviceStates)
{
state.Value = state.Value + 1;
}
device.OnPropertyChanged("DeviceStates");
}
}
So I have to raise the PropertyChangedEvent on the "parent" collection of the state. That's weird.
The only think that I can now think of, is to implement an event in the State class and let the parent collection object subscribe to it.
Does someone has a better idea?
If your State object implements INotifyPropertyChanged (which all your bindable data objects should), and if your datagrid column binds directly to a State object (i.e. via a binding path that contains an index) then your UI will update as you expect.
You could subscribe to the State property change events from the parent Device object and then re-notify from there, but that is a long winded way to do it and means that you have to subscribe to the CollectionChanged event of the ObservableCollection that contains the State objects so that you can attach/unattach from the PropertyChange event on each State object (sometimes this will be the only way, but avoid it if you can).

why does my ObservableCollection not filter

i have a WPF desktop app.
I load an ObservableCollection with a list of objects.
I use the ICollectionView object to 'wrap' a filter around this ObservableCollection .
I set the filter and refresh but it does nto work so:
public class DataFilters : ViewModelBase
{
private ICollectionView _UserMappedRolesView { get; set; }
private ObservableCollection<UserMappedRoles> _UserMappedRoles;
public ObservableCollection<UserMappedRoles> UserMappedRoles
{
get
{
_UserMappedRolesView = CollectionViewSource.GetDefaultView(_UserMappedRoles);
_UserMappedRolesView.Filter = UserMappedRolesFilter;
_UserMappedRolesView.Refresh();
return _UserMappedRoles;
}
set
{
_UserMappedRoles = value;
}
}
public void LoadUserMappedRoles()
{
var baseData = InformedWorkerBusinessService.UserMappedRoles.Get();
var modelData =
from data in baseData
select new UserMappedRoles
{
Enabled = 1,
Login = data.Login,
UserMappedRolesRef = data.UserMappedRolesRef,
UserRoleRef = data.UserRoleRef
};
_UserMappedRoles = new ObservableCollection<UserMappedRoles>(modelData);
}
public string Login { get; set; }
private bool UserMappedRolesFilter(object item)
{
UserMappedRoles UserMappedRole = item as UserMappedRoles;
if (UserMappedRole.Login== Login)
{
return true;
}
else
{
return false;
}
}
}
and my test script:
UI.InformedWorkerViewModel.Models.HeartBeat VM = new UI.InformedWorkerViewModel.Models.HeartBeat();
VM.CommonData.DataFilters = new UI.InformedWorkerViewModel.Models.DataFilters();
VM.CommonData.DataFilters.LoadUserMappedRoles();
var data = VM.CommonData.DataFilters.UserMappedRoles;
VM.CommonData.DataFilters.Login = "David";
var filtered = VM.CommonData.DataFilters.UserMappedRoles;
I know my data only contains oUserMappedRoles where the Login name is 'Andy' so, by setting the Login filter name to 'David' I expect to get no records back.
I have set breakpoints everywhere and everyline of code gets 'hit'.
Have I (obviously) implemented this wrong?
Thanks
It is the ICollectionView that gets filtered, not the ObservableCollection.
So you should bind to the ICollectionView property:
<ListBox ItemsSource="{Binding UserMappedRolesView}" DisplayMemberPath="Login" />
...or look for the filtered items in this one:
var filtered = VM.CommonData.DataFilters.UserMappedRolesView;
You also need to refresh the CollectionView whenever you want to re-apply the filter, i.e. whenever your Login property is set to a new value. Something like this:
public class DataFilters : ViewModelBase
{
private ICollectionView _UserMappedRolesView;
public ICollectionView UserMappedRolesView
{
get { return _UserMappedRolesView; }
set { _UserMappedRolesView = value; NotifyPropertyChanged(); }
}
private ObservableCollection<UserMappedRoles> _UserMappedRoles;
public ObservableCollection<UserMappedRoles> UserMappedRoles
{
get
{
return _UserMappedRoles;
}
set
{
_UserMappedRoles = value;
NotifyPropertyChanged();
UserMappedRolesView = CollectionViewSource.GetDefaultView(_UserMappedRoles);
UserMappedRolesView.Filter = UserMappedRolesFilter;
UserMappedRolesView.Refresh();
}
}
public void LoadUserMappedRoles()
{
var baseData = InformedWorkerBusinessService.UserMappedRoles.Get();
var modelData =
from data in baseData
select new UserMappedRoles
{
Enabled = 1,
Login = data.Login,
UserMappedRolesRef = data.UserMappedRolesRef,
UserRoleRef = data.UserRoleRef
};
UserMappedRoles = new ObservableCollection<UserMappedRoles>(modelData);
}
private string _login;
public string Login
{
get { return _login; }
set { _login = value; _UserMappedRolesView.Refresh(); }
}
private bool UserMappedRolesFilter(object item)
{
UserMappedRoles UserMappedRole = item as UserMappedRoles;
if (UserMappedRole.Login == Login)
{
return true;
}
else
{
return false;
}
}
}

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

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

Is CompositeTransform used only in silverlight?

CompositeTransform is only used for silverlight?. Is there anyway we can use that in WPF or any equivalent replacement?
There is no CompositeTransform in WPF however there is a TransformGroup. Hence an equivalent replacement is a TransformGroup containing ScaleTransform, SkewTransform, RotateTransform and TranslateTransform in that order.
Here is a much nicer solution if you are anal about code cleanliness:
http://www.singulink.com/CodeIndex/post/getting-rid-of-ugly-transformgroup-blocks-in-wpf
Its easy on the eyes and because it just returns a TransformGroup, you can still use the Blend designer to work with animating over the transform!
<Rectangle Width="100" Height="100" Fill="LightGreen"
RenderTransform="{data:CompositeTransform ScaleX=2.5, ScaleY=1, SkewX=-60, Rotation=145}"
RenderTransformOrigin="0.5,0.5" />
Implementation:
public class CompositeTransformExtension : MarkupExtension
{
public double CenterX
{
get { return _scale.CenterX; }
set
{
_scale.CenterX = value;
_skew.CenterX = value;
_rotate.CenterX = value;
}
}
public double CenterY
{
get { return _scale.CenterY; }
set
{
_scale.CenterY = value;
_skew.CenterY = value;
_rotate.CenterY = value;
}
}
public double ScaleX
{
get { return _scale.ScaleX; }
set { _scale.ScaleX = value; }
}
public double ScaleY
{
get { return _scale.ScaleY; }
set { _scale.ScaleY = value; }
}
public double SkewX
{
get { return _skew.AngleX; }
set { _skew.AngleX = value; }
}
public double SkewY
{
get { return _skew.AngleY; }
set { _skew.AngleY = value; }
}
public double Rotation
{
get { return _rotate.Angle; }
set { _rotate.Angle = value; }
}
public double TranslateX
{
get { return _translate.X; }
set { _translate.X = value; }
}
public double TranslateY
{
get { return _translate.Y; }
set { _translate.Y = value; }
}
private ScaleTransform _scale = new ScaleTransform();
private SkewTransform _skew = new SkewTransform();
private RotateTransform _rotate = new RotateTransform();
private TranslateTransform _translate = new TranslateTransform();
public CompositeTransformExtension()
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var group = new TransformGroup();
group.Children.Add(_scale);
group.Children.Add(_skew);
group.Children.Add(_rotate);
group.Children.Add(_translate);
return group;
}
}

Resources