WPF TemplateBinding does not work for every property - c#

I have created custom control, but TemplateBinding does not work for every property?
Template Binding does start working if I use dummy Converter that just forwards original values.
Simplified example that has the same issue:
<Style TargetType="{x:Type controls:ElipticProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:ElipticProgressBar}">
<ControlTemplate.Resources>
<converters:DebugConverter x:Key="DebugConverter"/>
</ControlTemplate.Resources>
<StackPanel>
<!--BarColor works always-->
<!--BarTickness works-->
<Label Background="{TemplateBinding BarColor}" Content="{TemplateBinding BarTickness}"/>
<!--BarTickness does not works-->
<TextBlock Background="{TemplateBinding BarColor}" Text="{TemplateBinding BarTickness}"/>
<!--BarTickness works-->
<TextBlock Background="{TemplateBinding BarColor}" Text="{Binding BarTickness, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
<!--BarTickness works-->
<TextBlock Background="{TemplateBinding BarColor}" Text="{TemplateBinding BarTickness, Converter={StaticResource DebugConverter}}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
back code:
public class ElipticProgressBar : Control
{
static ElipticProgressBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ElipticProgressBar), new FrameworkPropertyMetadata(typeof(ElipticProgressBar)));
}
public static readonly DependencyProperty BarTicknessProperty = DependencyProperty.Register(
"BarTickness", typeof(int), typeof(ElipticProgressBar), new FrameworkPropertyMetadata(default(int)));
public int BarTickness
{
get { return (int)GetValue(BarTicknessProperty); }
set { SetValue(BarTicknessProperty, value); }
}
public static readonly DependencyProperty BarColorProperty = DependencyProperty.Register(
"BarColor", typeof(Brush), typeof(ElipticProgressBar), new FrameworkPropertyMetadata(default(Brush)));
public Brush BarColor
{
get { return (Brush)GetValue(BarColorProperty); }
set { SetValue(BarColorProperty, value); }
}
}
usage:
controls:ElipticProgressBar BarTickness="30" BarColor="Orange"
DebugConverter:
public class DebugConverter : IValueConverter
{
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;
}
}

{TemplateBinding} is an optimized version of a binding that has some limitations.
In this case you need to change the type of the BarThickness property to string to be able to bind it directly to the Text property of a TextBlock using a {TemplateBinding}.

The docs state that a {TemplateBinding} is
an optimized form of a Binding for template scenarios, analogous to a Binding constructed with {Binding RelativeSource={RelativeSource TemplatedParent}}
with some limitations - most importantly that it works only within the visual tree!
What that means for your specific case: while you cannot use
<TextBlock Text="{TemplateBinding BarTickness}" />
you can (and have to) use
<TextBlock Text="{Binding BarTickness, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" />
because TextBlock.Text property is of type string while the BarTickness is not.
{TemplateBinding} does not do that kind of converting; that's why you need to use the more generic {Binding} or a converter, whose both methods
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;
}
both convert the actual value to an object.

Related

Change Label Foreground if content matches another property value

