Updating the value of another property when a DependencyProperty changes - wpf

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}}" />

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>

How can I set a Static/Dynamic resource from a binding value?

I want to add dynamic items with a datatemplate that contains a TextBlock control, but the text of the TextBlock control will be selected from a XAML ResourceDictionary. The staticresource name will be obtained based on the result of the binding value.
How can I do that?
I'm trying something like this, but doesn't works.
<DataTemplate x:Key="languageItemTemplate">
<ContentControl>
<StackPanel>
<TextBlock Text="{StaticResource {Binding ResourceName}}"></TextBlock>
<TextBlock Text="{DynamicResource {Binding ResourceName}}"></TextBlock>
</StackPanel>
</ContentControl>
</DataTemplate>
UPDATE
Thanks to Tobias, the fist option of his answer works. But I need to instance the converter first to get it work. Which one is the best idea to do that?
In the application_startup method and use it for all the application or in the Window.Resources of the window I use the converter?
Maybe a merge of both and do that on the Application.Resources?
thanks for your answer.
private void Application_Startup(object sender, StartupEventArgs e)
{
LoadConverters();
}
private void LoadConverters()
{
foreach (var t in System.Reflection.Assembly.GetExecutingAssembly().GetTypes())
{
if (t.GetInterfaces().Any(i => i.Name == "IValueConverter"))
{
Resources.Add(t.Name, Activator.CreateInstance(t));
}
}
}
OR
<local:BindingResourceConverter x:Key="ResourceConverter"/>
<DataTemplate x:Key="languageItemTemplate">
<ContentControl>
<StackPanel>
<TextBlock Text="{Binding Name, Converter={StaticResource ResourceConverter }}" />
</StackPanel>
</ContentControl>
</DataTemplate>
If the resource is an application level resource you could simply use a converter to convert from the resource name to the actual object like this:
public class BindingResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string resourceKey = value as string;
if (!String.IsNullOrEmpty(resourceKey))
{
var resource = Application.Current.FindResource(resourceKey);
if (resource != null)
{
return resource;
}
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And use it like this:
<TextBlock Text="{Binding ResourceKey, Converter={StaticResource ResourceConverter}}" />
If the resource is in a local scope, we need a reference to the control to search its resources. You can get the resource name and the control by using an attached property:
public class TextBlockHelper
{
public static readonly DependencyProperty TextResourceKeyProperty =
DependencyProperty.RegisterAttached("TextResourceKey", typeof(string),
typeof(TextBlockHelper), new PropertyMetadata(String.Empty, OnTextResourceKeyChanged));
public static string GetTextResourceKey(DependencyObject obj)
{
return (string)obj.GetValue(TextResourceKeyProperty);
}
public static void SetTextResourceKey(DependencyObject obj, string value)
{
obj.SetValue(TextResourceKeyProperty, value);
}
private static void OnTextResourceKeyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
string resourceKey = e.NewValue as string;
if(d is TextBlock tb)
{
var r = tb.TryFindResource(resourceKey);
if (r != null)
{
tb.Text = r.ToString();
}
}
}
}
And you can use it like this:
<Grid>
<Grid.Resources>
<sys:String x:Key="SomeLocalResource">LocalResource</sys:String>
</Grid.Resources>
<TextBlock h:TextBlockHelper.TextResourceKey="{Binding ResourceKey}" />
</Grid>

WPF/XAML: How to make all text upper case in TextBlock?

