WPF RelativeSource UpdateSourceTrigger=PropertyChanged not worked - wpf

I'm using this DataTrigger:
<Window x:Class="_11_5_Style_demo4_DataTrigger.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:_11_5_Style_demo4_DataTrigger"
mc:Ignorable="d"
Title="MainWindow" Height="130" Width="300">
<Window.Resources>
<local:L2BConverter x:Key="cvtr"/>
<!--TextBox DataTrigger-->
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={x:Static RelativeSource.Self},
Path=Text.Length,
Converter={StaticResource cvtr},
UpdateSourceTrigger=PropertyChanged}"
Value="false">
<DataTrigger.Setters>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="1"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Margin="5"/>
<TextBox Margin="5,0"/>
<TextBox Margin="5"/>
</StackPanel>
</Window>
I expected that the TextBox will have the red border when typed more than 6 characters, and UpdateSourceTrigger=PropertyChanged just not work when there are more than 6 characters in the TextBox. It updated only when lost focus.
Here is the Converter:
public class L2BConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int textLength = (int)value;
return textLength < 6 ? true : false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I've googled, but didn't find relative problem. Can anybody explains why this not work, am I not using it right?

Your trigger is working correctly, but your expectations of the result of its work are not correct. The trigger correctly sets the border brush, but the problem is that the color of the border while the TextBox has focus is not taken from the TextBox.BorderBrush brush, but from the TextBox Template constant. And you can't change it with a trigger. You need to change the template of the TextBox itself or apply another way to solve your problem.
You can make sure that the trigger works correctly, for example, by changing the frame thickness:
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={x:Static RelativeSource.Self},
Path=Text.Length,
Converter={StaticResource cvtr}}"
Value="false">
<DataTrigger.Setters>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="10"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
Another way to implement such validation is to use a ValidationRule. But its use is only possible in a binding that you don't have. You can use a little "voodoo magic" for this:
public class LengthValidate : ValidationRule
{
public LengthValidate() :base(ValidationStep.UpdatedValue, true) { }
public int LengthLimit { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int limit = LengthLimit;
if(value is BindingExpression expression)
{
value = expression.GetSourceValue();
}
if (value is not string text)
{
text= value?.ToString()?? string.Empty;
}
return text.Length <= limit
? ValidationResult.ValidResult
: new ValidationResult(false, $"String length exceeds limit={limit}.");
}
}
The GetSourceValue method from the BindingExpressionHelper class is used.
Style with this rule:
<Style TargetType="TextBox">
<Setter Property="Tag">
<Setter.Value>
<Binding Path="Text" RelativeSource="{RelativeSource Self}">
<Binding.ValidationRules>
<local:LengthValidate LengthLimit="6"/>
</Binding.ValidationRules>
</Binding>
</Setter.Value>
</Setter>
</Style>

Related

WPF - Binding data template

I'm trying to bind <DataTemplate> on Telerik RadDiagram ShapeStyle:
<telerik:RadDiagram x:Name="Diagram"
ShapeStyle="{StaticResource NodeStyle}"
GraphSource="{Binding GraphSource}"/>
This style looks like this:
<Grid.Resources>
<Style x:Key="NodeStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<MultiBinding Converter="{StaticResource StyleConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}"/>
<Binding Path=""/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
...
</Grid.Resources>
StyleConverter:
class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
if (values[0] is BilledMsisdnViewModel)
return targetElement.FindResource("BilledMsisdnControl");
else if(values[0] is BilledImeiViewModel)
return targetElement.FindResource("ImeiControl");
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Those resources (BilledMsisdnControl and ImeiControl) are defined in <UserControl.Resources>:
<UserControl.Resources>
<ResourceDictionary>
<local:BilledMsisdnControl x:Key="BilledMsisdnControl"/>
<local:ImeiControl x:Key="ImeiControl"/>
<local:StyleConverter x:Key="StyleConverter"/>
</ResourceDictionary>
</UserControl.Resources>
And they are created by me user controls.
So my goal here is according to binded view model type select user controll (this part works) and then use it as ContentTemplate (this don't work). I'm getting just object namespace nad name for ImeiControl or BilledMsisdnControl in diagram node.
How can I fix this?
According to the documentation you should use style selector
public class NodeStyleSelector : StyleSelector
{
public Style BilledMsisdnStyle { get; set; }
public Style BilledImeiStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is BilledMsisdnViewModel)
return BilledMsisdnStyle;
else if (item is BilledImeiViewModel)
return BilledImeiStyle;
else return base.SelectStyle(item, container);
}
}
Define styles with ContentTemplate. Inside ContentTemplate add your user controls.
<Style x:Key="BilledMsisdnStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<local:BilledMsisdnControl/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BilledImeiStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<local:ImeiControl/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Add shape style selector
<styleselectors:NodeStyleSelector x:Key="CustomShapeStyleSelector"
BilledMsisdnStyle="{StaticResource BilledMsisdnStyle}"
BilledImeiStyle="{StaticResource BilledImeiStyle}" />
Use ShapeStyleSelector for your RadDiagram.
<telerik:RadDiagram x:Name="Diagram"
ShapeStyleSelector="{StaticResource CustomShapeStyleSelecto}"
GraphSource="{Binding GraphSource}"/>

