WPF data-binding with multiple dependencies? - c#

I believe this code may require more effort to comprehend than average, so I'm using contextual code-comments to explain my predicament in an effort to make it a little easier to understand my question.
public class MyControl : System.Windows.FrameworkElement
{
public double Property1 { get; set; }
public double Property2 { get; set; }
}
...
<StackPanel>
<local:MyControl Name="ControlA" />
<local:MyControl Name="ControlB" />
<!--We want to use data-binding to force ControlA.MinWidth to be equal to
ControlA.Property1 * ControlB.Property2 at all times-->
</StackPanel>
...
static readonly MyPrimaryConverter PrimConv = new MyPrimaryConverter();
static readonly MySecondaryConverter SecConv = new MySecondaryConverter();
public Window1()
{
InitializeComponent();
System.Windows.Data.BindingExpressionBase BindExp1, BindExp2;
System.Windows.Data.Binding MyBinding;
MyBinding = new System.Windows.Data.Binding("Property1");
MyBinding.Source = ControlA;
MyBinding.Mode = System.Windows.Data.BindingMode.OneWay;
MyBinding.Converter = PrimConv;
MyBinding.ConverterParameter = ControlB;
BindExp1 = ControlA.SetBinding(
System.Windows.UIElement.MinWidthProperty, MyBinding);
//Binds correctly and shows "Status" as active
MyBinding = new System.Windows.Data.Binding("Property2");
MyBinding.Source = ControlB; // ControlB.Property2 is the source value now
MyBinding.Mode = System.Windows.Data.BindingMode.OneWay;
MyBinding.Converter = SecConv; //Uses the secondary converter
MyBinding.ConverterParameter = ControlA; //ControlA is the parameter now
BindExp2 = ControlA.SetBinding(
System.Windows.UIElement.MinWidthProperty, MyBinding);
/* Also binds correctly and shows "Status" as active,
but causes BindExp1 to show "Status" as "Detached"
****THIS IS THE PROBLEM I NEED HELP WITH****
How can I successfully bind ControlA.MinWidth to BOTH of the values
that are used to calculate MinWidth?
This is required so that MinWidth will be recalculated if EITHER
value changes at any time
Bonus points if you can show how to do it with
only one converter class instead of two */
}
...
[System.Windows.Data.ValueConversion(typeof(double), typeof(double))]
public class MyPrimaryConverter : System.Windows.Data.IValueConverter
{
public object Convert( object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if ((value is double) && (parameter is MyControl))
return ((double)value) * (parameter as MyControl).Property2;
/* This converter ALWAYS returns:
ControlA.Property1 * ControlB.Property2
value == ControlA.Property1 and parameter == ControlB,
so we return value * parameter.Property2 */
return System.Windows.DependencyProperty.UnsetValue;
}
public object ConvertBack( object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{ return System.Windows.DependencyProperty.UnsetValue; }
}
[System.Windows.Data.ValueConversion(typeof(double), typeof(double))]
public class MySecondaryConverter : System.Windows.Data.IValueConverter
{
public object Convert( object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if ((value is double) && (parameter is MyControl))
return ((double)value) * (parameter as MyControl).Property1;
/* This converter also ALWAYS returns:
ControlA.Property1 * ControlB.Property2
"value" and "parameter" are swapped:
value == ControlB.Property2 and parameter == ControlA
so we return value * parameter.Property1
instead of value * parameter.Property2 */
return System.Windows.DependencyProperty.UnsetValue;
}
public object ConvertBack( object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{ return System.Windows.DependencyProperty.UnsetValue; }
}

I think you should use a MultiValueConverter

Related

Work with GridUnitType.Star in Grid column converter

I use a converter to fill the remaining space of a hidden column in WPF (set width to 0.0), but in fact I need the ratio 1.7* vs * if visible!
How can I calculate the real value to set for the converter?
Converter:
public class ColumnWidthConverter : IValueConverter
{
public object Convert(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
var isVisible = (bool) value;
var width = parameter as string == "*"
? new GridLength(1, GridUnitType.Star).Value
: double.Parse(parameter as string);
return isVisible ? width : 0.0;
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
As you can see I tried with new GridLength(1, GridUnitType.Star).Valuebut it's not the correct way!
XAML (condensed):
<appf:ViewUserControl.Resources>
<local:ColumnWidthConverter x:Key="ColumnWidthConverter" />
</appf:ViewUserControl.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.7*"/>
<ColumnDefinition Width="{Binding Path=Visible, Converter={StaticResource ColumnWidthConverter}, ConverterParameter=*}"/>
</Grid.ColumnDefinitions>
Visible is just a boolean property in the corresponding ViewModel!
Sometimes it's easy: use string instead of double value
public class ColumnWidthConverter : IValueConverter
{
public object Convert(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
var isVisible = (bool) value;
var width = parameter as string;
return isVisible ? width : "0.0";
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}

c# Change property value before binding

I am not sure if this question has been asked before or not but i am currently in a situation where i bind my control's property to a DependencyProperty.However, the value returned by the Property is of type Double. When setting the binding, how do i subtract 20 or a given value from the property and then bind the control ? Do i need to implement IValueConverter for this ? I am still studying WPF so any help would be appreciated.
Dependency property
public static readonly DependencyProperty ProgressbarValueDependency = DependencyProperty.Register("PrValue", typeof(double), typeof(LinearProgressBar));
public double PrValue
{
get
{
return System.Convert.ToDouble(GetValue(ProgressbarValueDependency));
}
set
{
SetValue(ProgressbarValueDependency, value);
}
}
Binding to the property
{
MainGrid = GetTemplateChild("MainGrid");
Binding MainGridWidthBinding = new Binding("PrValue")
{
Source = this,
Mode = BindingMode.TwoWay
};
MainGrid.SetBinding(Grid.WidthProperty, MainGridWidthBinding);
}
To achieve your purpose, you need to write a class implement IValueConverter interface.
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, string language)
{
// Contain your source to target convertion logic.
}
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
// Contain your target to source convertion logic.
// Only needed if using TwoWay or OneWayToSource Binding Mode.
}
}
Then set its instance to Binding.Converter property before call SetBinding method.
Binding MainGridWidthBinding = new Binding("PrValue")
{
Source = this,
Mode = BindingMode.TwoWay,
Converter = new MyConverter(),
};
MainGrid.SetBinding(Grid.WidthProperty, MainGridWidthBinding);
The best way is to implement an IValueConverter, here's an example.
using System;
using System.Windows.Data;
namespace WpfApp1
{
public class DoubleModifierConverter : IValueConverter
{
public double Modifier { get; set; }
public Operator Operator { get; set; } = Operator.Addition;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is double doubleValue))
throw new InvalidCastException();
switch (Operator)
{
case Operator.Addition:
return doubleValue + Modifier;
case Operator.Substraction:
return doubleValue - Modifier;
case Operator.Multiplication:
return doubleValue * Modifier;
case Operator.Division:
return doubleValue / Modifier;
default:
throw new ArgumentOutOfRangeException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is double doubleValue))
throw new InvalidCastException();
switch (Operator)
{
case Operator.Addition:
return doubleValue - Modifier;
case Operator.Substraction:
return doubleValue + Modifier;
case Operator.Multiplication:
return doubleValue / Modifier;
case Operator.Division:
return doubleValue * Modifier;
default:
throw new ArgumentOutOfRangeException();
}
}
}
public enum Operator
{
Addition = 0,
Substraction = 1,
Multiplication = 2,
Division = 3,
}
}
And this is how you'd use it.
MainGrid = GetTemplateChild("MainGrid");
Binding MainGridWidthBinding = new Binding(nameof(PrValue)){
Source = this,
Mode = BindingMode.TwoWay,
Converter = new DoubleModifierConverter
{
Modifier = 20.0,
Operator = Operator.Substraction
}
};
MainGrid.SetBinding(Grid.WidthProperty, MainGridWidthBinding);