I want all characters in a TextBlock to be displayed in uppercase
<TextBlock Name="tbAbc"
FontSize="12"
TextAlignment="Center"
Text="Channel Name"
Foreground="{DynamicResource {x:Static r:RibbonSkinResources.RibbonGroupLabelFontColorBrushKey}}" />
The strings are taken through Binding. I don't want to make the strings uppercase in the dictionary itself.
Or use
Typography.Capitals="AllSmallCaps"
in your TextBlock definition.
See here: MSDN - Typography.Capitals
EDIT:
This does not work in Windows Phone 8.1, only in Windows 8.1 ...
Implement a custom converter.
using System.Globalization;
using System.Windows.Data;
// ...
public class StringToUpperConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is string )
{
return ((string)value).ToUpper();
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then include that in your XAML as a resource:
<local:StringToUpperConverter x:Key="StringToUpperConverter"/>
And add it to your binding:
Converter={StaticResource StringToUpperConverter}
You can use an attached property like this:
public static class TextBlock
{
public static readonly DependencyProperty CharacterCasingProperty = DependencyProperty.RegisterAttached(
"CharacterCasing",
typeof(CharacterCasing),
typeof(TextBlock),
new FrameworkPropertyMetadata(
CharacterCasing.Normal,
FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.NotDataBindable,
OnCharacterCasingChanged));
private static readonly DependencyProperty TextProxyProperty = DependencyProperty.RegisterAttached(
"TextProxy",
typeof(string),
typeof(TextBlock),
new PropertyMetadata(default(string), OnTextProxyChanged));
private static readonly PropertyPath TextPropertyPath = new PropertyPath("Text");
public static void SetCharacterCasing(DependencyObject element, CharacterCasing value)
{
element.SetValue(CharacterCasingProperty, value);
}
public static CharacterCasing GetCharacterCasing(DependencyObject element)
{
return (CharacterCasing)element.GetValue(CharacterCasingProperty);
}
private static void OnCharacterCasingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is System.Windows.Controls.TextBlock textBlock)
{
if (BindingOperations.GetBinding(textBlock, TextProxyProperty) == null)
{
BindingOperations.SetBinding(
textBlock,
TextProxyProperty,
new Binding
{
Path = TextPropertyPath,
RelativeSource = RelativeSource.Self,
Mode = BindingMode.OneWay,
});
}
}
}
private static void OnTextProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(System.Windows.Controls.TextBlock.TextProperty, Format((string)e.NewValue, GetCharacterCasing(d)));
string Format(string text, CharacterCasing casing)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
switch (casing)
{
case CharacterCasing.Normal:
return text;
case CharacterCasing.Lower:
return text.ToLower();
case CharacterCasing.Upper:
return text.ToUpper();
default:
throw new ArgumentOutOfRangeException(nameof(casing), casing, null);
}
}
}
}
Then usage in xaml will look like:
<StackPanel>
<TextBox x:Name="TextBox" Text="abc" />
<TextBlock local:TextBlock.CharacterCasing="Upper" Text="abc" />
<TextBlock local:TextBlock.CharacterCasing="Upper" Text="{Binding ElementName=TextBox, Path=Text}" />
<Button local:TextBlock.CharacterCasing="Upper" Content="abc" />
<Button local:TextBlock.CharacterCasing="Upper" Content="{Binding ElementName=TextBox, Path=Text}" />
</StackPanel>
If it's not a big deal you could use TextBox instead of TextBlock like this:
<TextBox CharacterCasing="Upper" IsReadOnly="True" />
While there's already a great answer here that uses a converter, I'm providing an alternative implementation that simplifies the conversion to a single line (thanks to null coalescing), as well as making it a subclass of MarkupExtension so it's easier to use in XAML.
Here's the converter...
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace IntuoSoft.Wpf.Converters {
[ValueConversion(typeof(string), typeof(string))]
public class CapitalizationConverter : MarkupExtension, IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> (value as string)?.ToUpper() ?? value; // If it's a string, call ToUpper(), otherwise, pass it through as-is.
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
public override object ProvideValue(IServiceProvider serviceProvider) => this;
}
}
And here's how you use it (Note: This assumes the above namespace is prefixed with is in your XAML):
<TextBlock Text={Binding SomeValue, Converter={is:CapitalizationConverter}}" />
Because it's a MarkupExtension subclass, you can simply use it right where/when it's needed. No need to define it in the resources first.
I use a character casing value converter:
class CharacterCasingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = value as string;
if (s == null)
return value;
CharacterCasing casing;
if (!Enum.TryParse(parameter as string, out casing))
casing = CharacterCasing.Upper;
switch (casing)
{
case CharacterCasing.Lower:
return s.ToLower(culture);
case CharacterCasing.Upper:
return s.ToUpper(culture);
default:
return s;
}
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

WPF - Attached property needs to reference another bound attached property