WPF converter for labels' content

I'm trying to override the output of a label, say it contained "Account" and a client wants account rendered as "Member" (so kind of think of this as a localisation converter?)
My Question; is this possible with "hardcoded" content? or MUST i create a static file containing all label content (with iNotifiyStatic of course)? *for binding?
xaml:
<Label Style="{StaticResource LabelLeft}" Content="Account Name:"></Label>
Resource File: Including all attempts made, from multiple sources heres the most meaningful one.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters ="clr-namespace:Company.Resources.Converters">
<converters:LocationLabelsConverter x:Key="LocationLabelsConverter" />
<Style x:Key="LabelLeft" TargetType="{x:Type Label}" >
<Setter Property="Margin" Value="10 0 0 0"></Setter>
<Setter Property="Height" Value="22"></Setter>
<Setter Property="Padding" Value="0 0 0 0"></Setter>
<Setter Property="VerticalContentAlignment" Value="Center"></Setter>
<!-- Att1 -->
<!--<Setter Property="TextBlock.Text" Value="{Binding RelativeSource={RelativeSource self},
Path=Content,
Converter={StaticResource LocationLabelsConverter}}"></Setter>-->
<!-- Att2 -->
<!--<Setter Property="Content">
<Setter.Value>
<Binding Path="Content" RelativeSource="{RelativeSource self}">
<Binding.Converter>
<converters:LocationLabelsConverter/>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>-->
<!-- Att3 -->
<!--<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource self},
Path=Content,
Converter={StaticResource LocationLabelsConverter}}">
<Setter Property="Content" Value="Test123"/>
</DataTrigger>
</Style.Triggers>-->
</Style>
And here's the converter:
[ValueConversion(typeof(string), typeof(string))]
public sealed class LocationLabelsConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value != null)
{
return "hello sweety";// (string)value; //The goal here in the end is to run it through a method to replace string with correct text.
}
else return null;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (string)value;
}
}
you can apply converter like this:
<Label Content="{Binding Source='Account Name:', Converter={StaticResource LocationLabelsConverter}"/>

define a ToolTip Style that hides a tooltip if Converter(ToolTip) returns fale

