Automate the creation of converters or other generic solution - wpf

When i need to display an enumeration in a View, i write a converter for this type of enumeration
For Example:
Public Enum ReportTypes
Overview
Crosstable
AllCPDetails
End Enum
My converter is:
Public Class ReportTypeToStringConverter
Implements IValueConverter
Public Property Overview As String
Public Property Crosstable As String
Public Property AllCPDetails As String
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
Dim enumValue As ReportTypes = DirectCast(value, ReportTypes)
Select Case enumValue
Case ReportTypes.Overview
Return Overview
Case ReportTypes.Crosstable
Return Crosstable
Case ReportTypes.AllCPDetails
Return AllCPDetails
Case Else
Return DependencyProperty.UnsetValue
End Select
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
then, in my XAML i can write this:
<local:ReportTypeToStringConverter x:Key="ReportTypeToStringConverter"
Overview="Overview"
Crosstable="Cross Table"
AllCPDetails="All CP Details"/>
In this example there is an relationship of "Enum <--> String", but i can need write another relationships: "Enum <--> Color", "Enum <--> Visibility"....
The code is very clean and all work ok, but when i have many enums, or many relationships... How can i automatice the converter creation? Snippets? Item Template in Visual Studio?
Another generic solution?

The probably most generic converter could use a ResourceDictionary to map from the a value's ToString() result to an arbitrary object. The source value does not even need to be an enum.
public class GenericConverter : IValueConverter
{
private readonly ResourceDictionary resources = new ResourceDictionary();
public ResourceDictionary Resources
{
get { return resources; }
}
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
return resources[value.ToString()];
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You would e.g. use it like this to convert Visibility values:
<Window.Resources>
<local:GenericConverter x:Key="VisibilityToSomeStringConverter">
<local:GenericConverter.Resources>
<sys:String x:Key="Visible">VISIBLE</sys:String>
<sys:String x:Key="Hidden">HIDDEN</sys:String>
<sys:String x:Key="Collapsed">COLLAPSED</sys:String>
</local:GenericConverter.Resources>
</local:GenericConverter>
<local:GenericConverter x:Key="VisibilityToSomeIntConverter">
<local:GenericConverter.Resources>
<sys:Int32 x:Key="Visible">10</sys:Int32>
<sys:Int32 x:Key="Hidden">20</sys:Int32>
<sys:Int32 x:Key="Collapsed">30</sys:Int32>
</local:GenericConverter.Resources>
</local:GenericConverter>
</Window.Resources>

Related

Binding 2 textboxes with calculated ratio

I have a model that contains a HorsePower and a Kilowatt property
1 HP = 0.745 KW
I then have 2 textboxes where i want to update 'the other' with the calculated value.
eg: if i input '100' into the bound HP textbox, i want the KW textbox to update with '74,5'
and vise versa : 74,5 into KW -> updates HP to 100
one solution i figured, would be to bind to only 1 property, eg: HP and then use a value converter for the KW textbox (and then update KW on property changed)
is there another way to do this ?
You will need a binding converter like this:
public class MinusValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
return 100 - System.Convert.ToInt32(value);
}
catch (Exception)
{
return null;
}
}//END Convert
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}//END ConvertBack
}//END class ValueToCurrentConverter : IValueConverter
And XAML:
<TextBox Name="tbHorsepower" Text="8"/>
<TextBox Text="{Binding ElementName=tbHorsepower, Path=Text, Converter={StaticResource SubstractFrom100}}" IsReadOnly="True">
<TextBox.Resources>
<local1:MinusValueConverter x:Key="SubstractFrom100"/>
</TextBox.Resources>
</TextBox>
(Your textboxes will most likely look different)

Add extra column to WPF DataGrid which is not in the collection

