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.
`
Related
I would like to populate WPF combobox (and other similar controls) with the geniric enum to List<string> below:
How do I call it in XAML?
public class ToList<TEnum> : List<string> where TEnum : struct, Enum
{
public static ToList()
{
foreach (TEnum e in Enum.GetValues(typeof(TEnum)))
{
// Skips some values (TEnum.NotDefined)
if (e.Equals((TEnum)Enum.ToObject(typeof(TEnum), -1))) continue;
// GetDescription() is an extension method based on the [Description] attribute
Add(e.GetDescription());
}
}
}
Example with hardcoded enum
XAML with MyEnum a property of the view model
<converters:MyEnumToStringConverter x:Key="MyEnumToString"/>
...
<ComboBox ItemsSource="{StaticResource MyEnumList}" SelectedItem="{Binding MyEnum, Converter=MyEnumToString}">
Non-generic enum-to-List method
public class MyEnumList : List<string>
{
public MyEnumList()
{
foreach (MyEnum myEnum in Enum.GetValues(typeof(MyEnum)))
{
if (myEnum == myEnum.NotDefined) continue;
Add(myEnum.GetDescription());
}
}
}
Value converter (with culture localization potentially in the future). Can I pass the enum as targetType or parameter?
public class MyEnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => ((MyEnum)value).GetDescription();
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing;
}
Basically, I don't want to hard-code the example above for MyEnumA, MyEnumB and so on.
Thanks for any insights :-)
In WPF one has the possibility to use a converter in binding, so that one can bind for instance a Visibility property of a control to a Boolean property in the view model.
For this specific pairing (Visibility and Boolean) WPF does offer an out-of-the-box converter called BooleanToVisibilityConverter.
But let's say I'd like to bind a Boolean property of a control to a Visibility property in the view model. Is there any way to use the standard BooleanToVisibilityConverter and tell the binding to invert it (to use ConvertBack instead on Convert and vice versa)?
Or do I have to write another converter for that case?
So, there is no built-in way of inverting the converter. We can, however, work around that by introducing a "shim" converter like this one:
public class InverterConverter : IValueConverter
{
public IValueConverter Converter { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Converter.ConvertBack(value, targetType, parameter, culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Converter.Convert(value, targetType, parameter, culture);
}
}
With the usage as follows:
<ContentControl>
<ContentControl.Content>
<Binding>
<Binding.Converter>
<InverterConverter Converter="{StaticResource YourConverter}" />
</Binding.Converter>
</Binding>
</ContentControl.Content>
</ContentControl>
This, obviously, is some heavy syntax but we can simplify it with this little markup extension:
public class InvertedExtension : MarkupExtension
{
public IValueConverter Converter { get; set; }
public InvertedExtension(IValueConverter converter)
{
Converter = new InverterConverter() { Converter = converter };
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Converter;
}
}
<ContentControl Content="{Binding Converter={Inverted {StaticResource MyConverter}}}" />
Is there any way to use the standard BooleanToVisibilityConverter and tell the binding to invert it (to use ConvertBack instead on Convert and vice versa)?
No.
Or do I have to write another converter for that case?
Yes.
You could implement a generic converter that accepts "true" and a "false" values of any type:
public class BooleanConverter<T> : IValueConverter
{
public T True { get; set; }
public T False { get; set; }
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
value is bool && ((bool)value) ? True : False;
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
value is T && EqualityComparer<T>.Default.Equals((T)value, True);
}
...and derive from this for each type that you want to handle:
public class BooleanToVisibilityNegationConverter : BooleanConverter<Visibility>
{
public BooleanToVisibilityNegationConverter()
: base()
{
True = Visibility.Hidden;
False = Visibility.Visible;
}
}
When you drag the slider, you get a ConvertBack (expected), but why do I then get a "Convert" straight after that? I'd only expect Convert to be called when its first initialized, or if it was raising a property change notification, but it doesn't.
<Window x:Class="WpfApplication10.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication10"
Title="MainWindow">
<Slider Value="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={l:Converter}}"/>
</Window>
public class Converter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
public partial class MainWindow : Window
{
public double Value { get; set; }
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
}
This is likely occurring because you're using .NET 4, which changed the way bindings work a little bit : http://karlshifflett.wordpress.com/2009/05/27/wpf-4-0-data-binding-change-great-feature/
Whereas prior to 4 the binding wouldn't update back (assuming that it didn't need to), the new behavior is to do that by default. Rationale is explained in the linked blog.
Edit: I suppose I should ask if there is a reason you don't want it to convert back, or if it's just curiosity? If you need to prevent converting back, the clearest strategy is probably to keep track of your last converted value to parrot back :
public class Converter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object lastValue;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return lastValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
lastValue = value;
return value;
}
}
There's also the option of setting the binding to OneWayToSource, which would keep it from setting back to the slider value... assuming you don't need to push data back to the slider.
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;
}
}
Does XAML allows modification of bound value?
Like Width="{Binding Elementname="lstMine", Path=Width}" -100 ? So that I can have a relative value.
You can use converters for this purpose, and my WPF Converters library includes an ExpressionConverter that allows you to do exactly that:
Width="{Binding Width, ElementName=lstMine, Converter={con:ExpressionConverter {}{0}-100}}"
use Converter for these purpose
You can use a converter, its simple to use, doesn't require any libraries and is easy to customize if you want for example add value or multiply.
I got this one from this post
Converter class:
public class SubtractConverter : MarkupExtension, IValueConverter
{
public double Value { get; set; }
public object Convert(object baseValue, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double val = System.Convert.ToDouble(baseValue);
// Change here if you want other operations
return val - Value;
}
public object ConvertBack(object baseValue, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Usage (remember to add the namespace and the converter to window resources):
Width="{Binding Width, ElementName=lstMine, Converter={converters:SubtractConverter Value=15}}"