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);
Related
I'm using the Xceed PropertyGrid with data binding and AutoGenerateProperties = true. I have nullable properties like described below that result in a strange UI behavior.
The grid lets me click on the values Yes and No but the null choice is slightly obsecured by the property grid and won't allow me to click on it to select it. If I select Yes and use the UP Arrow key I'm able to select it. The Microsoft property grid full shows the empty choice and allows me to click it.
Am I doing something wrong or is this a bug? I asked in GitHub Issues but have had no responses to my issue.
YesNo? _compressed;
[CategoryAttribute("Package")]
[Description("Set to 'yes' to have compressed files in the source. This attribute cannot be set for merge modules. ")]
public YesNo? Compressed { get { return _compressed; } set { _compressed = value; RaisePropertyChangedEvent("Compressed"); } }
This is not a bug really. If you want to display the value of default(YesNo?) as something else than an empty string or null, you need to define how you want it to be displayed somehow. You could do this by creating your own custom editor:
public class CustomEditor : Xceed.Wpf.Toolkit.PropertyGrid.Editors.ComboBoxEditor
{
protected override IValueConverter CreateValueConverter()
{
return new CustomValueConverter<T>();
}
protected override ComboBox CreateEditor()
{
ComboBox comboBox = base.CreateEditor();
FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetBinding(TextBlock.TextProperty, new Binding(".") { Converter = new CustomValueConverter<T>() });
comboBox.ItemTemplate = new DataTemplate() { VisualTree = textBlock };
return comboBox;
}
protected override IEnumerable CreateItemsSource(Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem propertyItem)
{
return new string[1] { CustomValueConverter<T>.Null }
.Concat(Enum.GetValues(typeof(T)).OfType<T>().Select(x => x.ToString()));
}
}
public class CustomValueConverter: IValueConverter
{
internal const string Null = "";
public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return Null;
return value.ToString();
}
public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture)
{
string s = value?.ToString();
if (s == Null)
return null;
return Enum.Parse(typeof(T), s);
}
}
Usage:
YesNo? _compressed;
[CategoryAttribute("Package")]
[Description("Set to 'yes' to have compressed files in the source. This attribute cannot be set for merge modules. ")]
[Editor(typeof(CustomEditor<YesNo>), typeof(CustomEditor<YesNo>))]
public YesNo? Compressed { get { return _compressed; } set { _compressed = value; RaisePropertyChangedEvent("Compressed"); } }
I'm using a Type Converter to do some string manipulations on text being bound to TextBlocks in a UWP app. I used a type converter class that I have used in a number of UWP apps with no issues. But in a new UWP app I keep seeing the issue where the Binding value gets passed back and no changes have been made in the type converter to my passed values. I had been using a type converter that takes parameters to take custom actions and that isn't working as well as a simple new type converter I created. The odd thing is that the type converters work at run-time but as ignored at design-time.
Here are the parts of my code and XAML:
This is a data class that creates sample data for this repro to use for my bindings:
public class Alerts_DataClass
{
public string id { get; set; }
public string severity { get; set; }
public string status { get; set; }
public string title { get; set; }
public DateTime lastEventTime { get; set; }
}
public class AlertsSampleData : Alerts_DataClass
{
public AlertsSampleData()
{
Alerts_DataClass myAlert = new Alerts_DataClass { id = "636531490526731751_2124891065", title = "Category Type XVI", severity = "Medium", lastEventTime = new DateTime(2018, 2, 02) };
this.id = myAlert.id;
this.status = myAlert.status;
this.title = myAlert.title;
this.lastEventTime = myAlert.lastEventTime;
this.severity = myAlert.severity;
}
}
Here is the XAML:
<Page
x:Class="NestedBindingsTester.TypeConverterTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:NestedBindingsTester"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="using:System"
mc:Ignorable="d">
<Page.Resources>
<local:StringFormatConverterWithChangeType x:Key="StringFormatConverter"/>
<local:DebugDataBindingConverter x:Key="DebugBinding"/>
<local:ValueToStringConverter x:Key="valueToStringConverter" />
<local:AlertsSampleData x:Key="data2"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="103*"/>
<RowDefinition Height="897*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" d:DataContext="{StaticResource data2}">
<TextBlock Text="{Binding title, ConverterParameter=lower, Converter={StaticResource StringFormatConverter}, Mode=TwoWay}" Style="{StaticResource BaseTextBlockStyle}" Margin="50,0,0,0" />
<TextBlock Text="{Binding title, Converter={StaticResource valueToStringConverter}, Mode=TwoWay}" Style="{StaticResource BaseTextBlockStyle}" Margin="50,0,0,0" />
</StackPanel>
</Grid>
</Page>
And finally here are the two Type Converter modules. The first is the simple one, valueToStringConverter which I just have take any String text passed and return it as lower case:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Data;
namespace NestedBindingsTester
{
public sealed class ValueToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
//return value?.ToString();
string strThisTest = value.ToString().ToLower();
return strThisTest;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException("Converting from string is not supported.");
}
}
}
And the original type converter I was using that takes parameters as switches for various actions called StringFormatConverter. In my XAML example I am passing the parameter of 'lower' to tell it to convert the string value to lowercase:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.UI.Xaml.Data;
namespace NestedBindingsTester
{
public class DebugDataBindingConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
Debugger.Break();
return "This is";
////return value;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
Debugger.Break();
return "it";
////return value;
}
public object Convert(object value, Type targetType, object parameter, string language)
{
////throw new NotImplementedException();
return "Hello moto";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Sample usage:
/// Text="{Binding Budget, Mode=TwoWay, Converter={StaticResource StringFormatConverterWithChangeType}, ConverterParameter='\{0:C2\}'}"
/// </summary>
public class StringFormatConverterWithChangeType : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, string language)
{
// Used in UWP app
//
if (value != null)
{
// Arbitrarily remove dollar sign.
//value = value.ToString().Replace("$", "");
}
else
{
value = "**NULL**";
}
if (parameter != null)
{
string formatString = parameter.ToString();
if (!string.IsNullOrEmpty(formatString))
{
string strValue = value.ToString();
if (strValue != null && !string.IsNullOrWhiteSpace(strValue))
{
switch (formatString)
{
case "%": // Add %
return String.Format("{0}%", value);
case "mi": // Add miles
return String.Format("{0} miles", value);
case "caps": // Convert to Initial caps
return UpperCaseFirstCharacter(value.ToString());
case "lower": // Convert to Initial caps
return value.ToString().ToLower();
case "LiGa": // Convert from Liters to Gallons
{
//
// Convert kilometers to miles.
// 1 liter = 0.264172052 US gallons
//
double lConversion = 0.264172052;
double liters = System.Convert.ToDouble(strValue);
double gallons = lConversion * liters;
return String.Format("{0:N1} gallons", gallons);
}
case "TimeMath": // Compute time remaining until fully charged
{
int Minutes = System.Convert.ToInt16(value);
DateTime currentTime = DateTime.Now;
TimeSpan twoAndAHalfHours = new TimeSpan(0, Minutes, 0);
DateTime estimatedFullChargedTime = DateTime.Now.AddMinutes(Minutes);
string formattedTime = estimatedFullChargedTime.ToString("HH:mm");
return formattedTime;
}
case "UTCtoShortDate": // 02/03.2018
{
string utcDate = value.ToString(); // "2018-01-26T02:17:23.5099802Z";
DateTime dateTime;
DateTime.TryParse(utcDate, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out dateTime);
Debug.WriteLine(dateTime.ToString());
string strWDATP_Date = String.Format("{0:MM.dd.yyyy}", dateTime);
return strWDATP_Date;
}
case "{0:d}": // Short date.
case "{0:D}": // Long date.
////return String.Format(culture, formatString, System.Convert.ChangeType(value, typeof(DateTime), null));
return String.Format("US", formatString, System.Convert.ChangeType(value, typeof(DateTime), null));
}
}
else
{
return String.Format("US", formatString, value);
}
}
}
return value.ToString();
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
// Arbitrarily remove dollar sign.
//value = value.ToString().Replace("$", "");
}
if (parameter != null)
{
string formatString = parameter.ToString();
if (!string.IsNullOrEmpty(formatString))
{
string strValue = value.ToString();
if (strValue != null && !string.IsNullOrWhiteSpace(strValue))
{
switch (formatString)
{
case "%": // Add %
return String.Format("{0}%", value);
case "mi": // Add miles
return String.Format("{0} miles", value);
case "caps": // Convert to Initial caps
return UpperCaseFirstCharacter(value.ToString());
case "LiGa": // Convert from Liters to Gallons
{
//
// Convert kilometers to miles.
// 1 liter = 0.264172052 US gallons
//
double lConversion = 0.264172052;
double liters = System.Convert.ToDouble(strValue);
double gallons = lConversion * liters;
return String.Format("{0:N1} gallons", gallons);
}
case "TimeMath": // Compute time remaining until fully charged
{
int Minutes = System.Convert.ToInt16(value);
DateTime currentTime = DateTime.Now;
TimeSpan twoAndAHalfHours = new TimeSpan(0, Minutes, 0);
DateTime estimatedFullChargedTime = DateTime.Now.AddMinutes(Minutes);
string formattedTime = estimatedFullChargedTime.ToString("HH:mm");
return formattedTime;
}
case "{0:d}": // Short date.
case "{0:D}": // Long date.
return String.Format(culture, formatString, System.Convert.ChangeType(value, typeof(DateTime), null));
}
}
else
{
return String.Format(culture, formatString, value);
}
}
}
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public string UpperCaseFirstCharacter(string text)
{
//return Regex.Replace(text, "^[a-z]", m => m.Value.ToUpper());
return text.First().ToString().ToUpper() + text.Remove(0, 1).ToLower();
}
#endregion
};
}
I am using Visual Studio 2017 and targeting the Windows 10 Fall Creators Update SDK (16299).
I just can't spot why it's no longer working as these type converters have been working fine in two other UWP projects.
NOTE: I am using Binding in my binding for the TextBlocks since x:Bind does not work with design-time data.
Thanks for taking a look, I have been staring at this code for a while and can't find the problem.
Rick
I have a class with a List<string> Tags that I want to display in a TextBox. For this I use a IValueConverter.
ListToStringConverter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var list = value as List<string>;
var returnvalue = string.Empty;
if(list != null)
list.ForEach(item => returnvalue += item + ", ");
return returnvalue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var strValue = value as string;
if(strValue != null)
return strValue.Split(new char[] { ',' });
return null;
}
Class Test:
public class Test : IDataErrorInfo
{
public Test()
{
Tags = new List<string>();
}
public List<string> Tags { get; set; }
string errors;
const string errorsText = "Error in Test class.";
public string Error
{
get { return errors; }
}
public string this[string propertyName]
{
get
{
errors = null;
switch (propertyName)
{
case "Tags":
if (Tags.Count <= 1)
{
errors = errorsText;
return "...more tags plz..";
}
break;
}
return null;
}
}
}
Now I want to Validate the Tags:
<TextBox Text="{Binding Test.Tags, Converter={StaticResource ListToStringConverter}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
But there is no error displayed. Maybe because of the conversion but how can i still accomplish a validation?
I don't see why it shouldn't work. If you will run this at first time - SL will highlight the TextBox with red border. But if you will try to change the list of tags - you will see nothing, because you in your converter you have a bug in ConvertBack you return type array of strings (string[]), but property expects object with type List, so you just need to update the ConvertBack method to:
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var strValue = value as string;
if (strValue != null)
return strValue.Split(new char[] { ',' }).ToList();
return null;
}
One more note about Convert method, you can use Join method:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var list = value as List<string>;
if (list != null)
return String.Join(", ", list);
return null;
}
And one more thing about combining the strings. Don't use operator +, because you can have performance issues with it, use StringBuilder instead.
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
}
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