How to bind ToolTip content in a custom textblock styles property setter - c#

I am trying to create a textblock that shows a tooltip of the textblock's text when its being trimmed. I have the actual visibility part of this taken care of in a converter. My problem is binding the ToolTip's content to its partent textblock's text. I have been fiddling with different relative paths for awhile now and can never get anything but a blank tooltip. The text shows up fine when I want if I hardcode something in the tooltips content.
<Style x:Key="InfoToolTipBaseTextBlockStyle" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockBase}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Visibility="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget, Converter={StaticResource TrimmedVisibilityConverter}}" Content="{Binding Path=Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBlock}}}"/>
</Setter.Value>
</Setter>
</Style>

Instead of creating a tooltip that's always there, setting its content, and then switching its visibility, just have a style trigger on the TextBlock that sets the ToolTip property to Text when needed.
I've used your existing converter, but you might want to rewrite it to return bool. Then you'd have Value="True" in the DataTrigger.
<Style
x:Key="InfoToolTipBaseTextBlockStyle"
TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource TextBlockBase}"
>
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource TrimmedVisibilityConverter}}"
Value="Visible"
>
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Self}}" />
</DataTrigger>
</Style.Triggers>
</Style>
I don't know if this is an issue for you, but if Text changes at runtime, the the tooltip won't be updated, because that Binding doesn't know you care about the Text property. The fix for that would be to rewrite the converter as IMultiValueConverter so you can use it with a MultiBinding that would have bindings to Text as well as Self. It wouldn't have to use Text, but it would update the target when Text changes.
A better solution, simpler and WPFish, would be to write a behavior for TextBlock that recycles the guts of your converter, handles change notifications on Text, and updates an attached TextBlockExt.IsTextElided bool property on the TextBlock.

Solved it simply by this.
<Style x:Key="InfoToolTipBaseTextBlockStyle" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockBase}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Visibility="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget, Converter={StaticResource TrimmedVisibilityConverter}}"
Content="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Text}"/>
</Setter.Value>
</Setter>
</Style>

Related

DataTrigger to a control's Validation.HasError

I have a user control which uses the INotifyDataErrorInfo interface and it turns red when it has errors, inside of this user control I put a TextBlock and the following DataTrigger doesn't seem to work:
<TextBlock Text="{Binding DurationText}"
Grid.Row="1">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ActivityUserControl, Path=(Validation.HasError)}"
Value="True">
<Setter Property="Foreground"
Value="White">
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I made sure that the x:Name of my user control is correct (ActivityUserControl), the user control itself turns red when it has errors but the TextBlock's DataTrigger doesn't work (text stays black).
Your should set the Value of the DataTrigger to begin with:
<DataTrigger Binding="{Binding ElementName=ActivityUserControl, Path=(Validation.HasError)}"
Value="True">
...
You should also make sure that the ActivityUserControl is in the same namescope as the TextBlock and that it acually contains some validation errors.
You can solve the namescope issue by binding to the parent UserControl using the RelativeSource property:
Binding="{Binding Path=(Validation.HasError),
RelativeSource={RelativeSource AncestorType=UserControl}}"

TemplatedParent is null when used inside a ControlTemplate's DataTrigger

Consider this (edited-down) Style, designed for a Button whose Content is a String:
<Style x:Key="Test" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel>
<TextBlock x:Name="text" Text="{TemplateBinding Content}" />
<TextBlock x:Name="demo" Text="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}">
<DataTrigger.Value>
<system:String>Test</system:String>
</DataTrigger.Value>
<Setter TargetName="test" Property="Foreground" Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The intention in this example is to turn the button text red if it equals the word "Test"1. But it doesn't work, because the trigger's TemplatedParent binding resolves to null instead of to the Button the Style is applied to. However, the TextBlock named "demo" will have its Text set to "System.Windows.Controls.Button: [ButtonText]" as expected, which means TemplatedParent works correctly at that level. Why doesn't it work inside the DataTrigger?
1 I know there are other ways to achieve that, but I'm trying to understand why the binding doesn't work the way I expect it to.
TemplatedParent in your ControlTemplate.Triggers is not what you expect. Inside trigger it actually references Button.TemplatedParent. As such, it will only be non-null if your create that button inside template. You don't create button inside template, so it is null in your case. Now consider this xaml:
<Window.Resources>
<Style x:Key="Test"
TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel>
<TextBlock x:Name="text"
Text="dummy" />
<TextBlock x:Name="demo"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}">
<DataTrigger.Value>
<system:String>Test</system:String>
</DataTrigger.Value>
<Setter TargetName="text"
Property="Foreground"
Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Test2" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Button Style="{StaticResource Test}"></Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<!--<Button Content="Test" Style="{StaticResource Test}"/>-->
<ContentControl Style="{StaticResource Test2}" Content="Test" />
</Grid>
Here I retemplate ContentControl and inside template I use button with your template. If you run this code, you will see "dummy" text in red, because Button.TemplatedParent is now ContentControl, and it has it's Content equals "Test", which confirms what I said above.
Now back to your problem: just change RelativeSource TemplatedParent to RelativeSource Self (no need to change DataTrigger to Trigger) - this one would reference your Button.
I think it might be a similar issue in .NET Core WPF.
My DataTrigger was not firing with {Binding MyProp, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Convert}}, but instead when I changed the RelativeSource to Self the binding started to work. I'm not sure whether it's a hack or a solution, but it worked.
Maybe it's worth mentioning that my template was based on MyView (see below) and I was binding to a DependencyProperty on MyView.
So my final code looked like this:
<ControlTemplate x:Key="Template" TargetType="{x:Type ns:MyView}">
<!-- Some template -->
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding MyProp, RelativeSource={RelativeSource Self}, Converter={StaticResource Convert}}" Value="True">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
I'm not quite sure, but I think the trigger equals by referenc, because Content returns an Object. So it will never be true with your string defined within the trigger.

