TemplateBinding doesn't bind to effect's property? - wpf

Imagine a control named Testko like this:
public class Testko: Control
{
public static readonly DependencyProperty TestValueProperty;
static Testko()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Testko), new FrameworkPropertyMetadata(typeof(Testko)));
TestValueProperty = DependencyProperty.Register("TestValue", typeof(double), typeof(Testko), new UIPropertyMetadata((double)1));
}
public double TestValue
{
get { return (double)GetValue(TestValueProperty); }
set { SetValue(TestValueProperty, value); }
}
}
Nothing fancy, just an empty control with a single double property with a default value set to (double)1.
Now, image a generic style like this:
<Style TargetType="{x:Type local:Testko}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Testko}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Orientation="Vertical">
<StackPanel.Effect>
<BlurEffect Radius="{TemplateBinding TestValue}" />
</StackPanel.Effect>
<Button Content="{TemplateBinding TestValue}" Margin="4" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, the problem is that Radius property is never bound for some reason. Wheras Button's content is properly bound to TestValue property.
I am sure I am missing something obvious. Or not?

If it is obvious, it is not to me :-)
My favorite book (WPF Unleashed) mentions that sometimes TemplatedBinding doesn't work (but the enumerated reasons don't match your circumstances).
But TemplatedBinding is a shortcut for:
{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TestValue}
I reproduced your case, i.e. changing TestValue only has effect on the button.
After replacing the TemplatedBinding by this, I get the desired effect.

Related

How to set a property of a custom control from the main window?

I have a custom control, this is the generic.axml code:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Calendario"
xmlns:MyNamespace="clr-namespace:Calendario;assembly=Calendario"
xmlns:Converters="clr-namespace:Calendario.Converters">
<Converters:DateConverter x:Key="DateConverter"></Converters:DateConverter>
<Converters:DayBorderColorConverter x:Key="DayBorderColorConverter"></Converters:DayBorderColorConverter>
<Style TargetType="{x:Type local:CalendarioPersonalizado}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CalendarioPersonalizado}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<TextBlock Text="{Binding Date}" />
<Grid Height="30" DockPanel.Dock="Top">
</Grid>
<ItemsControl ItemsSource="{Binding Days}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="{Binding ColorRecuadroExterno, Mode=TwoWay}" BorderThickness="1" Padding="0">
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And I have my Calendario.cs with the dpendency property:
public static readonly DependencyProperty ColorRecuadroExternoProperty = DependencyProperty.Register("ColorRecuadroExterno", typeof(Brush), typeof(CalendarioPersonalizado));
public Brush ColorRecuadroExterno
{
get { return (Brush)GetValue(ColorRecuadroExternoProperty); }
set { SetValue(ColorRecuadroExternoProperty, value); }
}
And later in my main windows I use the control:
<local:CalendarioPersonalizado x:Name="cCalendario" ColorRecuadroExterno="Green"/>
The problem is that the border of the day in the calendar is not set to green like I have tried to set in the main window.
Also in the code behid I have tried this:
cCalendario.ColorRecuadroExterno = System.Windows.Media.Brushes.Green;
But the the color is not set.
What I want to do is set the color of the border in my custom cotrol from my main application.
Thanks.
If you put a Callback method in your local:CalendarioPersonalizado class and set your backround in this callback method. I think it is going to work.
public static readonly DependencyProperty ColorRecuadroExternoProperty = DependencyProperty.Register("ColorRecuadroExterno", typeof(Brush), typeof(CalendarioPersonalizado),
new PropertyMetadata(Brushes.Brown, Callback));
private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CalendarioPersonalizado obj = d as CalendarioPersonalizado;
obj.ColorRecuadroExterno.Background = (Brush)e.NewValue;
}
Your DependencyProperty is of type Brush. You can't implicitly convert "Green" to a Brush.
You either need to use a converter to convert a string representation of a color to a brush, or make your property type Color, and bind it to an appropriate property.

WPF Binding in ItemTemplate for custom ItemControl