Value converter fails on 2nd run but succeeds with try catch C#

I have a combo-box im populating with a collection of objects in my ViewModel.
<ComboBox x:Name="ChangelistComboBox"
SelectedIndex="{Binding MyObjectSelectionIndex, Mode=TwoWay}"
ItemsSource="{Binding MyObjectList, Mode=OneWay}"
Margin="5"
Grid.Column="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MyObjectToComboBoxConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Im using a Converter to convert parameters from the object to a displaystring that shows up in the ComboBox
[ValueConversion(typeof(MyObject), typeof(string))]
class MyObjectToComboBoxConverter : ValueConverterBase
{
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
try {
MyObject theObject = (MyObject)value;
int id = theObject.Id;
return (((id != -1) ? id.ToString() : "default") + " : " + theObject.Description);
} catch(InvalidCastException e) {
return (String)value;
}
}
public override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return new MyObject(); //Not used
}
}
abstract class ValueConverterBase : IValueConverter
{
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return DependencyProperty.UnsetValue;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return DependencyProperty.UnsetValue;
}
}
In the Model the list is defined as:
private ObservableCollection<MyObject> _MyObjectList;
public ObservableCollection<MyObject> MyObjectList {
get
{
if (_MyObjectList != null) { return _MyObjectList; } else { return new ObservableCollection<MyObject>(); }
}
set
{
if (_MyObjectList != value) {
_MyObjectList = value;
NotifyPropertyChanged("MyObjectList");
}
}
}
In the ViewModel the MyObjectList is simply referenced from the model through the interface:
public ObservableCollection<MyObject> MyObjectList {
get
{
if (Model != null) {
return Model.MyObjectList;
} else {
return new ObservableCollection<MyObject>();
}
}
}
Without the TryCatch, this converter crashes when my MyObjectList is updated. It gives an error like Cannot cast type string to object on the MyObject theObject = (MyObject)value; line
With the TryCatch the converter works as intended. It even returns a correctly assembled string. The problem is I get InvalidCastExceptions in the error log, which isnt good. Also I have no idea why it works despite the exception.
The only hunch I have is that for some reason the object is being converted twice, once from object to string and then it tries to convert the string to string and fails there. I cant figure out why it would be doing that though.
MVVM IValueConverter Convert method getting empty string argument when expecting a float
Just found this which seems to be the same issue that I'm experiencing. I dont fully understand the explanation for it given there, but if modifying the Convert to accept an empty string is a viable solution i can live with that
Still feels like a hack but beggars cant be choosers i guess
You bind the ComboBox source to a list of MyObject, so the value should never be of type string. I guess there is a problem in your model state.
The way you are binding your view is correct, also the first version of the converter:
[ValueConversion(typeof(MyObject), typeof(string))]
class MyObjectToComboBoxConverter : IValueConverter
{
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
try {
var theObject = (MyObject)value;
var id = theObject.Id;
return (((id != -1) ? id.ToString() : "default") + " : " + theObject.Description);
} catch(InvalidCastException e) {
return (string)value;
}
}
public override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
Please start to change your ViewModel to:
public ObservableCollection<MyObject> MyObjectList { get; set; } = new ObservableCollection<MyObject>();
You need to implement the OnPropertyChangeEvent on the properties of the MyObject model (https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-create-and-bind-to-an-observablecollection)
so you only need to set the ObservableCollection in your ViewModel to an empty instance by default.
Then adding items:
MyObjectList.Add(new MyObject{ id=1, Description="First"});