WPF Style Triggers for DataTemplates

I would like to set Triggers for the controls in a DataTemplate. Whenever I set a property of the control within the DataTemplate, it seems not working. However, If do not set the property within the TextBlock inside the DataTemplate, then I can see the effect of Trigger in the style (it works). I am not sure whether using Style Triggers with DataTemplate is good or not! The XAML is below;
<Grid>
<Grid.Resources>
<Style TargetType="TextBlock" x:Key="BlockOf">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="22" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
...........
DataTemplate for the button,
<Button.ContentTemplate>
<DataTemplate DataType="Button">
<TextBlock Style="{DynamicResource BlockOf}" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"
FontStyle="Italic" FontSize="9"/>
</DataTemplate>
</Button.ContentTemplate>
I can see two problems here. First one is that current trigger will work only for TextBlock inside Button, not over whole Button. You can change that by using DataTrigger with RelativeSource binding. Second problem is that even when mouse is over TextBlock Style.Trigger cannot overwrite local value that you set against TextBlock so you need to bring default values as Setter into your Style. Check Dependency Property Setting Precedence List
<Style TargetType="TextBlock" x:Key="BlockOf">
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="FontSize" Value="9"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=IsMouseOver}" Value="True">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="22" />
</DataTrigger>
</Style.Triggers>
</Style>
and then TextBlock simply
<TextBlock Style="{DynamicResource BlockOf}" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />

Make xaml datatrigger work in c# code

I am new to xaml, i have following code below, my question is how can i call InvalidForeground from c# code to change the color of checkbox text?
<ControlTemplate x:Key="ItemTemplate"
TargetType="ListViewItem">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="CkBoxVisual">
<CheckBox.IsChecked>
<Binding Path="IsSelected"
Mode="TwoWay">
<Binding.RelativeSource>
<RelativeSource Mode="TemplatedParent" />
</Binding.RelativeSource>
</Binding>
</CheckBox.IsChecked>
<DataTrigger Binding="{Binding InvalidForeground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Value="true">
<Setter TargetName="CkBoxVisual" Property="Foreground" Value="Red"/>
</DataTrigger>
</CheckBox>
<ContentPresenter />
</StackPanel>
</ControlTemplate>
In your code, you are not calling anything. In it, you're hoping a change of dependency property..
But a control of type Window does not have a dependency property with the name: "InvalidForeground".
This trigger will never be triggered.
What is your goal, change a property or be notified of a change (trigger)?
Edit: There are a number of rules you must follow:
1) The control referenced in Binding property from DataTrigger (RelativeSource):
Binding="{Binding Path=InvalidForeground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Must have a dependency property called "InvalidForeground", which works correctly, based on this, will not work:
{x:Type Window}
its type must be declared, will work, for example:
{x:Type my:ControlName}
2) A property changed by a Trigger or DataTrigger can never be explicitly stated, for example. Will not work:
<TextBlock Text="{Binding Any}" Foreground="#FFCCCCCC">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem, Mode=FindAncestor}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Will work:
<TextBlock Text="{Binding Any}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#FFCCCCCC"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem, Mode=FindAncestor}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
3) Check the operation of Bindings using RelativeSource they have some peculiarities (you should know a little about the visual tree of WPF).
http://msdn.microsoft.com/en-us/library/ms743599.aspx
4) You should check that the binding is working properly and can follow these steps:
http://www.codeproject.com/Articles/244107/Debugging-WPF-data-bindings

Adding Popup Border Animation around TextBox in C# wpf

I am currently developing a WPF application. When the user submits the form I have a method which checks to ensure that the fields are filled in and what I want to do is, if they are not, display a red to white gradient going around the box as a fade in animation.
Is this possible?
Use validation in your binding, and then assign a validation error template to the TextBox. Here's one that does a red rectangle:
<ControlTemplate x:Key="errorTemplate">
<Canvas Width="{Binding Path=AdornedElement.ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}"
Height="{Binding Path=AdornedElement.ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}">
<Border BorderBrush="Red" BorderThickness="1" >
<AdornedElementPlaceholder/>
</Border>
</Canvas>
</ControlTemplate>
Add this to the binding: Validation.ErrorTemplate="{StaticResource errorTemplate}
You can use a style and use data trigger to achieve this. This way, when your textbox is empty, you will see a red border and light red background. See the sample code below:
<Style x:Key="RequiredField" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
<Setter Property="TextBox.BorderBrush" Value="{StaticResource MySolidBrush}" />
<Setter Property="TextBox.Background" Value="{StaticResource MyInnerBrush}"/>
<Setter Property="TextBox.ToolTip" Value="This Field is Mandatory"/>
</DataTrigger>
</Style.Triggers>
</Style>

Categories