I add a WPF custom control and make it derive from ItemsControl. The class is called IC4 and is declared as follows:
public class IC4 : ItemsControl
I add the following properties to it:
public class P
{
public string S { get; set; }
public string T { get; set; }
}
public List<P> LP { get; set; } = new List<P>();
Then in the constructor I do the following:
public IC4()
{
LP.Add(new P { S = "fred", T = "jim" });
LP.Add(new P { S = "fred", T = "jim" });
this.ItemsSource = LP;
this.DataContext = this;
}
Visual studio added a style entry to themes/generic.xaml - I have modified it as follows:
<Style TargetType="{x:Type local:IC4}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<!-- this is almost certainly wrong: -->
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=S}"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:IC4}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In mainwindow.xaml I added:
<StackPanel>
<Label Content="before"/>
<local:IC4 ItemsSource="{Binding LP}"/>
<Label Content="after"/>
</StackPanel>
I am fairly certain the binding for the Textbox in the data template is incorrect since I get the following runtime error (shown in the output window):
System.Windows.Data Error: 40 : BindingExpression path error: 'S' property not found on 'object' ''ContentPresenter' (Name='')'. BindingExpression:Path=S; DataItem='ContentPresenter' (Name=''); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
How do I set the binding to be able to show the S elements of the LP property?
(Note that for the sake of simplicity I am not interested in property change notifications).
Thanks
As far as I can see it should be just
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=S}"/>
</Grid>
</DataTemplate>
DataContext of each item, so for everything in your ItemTemplate, will be an instance of P class so all you should need to specify is the Path

Image.Height TemplateBinding does not work

I have created a CustomControl implemented from Button class in WPF.
public class ImageButton : Button
{
...
public int ImageHeight
{
get { return (int)GetValue(ImageHeightProperty); }
set { SetValue(ImageHeightProperty, value); }
}
public static readonly DependencyProperty ImageHeightProperty =
DependencyProperty.Register("ImageHeight", typeof(int), typeof(ImageButton), new UIPropertyMetadata(32));
...
}
And I have resource template like this:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type custom:ImageButton}">
<Border>
<StackPanel>
<Image Name="image" Height="{TemplateBinding ImageHeight}"/>
<TextBlock Text="{TemplateBinding Text}" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
ImageHeight dependecy property doesn't binding.
When I write like as below it works successful.
Height="32"
What is wrong with this?
Did you try using {Binding RelativeSource={RelativeSource TemplatedParent}, Path=Progress} instead ?
See these answers for more details...
WPF TemplateBinding vs RelativeSource TemplatedParent
Binding custom dependency property to custom WPF style
hope this helps

Binding from an XAML resource dictionary to a class property

I am having a very difficult time setting up a binding which I think should be easy. Help is greatly appreciated.
I have a resource dictionary named FormResource.xaml. In this dictionary contains a Style for the ScrollView that I redine the template for. The purpose is I want a wider vertical scrollbar on it.
<Style x:Key="LargeScrolling" TargetType="ScrollViewer">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Grid Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollContentPresenter x:Name="ScrollContentPresenter"
Margin="{TemplateBinding Padding}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
<ScrollBar x:Name="PART_VerticalScrollBar"
Style="{StaticResource LargeVerticalScrollBar}"
Width="{Binding ElementName=MDTForm, Path=ScrollBarWidth}"
IsTabStop="False"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Grid.Column="1" Grid.Row="0" Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Maximum="{TemplateBinding ScrollableHeight}"
Minimum="0"
Value="{TemplateBinding VerticalOffset}"
Margin="0,-1,-1,-1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have a UserControl named FormControl.
public class FormControl : UserControl
I used to have this as a partial class with a XAML componenet, in which what I am trying to do worked, but I had to remove the XAML since I derive from this class in another assembly and WPF does not allow you to derive from a partial class in another assembly.
In FormControl I define a ScrollBarWidth property.
public static readonly DependencyProperty ScrollBarWidthProperty = DependencyProperty.Register("ScrollBarWidth", typeof(double), typeof(FormControl));
public double ScrollBarWidth
{
get { return (double)base.GetValue(ScrollBarWidthProperty); }
set { base.SetValue(ScrollBarWidthProperty, value); }
}
When I had this as a partial class in the main declaration I gave the FormControl class a Name of MDTForm, which is what I am using as the ElementName in my binding. I tried registering this name in FormClass.cs but no matter what I do the scrollbar is not picking up the property value.
Here is where I create my ScrollViewer in the FormControl class.
_canvasScrollViewer = new ScrollViewer();
_canvasScrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
_canvasScrollViewer.VerticalAlignment = VerticalAlignment.Top;
_canvasScrollViewer.MaxHeight = Constants.ScrollViewMaxHeight;
_canvasScrollViewer.Style = (Style)FindResource("LargeScrolling");
The only way that I got this to work was to bind to a static property. I used this for the binding.
Width="{Binding Source={x:Static form:FormControl.ScrollBarWidthP}}"
Then defined the property as such.
public static double ScrollBarWidth { get; set; }
However, I don't want this as I can have multiple FormControl objects loaded at the same time and they may not all have the same scroll bar width property.
Use a RelativeSource Binding instead of ElementName:
{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type controls:FormControl}}, Path=ScrollBarWidth}
This will walk up the visual tree at runtime to find the parent control containing the ScrollViewer, which solves both your scoping and multiple instance issues.

