Binding Tooltip to Content Presenter Content - c#

I am styling a TreeViewItem. The item has a content presenter that I wish to have a tooltip appear:
<ContentPresenter x:Name="PART_Header"
Cursor="Hand"
Grid.Column="1"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}">
<ContentPresenter.ToolTip>
<ToolTip
Placement="RelativePoint"
VerticalOffset="-2"
HasDropShadow="False"
BorderBrush="#767676"
Background="#FFF"
Padding="0,1,0,1"
VerticalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContentPresenter}}, Path=Content}"/>
</ContentPresenter.ToolTip>
</ContentPresenter>
This is effectively part of a larger setter that sets the item's control template. However, I cannot seem to get the Tooltip's content binded to the content presenter's content. It keeps appearing blank (a tiny black box). I am new to binding and WPF, so please excuse.

ToolTips and other popups exist outside the main visual tree and so can't use RelativeSource to get to parents. Try instead using the ToolTip's PlacementTarget property to get to its parent ContentPresenter:
Content="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Content}"

I want use Binding like if my tooltip or popup is child of control which creates them.
In previous answer i can't do it.
I write example for custom tooltip, but you can do it for popup, use behavior or another - it is easy.
My custom ToolTip:
public class ToolTipEx : ToolTip
{
private readonly FrameworkElement _coreParent;
static ToolTipEx()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ToolTipEx), new FrameworkPropertyMetadata(typeof(ToolTipEx)));
}
public ToolTipEx(FrameworkElement parent)
{
_coreParent = parent;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var method = typeof(FrameworkElement).GetMethod("AddLogicalChild", BindingFlags.Instance | BindingFlags.NonPublic);
method.Invoke(_coreParent, new object[] { Parent });
}
}
After it you can use correct bindings:
<Style TargetType="{x:Type controls:ToolTipEx}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolTip}">
<Border Background="Black"
CornerRadius="3">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyControl}}, Path=MyProperty}"
Margin="1"
Foreground="#FFFFFF"
FontWeight="Bold"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
TextAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

I dont understand why you would just add the binding to the tooltip property itself?
<ContentPresenter ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Content}">

Related

Using custom dependency property in style