I'm using a DataGrid control to display content of a DataTable. Therefore the data table is set as ItemsSource of DataGrid.
Following columns of the DataGrid represent columns of the data table:
Type, Name, Domain, Subdomain
Now the dynamic column, called "Properties" should contain specific information, depending on the value of the "Type" column.
Something like this:
switch (Type)
case ABC: content="row.Field1"
case DEF: content="row.Field2"
case XYZ: content="row.FieldX"
Where Field1 .. FieldX are all columns from the data table.
I'm currently using a DataGrid together with a BindingListCollectionView.
Best if the solution would build up on this.
I tried with multivalue binding and a multivalue converter but I would like to have more freedom and not having to pre-select the fields:
var bind = new MultiBinding();
bind.Bindings.Add(new Binding("Protocol"));
bind.Bindings.Add(new Binding("Path1"));
bind.Bindings.Add(new Binding("Path2"));
bind.Bindings.Add(new Binding("Path3"));
bind.Bindings.Add(new Binding("Path4"));
bind.Converter = _ConfigurationMultiValueConverter;
col.Binding = bind;
You could use something like that. Of course it's only an example.
public class YourClass
{
public YourType Protocol;
public YourType2 Path1;
public YourType3 Path2;
public YourType4 Path3;
public YourType5 Path4;
public int ChooseExpression;
public YourType6 Field1;
public YourType7 Field3;
public YourType8 FieldX;
}
and then in your DataGrid
<DataGrid Name="IfYouNeedAName" AutoGennerateColumn="False" ItemsSource={Binding YourClass} >
<DataGrid.Columns>
<DataGridTextColumn Header="ProtocolHeader" Binding={Binding Protocol} />
<DataGridTextColumn Header="Path1Header" Binding={Binding Path1} />
...
<DataGridTextColumn Header="TheChoosenOne" Binding={Binding YourClass, YourBindingConverer} />
</DataGrid.Columns>
</DataGrid>
and at last the converter, which inherit by IValueConverter
public class YourBindingConverer : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
switch(value.ChooseExpression)
case 1: return Field1.ToString();
case 2: return Field3.ToString();
case 3: return FieldX.ToString();
else
return string.Empty;
end;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I've wrote it from my memory, becasue I don't have VS here. But I think, it could be something like that.
If you have more questions, feel free to ask.
And for bonus, here is good solution for value converters.

Silverlight: Sorting a column that's bound with an IValueConverter?

I have a silverlight datagrid and am dynamically adding the columns to it programmatically.
One of the columns is bound to a complex property which is a List of business objects.
In order to display the correct property on the right object in the list I'm using a custom value converter, passing in the List and the object's name as an optional parameter.
I'm then spinning through the list, and finding the correct object in the list to bind to its value property.
This works perfectly for display purposes, but it seems that it disables or messes up prohibits the sorting of the column.
I know it has to do specifically with the converter because any other dynamic column that I'm adding, with a standard property binding, it sorts fine.
Here is the code of my IValueConverter:
public class MetaDataValueConverter : IValueConverter
{
public MetaDataValueConverter()
{
}
public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string result = string.Empty;
List<XFerMetadata> md = (List<XFerMetadata>)value;
foreach(XFerMetadata val in md)
{
if (val.Name.Match(parameter.ToString()))
{
result = val.Value;
break;
}
}
return result;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
And here is the code where I'm dynamically creating the column and setting the binding.
DataGridTextColumn col = new DataGridTextColumn();
col.Header = schema.FieldName;
Binding binding = new Binding("MetaData");
binding.Converter = new MetaDataValueConverter();
binding.ConverterParameter = schema.FieldName;
col.Binding = binding;
cols.Add(col);

WPF Binding with invalid value for source

I have a TextBox that binds to an integer property.
What can I do so that when there is nothing no valid text in the TextBox that the property gets set to 0.
Really I think this can be extended so that if the binding fails then we set the source to default(T).
I need a nudge in the right direction.
TargetNullValue is the opposite of what I'm looking for(I think), that sets the TextBox text when the source is null. I want when the TextBox text is an invalid binding value to set the source as its default.
Applying a Converter such as the following to your binding should do the trick:
public class TextConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
int actual = (int)value;
return actual.ToString();
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
string actual = (string)value;
int theValue = 0;
int.TryParse(actual, out theValue);
return theValue;
}
}
Your TextBox binding would look something like this:
<TextBox Text="{Binding ... Converter={StaticResource convert}}"></TextBox>
With the convertor defined as a resource of your Window/Control/...

Problem with binding Nullable value to WPF ComboBox

