WPF ComboBox twoway databinding is not working as expected - c#

I have an Products object with many properties. One of them is as follows
class Products : DependencyObject
{
public int ID
{
get { return (int)GetValue(IDProperty); }
set { SetValue(IDProperty, value); }
}
public static readonly DependencyProperty IDProperty = DependencyProperty.Register("ID", typeof(int), typeof(Products), new PropertyMetadata(0, new PropertyChangedCallback(IDChanged)));
private static void IDChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine($"New ID {e.NewValue}");
}
}
I have bound it with my combobox selecteditem property. The combobox itemsource is populated by a separate function which provides a list of Product IDs. As the IDs are not known initially it is set to default 0 at first.
The binding is done as follows
private void testClick(object sender, RoutedEventArgs e)
{
Products products = new Products();
ProductListCombobox.ItemsSource = GetProductIDList();
ProductListCombobox.SetBinding(ComboBox.SelectedItemProperty, new Binding { Source = items, Path = new PropertyPath(Products.IDProperty), Mode = BindingMode.TwoWay, Converter = new ProductsIDConverter(), ConverterParameter = ProductListCombobox });
}
The converter class for the binding
class ProductsIDConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var id = (int)value;
var cmb = (ComboBox)parameter;
if (!cmb.Items.Contains(id)) return cmb.Items[0];
else return id;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Here in the converter class I set the first value of the items list to be the selecteditem which in turn should update the ID property on the source too as it is two way binding as far as I know. But it does not update the property back. But it updates if I manually change the selecteditem. I am pretty new to this databinding thing so if anyone cares to give some solution here will help me a lot.
Thanks

Related

How do I properly bind an ASCII string in the form of a list<byte> to a textbox?

I have a List that represents an ASCII string, and I'm trying to let it be edited via a textbox. I've set up the binding like this:
public List<byte> ParamData;
var b = new TextBox();
b.DataContext = ParamData;
var binding = new Binding(".");
binding.Converter = new ListToStringConverter();
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b.SetBinding(TextBox.TextProperty, binding);
internal class ListToStringConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var orig = (List<byte>) value;
var res = Encoding.ASCII.GetString(orig.ToArray());
return res;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
var orig = (string) value;
var res = Encoding.ASCII.GetBytes(orig);
return new List<byte>(res);
}
}
However, I find that changing the text in the textbos doesn't trigger ConvertBack, and ParamData doesn't actually get updated. I've tried triggering UpdateSource() on text change in the textbox, nbut itl still nothing.
Any ideas?
you need to set binding source to a property, which can be updated by binding. as of now, binding uses entire object as its source and cannot replace it with completely different object (new list from ConvertBack method)
public class ByteListWrapper
{
public List<byte> ParamData { get; set; }
}
var b = new TextBox();
b.DataContext = new ByteListWrapper { ParamData = someData };
var binding = new Binding("ParamData")
{
Converter = new ListToStringConverter(),
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
b.SetBinding(TextBox.TextProperty, binding);
so again: now binding updates value of a source property, instead of replacing source (which it cannot do)

IValueConverter Using Lookup Collection

I am using a converter like this:
public class BreedConverter : IValueConverter
{
static ObservableCollection<Breed_> Breeds = Breed_.GetBreeds();
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && Breeds.Count > 0)
{
short breedID = (short)value;
Breed_ breed = Breeds.Single(s => s.BreedID == breedID);
return (string)breed.Breed;
}
else
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The Breeds collection is retrieved from a SQL Server database. I want to retrieve it once and then use it to do the conversion. I don't want to go to the database each and every time I need to convert.
Is there a better way to do this, e.g. ResourceDictionary (which I don't know how to use in this scenario, since I'm still a noob)?
This is what I have been able to get to work. As #Darthchai mentioned, I have created the collection in the class/window. However, I cannot figure out how to use INotifyPropertyChanged to let the specific TextBlock know to do the conversion. I am a noob, after all. :) What I was able to do - and I don't know if this is the right approach - was to use the Loaded event of the TextBlock to do the conversion. here is the code:
private void BreedTextBlock_Loaded(object sender, RoutedEventArgs e)
{
TextBlock textBlock = (TextBlock)sender;
if (textBlock != null && Breeds.Count > 0)
{
try
{
short breedID = short.Parse(textBlock.Text);
Breed_ breed = Breeds.Single(s => s.BreedID == breedID);
textBlock.Text = (string)breed.Breed;
}
catch
{
}
}
return;
}

Datepicker ValidationRules from code behind: validation rule not called on user input

I'm creating a wpf UserControl that contains a Datepicker. This datepicker is generated from code behind in c#.
public partial class EditorDatePicker : UserControl
{
public EditorDatePicker(TagEntry element, bool isTagPresent)
{
InitializeComponent();
// datepicker binding and validation
Binding binding = new Binding();
binding.Path = new PropertyPath("DateDict[" + element.ParentTag + element.ChildTag + "]");
binding.NotifyOnValidationError = true;
binding.ValidatesOnDataErrors = true;
binding.Converter = new DateTimeConverter();
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
binding.ValidationRules.Add(new DateValidationRule());
this.datePicker.SetBinding(DatePicker.SelectedDateProperty, binding);
}
class DateTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
try
{
DateTime test = (DateTime)value;
string date = test.ToString("d/M/yyyy");
return (date);
}
catch
{
return null;
}
}
return null;
}
The fact is that the validation rule is never called when I manualy enter a date in the DatePicker text field (But it's called when using the datepicker). The only thing I got is a FormatException on lost focus.
Any idea? Thanx.
One possibility is to use converter:
public class DateTimeNullConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider) => this;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime)
return value.ToString();
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var text = value as string;
DateTime result;
if (text != null && DateTime.TryParse(text, out result))
return result;
return null;
}
}
You can use it like this to bind to public DateTime? DateTime property:
<TextBox Text="{Binding DateTime, Converter={local:DateTimeNullConverter}}" />
ConvertBack will be called on lost focus.