QStackedLayout equivalent in WPF

I'm a quite experienced Qt programmer and I used QStackedLayout a lot to show different widgets in the main window. Can someone please point me to an equivalent construct in WPF: Is there such a thing like QStackedLayout? If not, how is this pattern used in WPF?
Basically I have a WPF Ribbon Application and if the Ribbon Group is switched the corresponding "widget" / XAML should be displayed in the remaining area ("content").
Thanks, dude.
There isn't a native panel or control that would do that, but you could leverage the TabControl to accomplish it. You'd need to use a custom Style, though like so:
<Style x:Key="NoTabsTabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained">
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then use it like:
<TabControl Style="{StaticResource NoTabsTabControlStyle}">
<TabItem Content="One" />
<TabItem Content="Two" />
</TabControl>
Then to display one set of content, you'd set SelectedIndex on the TabControl.
A bit late for topic starter but may be of some help to people who comes here searching for WPF version of QStackedLayout, like me.
I used the very simplified implementation of WPF layout example, throwing out virtually all things layout.
The component is based on StackLayout to allow for simple visual design, in design time it just behaves like normal stack panel.
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
namespace org.tequilacat.stacklayout {
/// <summary>
/// QStackedLayout implementation for WPF
/// only one child is displayed extended to the panel size.
/// In design time it behaves like stack panel
/// </summary>
public class StackLayoutPanel : StackPanel {
private bool isDesignTime() {
return System.ComponentModel.DesignerProperties.GetIsInDesignMode(this);
}
private bool useBaseBehaviour() {
return isDesignTime();
}
// in runtime just return the given arg
protected override Size MeasureOverride(Size availableSize) {
if (useBaseBehaviour()) {
return base.MeasureOverride(availableSize);
}
return availableSize;
}
// in runtime arrange all children to the given arg
protected override Size ArrangeOverride(Size finalSize) {
if (useBaseBehaviour()) {
return base.ArrangeOverride(finalSize);
}
foreach (UIElement child in InternalChildren) {
child.Arrange(new Rect(finalSize));
}
return finalSize;
}
}
}
The XAML is
<Window ... xmlns:uilib="clr-namespace:org.tequilacat.stacklayout">
<uilib:StackLayoutPanel >
<StackPanel Name="projectPropertyPanel"> ... </StackPanel>
<StackPanel Name="configurationPanel"> ... </StackPanel>
<StackPanel Name="casePanel"> ... </StackPanel>
</uilib:StackLayoutPanel>
In Run time the visible component is chosen via Visibility property (here depends on my business logic, uiState can take 3 values activating one of panels). It's very basic, one can implement own CurrentPage property or so, I just kept it simple:
projectPropertyPanel.Visibility = (uiState == UiState.ProjectProperties) ?
Visibility.Visible : Visibility.Collapsed;
configurationPanel.Visibility = (uiState == UiState.ConfigurationSelected) ?
Visibility.Visible : Visibility.Collapsed;
casePanel.Visibility = (uiState == UiState.CaseSelected) ?
Visibility.Visible : Visibility.Collapsed;

Resources