I have a DataGrid displaying a list of maps with some details. I'm trying to change the foreground colour of the map which is currently active but I'm struggling. I've written the following converter to check if the DataGrid item's value matches the current map's name, but I'm not sure how to pass the name of the current map from XAML.
Converter:
public class StringToBrushConverter : IValueConverter
{
public string CurrentMapName { get; set; }
public Color CurrentMapColor { get; set; }
public Color OtherMapColor { get; set; }
string _value = "";
public StringToBrushConverter(string currentMapName, Color currentMapColor, Color otherMapColor)
{
CurrentMapName = currentMapName;
CurrentMapColor = currentMapColor;
OtherMapColor = otherMapColor;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
_value = value as string;
return (string)value == CurrentMapName ? new SolidColorBrush(CurrentMapColor) : new SolidColorBrush(OtherMapColor);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return _value; //TODO: Will this work?
}
}
public sealed class RaceMapColorConverter : StringToBrushConverter
{
public RaceMapColorConverter() : base("", new Color { R = 255, G = 0, B = 0 }, new Color { R = 255, G = 255, B = 255 }) { }
}
XAML:
<DataGrid x:Name="MapHistory"
AutoGenerateColumns="False"
IsReadOnly="True"
HeadersVisibility="Column"
GridLinesVisibility="None"
CanUserSortColumns="False"
Style="{StaticResource MapTimerDataGrid}"
CellStyle="{StaticResource MapTimerControlCell}"
ItemsSource="{Binding Plan.Plan}">
<DataGrid.Resources>
<!-- The Binding in CurrentMapName is not valid. It says it can only be set on a dependency property but I'm not sure how to do that -->
<conv:RaceMapColorConverter x:Key="MapColorConverter" CurrentMapName="{Binding CurrentMap.MapName}" CurrentMapColor="Red" OtherMapColor="White" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Map" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Map.MapName}" Foreground="{Binding Map.MapName, Converter={StaticResource MapColorConverter}}" Padding="0" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Other columns removed to shorten -->
</DataGrid.Columns>
</DataGrid>
I'm also not sure how to implement 'ConvertBack()`. Will storing the original value in the converter work? or could a new instance be created for the same control? Am I even going about this the right way? Thought it's also worth mentioning that I'm using Caliburn.Micro just in case it has any tricks that may help. Any help would be appreciated.
As a general rule-of-thumb you should use converters when the data conversion needs to be two-way (i.e. view-model to view and back again) and DataTriggers when it's one-way only. That answers your question about ConvertBack (which in the example you've provided never gets called anyway, in which case you can just return Binding.DoNothing).
In this particular case, however, a converter is warranted because you need to compare two bindings. Instead of writing an application-aware converter for this one very specific task, I would instead opt for a more generic MultiConverter that simply compares two values that you provide it:
public class EqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return (values.Length==2) && Object.Equals(values[0], values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
Then back in your application you pass both of your bindings into a MultiBinding of this type and use a DataTrigger to change the map color when the result of the comparison is true:
<Label Content="{Binding Map.MapName}">
<Label.Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="Foreground" Value="Red" /> <!-- normal map color -->
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityConverter}" Mode="OneWay">
<Binding Path="Map.MapName" />
<Binding Path="CurrentMapName" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="White" /> <!-- other map color -->
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>

Redraw DataTemplate of ContentControl

I have the following ContentControl:
<ContentControl
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedEntry}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="controls:HCITextListEntry">
<controls:MyCustomControl
Text="{Binding Text}"
Parameter="{Binding Parameters}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Everytime the SelectedEntry property changes, I want to redraw/reinit MyCustomControl. Actually only the properties are updated.
You may drop the ContentTemplate and write a converter for the Content Binding that returns a MyCustomControl instance:
<ContentControl Content="{Binding SelectedEntry,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource MyCustomControlConverter}}"/>
The converter:
public class MyCustomControlConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var control = new MyCustomControl();
control.SetBinding(MyCustomControl.TextProperty,
new Binding("Text") { Source = value });
control.SetBinding(MyCustomControl.ParameterProperty,
new Binding("Parameters") { Source = value });
return control;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

WPF - Bind ComboBox Item Foreground to Its Value

I created a ComboBox listing the colors that System.Windows.Media.Colors predefines, using the approach told in this question: How can I list colors in WPF with XAML?
My XAML code now is:
<Window ...>
<Window.Resources>
<ObjectDataProvider
ObjectInstance="{x:Type Colors}" MethodName="GetProperties" x:Key="ColorList" />
<local:StringToBrushConverter x:Key="FontColorConversions" />
</Window.Resources>
<Grid Background="Black">
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource ColorList}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource FontColorConversions}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
...
</Grid>
</Window>
And besides, please note that I bind SelectedValue to a VM class's FontColor property, which is of string type.
class FontSetting : INotifyPropertyChanged
{
private string _fontColor = "Lavender"; // initial color
public event PropertyChangedEventHandler PropertyChanged;
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
OnPropertyChanged("FontColor");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I set the DataContext of the Window containing this ComboBox to an instance of FontSetting.
So each item in the ComboBox actually display a string representing a certain color now, what I want to do is set an item's Foreground color to that color its content indicates, like this:
Can anyone help? Thanks.
UPDATED:
Since most of the solutions have a converter which converts string to Brush and actually I already have it, now I want to put mine here, as I binded a TextBox's Foreground to FontSetting's FontColor property, so that when you change the ComboBox, the color of that TextBox changes accordingly.
Here is my converter class, and it works fine by now:
class StringToBrushConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = conv.ConvertFromString("Lavender") as SolidColorBrush;
if (null != value)
{
brush = conv.ConvertFromString(value.ToString()) as SolidColorBrush;
}
return brush;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
When I click the ComboBox to open the dropdown list, I got an exception:
CONCLUSION
Amine's solution works, it's my mistake. I explain briefly now, if you bind a ComboBox to System.Windows.Media.Colors like what I am doing, when the item is rendered, the Convert() method of the converter class (which you assign to the binding) is executed, and actually the value passed to Convert() as its first parameter is a Syetem.Windows.Media.Color instance. I made mistake coz I thought it was of string type.
Therefore, in my case I need two converter classes, one converting string to Brush, and the other one converting Color to Brush. So I will keep my own StringToBrush converter and add Amine's ColorToBrush converter.
However, I simplified Amine's implementation a bit:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = SolidColorBrush)conv.ConvertFromString(FontSetting.DEFAULT_FONT_COLOR);
if (null != value)
{
PropertyInfo pi = value as PropertyInfo;
if (null != pi)
{
brush = conv.ConvertFromString(pi.Name) as SolidColorBrush;
}
}
return brush;
}
Moreover, Joe's input is also valuable, put all them together, I can keep the items' color consistent, which is perfect.
You can set Style of ComboBoxItem as bellow :
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240" x:Name="CB"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource converter}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
By using this converter:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
object obj = ((System.Reflection.PropertyInfo)value).GetValue(this,null);
return (SolidColorBrush)new BrushConverter().ConvertFromString(obj.ToString());
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return value;
}
}
Two ways, use a value converter or an intermediate property. The easiest is probably an intermediate property as you are using well structured bindings already for your SelectedItem (but value converters are fun too!).
SelectedValue is bound to FontColor, so in your setter set another value:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
ForegroundColorToDisplay = GetBrushFromColorString(value);
OnPropertyChanged("FontColor");
}
}
public Brush _foregroundColorToDisplay
public Brush ForegroundColorToDisplay
{
get
{
return _foregroundColorToDisplay;
}
set
{
_foregroundColorToDisplay= value;
OnPropertyChanged("ForegroundColorToDisplay");
}
}
or, if you don't want to store it:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
//note it fires two changed events!
OnPropertyChanged("ForegroundColorToDisplay");
OnPropertyChanged("FontColor");
}
}
public Brush ForegroundColorToDisplay
{
get
{
return GetBrushFromColorString(value);;
}
}
You can bind to this new property in your xaml:
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding ForegroundColorToDisplay, Mode=OneWay}"/>
If you are interested, a value converter would work like this:
Create a class in your code behind (or elsewhere if it's going to be used a lot) that implements IValueConverter, takes the string and returns a Brush:
public class StringToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return GetBrushFromString(value as string)
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
Throw new SomeException();
}
}
and use it in your binding in xaml to convert the selectedItem binding to Foreground brush:
<Window.Resources>
<local:StringToBrushConverter x:Key="converter" />
</Window.Resources>
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding FontColor, Mode=OneWay, Converter={StaticResource converter}}"/>

Control type switch based on data type being input or loaded

I have a field in my UI that can either take a date or a string.
I would like to switch on the type of control displayed depending on the data that is loaded in there or being input by the user.
In other words:
DatePicker if the user starts inputting some numbers (or data loaded is a date)
TextBox is a string input (or string loaded)
Can't find yet how to switch. Happy if you had some tip. Thank you!
You would need to use templates depending on a type. To do this you would need either have 2 properties , one with type of a property and another with the actial object (both notifying INotifyPropertyChanged ).
public object YourProperty
{
get
{
return yourProperty;
}
set
{
yourProperty = value;
OnPropertyChanged();
DateTime date;
if(yourProperty is String && DateTime.TryParse((string) yourProperty, out date))
{
YourProperty = date;
}
}
}
private object yourProperty = string.Empty;
//public Type YourPropertyType { get; set; }
You also can create a converter which will return the type of a property, so you can get rid of additional property (commented out above):
public class TypeOfConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value == null) ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
And finally bind a ContentControl to your property and select a template using the converter above:
<ContentControl Content="{Binding YourProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ContentControl.Resources>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=YourProperty,Converter={flowMathTest:TypeOfConverter}}" Value="{x:Type system:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Content, RelativeSource={RelativeSource AncestorType=ContentControl}, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=YourProperty,Converter={flowMathTest:TypeOfConverter}}" Value="{x:Type system:String}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Content, RelativeSource={RelativeSource AncestorType=ContentControl}, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Resources>
</ContentControl>
Thant should do it.
EDIT: I did not read the second part so the switching between controls should happen as user writes.
What you can do in this case is to change bindings to Mode=TwoWay and UpdateSourceTrigger=PropertyChanged and handle detection of type in code.
By default set YourProperty to string.Empty. Then on every change check if the text entered is already date using DateTime.Parse. If it is, set YourProperty to this date.
So I updated the code above to reflect these changes.

Is there a way to bind the Enabled property of a button to a checkbox's Checked property?

I want to bind IsEnabled of Button in WPF as follows:
WPF Code:
<Button Content="TestButton" IsEnabled="{Binding ??}" />
C# code:
private MyObjectClass _Checked;
public MyObjectClass Checked
{
get { return _Checked; }
set
{
_Checked = value;
RaisePropertyChanged("Checked");
}
}
In WPF code above, I want the button to be enabled only when Checked object is not null. I know one way is to have a bool property in C# code which tells me whether the Checked object is null or not, and then bind it to IsEnabled property. I want to know if there is a way I can bind IsEnabled to Checked object directly?
Use DataTrigger and check for {x:Null} value of binding:
<Button Content="TestButton">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Checked}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Also, you can use IValueConverter which will return false if value is null otherwise true.
public class ObjectToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
bool returnValue = true;
if (value == DependencyProperty.UnsetValue || value == null)
{
returnValue = false;
}
return returnValue;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
return Binding.DoNothing;
}
}
and bind in XAML:
<Button Content="TestButton"
IsEnabled="{Binding Checked,
Converter={StaticResource ObjectToBoolConverter}}" />
Ofcourse you need to declare instance of converter in your XAML for this.
You can use a converter to convert an object into a bool. Look into IValueConverter.
public class IsNotNullToBoolConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Two-way binding not supported by IsNotNullToBoolConverter");
}
}
And your xaml would look like this:
<Window.Resources>
<local:IsNotNullToBoolConverter x:Key="IsNotNull" />
</Window.Resources>
...
<Button IsEnabled="{Binding Converter={StaticResource IsNotNull}}" />
I know this is an old issue but you can do this without an extra code. Just add the "TargetNullValue=false" to the binding.
IsEnabled="{Binding SomeProperty, TargetNullValue=false}"

Categories

Resources