I want to hide empty ToolTips
this is what worked up to a certain Point:
<Style TargetType="ToolTip">
<Style.Triggers>
<Trigger Property="Content"
Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility"
Value="Collapsed" />
</Trigger>
<Trigger Property="Content"
Value="{x:Null}">
<Setter Property="Visibility"
Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
Problem is: Trigger only operates on null or string.IsEmpty
I want to catch whitespace tooltips as well.
so I tried:
<Style TargetType="ToolTip">
<Setter Property="Visibility">
<Setter.Value>
<Binding > <!------------ what comes here? -->
<Binding.Converter>
<loc:NotEmptyToVisibilityConverterEx/>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
</Style>
My Problem is, I'm not able to (dunno how) access the actual tooltip text.
[ValueConversion(typeof(string), typeof(Visibility))]
public class NotEmptyToVisibilityConverterEx : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var visible = !string.IsNullOrEmpty(((string) value).Trim());
return visible ? Visibility.Visible : Visibility.Collapsed;
}
}
Anybody have an idea?
This should work:
<Window x:Class="HideEmptyToolTipsWithTrigger.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HideEmptyToolTipsWithTrigger"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:EmptyToolTipToBooleanConverter x:Key="EmptyToolTipToBooleanConverter" />
<Style TargetType="ToolTip">
<Style.Triggers>
<DataTrigger Binding="{Binding Content, RelativeSource={RelativeSource Self}, Converter={StaticResource EmptyToolTipToBooleanConverter}}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBlock Text="This TextBlock has ToolTip" ToolTip="Almafa"></TextBlock>
<TextBlock Text="This TextBlock has ToolTip with whitespaces" ToolTip=" "></TextBlock>
<TextBlock Text="This TextBlock has ToolTip with empty string" ToolTip="{x:Static system:String.Empty}"></TextBlock>
</StackPanel>
</Window>
And the converter:
public class EmptyToolTipToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var contentAsString = value as string;
if (contentAsString != null)
{
return string.IsNullOrEmpty(contentAsString.Trim());
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note that a ToolTip can have any custom content, not just a string.
For example:
<TextBlock Text="This TextBlock has a complex ToolTip">
<TextBlock.ToolTip>
<StackPanel>
<TextBlock Text="First line"></TextBlock>
<TextBlock Text="Second line"></TextBlock>
</StackPanel>
</TextBlock.ToolTip>
</TextBlock>
This converter shows these tooltips.

Simple WPF IValueConverter and DataTrigger not working together

I've been having trouble using a value converter with a data trigger. In some of my code it seems like the DataTrigger's Path is being applied to the root element, rather than the element which the style applies to.
I created a simple test case, and I don't understand its behavior. I'm expecting the Button to turn red or blue depending on which value is being fed to the DataTrigger's converter, but the Button isn't being affected at all!
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SimpleWpfApplication"
x:Class="SimpleWpfApplication.SimpleUserControl"
ToolTip="UserControl ToolTip">
<UserControl.Resources>
<local:SimpleConverter x:Key="SimpleConverter" />
</UserControl.Resources>
<Button ToolTip="Button ToolTip">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=ToolTip, Converter={StaticResource SimpleConverter}}"
Value="Button ToolTip">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger
Binding="{Binding Path=ToolTip, Converter={StaticResource SimpleConverter}}"
Value="UserControl ToolTip">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</UserControl>
And a simple converter:
class SimpleConverter : 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)
{
throw new InvalidOperationException("SimpleConverter is a OneWay converter.");
}
}
Why isn't Convert being called? Why doesn't the Button turn red or blue?
Found the answer in another StackOverflow question: What’s wrong with my datatrigger binding?
The answer is to add RelativeSource={RelativeSource Self} to the binding:
<DataTrigger Binding="{Binding Path=ToolTip,
RelativeSource={RelativeSource Self},
Converter={StaticResource SimpleConverter}}" />

WPF Datatrigger not firing when expected

I have the following XAML:
<TextBlock Text="{Binding ElementName=EditListBox, Path=SelectedItems.Count}" Margin="0,0,5,0"/>
<TextBlock Text="items selected">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=EditListBox, Path=SelectedItems.Count}" Value="1">
<Setter Property="TextBlock.Text" Value="item selected"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
The first text block happily changes with SelectedItems.Count, showing 0,1,2, etc. The datatrigger on the second block never seems to fire to change the text.
Any thoughts?
Alternatively, you could replace your XAML with this:
<TextBlock Margin="0,0,5,0" Text="{Binding ElementName=EditListBox, Path=SelectedItems.Count}"/>
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="items selected"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=EditListBox, Path=SelectedItems.Count}" Value="1">
<Setter Property="Text" Value="item selected"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Converters can solve a lot of binding problems but having a lot of specialized converters gets very messy.
The DataTrigger is firing but the Text field for your second TextBlock is hard-coded as "items selected" so it won't be able to change. To see it firing, you can remove Text="items selected".
Your problem is a good candidate for using a ValueConverter instead of DataTrigger. Here's how to create and use the ValueConverter to get it to set the Text to what you want.
Create this ValueConverter:
public class CountToSelectedTextConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value == 1)
return "item selected";
else
return "items selected";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Add the namespace reference to your the assembly the converter is located:
xmlns:local="clr-namespace:ValueConverterExample"
Add the converter to your resources:
<Window.Resources>
<local:CountToSelectedTextConverter x:Key="CountToSelectedTextConverter"/>
</Window.Resources>
Change your second textblock to:
<TextBlock Text="{Binding ElementName=EditListBox, Path=SelectedItems.Count, Converter={StaticResource CountToSelectedTextConverter}}"/>

Resources