Binding with IValueConverter does not work

I'm trying to binding the TextColor from a label on ViewCell:
Label myLabel = new Label { Text = "SomeText" };
myLabel.SetBinding(Label.TextColorProperty,
new Binding("TheTextColor", BindingMode.TwoWay, new LabelTextColorConverter()));
Here's the converter:
public class LabelTextColorConverter : IValueConverter
{
public bool OldValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
OldValue = (bool) value;
Debug.WriteLine("asdadasdsadsada");
if ((bool)value)
return Color.Red;
else
return Color.Silver;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Debug.WriteLine("qwqweqewqeeqe");
return OldValue;
}
}
The debug output doesn't appear, and the color doesn't change either. I don't see anything wrong.
Why do you need Two Way binding for this? I don't think it's necessary.
myLabel.SetBinding(Label.TextColorProperty, new Binding("TheTextColor", BindingMode.OneWay, new LabelTextColorConverter()));
Then:
public class LabelTextColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool val = (bool)value;
if (val)
return Color.Red;
else
return Color.Silver;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
... and it should work fine. Also make sure you're setting BindingContext for your page/controls correctly.

workflow 4 activity designer IValueConverter

Lets say i have an activity with InArgument<int> ProductId
I'd like to expose in the activity designer a combobox to show all Products and the user can select a product.
I can show the list of product in the combo no problem. But how do I bind the selected product to the InArgument<int> of my custom activity?
I suppose I need some kind of ValueConverter? Not sure how to code the value converter for this case, if anybody has an idea, suggestion, will be helpful. I have to convert the InArgument<int> to an int? and the convert back from int to InArgument<int>
Thanks,
public class ArgumentToInt32Converter: IValueConverter {
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
object convertedValue = null;
if (value != null) {
ModelItem argumentModelItem = value as ModelItem;
if (argumentModelItem != null && argumentModelItem.Properties["Expression"] != null && argumentModelItem.Properties["Expression"].Value != null) {
if (argumentModelItem.Properties["Expression"].ComputedValue.GetType() == typeof(Literal <Int32> )) {
convertedValue = (argumentModelItem.Properties["Expression"].ComputedValue as Literal <Int32> ).Value;
} else {
convertedValue = null
}
}
}
return convertedValue;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
// Convert Int32 value to InArgument<Int32>
Int32 itemContent = (Int32) value;
VisualBasicValue <Int32> vbArgument = new VisualBasicValue <Int32> (itemContent);
InArgument <Int32> inArgument = new InArgument <Int32> (vbArgument);
return inArgument;
}
}
Modified from this answer
This is my attempt at making a more generic solution to this. I have several properties - some IEnumerable, some string, some int, and to make a value converter for each seems like the wrong approach. I'd be interested to know what cases I haven't caught here because I am relatively new to WF. Hopefully this helps someone.
public class ArgumentConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
object convertedValue = null;
if(value != null)
{
var argumentModelItem = value as ModelItem;
if(argumentModelItem != null)
{
ModelProperty argumentModelProperty = argumentModelItem.Properties["Expression"];
if(argumentModelProperty != null && argumentModelProperty.Value != null)
{
var computedValue = argumentModelProperty.ComputedValue;
var activity = (Activity) computedValue;
convertedValue = WorkflowInvoker.Invoke(activity)["Result"];
}
}
}
return convertedValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// here targetType should be InArgument<T>
// assume a single generic argument
Type arg0 = targetType.GetGenericArguments()[0];
ConstructorInfo argConstructor = targetType.GetConstructor(new[] {arg0});
var argument = argConstructor.Invoke(new[] { value });
return argument;
}
#endregion
}

Categories