I am binding a WPF ComboBox to a nullable property of type MyEnum? (where MyEnum is an enumerated type)
I am programmatically populating the ComboBox items like this:
// The enum type being bound to
enum MyEnum { Yes, No }
// Helper class for representing combobox listitems
// (a combination of display string and value)
class ComboItem {
public string Display {get;set}
public MyEnum? Value {get;set}
}
private void LoadComboBoxItems()
{
// Make a list of items to load into the combo
var items = new List<ComboItem> {
new ComboItem {Value = null, Display = "Maybe"},
new ComboItem {Value = MyEnum.Yes, Display = "Yes"},
new ComboItem {Value = MyEnum.No, Display = "No"},};
// Bind the combo's items to this list.
theCombo.ItemsSource = items;
theCombo.DisplayMemberPath = "Display";
theCombo.SelectedValuePath = "Value";
}
Also in the code-behind, I am setting the DataContext to an instance of a class with a property called TheNullableProperty (for this example anyway) of type MyEnum?.
The binding of theCombo's SelectedValue is done in my XAML file.
<ComboBox
Name="theCombo"
SelectedValue="{Binding Path=TheNullableProperty,
UpdateSourceTrigger=PropertyChanged}"/>
Problem:
When the value of the bound property is initially non-null, the combo box displays the value properly.
But when the value of the bound property is initially null, the combo box is blank.
It looks like every aspect of data binding is working apart from the representation of the null value when the combobox is first shown.
For example: you can select Maybe from the dropdown, and the bound property is correctly set to null. It's just that initial loading that's failing. Maybe I need to just manually set the SelectedValue initially...
What I Ended Up Doing
Add a hidden textblock databound to the underlying nullable enum value via a Converter that converts from the nullable enum to a string (enum.ToString, or "null").
Load up the combo box with 'ComboItems' each having a string Label (displayed in the combo) and a string Value equal to the enum values as strings (and "null" for the null value).
Data-bind the combo box to the textblock.
/// <summary>
/// Convert from EnumeratedType? to string (null->"null", enum values->ToString)
/// </summary>
public class EnumConverter<T> : IValueConverter where T:struct
{
public static string To(T? c)
{
if (c == null)
return "null";
return c.ToString();
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return To((T?)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = (string) value;
if (s == "null")
return null;
return (T?)Enum.Parse(typeof(T), s);
}
}
public class MyEnumConverter : EnumConverter<MyEnum>
{
}
public class ComboItem
{
public string Value { get; set; }
public string Label { get; set; }
public ComboItem(MyEnum? e, string label)
{
Value = MyEnumConverter.To(e);
Label = label;
}
}
static IEnumerable<ComboItem> GetItems()
{
yield return new ComboItem(null, "maybe");
yield return new ComboItem(MyEnum.Yes, "yup");
yield return new ComboItem(MyEnum.No, "nope");
}
private void SetupComboBox()
{
thecombo.ItemsSource = GetItems().ToList();
}
You cannot bind to a null value by design in WPF (at least 3.5 SP1). This means that at the moment Source gets a Null as a value your Binding will be automatically broken and won't work even if you specify a valid value for the Source. You need to provide some "heart beating" mechanism for your binding via Value Converter. So that Converter transforms null value to some "Undefined" (non-null) for the Targets and than is able to convert your "Undefined" back to null...
There seem to be many issues with having null be a valid value for a ComboBox or ListBox, as similar questions have been asked here, here and here.
There hasn't been a superb, ogre-slaying answer to any of those.
My own suggestion is to add Maybe as a member of MyEnum. I know it's just an example but if everything with a MyEnum has it as nullable, then maybe MyEnum should have an Undefined member (or something similar), this is pretty common with enums anyway.
I found this solution online, which I think is pretty deck. I don't fully understand the implementation -- it's some pretty serious .NET kung fu -- but it's great from my perspective as a user. Give it a try:
http://philondotnet.wordpress.com/2009/09/18/how-to-select-null-none-in-a-combobox-listbox-listview/
DependencyProperty.UnsetValue Field
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace MyConverters
{
[ValueConversion(typeof(DateTime), typeof(String))]
public class StringToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int number;
return int.TryParse(value.ToString(), out number) ? number : DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString();
}
}
}
Here's a veryy simple solution to this problem:
Instead of having an item with a value of null in your ItemsSource, use DbNull.Value as item or as the item's value property.
I described this approach in detail here: https://stackoverflow.com/a/44170898/6713814

Resources