WPF DataBinding to Flag Enum (in a PropertyGrid) - wpf

I need to have the ability to select multiple values as is the nature of a Flag enumeration from a WPF view (all be it, in a PropertyGrid).
The properties in question are dynamic and no pre-defined DataTemplates can be used as the type of the properties will be discovered at runtime. (A DataTemplate which can detect if an enumeration is a Flag may prove helpful, but from my understanding I would need to know the Flag Enum types ahead of time to achieve this and that will not be the case).
I have tried out a number of proprietary and open source property grids for WPF and none seem to support 'Flags' attributed enum types out of the box.
A solution to this issue would be anything that would allow me to databind to + select multiple values for said Flags Enum for any commercial or open source WPF PropertyGrid.
Code:
Example PropertyType:
public class PropertyTypeOne
{
public PropertyTypeOne()
{
IntProp = 1;
InProp2 = 2;
BoolProp = true;
Boolprop2 = false;
StringProp = "string1";
DoubleProp = 2.3;
EnumProp = FlagEnumDataTYpe.MarketDepth;
}
public int IntProp { get; set; }
public int InProp2 { get; set; }
public bool BoolProp { get; set; }
public bool BoolProp2 { get; set; }
public string StringProp { get; set; }
public double DoubleProp { get; set; }
//This is the property in question
public FlagEnumDataType EnumProp { get; set; }
}
Example Flag Enumeration Type:
[Flags]
public enum FlagEnumDataType : byte
{
None = 0,
Trade = 1,
Quote = 2,
MarketDepth = 4,
All = 255
}
Note:
If the solution makes use of the Open Source WPF PropertyGrid (http://www.codeplex.com/wpg) I will implement the changes /additions back into the control.
Thanks.

I haven't found a truly elegant way of doing this, but from talking with the Developers at Mindscape and here's something crude but functional thats works with the Mindscape PropertyGrid.
First, we create a template for the flag-enum editor itself. This is an ItemsControl populated using the EnumValuesConverter from the WPF Property Grid library:
<ms:EnumValuesConverter x:Key="evc" />
<local:FlaggyConverter x:Key="fc" />
<DataTemplate x:Key="FlagEditorTemplate">
<ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}">
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
Now we need to display the check boxes as checked according to whether the flag is on or off. This requires two things: first, an IMultiValueConverter so it can consider both the flag at hand and the context value, and second, a way for individual check boxes to read the context value. (By context value I mean the actual property value. E.g. the context value might be Flag1 | Flag4 | Flag32.) Here's the converter:
public class FlaggyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int flagValue = (int)values[0];
int propertyValue = (int)values[1];
return (flagValue & propertyValue) == flagValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
For propagating the context value, I'm going to take a shortcut and use Tag. You might prefer to create an attached property with a more meaningful name.
Now the control will display checks for the flags that are set, but won't yet update the value when you click a checkbox on or off. Unfortunately, the only way I've found to make this work is by handling the Checked and Unchecked events and setting the context value by hand. In order to do this, we need place the context value in a place where it can be updated from the check box event handlers. This means two-way binding a property of the check box to the context value. Again I'll use Tag though you may want something a bit cleaner; also, I'm going to use direct event handling, though depending on your design you may want to wrap this up into an attached behaviour (this would work particularly well if you were creating attached properties to carry around the context value).
<DataTemplate x:Key="FlagEditorTemplate">
<ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}" Tag="{Binding Value, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" Tag="{Binding Tag, ElementName=ic, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource fc}" Mode="OneWay">
<Binding />
<Binding Path="Tag" ElementName="ic" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
Note the two-way binding of the Tag: this is so that when we set Tag from our event handling code, it propagates back to ic.Tag, and from there to the property's Value.
The event handlers are mostly obvious but with one wrinkle:
<DataTemplate x:Key="FlagEditorTemplate">
<ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}" Tag="{Binding Value, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" Tag="{Binding Tag, ElementName=ic, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource fc}" Mode="OneWay">
<Binding />
<Binding Path="Tag" ElementName="ic" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
Event Handlers:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
int val = (int)(cb.Tag);
int flag = (int)(cb.Content);
val = val | flag;
cb.Tag = (Curses)val;
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
int val = (int)(cb.Tag);
int flag = (int)(cb.Content);
val = val & ~flag;
cb.Tag = (Curses)val;
}
Note the cast when setting cb.Tag. Without this, WPF internally fails to convert the value to the enum type when trying to propagate it back to the source. Here Curses is my enum type. If you want a fully flexible, type-agnostic editor, you'll want to provide this externally, for example as an attached property on the check box. You could either infer this using a converter or propagate it from an editor EditContext.
Finally we need to hook this up to the grid. You can do this either on a property-by-property basis:
<ms:PropertyGrid>
<ms:PropertyGrid.Editors>
<ms:PropertyEditor PropertyName="Curses" EditorTemplate="{StaticResource FlagEditorTemplate}" />
</ms:PropertyGrid.Editors>
</ms:PropertyGrid>
or by using a smart editor declaration to hook up all properties whose types have a FlagsAttribute. For info about creating and using smart editors, see http://www.mindscape.co.nz/blog/index.php/2008/04/30/smart-editor-declarations-in-the-wpf-property-grid/.
If you want to save space you can change the ItemsControl to a ComboBox though you'll need to do some additional work to handle the collapsed display; I haven't explored this in detail.