I have created an attached property to set the visibility of a UIElement based on a particular enum value. This works fine. However, I need to extend this so that the visibility can be overriden based on the "status" of the sender.
How can I achieve this? I had thought that I could create another attached property which the first attached property could reference, however I need to be able to bind a value to the second attached property rather than just set to an enum value.
EDIT
Below is an example of my problem:
<Window x:Class="AttachedProperty.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:attachedProperty="clr-namespace:AttachedProperty"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<TextBlock Text="Button should be enabled?"/>
<CheckBox IsChecked="{Binding Path=CanClick}"/>
<Button Content="Click Me" IsEnabled="{Binding Path=CanClick}"/>
<Button Content="Manager Only Click" attachedProperty:SecurityBehavior.IsEnabledRole="Mgr"/>
</StackPanel>
</Grid>
The first button's enabled property is just controlled using the checkbox.
The second button's enabled property is controlled by an attachedProperty that determines if you are in the correct security group:
public class SecurityBehavior
{
public static readonly DependencyProperty IsEnabledRoleProperty = DependencyProperty.RegisterAttached(
"IsEnabledRole", typeof (string), typeof (SecurityBehavior), new UIPropertyMetadata(OnIsEnabledRoleChanged));
private static void OnIsEnabledRoleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// In here have some logic that determines if the current user is authorised
// Principal.IsInRole(e.NewValue.ToString() ? true : false;
sender.SetValue(UIElement.IsEnabledProperty, true);
}
public static void SetIsEnabledRole(DependencyObject element, string value)
{
element.SetValue(IsEnabledRoleProperty, value);
}
public static string GetIsEnabledRole(DependencyObject element)
{
return (string) element.GetValue(IsEnabledRoleProperty);
}
}
When run both buttons are enabled - the 1st because the checkbox is checked and the second because I am a manager. When I uncheck the checkbox the 1st button is disabled and I want my attached property to be able to only enable if in the correct security group AND the checkbox is checked.
How can I change by sample so that I can get have the behavior that sets the IsEnabled based on 2 possible inputs?
Not sure why you're after attached properties for this. Based on your requirement you could pull this off with a simple IValueConverter and a Binding for the Visibility of the final control.
So say we have an enum:
public enum MyEnum {
StateOne,
StateTwo
}
and a CheckBox such as:
<CheckBox x:Name="chkBox"
Content="Check Me!!!" />
Now if we want some Button's Visibility to only be visible when enum is StateOne and the checkbox is checked,
we could just have a converter such as:
public class MyConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var checkBoxIsChecked = (bool)value;
var givenEnum = (MyEnum)parameter;
return checkBoxIsChecked && givenEnum == MyEnum.StateOne ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
and in xaml for the Button's:
<StackPanel>
<StackPanel.Resources>
<local:MyConverter x:Key="MyConverter" />
</StackPanel.Resources>
<CheckBox x:Name="chkBox"
Content="Check Me!!!" />
<Button Content="Button One"
Visibility="{Binding ElementName=chkBox,
Path=IsChecked,
Converter={StaticResource MyConverter},
ConverterParameter={x:Static local:MyEnum.StateOne}}" />
<Button Content="Button Two"
Visibility="{Binding ElementName=chkBox,
Path=IsChecked,
Converter={StaticResource MyConverter},
ConverterParameter={x:Static local:MyEnum.StateTwo}}" />
</StackPanel>
With this, "Button One" will become visible when the checkbox is checked, however button two will not as the ConverterParameter passed in for button two is StateTwo.
If it's the IsEnabled state you want to control of the Button, just switch the binding to that property and in the converter just return true or false accordingly instead of Visibility.Visible
Even if you choose to provide the enum value not static and dynamic, you could just make the Binding a MultiBinding and switch the IValueConverter to an IMultiValueConverter
Update:
If for whatever reason you have to go down the route of attached properties, then in the property changed callback of each attached property get the other properties value from the sender and perform your logic accordingly.
public class SecurityBehavior {
public static readonly DependencyProperty IsEnabledRoleProperty = DependencyProperty.RegisterAttached(
"IsEnabledRole",
typeof(string),
typeof(SecurityBehavior),
new UIPropertyMetadata(OnIsEnabledRoleChanged));
public static readonly DependencyProperty IsEnabled2RoleProperty = DependencyProperty.RegisterAttached(
"IsEnabled2Role",
typeof(bool),
typeof(SecurityBehavior),
new UIPropertyMetadata(OnIsEnabled2RoleChanged));
private static void OnIsEnabledRoleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
HandleAttachedPropertyUpdate(sender, (string)e.NewValue, GetIsEnabled2Role(sender));
}
private static void OnIsEnabled2RoleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
HandleAttachedPropertyUpdate(sender, GetIsEnabledRole(sender), (bool)e.NewValue);
}
private static void HandleAttachedPropertyUpdate(DependencyObject sender, string isEnabledRole, bool isEnabled2Role) {
sender.SetValue(UIElement.IsEnabledProperty, isEnabledRole == "Mgr" && isEnabled2Role);
}
public static void SetIsEnabledRole(DependencyObject element, string value) {
element.SetValue(IsEnabledRoleProperty, value);
}
public static string GetIsEnabledRole(DependencyObject element) {
return (string)element.GetValue(IsEnabledRoleProperty);
}
public static void SetIsEnabled2Role(DependencyObject element, bool value) {
element.SetValue(IsEnabled2RoleProperty, value);
}
public static bool GetIsEnabled2Role(DependencyObject element) {
return (bool)element.GetValue(IsEnabled2RoleProperty);
}
}
and xaml:
<StackPanel>
<CheckBox x:Name="chkBox"
Content="Check Me!!!" />
<Button Content="Button One"
local:SecurityBehavior.IsEnabled2Role="{Binding ElementName=chkBox,
Path=IsChecked}"
local:SecurityBehavior.IsEnabledRole="Mgr" />
<Button Content="Button Two"
local:SecurityBehavior.IsEnabled2Role="{Binding ElementName=chkBox,
Path=IsChecked}"
local:SecurityBehavior.IsEnabledRole="NotMgr" />
</StackPanel>

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