I have a custom button control with a custom property IsPlaying.
internal class PlayPauseButton : Button
{
public bool IsPlaying
{
get => (bool)GetValue(IsPlayingProperty);
set => SetValue(IsPlayingProperty, value);
}
public readonly static DependencyProperty IsPlayingProperty = DependencyProperty.Register(
nameof(IsPlaying), typeof(bool), typeof(PlayPauseButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
I want to bind to that property from its style. I want to show different images which depend on the IsPlaying flag value.
<Style TargetType="c:PlayPauseButton">
<Setter Property="IsPlaying" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Image Width="100" Height="100" Visibility="{TemplateBinding IsPlaying, Converter={c:InverseBoolToVisibilityConverter}}">
...
</Image>
<Image Width="100" Height="100" Visibility="{TemplateBinding IsPlaying, Converter={c:BoolToVisibilityConverter}}">
...
</Image>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I use the control like this:
<c:PlayPauseButton Width="200" Height="100" IsPlaying="{Binding Player.IsPlaying}"/>
But I have got a problem with the style. I can't bind to the IsPlaying property. You can see below error message:
"Failed to create 'DependencyProperty' based on text 'IsPlaying.': line number '10' and line position '75'."
You have to specify the TargetType of the ControlTemplate, otherwise the property cannot be resolved in the TemplateBinding.
ControlTemplate TargetType="c:PlayPauseButton">
An alternative is to use a relative source binding to the templated parent.
<ControlTemplate>
<Grid>
<Image Width="100" Height="100" Visibility="{Binding IsPlaying, RelativeSource={RelativeSource TemplatedParent}, Converter={c:InverseBoolToVisibilityConverter}}">
<!-- ... -->
</Image>
<Image Width="100" Height="100" Visibility="{Binding IsPlaying, RelativeSource={RelativeSource TemplatedParent}, Converter={c:BoolToVisibilityConverter}}">
<!-- ... -->
</Image>
</Grid>
</ControlTemplate>

Bind public string property to TextBox text via content presenter in wpf

I have a custom wpf control that I created in hopes of reusing it over and over to reduce time spent writing, or copying and pasting, a bunch of xaml code. It, seems, pretty simple. It's to be used for a Label and TextBox pair so:
Label here
[ this is the text box (or other control) ]
It's called like so:
<controls:LabeledContentControl Margin="5"
MaxHeight="25"
LabelText="{Binding LabelString}"
Content="{Binding LabelTextBoxTestString, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
or
<controls:LabeledContentControl Margin="5"
LabelText="{Binding LabelString}">
<TextBox Text="{Binding LabelTextBoxTestString,
ValidatesOnNotifyDataErrors=True,
UpdateSourceTrigger=PropertyChanged,
Delay=100}" />
</controls:LabeledContentControl>
The LabeledContentControl extends ContentControl, the c# code doesn't seem relevant as it's dependency properties and their setters. Along with the two constructors similar the ones implemented in the Microsoft reference documents for other controls that extend ContentControl
https://referencesource.microsoft.com/#PresentationFramework/Framework/System/Windows/Controls/HeaderedContentControl.cs
I'm having issues with binding the backing view model property/field set when using the first approach for the labeled content control.
The XAML code for the control is like such:
<Style x:Key="{x:Type controls:LabeledContentControl}" TargetType="{x:Type controls:LabeledContentControl}">
<!-- other setters.... -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:LabeledContentControl}">
<Border Name="OuterBd" BorderThickness="{TemplateBinding BorderThickness}" >
<Border Name="InnerBd" Background="{TemplateBinding BorderBrush}" BorderThickness="0">
<Border Name="Bd" Background="{TemplateBinding Background}" BorderThickness="0">
<DockPanel>
<DockPanel DockPanel.Dock="Top" IsHitTestVisible="False">
<TextBlock x:Name="FieldRequiredInd"
Text="* "
Foreground="Red"
Visibility="Collapsed"
DockPanel.Dock="Left"/>
<TextBlock x:Name="LabelTextBlock"
DockPanel.Dock="Left"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelText, Mode=OneWay}" />
</DockPanel>
<ContentPresenter x:Name="CustomContent" DockPanel.Dock="Bottom"
Content="{TemplateBinding Content}"
ContentTemplateSelector="{StaticResource LabeledContentControlDataTemplateSelector}"
MaxHeight="{TemplateBinding MaxHeight}"/>
</DockPanel>
</Border>
</Border>
</Border>
<ControlTemplate.Triggers>
...
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
The template selector, again super basic, if the content is of type string using GetType a custom datatemplate for strings is used so that they'll be in a text box and editable.
<DataTemplate x:Key="StringDataTemplate" DataType="{x:Type system:String}">
<TextBox>
<TextBox.Text>
<Binding Path="." />
</TextBox.Text>
</TextBox>
</DataTemplate>
<local:LabeledContentControlDataTemplateSelector x:Key="LabeledContentControlDataTemplateSelector"
StringDataTemplate="{StaticResource StringDataTemplate}"/>
How do/can I bind LabelTextBoxTestString correctly so that when I update/type something in the text box generated by the StringDataTemplate that it will update the bound property in the ViewModel? Currently, when I use the first approach, when I type in the text box created by the StringDataTemplate the backing field isn't updated with the new content, but when using the second approach the backing field is updated.
You will need some modifications:
first is the correct Binding in your TextBox.Text to:
<TextBox.Text>
<Binding Path="Content" RelativeSource="{RelativeSource TemplatedParent}" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
then change the Binding of ContentPresenter to:
<ContentPresenter x:Name="CustomContent"
MaxHeight="{TemplateBinding MaxHeight}"
Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:LabeledContentControl}, Path=Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ContentTemplateSelector="{StaticResource LabeledContentControlDataTemplateSelector}"
DockPanel.Dock="Bottom" />
now you can bind your control. And take care, that Mode is set to TwoWay for Content property (default is OneWay), thats why you didn't got a feedback:
<controls:LabeledContentControl Margin="5"
MaxHeight="25"
LabelText="{Binding LabelString}"
Content="{Binding LabelTextBoxTestString, ValidatesOnNotifyDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

How to change XAML elemen't template from code-behind?

I have next button's style defined in resources:
<Style x:Key="OKBtn" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle .../>
<TextBlock x:Name="Text" ..>
<Run Language="en-en" Text="OK"/>
</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I want in some specified case from code change Button's text.
I.e. change "OK" (<Run Language="en-en" Text="OK"/>) to "Accept".
How can I do that?
Is it possible to access this TextBlock "Text" and change content exactly for my one button, but not for all OK buttons?
My button:
<Button x:Name="OkButton" Style="{DynamicResource OKBtn}" />
You can borrow some props from template Template, for example Tag property. So the TextBlock text in the ControlTemplate should be like this.
<Run Language="en-en" Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag}"/>
And you can change the button caption by setting it's Tag property.
OkButton.Tag = "Accept";
And for not set all button texts manually you can create some ValueConverter to set TextBlock text in the ControlTemplate to the "Ok" whenever Tag property is empty.
At first, you should declare ContentPresenter to show any object in your Content property of Button control.
<Style x:Key="OkBtn" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle/>
<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"></ContentPresenter>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then, it is possible to set another Content by using code behind or binding:
By code behind:
okButton.Content="desirableText";
By binding:
<Button x:Name="OkButton" Style="{DynamicResource OKBtn}" Content="{Binding FooText}" />
private string fooText;
public string FooText
{
get { return fooText; }
set
{
fooText = value;
OnPropertyChanged("FooText");
}
}

WPF: Binding Property to an object in Style