Databinding Attribute values to a combobox

I have a set of classes with a set of properties like this; Each having a custom attribute indicating the possible values it could take. Is there anyway to databind these values to a combobox instead of hardcoding using <ComboBoxItem/> ?
[Values("Cash","Bank","Not Applicable")]
public Nullable<int> PaymentMethod{ get; set; }
Edit: My attribute looks like this
class ValuesAttribute:Attribute
{
public List<string> values { get; set; }
public ValuesAttribute(params String[] values)
{
this.values= new List<string>();
foreach (var v in values)
{
this.values.Add(v);
}
}
}
I would use a converter for this. Send it the underlying object and the property name as the parameter. Return a key/value array so that you can bind both the value (index/enum value) and display text:
<ComboBox ItemsSource="{Binding ConverterParameter='PaymentMethod',Converter={StaticResource AttributeConverter}}"
DisplayMemberPath="Value" SelectedValuePath="Key"
/>
The converter can then get the values using reflection:
public class AttributeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && parameter as string != null)
{
var property = value.GetType().GetProperty((string)parameter);
if (property != null)
{
var attribute = property.GetCustomAttributes(typeof(ValuesAttribute), false).OfType<ValuesAttribute>().FirstOrDefault();
if (attribute != null)
return attribute.values.Select((display, index) =>
new KeyValuePair<int, string>(index, display)
).ToArray();
}
}
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note: if you need to do this a lot in your application, it might be worthwhile subclassing ComboBox, or creating a Behavior that applies the relevant properties.

Event on TreeView that signals when children changes

I'm trying to bind the itemssource of DataGrid to a TreeView. I have a converter that flattens the items shown in the TreeView to a list.
I'm trying to write a treegridview and the idea is to place a TreeView and a DataGrid next to each other wired up so that the DataGrid shows the rows currently visible in the TreeView. Like in this question.
It looks like the converter works ok but my problem is how to trigger the binding to update.
I tried the TreeViewItem.Expanded="UpdateDataGrid"and collapsed events but it fires before the containers are generated so it does not work the first time a node is expanded.
What is a good event for this?
Code for converter if anyone is interested:
class TreeViewChildrenConverter :IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var treeView = (TreeView) value;
var allItemContainers = GetAllItemContainers(treeView);
return allItemContainers.Select(x => x.DataContext);
}
private IEnumerable<TreeViewItem> GetAllItemContainers(ItemsControl itemsControl)
{
for (int i = 0; i < itemsControl.Items.Count; i++)
{
var containerFromIndex = itemsControl.ItemContainerGenerator.ContainerFromIndex(i);
var childItemContainer = containerFromIndex as TreeViewItem;
if (childItemContainer != null)
{
yield return childItemContainer;
if (childItemContainer.IsExpanded)
{
foreach (var treeViewItem in GetAllItemContainers(childItemContainer))
{
yield return treeViewItem;
}
}
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Categories

Resources