Manipulating Value Converters inside DataTemplate - silverlight

I have a set of data templates I am using for my custom control. It works well, but I want to be able to bind it to data and have values scale based on the min / max of the set. I have created the following value converter:
public class ScaleValueConverter : IValueConverter
{
/// <summary>
/// The property to use the value of
/// </summary>
public string ValueProperty { get; set; }
/// <summary>
/// The minimum value to be scaled against. Will become 0%
/// </summary>
public int MinValue { get; set; }
/// <summary>
/// The maximum value to be scaled against. Will become 100%.
/// </summary>
public int MaxValue { get; set; }
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var type = value.GetType();
var property = type.GetProperty(ValueProperty);
if (property == null)
return 0;
var result = System.Convert.ToDecimal(property.GetValue(value, null));
//TODO: Scale stuff
return result + 100;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
The aim is to have a generic value converter, and simply supply the binding source object, to the value converter in the XAML, and have it sort things out.
I'm not sure how to do this however, as I am unable to access the value converter I have created from my templated control.
I'm looking for something that would work roughly as follows:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//Get Value Converters
var topScaleValueConverter = GetTemplateChild("TopScaleValueConverter");
var bottomScaleValueConverter = GetTemplateChild("BottomScaleValueConverter");
//Setup value converter Min / Max / ValueProperty here
}
Ideally they would be parts of my template, and I could extract them as parts, but that doesn't appear to work.
Can anyone point me in the right direction for getting this type of behavior working?
Regards
Tristan
EDIT: I suppose it would be nice to be able to dependency inject them. Does anyone know if this is possible?

Derive ScaleValueConverter from DependDencyObject and implement your properties as Dependency Properties.
public class ScaleValueConverter : DependencyObject, IValueConverter
{
public double MinValue
{
get { return (double)GetValue(MinValueProperty); }
set { SetValue(MinValueProperty, value); }
}
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(double), typeof(ScaleValueConverter), new PropertyMetadata(0.0d));
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double result = double.Parse(value.ToString())
if (result < MinValue)
{
result = MinValue;
}
return result;
}
}
You will then be able to "inject" data into your properties via a VM.
<ns:ScaleValueConverter x:Key="scaleValue" MinValue="{Binding MinValue,Source={StaticResource MyModelSource}" />
In short, treat your converter the same as any other dependency object and bind as usual.

Related

wpf - bind polyline to custom class