Found on the Internet, slightly improved, but didn't have time to test it yet.
/// <summary>
/// Two-way conversion from flags to bool and back using parameter as mask
/// Warning: The trick is in storing value locally between calls to Convert and ConvertBack
/// You must have a single instance of this converter per flags property per object
/// Do not share this converter between different objects or properties
/// Typical usage:
/// [Flags] enum FlagType { None = 0, Trade = 1, Quote = 2, Report = 4, All = 255 }
/// <local:EditableFlagsToBooleanConverter x:Key="FlagsToBooleanConverter" />
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay,
/// ConverterParameter={x:Static local:FlagType.Trade}}" >Trade</CheckBox>
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay,
/// ConverterParameter={x:Static local:FlagType.Quote}}" >Quote</CheckBox>
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay,
/// ConverterParameter={x:Static local:FlagType.Report}}" >Report</CheckBox>
/// </summary>
public class EditableFlagsToBooleanConverter : IValueConverter
{
private ulong _target;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is Enum && value is Enum)
{
var mask = (ulong) parameter;
_target = (ulong) value;
return ((mask & _target) != 0);
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool && parameter is Enum)
{
var mask = (ulong)parameter;
if ((bool)value)
{
_target |= mask;
}
else
{
_target &= ~mask;
}
return _target;
}
return Binding.DoNothing;
}
}

Related

Binding float data type with UpdateSourceTrigger set toPropertyChanged in WPF

