Use IValueConverter with DynamicResource? - wpf

Is there a way to define a converter when using the DynamicResource extension? Something in the lines of
<RowDefinition Height="{Binding Source={DynamicResource someHeight}, Converter={StaticResource gridLengthConverter}}" />
which unfortunately gives me the following excpetion:
A 'DynamicResourceExtension' cannot be
set on the 'Source' property of type
'Binding'. A
'DynamicResourceExtension' can only be
set on a DependencyProperty of a
DependencyObject.

I know i am really late to this but what definitely works is using a BindingProxy for the DynamicResource like this
<my:BindingProxy x:Key="someHeightProxy" Data="{DynamicResource someHeight}" />
Then applying the converter to the proxy
<RowDefinition Height="{Binding Source={StaticResource someHeightProxy}, Path=Data, Converter={StaticResource gridLengthConverter}}" />

Try something like that:
Markup extension:
public class DynamicResourceWithConverterExtension : DynamicResourceExtension
{
public DynamicResourceWithConverterExtension()
{
}
public DynamicResourceWithConverterExtension(object resourceKey)
: base(resourceKey)
{
}
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public override object ProvideValue(IServiceProvider provider)
{
object value = base.ProvideValue(provider);
if (value != this && Converter != null)
{
Type targetType = null;
var target = (IProvideValueTarget)provider.GetService(typeof(IProvideValueTarget));
if (target != null)
{
DependencyProperty targetDp = target.TargetProperty as DependencyProperty;
if (targetDp != null)
{
targetType = targetDp.PropertyType;
}
}
if (targetType != null)
return Converter.Convert(value, targetType, ConverterParameter, CultureInfo.CurrentCulture);
}
return value;
}
}
XAML:
<RowDefinition Height="{my:DynamicResourceWithConverter someHeight, Converter={StaticResource gridLengthConverter}}" />

#Thomas's post is very close, but as others have pointed out, it only executes at the time the MarkupExtension is executed.
Here's a solution that does true binding, doesn't require 'proxy' objects, and is written just like any other binding, except instead of a source and path, you give it a resource key...
How do you create a DynamicResourceBinding that supports Converters, StringFormat?

I like the answer of mkoertgen.
Here is an adapted example for a IValueConverter proxy in VB.NET that worked for me. My resource "VisibilityConverter" is now included as DynamicResource and forwarded with the ConverterProxy "VisibilityConverterProxy".
Usage:
...
xmlns:binding="clr-namespace:Common.Utilities.ModelViewViewModelInfrastructure.Binding;assembly=Common"
...
<ResourceDictionary>
<binding:ConverterProxy x:Key="VisibilityConverterProxy" Data="{DynamicResource VisibilityConverter}" />
</ResourceDictionary>
...
Visibility="{Binding IsReadOnly, Converter={StaticResource VisibilityConverterProxy}}"
Code:
Imports System.Globalization
Namespace Utilities.ModelViewViewModelInfrastructure.Binding
''' <summary>
''' The ConverterProxy can be used to replace StaticResources with DynamicResources.
''' The replacement helps to test the xaml classes. See ToolView.xaml for an example
''' how to use this class.
''' </summary>
Public Class ConverterProxy
Inherits Freezable
Implements IValueConverter
#Region "ATTRIBUTES"
'Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
Public Shared ReadOnly DataProperty As DependencyProperty =
DependencyProperty.Register("Data", GetType(IValueConverter), GetType(ConverterProxy), New UIPropertyMetadata(Nothing))
''' <summary>
''' The IValueConverter the proxy redirects to
''' </summary>
Public Property Data As IValueConverter
Get
Return CType(GetValue(DataProperty), IValueConverter)
End Get
Set(value As IValueConverter)
SetValue(DataProperty, value)
End Set
End Property
#End Region
#Region "METHODS"
Protected Overrides Function CreateInstanceCore() As Freezable
Return New ConverterProxy()
End Function
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert
Return Data.Convert(value, targetType, parameter, culture)
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Data.ConvertBack(value, targetType, parameter, culture)
End Function
#End Region
End Class
End Namespace

Related

