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>
Related
I'm using the Validation.ErrorTemplate property in my XAML view to display an icon next to any invalid control.
For one particular control in my view, I need to set the icon in a slightly different position. Is it possible for me to use the same control template, but somehow update the Margin property for a certain control?
Here's my (abridged) code:
<UserControl>
<UserControl.Resources>
<ControlTemplate x:Key="ValidationTemplate" TargetType="Control">
<DockPanel>
<Grid
DockPanel.Dock="Right"
Height="16"
Margin="10,0,0,0"
VerticalAlignment="Center"
Width="16">
<Image
AutomationProperties.AutomationId="_validationIcon"
Source="{x:Static icons:Icons.ValidationIcon}"
ToolTip="{Binding Path=ErrorContent}" />
</Grid>
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
<ItemsControl Validation.ErrorTemplate="{StaticResource ValidationTemplate}" />
</UserControl>
The only way I've managed to achieve what I need is to create a new ControlTemplate just for the control that requires a different icon placement. I would rather reuse my original control template if possible.
I had exactly same requirement in my old project. I solved it using an attached dependency property:
public static class ErrorTemplateProperties
{
public static readonly DependencyProperty ErrorMarginProperty = DependencyProperty.RegisterAttached
(
"ErrorMargin",
typeof(Thickness),
typeof(ErrorTemplateProperties),
new FrameworkPropertyMetadata(new Thickness(10,0,0,0))
);
public static Thickness GetErrorMargin(DependencyObject obj)
{
return (Thickness)obj.GetValue(ErrorMarginProperty);
}
public static void SetErrorMargin(DependencyObject obj, Thickness value)
{
obj.SetValue(ErrorMarginProperty, value);
}
}
add it to ValidationTemplate:
<ControlTemplate x:Key="ValidationTemplate" TargetType="Control">
<DockPanel>
<Grid
DockPanel.Dock="Right"
Margin="{Binding Path=AdornedElement.(local:ErrorTemplateProperties.ErrorMargin), ElementName=ui}"
Height="16"
VerticalAlignment="Center"
Width="16">
<Image
AutomationProperties.AutomationId="_validationIcon"
Source="{x:Static icons:Icons.ValidationIcon}"
ToolTip="{Binding Path=ErrorContent}" />
</Grid>
<AdornedElementPlaceholder x:Name="ui"/>
</DockPanel>
</ControlTemplate>
and then optionally change on required ui elements:
<ItemsControl helpers:ErrorTemplateProperties.ErrorMargin="0,0,0,0"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}" />
I also found a workaround with DynamicResources but attached DP are more flexible and laconic in my opinion
With "Template10.Validation", I want to change style.
First I made this Style for "validate:ControlWrapper".
<Style TargetType="validate:ControlWrapper">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="validate:ControlWrapper">
<StackPanel>
<ContentPresenter Content="{TemplateBinding Content}" />
<ItemsControl ItemsSource="{Binding Errors, Source={TemplateBinding Properties[PropertyName]}}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="Red" Text="{Binding}" Visibility="{Binding IsValid}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and Here is the result.
Something is strange. because I want to display Validation warning message for ONLY first name. but It display every warning. from address, from postal code.
My main quesiton
How to access "ValidatableModelBase.Property["PropertyName"].Errors" in Xaml.
because [] branket is not possible to use in XAML binding. How to accesss ??
with lot of my time, I finally find a solution for my own question...
First is my model class.
public class SettingEmail
: Template10.Validation.ValidatableModelBase
{public string EmailReceivers { get { return Read<string>(); } set { Write(value); } }}
Next is Property to bind. ( in my ViewModel class )
public SettingEmail SettingEmailModel{ get { return se; } set { this.Set(ref se, value); } }
Next is XAML code.
<validate:ControlWrapper DataContext="{Binding SettingEmailModel}"
PropertyName="EmailReceivers"
Style="{StaticResource validationNotify}">
<TextBox Text="{Binding EmailReceivers, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinHeight="400" Style="{StaticResource SettingStyle_MultilineTextBox}"/>
</validate:ControlWrapper>
and Last is Style in Resource file.
<Style x:Key="validationNotify" TargetType="validate:ControlWrapper">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="validate:ControlWrapper">
<StackPanel >
<ContentPresenter Content="{TemplateBinding Content}"/>
<ItemsControl DataContext="{TemplateBinding Property}"
ItemsSource="{Binding Errors, Source={TemplateBinding Property}}"
Style="{StaticResource validationNotifyMessage}"
>
<ItemsControl.ItemTemplate >
<DataTemplate>
<StackPanel>
<TextBlock Foreground="Red" Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I hope it help someone.
and I am sorry with my poor question's descriptions...I will try improve next...
So, I have created a UserControl which really is an "advanced button".
I have implemented Dependency Properties, one of which is ICommand, that is supposed to be bindable further when control is used in an actual Window.
However, for some reason the Command doesn't work.
When I tried an exact same approach on a regular button, everything worked fine (thus it's not the fault of my DelegateCommand implementation or my ViewModel).
I tried to followup on why the bound command doesn't fire, but I couldn't find a reliable reason for it not to.
Here is my UserControl XAML:
<Window x:Class="NoContact.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:NoContact"
mc:Ignorable="d"
xmlns:userControls="clr-namespace:NoContact.UserControls"
Title="MainWindow" Height="800" Width="960" Background="#22282a">
<Border BorderThickness="1" BorderBrush="#ffcd22" Margin="10,10,10,10">
<Grid>
<Button HorizontalContentAlignment="Stretch" Foreground="{Binding ElementName=ImageButtonUC, Path=Foreground}"
Background="{Binding ElementName=ImageButtonUC, Path=Background}">
<DockPanel Width="{Binding ElementName=ImageButtonUC, Path=ActualWidth}">
<Image Source="{Binding ElementName=ImageButtonUC, Path=Image}" DockPanel.Dock="Left"
Height="{Binding ElementName=ImageButtonUC, Path=ActualHeight, Converter={StaticResource HeightConverter}}"
Width="{Binding ElementName=ImageButtonUC, Path=ActualWidth, Converter={StaticResource HeightConverter}}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding ElementName=ImageButtonUC, Path=Text}" FontSize="17" VerticalAlignment="Center" />
</DockPanel>
</Button>
</Grid>
<UserControl.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
And here is my UserControl code-behind:
public partial class ImageButton : UserControl
{
// OTHER IRRELEVANT CLASS PARAMETERS ARE HERE
public ICommand ClickCommand
{
get { return (ICommand)GetValue(ClickCommandProperty); }
set { SetValue(ClickCommandProperty, value); }
}
// OTHER IRRELEVANT DEPENDENCY PROPERTIES ARE HERE
public static DependencyProperty ClickCommandProperty =
DependencyProperty.Register("ClickCommand", typeof(ICommand), typeof(ImageButton));
public ImageButton()
{
InitializeComponent();
}
}
Finally, this is how I use my control:
<userControls:ImageButton x:Name="phoneButton" ClickCommand="{Binding Path=MyButtonClickCommand}" Style="{StaticResource phoneImageButtonUCStyle}" Text="Telefon" Width="200" Height="100" VerticalAlignment="Top" />
The DataContext of the control is set on a stackpanel, that is wrapped around my control. I have also tried setting it directly on the control, no effect.
Again, doing the same on a regular button works just fine.
I have finally solved the problem - and it was pretty trivial.
I had ommited the most important binding - UserControl's button Command to UserControl's Dependency property.
Changing it like this made it work:
<Button HorizontalContentAlignment="Stretch" Foreground="{Binding ElementName=ImageButtonUC, Path=Foreground}"
Background="{Binding ElementName=ImageButtonUC, Path=Background}"
Command="{Binding ElementName=ImageButtonUC, Path=ClickCommand}">
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");
}
}
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}">