I have this style:
<Style x:Key="{x:Type TextBox}" TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True" CornerRadius="0,10,10,0" Padding="5,0,10,0" MinWidth="0" VerticalAlignment="Stretch">
<Grid VerticalAlignment="Center">
<Label x:Name="label" Content="{Binding LContent}" HorizontalAlignment="Left" VerticalAlignment="Stretch" Foreground="Gray" Padding="0,0,5,0" Margin="0" BorderBrush="#FF2C2C2C" BorderThickness="0,0,1,0"/>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">
<ScrollViewer x:Name="PART_ContentHost" Focusable="False" Template="{DynamicResource ComboBoxScrollViewerControlTemplate}" Margin="30,1,0,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" />
</Grid>
</Grid>
</Border>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="CaretBrush" Value="#FF646464"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="CaretBrush" Value="#FF323232"/>
</Trigger>
</Style.Triggers>
</Style>
and in behind code, I wrote this function as new property for Label object to binding LContent value to label object:
public string LabelContent
{
get { return (string)GetValue(LContent); }
set { SetValue(LContent, value); }
}
public static readonly DependencyProperty LContent =
DependencyProperty.Register("LabelContent", typeof(string), typeof(CustomizedTextBox), new PropertyMetadata("Label"));
but label content doesn't change.
can you help me?
You've approached template binding in the same way as you would a normal control, and this is wrong. Think of it this way: it is a total and utter waste of time to define a template if you are going to explicitly bind to a specific property. A template is supposed to be reused across multiple instances of a control, and they can't all be binding to that one property, could they?
Instead what you need to do is use:
{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Name}
or its shorted version:
{TemplateBinding Name}
This tells the binding subsystem to use the parent control (the one you are templating) as a source for the binding.
This cheat sheet might be a valuable reference for you. The previous SO question What is the template binding vs binding? also has a nice simple example of this.
First of all I'm not sure what x:Key="{x:Type TextBox}" does, but make sure the data context is propely set. Like if you're using it in a window
You can do
<Window.DataContext>
<local:CustomizedTextBox></local:CustomizedTextBox>
</Window.DataContext>
Though it's not a good practice.
And also I noticed that you've Registered the Property Name as LabelContent and you are using LContent in the Binding. Changing it to LabelContent can help.
<Label x:Name="label" Content="{Binding LabelContent}" >
You've set you're TargetType to be TextBox and even if your custom control is derived from TextBox it will not apply to it, you should directly Specify your custom controls class name that is CustomTextBox.
To make things clear, I would say that If you've a x:Key attribute in you're style, if will not automatically get applied to all the targetType element. Then you have to explicitly specify the style for each control.
And to get you exited, I have made this work. If you want I will later post what I have done. But now I am in a hurry.
Hope it, helps.
To set the label content directly from the text box text, we need to set the content of the label to the Text property of the Text box
<Label x:Name="label" "{Binding Text, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}"
To read the set value of the label which can be done directly using the property with INotifyPropertyChanged or Dependency property in your case where MyMainWindow is the my usercontrol window name
<TextBox Grid.Row="0" x:Name="CustomizedTextBox" Text="{Binding ElementName=MyMainWindow, Path=LabelContent }"
In Code base we have DP defined as below
public static readonly DependencyProperty LContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(MainWindow));
public string LabelContent
{
get { return (string)GetValue(LContentProperty); }
set { SetValue(LContentProperty, value); }
}

WPF: Multiple content presenters in a custom control?

I'm trying to have a custom control that requires 2 or more areas of the XAML to be defined by a child control - that inherits from this control. I'm wondering if there's a way to define multiple contentpresenters and one which acts as the default content presenter
<MyControl>
<MyControl.MyContentPresenter2>
<Button Content="I am inside the second content presenter!"/>
</MyControl.MyContentPresenter2>
<Button Content="I am inside default content presenter" />
</MyControl>
Is this possible, how do I define this in the custom control's template?
The template can just bind the separate ContentPresenter instances like this (I've only set one property here but you'll likely want to set others):
<ContentPresenter Content="{TemplateBinding Content1}"/>
<ContentPresenter Content="{TemplateBinding Content2}"/>
The control itself should expose two properties for content and set the default using the ContentPropertyAttribute:
[ContentProperty("Content1")]
public class MyControl : Control
{
// dependency properties for Content1 and Content2
// you might also want Content1Template, Content2Template, Content1TemplateSelector, Content2TemplateSelector
}
You can use an "ItemsControl" with a custom template.
<ItemsControl>
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[0]}"/>
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[1]}"/>
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[2]}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
<TextBlock Text="Item 3"/>
</ItemsControl>
Here's another option that doesn't require making a custom control and is more typesafe than doing the ItemsControl thing (if type safety is something you want..perhaps not):
...Use an attached property!
Create an attached property of the appropriate type. We happened to need a text control so I did a string TextContent attached property. Then create a TemplateBinding to it from the template, and when instantiating in Xaml set it there as well. Works nicely.

Categories