I have a problem with Float data type while using UpdateSourceTrigger in WPF.I have a property with float data type and it is binded it to a TextBox and set its UpdateSourceTrigger of the binding to PropertyChanged,but WPF dosen't let me type '.' in the TextBox unless i change UpdateSourceTrigger to LostFocus.I think it's because of we can not type '.' in the end of float value.I don't have any idea how can i fix it because i need to type '.' and set UpdateSourceTrigger to PropertyChanged.
I have designed my textbox in such a way that it can take only 7 characters
for example
1) 12.3456
2) 1234.56 e.t.c
The property is:`
public float? Expenditure
{
get;set;
}
And in the XAML:
<TextBox Text="{Binding Expenditure, UpdateSourceTrigger=PropertyChanged}"/>
StringFormat does not help as decimal can be put anywhere.
Any help would be great.
Just change StringFormat property of the binding to display two decimal places of the property:
<TextBox Text="{Binding Expenditure, UpdateSourceTrigger=PropertyChanged, StringFormat='{}{0:F2}'}"/>
Also you can write a custom FloatToStringConverter (here is an example). Your own float-to-string and string-to-float conversion methods will allow you to handle empty text field of TextBox and convert it to null.
I wrote a value converter, that solves your problem:
Usage:
<Window x:Class="BindingExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingExample"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:DoubleToStringConverter x:Key="DoubleToStringConverter" DigitsCount="5"/>
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding FloatProperty, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource DoubleToStringConverter}}" Margin="5"/>
</StackPanel>
</Window>
Converter:
[ValueConversion(typeof(double), typeof(string))]
public class DoubleToStringConverter : IValueConverter
{
#region IValueConverter Members
public DoubleToStringConverter()
{
// Default value for DigitsCount
DigitsCount = 7;
}
// Convert from double to string
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return null;
double doubleValue = System.Convert.ToDouble(value);
// Calculate digits count
int digitsBeforePoint = System.Convert.ToInt32(Math.Ceiling(Math.Log10(doubleValue)));
int digitsAfterPoint = DigitsCount - digitsBeforePoint;
// TODO: You have to handle cases where digitsAfterPoint < 0
// Create formatString that is used to present doubleValue in desired format
string formatString = String.Format("{{0:F{0}}}", digitsAfterPoint);
return String.Format(formatString, doubleValue);
}
// Convert from string to double
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return null;
double? result = null;
try
{
result = System.Convert.ToDouble(value);
}
catch
{
}
return result.HasValue ? (object)result.Value : DependencyProperty.UnsetValue;
}
public int DigitsCount { get; set; }
#endregion
}

Binding to double field with validation

I'm trying to bind TextBox to double property of some object with UpdateSourceTrigger=PropertyChanged. The goal is to immediately during editing validate entered value to be in allowed range (and display an error if not). I want to implement validation on Model level, i.e. via IDataErrorInfo.
All works great when I bind to int property, but if property is double then a frustrating editing behavior appears: after erasing last significant digit in fractional part of number - the decimal separator is automatically erased (with all possible fractional zeroes). For example, after erasing digit '3' from number '12.03' the text is changed to '12' instead of '12.0'.
Please, help.
Here is the sample code:
MainWindow.xaml:
<Window x:Class="BindWithValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="80" Width="200" WindowStartupLocation="CenterOwner">
<StackPanel>
<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
</Window>
MainWindow.xaml.cs:
namespace BindWithValidation
{
public partial class MainWindow : Window
{
private UISimpleData _uiData = new UISimpleData();
public MainWindow()
{
InitializeComponent();
DataContext = _uiData;
}
}
}
UISimpleData.cs:
namespace BindWithValidation
{
public class UISimpleData : INotifyPropertyChanged, IDataErrorInfo
{
private double _doubleField = 12.03;
public double DoubleField
{
get
{
return _doubleField;
}
set
{
if (_doubleField == value)
return;
_doubleField = value;
RaisePropertyChanged("DoubleField");
}
}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "DoubleField":
{
if (DoubleField < 2 || DoubleField > 5)
validationResult = "DoubleField is out of range";
break;
}
default:
throw new ApplicationException("Unknown Property being validated on UIData");
}
return validationResult;
}
}
public string Error { get { return "not implemented"; } }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string property)
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
The behavior of binding float values to a textbox has been changed
from .NET 4 to 4.5. With .NET 4.5 it is no longer possible to enter a
separator character (comma or dot) with ‘UpdateSourceTrigger =
PropertyChanged’ by default.
Microsoft says, this (is) intended
If you still want to use ‘UpdateSourceTrigger = PropertyChanged’, you
can force the .NET 4 behavior in your .NET 4.5 application by adding
the following line of code to the constructor of your App.xaml.cs:
public App()
{
System.Windows.FrameworkCompatibilityPreferences
.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
}
(Sebastian Lux - Copied verbatim from here)
I realize I'm a little late to the party but I found a (I think) rather clean solution to this problem.
A clever converter that remembers the last string converted to double and returns that if it exists should do everything you want.
Note that when the user changes the contents of the textbox, ConvertBack will store the string the user input, parse the string for a double, and pass that value to the view model. Immediately after, Convert is called to display the newly changed value. At this point, the stored string is not null and will be returned.
If the application instead of the user causes the double to change only Convert is called. This means that the cached string will be null and a standard ToString() will be called on the double.
In this way, the user avoids strange surprises when modifying the contents of the textbox but the application can still trigger a change.
public class DoubleToPersistantStringConverter : IValueConverter
{
private string lastConvertBackString;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is double)) return null;
var stringValue = lastConvertBackString ?? value.ToString();
lastConvertBackString = null;
return stringValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is string)) return null;
double result;
if (double.TryParse((string)value, out result))
{
lastConvertBackString = (string)value;
return result;
}
return null;
}
}
Tried formatting the value with decimal places?
It may be weird though since you will then always have N decimal places.
<TextBox.Text>
<Binding Path="DoubleField" StringFormat="{}{0:0.00}" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True"/>
</TextBox.Text>
If having fixed decimal places is not good enough, you may have to write a converter that treats the value as a string and converts it back to a double.
The problem is that you are updating your property every time the value changes. When you change 12.03 to 12.0 it is rounded to 12.
You can see changes by providing delay by changing the TextBox in xaml like this
<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged,Delay=500, ValidatesOnDataErrors=True}">
but delay will notify and set the property after the delay time in mili sec.
Better use StringFormat like this
<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged,StringFormat=N2, ValidatesOnDataErrors=True}">
I have run into the same problem, and have found a quite simple solution: use a custom validator, which does not return "valid" when the Text ends in "." or "0":
double val = 0;
string tmp = value.ToString();
if (tmp.EndsWith(",") || tmp.EndsWith("0") || tmp.EndsWith("."))
{
return new ValidationResult(false, "Enter another digit, or delete the last one.");
}
else
{
return ValidationResult.ValidResult;
}
Try using StringFormat on your binding:
<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, StringFormat='0.0'}">
Not sure if that string format is even right since I've not done one for a while but it's just an example

WPF Binding and Dynamically Assigning StringFormat Property

I have a form that is generated based on several DataTemplate elements. One of the DataTemplate elements creates a TextBox out of a class that looks like this:
public class MyTextBoxClass
{
public object Value { get;set;}
//other properties left out for brevity's sake
public string FormatString { get;set;}
}
I need a way to "bind" the value in the FormatString property to the "StringFormat" property of the binding. So far I have:
<DataTemplate DataType="{x:Type vm:MyTextBoxClass}">
<TextBox Text="{Binding Path=Value, StringFormat={Binding Path=FormatString}" />
</DataTemplate>
However, since StringFormat isn't a dependency property, I cannot bind to it.
My next thought was to create a value converter and pass the FormatString property's value in on the ConverterParameter, but I ran into the same problem -- ConverterParameter isn't a DependencyProperty.
So, now I turn to you, SO. How do I dynamically set the StringFormat of a binding; more specifically, on a TextBox?
I would prefer to let XAML do the work for me so I can avoid playing with code-behind. I'm using the MVVM pattern and would like to keep the boundaries between view-model and view as un-blurred as possible.
Thanks!
This is a solution from Andrew Olson that uses attached properties and thus can be used in various situations.
Used like this:
<TextBlock
local:StringFormatHelper.Format="{Binding FormatString}"
local:StringFormatHelper.Value="{Binding Value}"
Text="{Binding (local:StringFormatHelper.FormattedValue)}"
/>
The required helper: (source Gist)
public static class StringFormatHelper
{
#region Value
public static DependencyProperty ValueProperty = DependencyProperty.RegisterAttached(
"Value", typeof(object), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null, OnValueChanged));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
RefreshFormattedValue(obj);
}
public static object GetValue(DependencyObject obj)
{
return obj.GetValue(ValueProperty);
}
public static void SetValue(DependencyObject obj, object newValue)
{
obj.SetValue(ValueProperty, newValue);
}
#endregion
#region Format
public static DependencyProperty FormatProperty = DependencyProperty.RegisterAttached(
"Format", typeof(string), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null, OnFormatChanged));
private static void OnFormatChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
RefreshFormattedValue(obj);
}
public static string GetFormat(DependencyObject obj)
{
return (string)obj.GetValue(FormatProperty);
}
public static void SetFormat(DependencyObject obj, string newFormat)
{
obj.SetValue(FormatProperty, newFormat);
}
#endregion
#region FormattedValue
public static DependencyProperty FormattedValueProperty = DependencyProperty.RegisterAttached(
"FormattedValue", typeof(string), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null));
public static string GetFormattedValue(DependencyObject obj)
{
return (string)obj.GetValue(FormattedValueProperty);
}
public static void SetFormattedValue(DependencyObject obj, string newFormattedValue)
{
obj.SetValue(FormattedValueProperty, newFormattedValue);
}
#endregion
private static void RefreshFormattedValue(DependencyObject obj)
{
var value = GetValue(obj);
var format = GetFormat(obj);
if (format != null)
{
if (!format.StartsWith("{0:"))
{
format = String.Format("{{0:{0}}}", format);
}
SetFormattedValue(obj, String.Format(format, value));
}
else
{
SetFormattedValue(obj, value == null ? String.Empty : value.ToString());
}
}
}
This code (inspired from DefaultValueConverter.cs # referencesource.microsoft.com) works for a two way binding to a TextBox or similar control, as long as the FormatString leaves the ToString() version of the source property in a state that can be converted back.
(i.e. format like "#,0.00" is OK because "1,234.56" can be parsed back, but FormatString="Some Prefix Text #,0.00" will convert to "Some Prefix Text 1,234.56" which can't be parsed back.)
XAML:
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource ToStringFormatConverter}"
ValidatesOnDataErrors="True" NotifyOnValidationError="True" TargetNullValue="">
<Binding Path="Property" TargetNullValue="" />
<Binding Path="PropertyStringFormat" Mode="OneWay" />
</MultiBinding>
</TextBox.Text>
</TextBox>
Note duplicate TargetNullValue if the source property can be null.
C#:
/// <summary>
/// Allow a binding where the StringFormat is also bound to a property (and can vary).
/// </summary>
public class ToStringFormatConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 1)
return System.Convert.ChangeType(values[0], targetType, culture);
if (values.Length >= 2 && values[0] is IFormattable)
return (values[0] as IFormattable).ToString((string)values[1], culture);
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
var targetType = targetTypes[0];
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
if (nullableUnderlyingType != null) {
if (value == null)
return new[] { (object)null };
targetType = nullableUnderlyingType;
}
try {
object parsedValue = ToStringFormatConverter.TryParse(value, targetType, culture);
return parsedValue != DependencyProperty.UnsetValue
? new[] { parsedValue }
: new[] { System.Convert.ChangeType(value, targetType, culture) };
} catch {
return null;
}
}
// Some types have Parse methods that are more successful than their type converters at converting strings
private static object TryParse(object value, Type targetType, CultureInfo culture)
{
object result = DependencyProperty.UnsetValue;
string stringValue = value as string;
if (stringValue != null) {
try {
MethodInfo mi;
if (culture != null
&& (mi = targetType.GetMethod("Parse",
BindingFlags.Public | BindingFlags.Static, null,
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider) }, null))
!= null) {
result = mi.Invoke(null, new object[] { stringValue, NumberStyles.Any, culture });
}
else if (culture != null
&& (mi = targetType.GetMethod("Parse",
BindingFlags.Public | BindingFlags.Static, null,
new[] { typeof(string), typeof(IFormatProvider) }, null))
!= null) {
result = mi.Invoke(null, new object[] { stringValue, culture });
}
else if ((mi = targetType.GetMethod("Parse",
BindingFlags.Public | BindingFlags.Static, null,
new[] { typeof(string) }, null))
!= null) {
result = mi.Invoke(null, new object[] { stringValue });
}
} catch (TargetInvocationException) {
}
}
return result;
}
}
One way may be to create a class that inherits TextBox and in that class create your own dependency property that delegates to StringFormat when set. So instead of using TextBox in your XAML you will use the inherited textbox and set your own dependency property in the binding.
Just bind the textbox to the instance of a MyTextBoxClass instead of MyTextBoxClass.Value and use a valueconverter to create a string from the value and formatstring.
Another solution is to use a multivalue converter which would bind to both Value and FormatString.
The first solution don't support changes to properties, that is if value or formatstring changes the value converter will not be called like it would be if you are using a multivalueconverter and binding directly to the properties.
One could create an attached behavior that could replace the binding with one that has the FormatString specified. If the FormatString dependency property then the binding would once again be updated. If the binding is updated then the FormatString would be reapplied to that binding.
The only two tricky things that I can think that you would have to deal with. One issue is whether you want to create two attached properties that coordinate with each other for the FormatString and the TargetProperty on which the binding exist that the FormatString should be applied (ex. TextBox.Text) or perhaps you can just assume which property your dealing with depending on the target control type. The other issue may be that it may be non-trivial to copy an existing binding and modifying it slightly given the various types of bindings out there which might also include custom bindings.
It's important to consider though that all of this only achieves formatting in the direction from your data to your control. As far as I can discover using something like a MultiBinding along with a custom MultiValueConverter to consume both the original value and the FormatString and produce the desired output still suffers from the same problem mainly because the ConvertBack method is only given the output string and you would be expected to decipher both the FormatString and the original value from it which at that point is almost always impossible.
The remaining solutions that should work for bidirectional formatting and unformatting would be the following:
Write a custom control that extends TextBox that has the desired formatting behavior like Jakob Christensen suggested.
Write a custom value converter that derives from either DependencyObject or FrameworkElement and has a FormatString DependencyProperty on it. If you want to go the DependencyObject route I believe you can push the value into the FormatString property using the OneWayToSource binding with a "virtual branch" technique. The other easier way may to instead inherit from FrameworkElement and place your value converter into the visual tree along with your other controls so that you can just bind to it when needed by ElementName.
Use an attached behavior similar to the one I mentioned at the top of this post but instead of setting a FormatString instead have two attached properties, one for a custom value converter and one for the parameter that would be passed to the value converter. Then instead of modifying the original binding to add the FormatString you would be adding the converter and the converter parameter to the binding. Personally I think this option would result in the most readable and intuitive result because attached behaviors tend to be more clean yet still flexible enough to use in a variety of situations other than just a TextBox.

WPF Textblock Convert Issue

am usina text block in usercontrol, but am sending value to textblock from other form, when i pass some value it viewed in textblock, but i need to convert the number to text. so i used converter in textblock. but its not working
<TextBlock Height="21" Name="txtStatus" Width="65" Background="Bisque" TextAlignment="Center" Text="{Binding Path=hM1,Converter={StaticResource TextConvert},Mode=OneWay}"/>
converter class
class TextConvert : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
if (value.ToString() == "1")
{
return value = "Good";
}
if (value.ToString() == "0")
{
return value = "NIL";
}
}
return value = "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (string)value;
}
}
is it right? whats wrong in it??
ok I think I know what the problem is - let see if I can define it for you :)
in your xaml file where you want to use TextConvert, define Resource for it (unless you are doing it already, then I haven't a clue why its not working)
<Grid.Resources>
<Shared:TextConvert x:Key="TextConvertKey" />
</Grid.Resources>
shared being the xmlns ofcourse.
Then in the textbox use it like:
Text="{Binding Path=hM1,Converter={StaticResource TextConvertKey},Mode=OneWay}"/>
EDIT:
If you set a breakpoint in the converter class, does the debugger go in there?????
EDIT 2:
am using like this voodoo
local:HealthTextConvert x:Key="TextConvert"
This is absolutely wrong. How can you Call it HealthTextConvert when the converter name is TextConvert???
it should be
local:TextConvert x:Key="whateverKeyNameYouWant"
and
in the textbox is should be
Text="{Binding Path=hM1,Converter={StaticResource whateverKeyNameYouWant},Mode=OneWay}"
I can see immediately a problem with your converter definition.
class TextConvert : IValueConverter
{
...
Should be declared public to be able to use it as a resource.
public class TextConvert : IValueConverter
{
...
Also, its not a good thing to be doing this...
return value = "Good";
...
return value = "NIL";
It should just be (even though it will not matter if you leave it, just bad programming =P):
return "Good";
...
return "Nill";
Try by removing Path in the below line
Text="{Binding **Path**=hM1,Converter={StaticResource TextConvert},Mode=OneWay}".
Sometimes it works without Path :).
Also look into the output window(Alt+Cntl+O)...to see where the issue is.

How to keep relative position of WPF elements on background image

I am new to WPF, so the answer to the following question might be obvious, however it isn't to me.
I need to display an image where users can set markers on (As an example: You might want to mark a person's face on a photograph with a rectangle), however the markers need to keep their relative position when scaling the image.
Currently I am doing this by using a Canvas and setting an ImageBrush as Background. This displays the image and I can add elements like a Label (as replacement for a rectangle) on top of the image. But when I set a label like this, it's position is absolute and so when the underlying picture is scaled (because the user drags the window larger) the Label stays at it's absolute position (say, 100,100) instead of moving to the new position that keeps it "in sync" with the underlying image.
To cut the matter short: When I set a marker on a person's eye, it shouldn't be on the person's ear after scaling the window.
Any suggestions on how to do that in WPF? Maybe Canvas is the wrong approach in the first place? I could keep a collection of markers in code and recalculate their position every time the window gets resized, but I hope there is a way to let WPF do that work for me :-)
I am interested in hearing your opinions on this.
Thanks
Okay that seems to work. Here's what I did:
Wrote a custom converter
Every time a user clicks on the canvas, I create a new Label (will exchange that with a UserComponent later), create bindings using my converter class and do the initial calculations to get the relative position to the canvas from the absolute position of the mouse pointer
Here's some sample code for the converter:
public class PercentageConverter : IValueConverter
{
/// <summary>
/// Calculates absolute position values of an element given the dimensions of the container and the relative
/// position of the element, expressed as percentage
/// </summary>
/// <param name="value">Dimension value of the container (width or height)</param>
/// <param name="parameter">The percentage used to calculate new absolute value</param>
/// <returns>parameter * value as Double</returns>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//input is percentage
//output is double
double containerValue = System.Convert.ToDouble(value, culture.NumberFormat);
double perc;
if (parameter is String)
{
perc = double.Parse(parameter as String, culture.NumberFormat);
}
else
{
perc = (double)parameter;
}
double coord = containerValue * perc;
return coord;
}
/// <summary>
/// Calculates relative position (expressed as percentage) of an element to its container given its current absolute position
/// as well as the dimensions of the container
/// </summary>
/// <param name="value">Absolute value of the container (width or height)</param>
/// <param name="parameter">X- or Y-position of the element</param>
/// <returns>parameter / value as double</returns>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//output is percentage
//input is double
double containerValue = System.Convert.ToDouble(value, culture.NumberFormat);
double coord = double.Parse(parameter as String, culture.NumberFormat);
double perc = coord / containerValue;
return perc;
}
}
And here's how you can create bindings in XAML (note that my canvas is declared as <Canvas x:Name="canvas" ... >):
<Label Background="Red" ClipToBounds="True" Height="22" Name="label1" Width="60"
Canvas.Left="{Binding Converter={StaticResource PercentageConverter}, ElementName=canvas, Path=ActualWidth, ConverterParameter=0.25}"
Canvas.Top="{Binding Converter={StaticResource PercentageConverter}, ElementName=canvas, Path=ActualHeight, ConverterParameter=0.65}">Marker 1</Label>
More useful, however, is to create Labels in code:
private void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
var mousePos = Mouse.GetPosition(canvas);
var converter = new PercentageConverter();
//Convert mouse position to relative position
double xPerc = (double)converter.ConvertBack(canvas.ActualWidth, typeof(Double), mousePos.X.ToString(), Thread.CurrentThread.CurrentCulture);
double yPerc = (double)converter.ConvertBack(canvas.ActualHeight, typeof(Double), mousePos.Y.ToString(), Thread.CurrentThread.CurrentCulture);
Label label = new Label { Content = "Label", Background = (Brush)new BrushConverter().ConvertFromString("Red")};
//Do binding for x-coordinates
Binding posBindX = new Binding();
posBindX.Converter = new PercentageConverter();
posBindX.ConverterParameter = xPerc;
posBindX.Source = canvas;
posBindX.Path = new PropertyPath("ActualWidth");
label.SetBinding(Canvas.LeftProperty, posBindX);
//Do binding for y-coordinates
Binding posBindY = new Binding();
posBindY.Converter = new PercentageConverter();
posBindY.ConverterParameter = yPerc;
posBindY.Source = canvas;
posBindY.Path = new PropertyPath("ActualHeight");
label.SetBinding(Canvas.TopProperty, posBindY);
canvas.Children.Add(label);
}
So basically, it's almost like my first idea: Use relative position instead of absolute and recalculate all positions on every resize, only this way it's being done by WPF. Just what I wanted, thanks Martin!
Note however, that these examples only work if the Image inside the ImageBrush has exactly the same dimensions as the surrounding Canvas, because this relative positioning does not take margins etc into account. I will have to tune that
Of the top of my head you could write a converter class that would take in a percentage and return an absolute position. As an example if your window was 200 X 200 and you placed the label at 100 X 100 when you scale the window to 400 X 400 the label would stay where it is (as per your original question). However if you used a converter so that instead you could set the labels position to 50% of its parent container's size then as the window scaled the label would move with it.
You may also need to use the same converter for width and height so that it increased in size to match as well.
Sorry for the lack of detail, if I get a chance I'll edit this with example code in a little while.
Edited to add
This question gives some code for a percentage converter.
Although this post is old and already answered, it can still be helpful to others so I will add my answer.
I came up with two ways for maintaining a relative position for elements in a Canvas
MultiValueConverter
Attached Properties
The idea is to provide two values (x,y) in range [0,1] that will define the relative position of the element with respect to the top-left corner of the Canvas. These (x,y) values will be used to calculate and set the correct Canvas.Left and Canvas.Top values.
In order to place the center of the element at a relative position, we will need the ActualWidth and ActualHeight of the Canvas and the element.
MultiValueConverter
The MultiValueConverter RelativePositionConverter:
This converter can be used to relatively position the X and/or Y position when binding with Canvas.Left and Canvas.Top.
public class RelativePositionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values?.Length < 2
|| !(values[0] is double relativePosition)
|| !(values[1] is double size)
|| !(parameter is string)
|| !double.TryParse((string)parameter, out double relativeToValue))
{
return DependencyProperty.UnsetValue;
}
return relativePosition * relativeToValue - size / 2;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Example usage of RelativePositionConverter:
A Canvas width and height are binded to an Image. The Canvas has a child element - an Ellipse that maintains a relative position with the Canvas (and Image).
<Grid Margin="10">
<Image x:Name="image" Source="Images/example-graph.png" />
<Canvas Background="#337EEBE8" Width="{Binding ElementName=image, Path=ActualWidth}" Height="{Binding ElementName=image, Path=ActualHeight}">
<Ellipse Width="35" Height="35" StrokeThickness="5" Fill="#D8FFFFFF" Stroke="#FFFBF73C">
<Canvas.Left>
<MultiBinding Converter="{StaticResource RelativePositionConverter}" ConverterParameter="0.461">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=Canvas}" Path="ActualWidth" />
<Binding RelativeSource="{RelativeSource Self}" Path="ActualWidth" />
</MultiBinding>
</Canvas.Left>
<Canvas.Top>
<MultiBinding Converter="{StaticResource RelativePositionConverter}" ConverterParameter="0.392">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=Canvas}" Path="ActualHeight" />
<Binding RelativeSource="{RelativeSource Self}" Path="ActualHeight" />
</MultiBinding>
</Canvas.Top>
</Ellipse>
</Canvas>
</Grid>
Attached Properties
The Attached Properties RelativeXProperty, RelativeYProperty and RelativePositionProperty:
RelativeXProperty and RelativeYProperty can be used to control the X and/or Y relative positioning with two separate attached properties.
RelativePositionProperty can be used to control the X and Y relative positioning with a single attached property.
public static class CanvasExtensions
{
public static readonly DependencyProperty RelativeXProperty =
DependencyProperty.RegisterAttached("RelativeX", typeof(double), typeof(CanvasExtensions), new PropertyMetadata(0.0, new PropertyChangedCallback(OnRelativeXChanged)));
public static readonly DependencyProperty RelativeYProperty =
DependencyProperty.RegisterAttached("RelativeY", typeof(double), typeof(CanvasExtensions), new PropertyMetadata(0.0, new PropertyChangedCallback(OnRelativeYChanged)));
public static readonly DependencyProperty RelativePositionProperty =
DependencyProperty.RegisterAttached("RelativePosition", typeof(Point), typeof(CanvasExtensions), new PropertyMetadata(new Point(0, 0), new PropertyChangedCallback(OnRelativePositionChanged)));
public static double GetRelativeX(DependencyObject obj)
{
return (double)obj.GetValue(RelativeXProperty);
}
public static void SetRelativeX(DependencyObject obj, double value)
{
obj.SetValue(RelativeXProperty, value);
}
public static double GetRelativeY(DependencyObject obj)
{
return (double)obj.GetValue(RelativeYProperty);
}
public static void SetRelativeY(DependencyObject obj, double value)
{
obj.SetValue(RelativeYProperty, value);
}
public static Point GetRelativePosition(DependencyObject obj)
{
return (Point)obj.GetValue(RelativePositionProperty);
}
public static void SetRelativePosition(DependencyObject obj, Point value)
{
obj.SetValue(RelativePositionProperty, value);
}
private static void OnRelativeXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element)) return;
if (!(VisualTreeHelper.GetParent(element) is Canvas canvas)) return;
canvas.SizeChanged += (s, arg) =>
{
double relativeXPosition = GetRelativeX(element);
double xPosition = relativeXPosition * canvas.ActualWidth - element.ActualWidth / 2;
Canvas.SetLeft(element, xPosition);
};
}
private static void OnRelativeYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element)) return;
if (!(VisualTreeHelper.GetParent(element) is Canvas canvas)) return;
canvas.SizeChanged += (s, arg) =>
{
double relativeYPosition = GetRelativeY(element);
double yPosition = relativeYPosition * canvas.ActualHeight - element.ActualHeight / 2;
Canvas.SetTop(element, yPosition);
};
}
private static void OnRelativePositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element)) return;
if (!(VisualTreeHelper.GetParent(element) is Canvas canvas)) return;
canvas.SizeChanged += (s, arg) =>
{
Point relativePosition = GetRelativePosition(element);
double xPosition = relativePosition.X * canvas.ActualWidth - element.ActualWidth / 2;
double yPosition = relativePosition.Y * canvas.ActualHeight - element.ActualHeight / 2;
Canvas.SetLeft(element, xPosition);
Canvas.SetTop(element, yPosition);
};
}
}
Example usage of RelativeXProperty and RelativeYProperty:
<Grid Margin="10">
<Image x:Name="image" Source="Images/example-graph.png" />
<Canvas Background="#337EEBE8" Width="{Binding ElementName=image, Path=ActualWidth}" Height="{Binding ElementName=image, Path=ActualHeight}">
<Ellipse Width="35" Height="35" StrokeThickness="5" Fill="#D8FFFFFF" Stroke="#FFFBF73C"
local:CanvasExtensions.RelativeX="0.461"
local:CanvasExtensions.RelativeY="0.392">
</Ellipse>
</Canvas>
</Grid>
Example usage of RelativePositionProperty:
<Grid Margin="10">
<Image x:Name="image" Source="Images/example-graph.png" />
<Canvas Background="#337EEBE8" Width="{Binding ElementName=image, Path=ActualWidth}" Height="{Binding ElementName=image, Path=ActualHeight}">
<Ellipse Width="35" Height="35" StrokeThickness="5" Fill="#D8FFFFFF" Stroke="#FFFBF73C"
local:CanvasExtensions.RelativePosition="0.461,0.392">
</Ellipse>
</Canvas>
</Grid>
And hear is how it looks:
The Ellipse that is a child of a Canvas maintains a relative position with respect to the Canvas (and an Image).

Resources