Silverlight ComboBox binding with value converter - silverlight

I'm attempting to convert the displayed value of a combobox by using its binding as a key to look for the value I would like to display. I can't seem to get it to work.
The datacontext of my user control is MyObject.
MyObject has a property "MasterDrawerId", which is the Id of "MyReferencedObject".
Elsewhere in my application, accessible through a static property of my App.xaml.cs is a collection of "MyOtherObjects". "MyReferencedObject" has a foreign key relationship with the Id of "MyOtherObject".
My combobox is bound to the "MasterDrawerId", which is what's passed into the converter.
I then use that as a lookup for "MyReferencedObject" to get the foreign key Id of "MyOtherObject" in order to display the name of that object.
I know it seems confusing but it's basically just using the property of the datacontext in order to do a lookup and display the name of another object in its place within a combobox.
This is my code:
masterSiteComboBox.DisplayMemberPath = "Name";
Binding binding = new Binding("MasterDrawerId");
binding.Mode = BindingMode.TwoWay;
binding.Converter = new DrwIdToSiteConverter();
masterSiteComboBox.SelectedItem = binding;
masterSiteComboBox.ItemsSource = ListOfMyOtherObjects;
Here is my converter code:
public class DrwIdToSiteConverter : IValueConverter
{
public DrwIdToSiteConverter()
{
}
public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
XferSite site = new XferSite();
foreach(XferUserDrawerPermissions perm in App.UserDrawerPermissions)
{
if (perm.DocumentTypeId.Match(value.ToString()))
{
site.Id = int.Parse(perm.SiteId);
site.Name = perm.SiteName;
break;
}
}
return site;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
I set a breakpoint at the first line of my "Convert" method of my converter and it never gets hit.

The selected item for a combo box must be an item that is already contained within the collection of objects that you set through the ItemsSource property.
In other words, if your ItemsSource is bound to a collection of Object1, Object2, Object3, you cannot set the SelectedItem to new Object() { Name = 1 }; If you do this, you must override the Equals and GetHashCode methods. This will allow you the ability to set the SelectedItem to a new object.
Example:
public class MyObject
{
public MyObject(string name)
{
if(string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
Name = name;
}
public string Name { get; private set; }
// override object.Equals
public override bool Equals(object obj)
{
//
// See the full list of guidelines at
// http://go.microsoft.com/fwlink/?LinkID=85237
// and also the guidance for operator== at
// http://go.microsoft.com/fwlink/?LinkId=85238
//
MyObject myObj = obj as MyObject;
if (myObj == null) return false;
return Name == myObj.Name;
}
// override object.GetHashCode
public override int GetHashCode()
{
return Name.GetHashCode;
}
}
var items = new List<MyObject>()
{
new MyObject {Name = "One"},
new MyObject {Name = "Two"},
new MyObject {Name = "Three"},
};
// Converter code
return new MyObject {Name = "One"};

Instead of
masterSiteComboBox.SelectedItem = binding;
do
masterSiteComboBox.SetBinding(ComboBox.SelectedItemProperty, binding);

Related

How to use custom object's list with typeconverter in winform?

I made custom type which can use in my custom winform control.
I want to display this custom type property in to default winform property window or my custom smart grid.
So i made type converter for my custom type.
[Serializable]
public class TestObj
{
private int a;
private int b;
public int A { get { return a; } set { a = value; } }
public int B { get { return b; } set { b = value; } }
public TestObj()
{
}
public TestObj(int a, int b)
{
this.a = a;
this.b = b;
}
}
// TestObj Converter
public class TestObjConverter : TypeConverter
{
//can convert string -> testobj?
public override bool CanConvertFrom(ITypeDescriptorContext context, >
Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
// string -> TestObj
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string[] v = ((string)value).Split(new char[] { ' ' });
return new TestObj(Int32.Parse(v[0]), Int32.Parse(v[1]));
}
return base.ConvertFrom(context, culture, value);
}
// TestObj -> string
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type
destinationType)
{
if (destinationType == typeof(string))
{
return ((TestObj)value).A + " " + ((TestObj)value).B;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
(If i did not made this typeconverter then resx error will occurred.)
Now i can use TestObj type for my custom control's property.
But i want to use list collection property for this type in my custom control class.
private List<TestObj> testData = new List<TestObj>();
[Category("CustomControl"), Description("Property")]
[TypeConverter(typeof(TestObjConverter))]
public List<TestObj> TestData
{
get { return testData; } set { testData = value; }
}
But i can not use this type's list for property.
If i use this, the collection editor was opened and i can add TestObj element too. But visual studio invoke error [invalid resx file, Culture=newtral, PublicKeyTokrn=null ] when i compile this.
How to use my custom type list in winform property?
But
This may be a little late but someone else may run across this. Your TestObj class that is being used for the property must be declared with the type converter and not the actual property you are exposing. the property is typed with the class as any property should.
[TypeConverter(typeof(TestObjConverter))]
public class TestObj
{
...
}
then in your main control class you just expose the property like normal without the type converter declaration like you have done
[Category("CustomControl"), Description("Property")]
public List<TestObj> TestData { get; set; }
I have several custom controls for winforms if you want a working example of this. Just ask me and i will send one to you

DataGrid Binding error for object already removed when BindingExpression uses array Index

I am trying to create a DataGrid which is populated by setting the ItemsSource property to an ObservableCollection of PropertyGroup objects where each PropertyGroup object contains an ObservableCollection of Property Objects. All the PropertyGroups have the same number of Property Objects, and so I am binding to them via a path using and array subscript. Every thing works fine, except that I get the following binding error AFTER I remove a PropertyGroup object from the DataGrid.
System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'PropertyElement')
from 'Children' (type 'ObservableCollection`1'). BindingExpression:Path=Children[3]
.Value; DataItem='PropertyGroupImpl' (HashCode=23661558); target element
is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException:
Specified argument was out of the range of valid values.
Parameter name: index'
My code:
public class DataGridView : UserControl
{
public DataGridView()
{
Rows = new ObservableCollection<PropertyGroup>();
m_DataGrid = new DataGrid();
m_DataGrid.AutoGenerateColumns = false;
m_DataGrid.ItemsSource = Rows;
Content = m_DataGrid;
}
public ObservableCollection<PropertyGroup> Rows { get; set; }
public void AddRowGroup(PropertyGroup propertyGroup)
{
if(Rows.Count == 0)
InitDataGrid(propertyGroup);
Rows.Add(propertyGroup);
}
public void RemoveRowGroup(PropertyGroup propertyGroup)
{
Rows.Remove(propertyGroup);
}
void InitDataGrid(PropertyGroup firstGroup)
{
for(int i = 0; i < firstGroup.Children.Count; ++i)
{
Property prop = firstGroup.Children[i] as Property;
DataGridColumn dgCol = null;
Binding bnd = new Binding();
bnd.Path = new PropertyPath("Children[" + i + "].Value");
if(prop.Type == Property.EnumType.eBool)
dgCol = CreateBooleanColumn(bnd);
else
dgCol = CreateTextColumn(bnd, prop.Value.GetType());
dgCol.Header = prop.Name;
m_DataGrid.Columns.Add(dgCol);
}
}
DataGridColumn CreateTextColumn(Binding bnd, Type propType)
{
var textCol = new DataGridTextColumn();
// Styling code removed for brevity
textCol.Binding = bnd;
return textCol;
}
DataGrid m_DataGrid;
DataGridColumn CreateBooleanColumn(Binding bnd)
{
var chkBoxCol = new DataGridCheckBoxColumn();
chkBoxCol.Binding = bnd;
return chkBoxCol;
}
}
public class PropertyGroup
{
public PropertyGroup()
{
Children = new ObservableCollection<PropertyElement>();
}
public ObservableCollection<PropertyElement> Children { get; set; }
}
public class Property : PropertyElement
{
public enum EnumType {eBool, eInt, eUInt, eFloat, eDouble, eString,
eVector2, eVector3, eVector4, eEnum};
public EnumType Type { get; set; }
public object Value { get; set; }
}
public class PropertyElement
{
public string Name { get; set; }
}
The binding error occurs after RemoveRowGroup() is called for a PropertyGroup when the child Property objects are being removed from the PropertyGroup's Children ObservableCollection.
It seems as though the BindingExpressions binding the DataGrid's cells to Property.Value are still trying to update after the object has been removed from the DataGrid.
Any ideas?
How about assign the Source property without index in PropertyPath?
Binding bnd = new Binding();
bnd.Path = new PropertyPath("Children[" + i + "].Value");
=>
Binding bnd = new Binding();
bnd.Source = firstGroup.Children[i];
bnd.Path = new PropertyPath("Value");
PS: I'm not good at english

Dynamically bind columns from Dictionary to Silverlight DataGrid

I want to bind a collection of objects to a DataGrid in Silverlight. The objects belong to the following type:
public class Seats
{
Dictionary<Group, long> dctValues = new Dictionary<Group, long>();
public int Id { get; set; }
public Dictionary<Group, long> Values
{
get { return dctValues; }
}
}
Whereas, Group is represented by:
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
}
I want to be able to generate columns based on the dictionary of groups, where each column would have the header set to Group.Name and the cell value for each item equal to the long value in the dictionary.
I'm going to assume that we can't guarantee that there is only a single instance of a Group class for each group name. (Else you would be generating columns based on a list of known groups no?)
Here is a class derived from DataGrid:
public class SeatsGrid : DataGrid, IValueConverter
{
public SeatsGrid()
{
AutoGenerateColumns = false;
}
#region public List<Seats> SeatsList
public List<Seats> SeatsList
{
get { return GetValue(SeatsListProperty) as List<Seats>; }
set { SetValue(SeatsListProperty, value); }
}
public static readonly DependencyProperty SeatsListProperty =
DependencyProperty.Register(
"SeatsList",
typeof(List<Seats>),
typeof(SeatsGrid),
new PropertyMetadata(null, OnSeatsListPropertyChanged));
private static void OnSeatsListPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SeatsGrid source = d as SeatsGrid;
List<Seats> value = e.NewValue as List<Seats>;
source.OnSeatsListPropertyChanged(value);
}
private void OnSeatsListPropertyChanged(List<Seats> value)
{
ItemsSource = null;
Columns.Clear();
var groups = value
.SelectMany(seats => seats.Values.Keys)
.Select(g => g.Name)
.Distinct();
foreach (var group in groups)
{
DataGridTextColumn col = new DataGridTextColumn();
col.Binding = new Binding()
{
Converter = this,
ConverterParameter = group
};
col.Header = group;
Columns.Add(col);
}
ItemsSource = value;
}
#endregion public List<Seats> SeatsList
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Seats seats = (Seats)value;
string group = (string)parameter;
Group actualGroup = seats.Values.Keys.FirstOrDefault(g => g.Name == group);
if (actualGroup != null)
{
return seats.Values[actualGroup].ToString();
}
else
{
return null;
}
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Place an instance of this class in Xaml and assign a List<Seats> to its SeatsList property and it generates the columns and renders the rows.
How does it work?
The magic starts in the OnSeatsListPropertyChanged method. It first gets a list of distinct Group names. It generates a new Text column for each group name setting the header naturally to the group name.
The weird stuff appears when setting the binding for the column. The binding is given a converter which for convience I decided to implement on the SeatsGrid class as well. The converter parameter is the group name. Since no path is specified the whole Seats object will be passed to the converter when binding actually occurs.
Now looking at the IValueConverter.Convert method. It finds an instance (is any) of Group in the seats that has the same name as the converter parameter. If found uses that Group as the key to lookup a value to return.
If the Groups were known to be unique per name then the code can be simplified but the principle is the same.

INotifyPropertyChanged does not update Value Converter

I have some properties which implement the INotifyPropertyChanged interface. It works fine. But in my code I also use some value converters (if value < 3 - make grid red, if value >3 and value < 10 - make grid blue, etc.).
The problem is how to refresh value converter after PropertyChanged was raised?
Is there simple code behind solution?
Thanks all and sorry for my bad English!
Here some code:
public class NotifyColors : INotifyPropertyChanged
{
private Color _TodayColor;
public Color TodayColor
{
get
{
return _TodayColor;
}
set
{
if (_TodayColor != value)
{
_TodayColor = value;
OnPropertyChanged("TodayColor");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
// it raised correctly when I change color with color picker control
}
}
}
// here is value converter
[ValueConversion(typeof(object), typeof(Brush))]
public class PositionToBackgroundConverter : IValueConverter
{
ModulePreferences ModulePrefs;
public PositionToBackgroundConverter(ModulePreferences ModulePrefs)
{
this.ModulePrefs = ModulePrefs;
}
#region IValueConverter Member
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (ModulePrefs.UseColoringByPosition)
{
try
{
if (value != null)
{
short value_short = (short)value;
if (value_short <= 3)
return (Brush)new SolidColorBrush(ModulePrefs.NotifyColorsObj._TodayColor); // here is changing property
else
return (Brush)new SolidColorBrush(ModulePrefs.NotifyColorsObj.T100PlusColor);
}
else
return Brushes.Transparent;
}
catch
{
return Brushes.Transparent;
}
}
else
return Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
#endregion
}
And here I apply my value converter to the grid:
// assign backgroundconverter
var grid = new FrameworkElementFactory(typeof(Grid));
bin = new Binding();
bin.Path = new PropertyPath(string.Format("DataItem.{0}", LastPositionColumnName));
bin.Converter = new PositionToBackgroundConverter(ProjectViewObj.ModulePrefs);
grid.SetValue(Grid.BackgroundProperty, bin);
If you have done it "correctly" the PropertyChanged event will cause an update of the bindings which bind to that property, when this occurs any converters that are used in the same binding will reconvert the values. So normally the conversions happen on their own. If this is not the case you are probably using the converters "inappropriately", please post some code because without it it's quite impossible to tell what exactly you are doing wrong.
Edit: You use grid.SetValue(Grid.BackgroundProperty, bin), you should use grid.SetBinding(Grid.BackgroundProperty, bin) instead since it is a binding.
Edit2: This really has nothing to do with converters.
In your sample code you bind to IntValue, then you change TodayColor and expect the binding to be updated, not gonna happen. If you want the binding to react to both properties you have to either use a MultiBinding or raise the respective events since your properties are interdependent.
i.e.
private Color _TodayColor;
public short _IntValue;
public short IntValue
{
get { return _IntValue; }
set
{
if (_IntValue != value)
{
_IntValue = value;
OnPropertyChanged("IntValue");
OnPropertyChanged("TodayColor");
}
}
}
public Color TodayColor
{
get { return _TodayColor; }
set
{
if (_TodayColor != value)
{
_TodayColor = value;
OnPropertyChanged("TodayColor");
OnPropertyChanged("IntValue");
}
}
}

WPF Get real value from a custom validation rule

I use custom validation rule to validate my data. But I can't access/determine the property value.
here is my code
public class MandatoryRule: ValidationRule
{
public MandatoryRule()
{
ValidationStep = System.Windows.Controls.ValidationStep.UpdatedValue;
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
BindingExpression exp = value as BindingExpression;
if (value == null)
return new ValidationResult(true, null);
return new ValidationResult(true, null);
}
}
I need to set the ValidationStep to UpdatedValue (for further business logic)
Then comes the problem: I don't know what's the property value? Because:
It is a generic validator, can't bound to a specific model
The value in parameter of Validate method is a BindingExpression
So how can I read the real value?
Thanks
At last, I come up with this idea.
Create a class DummyObject : DependencyObject.
Create a public static DependencyProperty DummyProperty.
Then create a new databinding, copy the source, binding path, element name, converter, etc from the (value as BindingExpression).ParentBinding.
Set the new databinding target to the dummyobject.
Then use the binding to UpdateTarget()
And now you can access the value from the dummyproperty.
Had the same issue and came accross this question, Gary's answer seems to be the way to go, but it lacked the source code. So here's my interpretation.
public class BindingExpressionEvaluator : DependencyObject
{
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("ValueProperty", typeof(object),
typeof(BindingExpressionEvaluator), new UIPropertyMetadata(null));
public static object Evaluate(BindingExpression expression)
{
var evaluator = new BindingExpressionEvaluator();
var binding = new Binding(expression.ParentBinding.Path.Path);
binding.Source = expression.DataItem;
BindingOperations.SetBinding(evaluator, BindingExpressionEvaluator.ValueProperty, binding);
var value = evaluator.Value;
BindingOperations.ClearBinding(evaluator, BindingExpressionEvaluator.ValueProperty);
return value;
}
}

Resources