Does anyone know whether it is possible to bind a polyline to a collection of custom objects?
For example, I have a class like so:
public class MyDataClass{
public double Value { get; set; } //I'd like to map this to a polyline point's x value
public double Position { get; set; } //I'd like to map this to a polyline point's y value
}
And I'd like to bind a polyline to a collection of those objects and translate the Value property to X and the Position property to Y.
Thanks!
Although already answered by Joseph, I'd like to add a shorter and more flexible implementation of the Convert method, which uses the LINQ Select method:
using System.Linq;
...
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var myDataCollection = value as IEnumerable<MyDataClass>;
if (myDataCollection == null)
{
return null;
}
return new PointCollection(
myDataCollection.Select(p => new Point(p.Value, p.Position)));
}
The Polyline is expecting a PointCollection of Points in order to draw them, you could use a converter to assure that :
Xaml
<Polyline Stretch="Fill" Grid.Column="0"
Name="Polyline" Stroke="Red"
Points="{Binding Points,Converter={StaticResource ToPointConverter}}">
</Polyline>
the converter is implemented like so:
public class ToPointConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return null;
var pointCollection=new PointCollection();
(value as List<MyDataClass>).ForEach(x=>{pointCollection.Add(new Point()
{
X = x.Value,
Y = x.Position
});});
return pointCollection;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and in your codebehind or your Viewmodel define the List<MyDataClass> property :
public List<MyDataClass> Points { get; set; }
don't forget to set the DataContext and set the ToPointConverter in your resource.
`

Display string value instead of boolean checkbox using datasource?

Boolean is for storing economically in SQL Database. But the datagridview when using datasource function in C# just show true or false by the checkbox each row.
I want to display the string value in the datagridview, not Boolean using checkbox .
True = "Spin On"
False = "Element"
How can I change the checkbox to a string value?
You have at least two options to obtain this:
Binding
First, you could change the binding during on RowDataBound of GridView. Give a look at this example, where you have the following class:
public class Student
{
public int Roll { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
}
and you can change the default behavior of GridView as follows:
/// <summary>
/// Handles the RowDataBound event of the GridView2 control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Web.UI.WebControls.GridViewRowEventArgs"/> instance containing the event data.</param>
protected void GridView2_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
Student s = (Student)e.Row.DataItem;
if (s.Status == true)
{
e.Row.Cells[2].Text = "1";
}
else
{
e.Row.Cells[2].Text = "0";
}
}
}
Formatting
Another way to address this problem consists in using custom formatting, handling the CellFormatting event according to your needs:
void gridView2_CellFormatting(object s, DataGridViewCellFormattingEventArgs evt)
{
if (evt.ColumnIndex == yourcolumnIndex){
if (evt.Value is bool){
evt.Value = ((bool)evt.Value) ? "Yes" : "No";
evt.FormattingApplied = true;
}
}
}
Using a Converter will allow you to do it easily
using System;
using System.Windows.Data;
namespace WpfApplication2
{
[ValueConversion(typeof(Boolean), typeof(String))]
internal class BooleanToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Boolean state = (Boolean)value;
return state ? "Spin On" : "Element";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
then, in your XAML, bind your String (a label in my example) to the Boolean ('state' variable in the example) using the Converter :
<Label Content="{Binding State, Converter={StaticResource BooleanToStringConverter }, Mode=OneWay}" />

Binding the Path Property of a Binding

is it possible to bind the Path property of a binding to another property?
I want to realize this code:
Text="{Binding Path={Binding Path=CurrentPath}}"
So I can adjust dynamically to which Property my actual binding is refering.
Thanks for your Help
Jonny
I worked it out on myself.
Heres the solution, I hope it might help anyone got the same problem like me.
public class CustomBindingBehavior : Behavior<FrameworkElement>
{
public bool IsBinding
{
get
{
return (bool)GetValue(IsBindingProperty);
}
set
{
SetValue(IsBindingProperty, value);
}
}
public string PropertyPath
{
get
{
return (string)GetValue(PropertyPathProperty);
}
set
{
SetValue(PropertyPathProperty, value);
}
}
public static DependencyProperty
PropertyPathProperty = DependencyProperty.Register("PropertyPath", typeof(string),
typeof(CustomBindingBehavior), null);
public static DependencyProperty
IsBindingProperty = DependencyProperty.Register("IsBinding", typeof(bool),
typeof(CustomBindingBehavior), null);
protected override void OnAttached()
{
if (AssociatedObject is TextBlock)
{
var tb = AssociatedObject as TextBlock;
tb.Loaded += new RoutedEventHandler(tb_Loaded);
}
}
private void tb_Loaded(object sender, RoutedEventArgs e)
{
AddBinding(sender as TextBlock, TextBlock.TextProperty);
}
private void AddBinding(DependencyObject targetObj, DependencyProperty targetProp)
{
if (IsBinding)
{
Binding binding = new Binding();
binding.Path = new PropertyPath(this.PropertyPath, null);
BindingOperations.SetBinding(targetObj, targetProp, binding);
}
else
{
targetObj.SetValue(targetProp, this.PropertyPath);
}
}
}
And heres the implementation in XAML:
<TextBlock >
<i:Interaction.Behaviors>
<behaviors:CustomBindingBehavior PropertyPath="{Binding Path=HeaderPropertyBinding}" IsBinding="{Binding Path=HeaderIsBinding}" />
</i:Interaction.Behaviors>
</TextBlock>
Greetings
Jonny
As other posters have mentioned, you can only set a binding on a dependency property - which path is not. The underlying reason is that xaml is source code that gets compiled. At compile time the compiler has no idea what the value of 'CurrentPath' is, and would not be able to compile. Essentially what you are looking to do is runtime reflection of a property value - which could be done using another property in the ViewModel you are binding to, or using a converter.
ViewModel:
public string CurrentValue
{
get
{
var property = this.GetType().GetProperty(CurrentPath);
return property.GetValue(this, null);
}
}
Using a converter:
public class CurrentPathToValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var viewModel = (ViewModel)value;
var property = viewModel.GetType().GetProperty(viewModel.CurrentPath);
var currentValue = property.GetValue(viewModel, null);
return currentValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Of couse these only work if you want to get a simple property of the object - if you want to get something more complex your reflection code is going to get a lot more complex.
Unless you are building something like a property grid, or for some other reason you actually want to introspect the objects running in your application, I would suggest you revisit your design, as reflection is really only suited to a few situations.
Path is not a dependency property, therefore the binding will not work.
Perhaps you could bind to a property that returns another property based on a switch statement and bind to that. Change the 'switch' property and you change the output of the other property.
Just don't forget to include your NotifyPropertyChanged stuff in the switch property for the bound property otherwise your view will not update.
e.g.
private int _mySwitch;
//Set this to determine what the other property will return.
public int SwitchProperty
{
get { return _mySwitch; }
set
{
_mySwitch = value;
NotifyPropertyChanged("MySwitchableProperty");
}
}
public String PropertyA { get; set; }
public String PropertyB { get; set; }
//Bind to this property
public String MySwitchableProperty
{
get
{
switch (SwitchProperty)
{
case 1:
return PropertyA;
break;
case 2:
return PropertyB;
break;
default :
return String.Empty;
break;
}
}
}
I think converter can helps your.
Expample
First control
Text="{Binding Path=CurrentPath}"
Second control
Text="{Binding Path=CurrentPath, Convertor={converters:MyConvertor}}"
Base converter
public abstract class ConvertorBase<T> : MarkupExtension, IValueConverter
where T : class, new()
{
public abstract object Convert(object value, Type targetType, object parameter,
CultureInfo culture);
public virtual object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
#region MarkupExtension members
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter == null)
_converter = new T();
return _converter;
}
private static T _converter = null;
#endregion
}
MyConverter
public class MyConverter: ConvertorBase<MyConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (string)value.Equals("blabla") ? "Yes" : "No"; // here return necessary parametr
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}

When do default converters kick in?

With the following code, although Text property is bound to a DateTime source property, I noticed WPF seems to automatically convert the text to a DateTime, without me needing to write a ValueConverter. Can someone please shed some light on how this is done
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
>
<StackPanel>
<DatePicker Height="25" Name="datePicker1" Width="213" Text="{Binding Path=DueDate,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
public class P
{
private DateTime? dueDate = DateTime.Now;
public DateTime? DueDate
{
get { return dueDate; }
set
{
dueDate = value;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
P p = new P();
this.DataContext = p;
}
}
It is using the DateTimeTypeConverter from the Base Class Library (EDIT: Well, it could have used a TypeConverter however it appears that from #DeviantSeev's answer that they did not).
There 'default' converters you are talking about are actually TypeConverters (MSDN) and they have been a part of the .NET Framework since v2.0 and they are used through-out the Base Class Libraries. Another example of TypeConverters in WPF is the ThicknessTypeConverter for Padding, Margin, and BorderThickness properties. It converts a comma-delimited string to a Thickness object.
There are plenty of articles available if you want to understand them further.
There are two parts to using a TypeConverter - implementation of the class and then marking up your properties/types with TypeConverterAttribute.
For example, I recently had a custom control that required a char[] that I wanted to set from Xaml like so:
<AutoCompleteTextBox MultiInputDelimiters=",;. " />
Usage
[TypeConverter(typeof(CharArrayTypeConverter))]
public char[] MultiInputDelimiters
{
get { return (char[])GetValue(MultiInputDelimitersProperty); }
set { SetValue(MultiInputDelimitersProperty, value); }
}
Implementation
public class CharArrayTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (Type.GetTypeCode(sourceType) == TypeCode.String);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
return ((string)value).ToCharArray();
return value;
}
}
When to use a TypeConverter?
You can only use TypeDescriptors if you are writing a custom control as you need to be able to mark-up the property with the TypeDescriptorAttribute. Also I would only use TypeConverter if the conversion is rather a straight-forward - as in the example above where I have a string and want a char[] - or if there are multiple possible formats that I want to convert from.
You write IValueConverter when you want more flexibility on how the value to converted by driving it by data or a passing a parameter. For example, a very common action in WPF is converting a bool to Visibility; there are three possible outputs from such a conversion (Visible, Hidden, Collapsed) and with only two inputs (true, false) it difficult to decide this in a TypeConverter.
In my applications, to achieve this two inputs to three output problem I have written a single BoolToVisibilityConverter with a TrueValue and FalseValue properties and then I instance it three times in my global ResourceDictionary. I'll post the code sample tomorrow morning, I don't it in front of me right now..
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibilityConverter : IValueConverter
{
public Visibility FalseCondition { get; set; }
public Visibility TrueCondition { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value) ? TrueCondition : FalseCondition;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
return TrueCondition;
return FalseCondition;
}
}
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" FalseCondition="Collapsed" TrueCondition="Visible"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityCollapsedConverter" FalseCondition="Visible" TrueCondition="Collapsed"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenConverter" FalseCondition="Visible" TrueCondition="Hidden"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenWhenFalseConverter" FalseCondition="Hidden" TrueCondition="Visible"/>
The DatePicker is a custom control that was initially part of the WPF Toolkit before being added as a standard control in .NET 4.
I just went to the source code repository for the control to find you the exact source code which is responsible for the conversion of the text to date:
#region Text
/// <summary>
/// Gets or sets the text that is displayed by the DatePicker.
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Identifies the Text dependency property.
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(DatePicker),
new FrameworkPropertyMetadata(string.Empty, OnTextChanged, OnCoerceText));
/// <summary>
/// TextProperty property changed handler.
/// </summary>
/// <param name="d">DatePicker that changed its Text.</param>
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DatePicker dp = d as DatePicker;
Debug.Assert(dp != null);
if (!dp.IsHandlerSuspended(DatePicker.TextProperty))
{
string newValue = e.NewValue as string;
if (newValue != null)
{
if (dp._textBox != null)
{
dp._textBox.Text = newValue;
}
else
{
dp._defaultText = newValue;
}
dp.SetSelectedDate();
}
else
{
dp.SetValueNoCallback(DatePicker.SelectedDateProperty, null);
}
}
}
private static object OnCoerceText(DependencyObject dObject, object baseValue)
{
DatePicker dp = (DatePicker)dObject;
if (dp._shouldCoerceText)
{
dp._shouldCoerceText = false;
return dp._coercedTextValue;
}
return baseValue;
}
/// <summary>
/// Sets the local Text property without breaking bindings
/// </summary>
/// <param name="value"></param>
private void SetTextInternal(string value)
{
if (BindingOperations.GetBindingExpressionBase(this, DatePicker.TextProperty) != null)
{
Text = value;
}
else
{
_shouldCoerceText = true;
_coercedTextValue = value;
CoerceValue(TextProperty);
}
}
#endregion Text
In most cases I believe WPF is calling ToString() for you however if you look at the code for the date picker the important line is
(string)GetValue(TextProperty)
notice it casts the value you assigned to the "Text" property to a string? The whole point is there is no default converter in the more traditional sense of BooleanToVisibilityConverter or something like that.

Bound Silverlight Combobox not showing default value with EnumBinder

My models use enums for fixed multiple selections. I'm using Dean Chalk's EnumBinder, found at http://deanchalk.me.uk/post/Enumeration-Binding-In-Silverlight.aspx , to bind to a combo box. Everything seems to work great except the default value isn't shown. The selected index is -1, and it doesn't matter if I bind to SelectedItem or SelectedValue. The combobox works fine otherwise. I have no problems with default values with any other bound comboboxes.
enumbindingsupport.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
/* All of this was once just part of the RichClient sub-namespace
* but it has its uses elsewhere, so it has been moved.
*/
/// <summary>
/// Container for enumeration values.
/// </summary>
/// <remarks>
/// http://deanchalk.me.uk/post/Enumeration-Binding-In-Silverlight.aspx
/// </remarks>
public sealed class EnumContainer
{
public int EnumValue { get; set; }
public string EnumDescription { get; set; }
public object EnumOriginalValue { get; set; }
public override string ToString() {
return EnumDescription;
}
public override bool Equals(object obj) {
if (obj == null)
return false;
if (obj is EnumContainer)
return EnumValue.Equals((int)((EnumContainer)obj).EnumValue);
return EnumValue.Equals((int)obj);
}
public override int GetHashCode() {
return EnumValue.GetHashCode();
}
}
/// <summary>
/// A collection to store a list of EnumContainers that hold enum values.
/// </summary>
/// <remarks>
/// http://deanchalk.me.uk/post/Enumeration-Binding-In-Silverlight.aspx
/// </remarks>
/// <typeparam name="T"></typeparam>
public class EnumCollection<T> : List<EnumContainer> where T : struct
{
public EnumCollection() {
var type = typeof(T);
if (!type.IsEnum)
throw new ArgumentException("This class only supports Enum types");
var fields = typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (var field in fields) {
var container = new EnumContainer();
container.EnumOriginalValue = field.GetValue(null);
container.EnumValue = (int)field.GetValue(null);
container.EnumDescription = field.Name;
var atts = field.GetCustomAttributes(false);
foreach (var att in atts)
if (att is DescriptionAttribute) {
container.EnumDescription = ((DescriptionAttribute)att).Description;
break;
}
Add(container);
}
}
}
enumvalueconverter.cs
using System;
using System.Globalization;
using System.Windows.Data;
/// <summary>
/// Supports two-way binding of enumerations.
/// </summary>
public class EnumValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) {
return (int)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture) {
if (value == null)
return null;
if (value.GetType() == targetType)
return value;
return ((EnumContainer)value).EnumOriginalValue;
}
}
enum used:
/// <summary>
/// Describes the available sorting options.
/// </summary>
public enum PeopleSortOptionsEnum
{
[Display(Order = 10)]
[Description("First Name, Last Name")]
FirstNameThenLastName,
[Display(Order = 20)]
[Description("Last Name, First Name")]
LastNameThenFirstName,
[Display(Order = 30)]
Grade,
[Display(Order = 40)]
Gender,
[Display(Order = 50)]
Age
}
property on my model:
/// <summary>
/// This is the list for the enumeration to bind to.
/// </summary>
public EnumCollection<PeopleSortOptionsEnum> AvailableSortOptions
{
get { return new EnumCollection<PeopleSortOptionsEnum>(); }
}
XAML snippet:
<ComboBox ItemsSource="{Binding Path=AvailableSortOptions, Mode=OneWay}" SelectedValue="{Binding Path=Preferences.SortOrder, Mode=TwoWay, Converter={StaticResource EnumConverter}}" Grid.Column="1" Grid.Row="2" Height="32" Margin="48,31,0,0" x:Name="cboSort" VerticalAlignment="Top" />
Where Preferences.SortOrder is of type PeopleSortOptionsEnum, the converter is in my app.xaml as a converter for all enum types.
Anyone have any idea why it won't set the index to the currently selected value? I'm about to just throw some code in the codebehind to set the selectedindex to the currently selected value on load, but I feel so dirty just thinking about it.
Besides this issue, it works wonderfully, so thanks Dean!
Edit Adding the Preferences.SortOrder code:
public PeopleSortOptionsEnum SortOrder
{
get { return sortOrder; }
set
{
if (sortOrder != value)
{
sortOrder = value;
PropertyHasChanged("SortOrder");
}
}
}
The issue is the Convert method on your enum converter class:
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) {
return (int)value;
}
When the combobox SelectedItem is gets the value from SortOrder it is return in Enum value which this converter is converting to an int. However, the combobox items is a collection of EnumContainer objects, not ints. So it fails to set the selected item.
To resolve the issue you have to do two things. First change your combobox bindings and set the SelectedValuePath:
<ComboBox ItemsSource="{Binding Path=AvailableSortOptions}"
SelectedValue="{Binding Path=SortOrder, Mode=TwoWay, Converter={StaticResource EnumConverter}}"
SelectedValuePath="EnumOriginalValue"/>
Second you have to slightly modify the Convert method on your converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
Once I made those two changes it started working as expected.

Resources