IValueConverter and binding a DependencyObject

I have a ComboBox that I need to do a converter on the SelectedItem. Problem is the IValueConverter needs the binding value but a Collection as well. Configured a DependencyObject but it is giving me an error message of
Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Collections.ObjectModel.ObservableCollection`1[MyClass]'.
Here is my IValueConverter
public class MyConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty FoldersProperty =
DependencyProperty.Register(nameof(MyCollection), typeof(ObservableCollection<MyClass>), typeof(MyClassModelToMyClassID), new FrameworkPropertyMetadata(new ObservableCollection<MyClass>()));
public ObservableCollection<MyClass> MyCollection
{
get { return GetValue(FoldersProperty) as ObservableCollection<MyClass>; }
set { SetValue(FoldersProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//Amazing Convert code that uses MyCollection and Value
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
//Amazing ConvertBack code that uses MyCollection and Value
}
}
Here is how I am calling it:
<Page.Resources>
<converter:MyConverter x:Key="Converter" MyCollection="{Binding DataCollection}" />
</Page.Resources>
....
<ComboBox
ItemsSource="{Binding DataCollection}"
SelectedItem="{Binding Path=MyValue, Converter={StaticResource TaxCodeConverter}}" />
edit: added the full IValueConvert subtracted the Convert and ConvertBack code
Like a BindingProxy, it needs to be a Freezable. Also, don't pass a new observable collection to your metatadata. That's a reference type, so all instances of this converter will be initialized with the same actual collection instance.
Let me know if you run into some other issue, but I've done this and been able to bind to the dependency property.
Many would argue that a better approach would be a multibinding and a multi-value converter. I think there's value in having a strongly typed property with a descriptive name.
public class MyConverter : Freezable, IValueConverter
{
/* omitted: Convert() and ConvertBack() */
public MyConverter()
{
// Initialize here if you need to
MyCollection = new ObservableCollection<MyClass>();
}
protected override Freezable CreateInstanceCore()
{
return new MyConverter();
}
public static readonly DependencyProperty MyCollectionProperty =
DependencyProperty.Register(nameof(MyCollection),
typeof(ObservableCollection<MyClass>), typeof(MyConverter),
new FrameworkPropertyMetadata(null));
public ObservableCollection<MyClass> MyCollection
{
get { return GetValue(MyCollectionProperty) as ObservableCollection<MyClass>; }
set { SetValue(MyCollectionProperty, value); }
}
}
XAML usage will be just as you have it in your question: Bind the dependency property, and the binding will update that property of that instance of MyConverter, provided that your Page's DataContext has an appropriately typed property named DataCollection.
<Page.Resources>
<converter:MyConverter x:Key="Converter" MyCollection="{Binding DataCollection}" />
</Page.Resources>

Updating the value of another property when a DependencyProperty changes

I have a DependencyProperty in my UserControl with a property changed callback. The property works as expected and the callback works as expected.
public double CurrentFlow
{
get { return (double)GetValue(CurrentFlowProperty); }
set { SetValue(CurrentFlowProperty, value); }
}
public static readonly DependencyProperty CurrentFlowProperty = DependencyProperty.Register("CurrentFlow", typeof(double), typeof(MyUserControl), new PropertyMetadata(0.0, OnCurrentFlowPropertyChanged));
private static void OnCurrentFlowPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("CurrentFlow changed.");
}
However, I have a TextBlock in my UserControl where I want to display CurrentFlow as a formatted string. Currently, I have the Text property of the TextBlock binded to CurrentFlow, and it works, but I'm not getting the format I need. (Too many numbers after the decimal.)
<TextBlock Text="{Binding Path=CurrentFlow, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Ideally, I'd like to have a property named CurrentFlowString that takes the value from CurrentFlow and formats it to what I want. For example: CurrentFlow.ToString("0.00");
What's the best way to go about this with DependencyProperties? I know how to do this with regular properties but I'm kinda stuck here.
Thanks!
If you want to have more flexibility than using StringFormat, you can also use a custom converter. For example,
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double d)
return $"{d:f2}";
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then add it to your UserControl.Resources, and use it in your Binding:
<UserControl.Resources>
<local:MyConverter x:Key="MyConverter" />
</UserControl.Resources>
<Grid>
<TextBlock Text="{Binding Path=CurrentFlow, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource MyConverter}}" />
</Grid>
Solution 2:
Based on your comment below, here's an alternative solution. First, create a new dependency property; for example, FormattedCurrentFlow:
public static readonly DependencyProperty FormattedCurrentFlowProperty = DependencyProperty.Register(
"FormattedCurrentFlow", typeof(string), typeof(MyControl), new PropertyMetadata(default(string)));
public string FormattedCurrentFlow
{
get { return (string)GetValue(FormattedCurrentFlowProperty); }
set { SetValue(FormattedCurrentFlowProperty, value); }
}
Since you already have a method to handle changes in CurrentFlow, update the new FormattedCurrentFlow when CurrentFlow changes:
private static void OnCurrentFlowPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var myControl = (MyControl)source;
myControl.FormattedCurrentFlow = $"{myControl.CurrentFlow:f2}";
}
The TextBox in the UserControl can now bind to FormattedCurrentFlow:
<TextBlock Text="{Binding Path=FormattedCurrentFlow, RelativeSource={RelativeSource AncestorType=UserControl}}" />

WPF Changing Binded Value from Integer to Image

I am trying to load a ListView thats binded to a database that brings a list of items. In the ListView i am showing two colums, "Items" and "Status." I am able to bring the values, but now I want to replace the value in Status to an Image.
Example:
1 = images/green.png
2 = images/red.png
3 = images/orange.png
And I would like to show the Image in the list, so as the user navigates, they see all the images automatically. I found something similar in another question but that has the image tag built in which I can't do in a ListView.
WPF Convert Integer to Image without DB Binding
Thanks for Help.
EDIT
Partial Class ImgConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object
Dim imageIndex As Integer
If Integer.TryParse(value.ToString(), imageIndex) Then
Select Case imageIndex
Case 1
Return BitmapSource = "Images/green.png"
Case 2
Return BitmapSource = "Images/red.png"
End Select
End If
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object
Throw New NotImplementedException()
End Function
End Class
With this code I am getting a IntelliSense error BitmapSource and Implements IvalueConverter saying that I need to implement a ConvertBack, which I did but it still bugs me.
EDIT #3
OK THE BITMAPSOURCE ERROR WAS BC I DIDN'T DECLARE THE VARIABLE. THATS SOLVED.
The IValueConverter error says:
Error 2 Class 'ImgConverter' must implement 'Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object' for interface 'System.Windows.Data.IValueConverter'. ...\Config (ABM,20)\RangoPage.xaml.vb 14 Cogent
Use an IValueConverter,
Which will take an integer as a parameter, and return a BitmapSource
<ListView ItemsSource="{Binding Collection}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Textblock Text="{Binding Item}" />
<Image Source="{Binding Status, Converter={StaticResource ValueConverter}}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class IntToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int imageIndex;
if(int.TryParse(value.ToString(), out imageIndex))
{
switch(imageIndex)
{
case 1:
return new ImageSource("images/red.png")
etc...
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Just in case if yo need to use it in UWP app there is working approach.
C#
public sealed class IntStateToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
try
{
var imagePath = "ms-appx:///Assets/Buttons/";
DeviceNodeTechStateEnum state = value.ObjectToEnums<DeviceNodeTechStateEnum>();
switch (state)
{
case DeviceNodeTechStateEnum.OK:
imagePath += "correct.png";
break;
case DeviceNodeTechStateEnum.BAD:
imagePath += "incorrect.png";
break;
default:
imagePath += "unknown.png";
break;
}
Uri imageUri = new Uri(imagePath, UriKind.Absolute);
BitmapImage imageBitmap = new BitmapImage(imageUri);
return imageBitmap;
}
catch (Exception)
{
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return !(value is bool && (bool)value);
}
}
Where is ms-appx:///Assets/Buttons/ is your project folder to keep images.
XAML of UserControl
<Image Source="{Binding State, Converter={StaticResource intStateToImageConverter}}" Width="20" Height="20" ></Image>
Where is State is a field of the class has type DeviceNodeTechStateEnum.
XAML of APP
<Application.Resources>
<ResourceDictionary>
<common:IntStateToImageConverter x:Key="intStateToImageConverter" />
C# Enums
public enum DeviceNodeTechStateEnum
{
Undefined = 1,
OK = 2,
BAD = 3,
}
Method to convert object to enums.
public static class Extensions
{
public static T ObjectToEnums<T>(this object o)
{
T enumVal = (T)Enum.Parse(typeof(T), o.ToString());
return enumVal;
}
}

Custom IValueConverter inheriting from DependencyObject

I wanted to experiment with being able to have a converter whose arguments can be bound with the current data context. Can anyone tell me why when reaching the Convert() function, the Source property is always null?
namespace WpfApplication32
{
public class ConverterTest : DependencyObject, IValueConverter
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(DependencyObject), typeof(ConverterTest));
public DependencyObject Source
{
get { return (DependencyObject)this.GetValue(SourceProperty); }
set { this.SetValue(SourceProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
Value = 7;
InitializeComponent();
DataContext = this;
}
public float Value
{
get;
set;
}
}
}
<Window x:Class="WpfApplication32.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication32">
<Slider>
<Slider.Value>
<Binding Path="Value">
<Binding.Converter>
<local:ConverterTest Source="{Binding}"/>
</Binding.Converter>
</Binding>
</Slider.Value>
</Slider>
</Window>
One possible solution is to make your Converter inherit from Freezable instead (Hillberg Freezable trick). Then you can even define your Converter in your Resources and reference it in your binding as an attribute instead of an extra child element.

Binding in Converter?

I'm trying to make a custom converter that inherits from DependencyObject, but it doesn't work:
Converter:
public class BindingConverter : DependencyObject , IValueConverter
{
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(BindingConverter), new PropertyMetadata(null));
public object Convert(object value, Type targetType, object parameter, Globalization.CultureInfo culture)
{
Debug.Assert(Value != null); //fails
return Value;
}
public object ConvertBack(object value, Type targetType, object parameter, Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Xaml:
<StackPanel x:Name="this">
<!--works-->
<ContentControl Content="{Binding ActualHeight, ElementName=this}"/>
<!--doesn't work-->
<ContentControl>
<Binding>
<Binding.Converter>
<BindingConverter Value="{Binding ActualHeight, ElementName=this}" />
</Binding.Converter>
</Binding>
</ContentControl>
<TextBlock Text="{Binding Animals}"/>
</StackPanel>
Am I missing out anything?
I have some places in my projects where I needed similar functionality. Can't show you exact sample, just an idea:
perhaps you have to inherit from FrameworkElement, not IValueConverter, Something like this:
public class BindingHelper : FrameworkElement
in the BindingHelper class, set Visibility to Collapsed and IsHitTestVisible to false;
to make it working, insert it into visual tree directly. In your example, it should be a child of the StackPanel. So, it will have the same DataContext as other StackPanel children;
then, you can add one ore more dependency properties depending on your needs. For example, you might have single property for the source of data and some different properties which you then will use as converter return values. Handle all changes to the source property in your BindingHelper class and change output properties accordingly;
bind other controls to properties of the BindingHelper class using ElementName syntax
in Silverlight, ActualHeight and ActualWidth properties don't do notifications on property updates. So, binding to them won't work.
Note! ActualHeight property's binding is buggy on binding!
Why you inherit DependencyObject when coding a converter? You should just implement IValueConverter.
Try that,
First add MyConverter by the key of "MyConverterResource" on your resources then,
You can do than on XAML side or on cs side by
//You may do it on XAML side <UserControl.Resources>...
this.Resources.Add("MyConverterResource",new MyConverter());
<TextBlock Text="{Binding ActualHeight,ElementName=this
,Converter=MyConverterResource}"/>
public class MyConverter: IValueConverter
{
public object Convert(object value, Type targetType
, object parameter,Globalization.CultureInfo culture)
{
return "Your Height is:"+Value.toString();
}
}
Hope helps

Resources