WPF Binding in ItemTemplate for custom ItemControl - wpf

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

Related

ControlTemplate LayoutTransform Binding System.Windows.Data Error 2 or 4

I'm creating a custom UserControl which will act as a container, showing a self-sized watermark behind its Content.
The relevant part of the ControlTemplate (I'll give you the full thing below) is
<TextBlock
Text="{TemplateBinding WatermarkText}"
Foreground="{TemplateBinding WatermarkBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold">
<TextBlock.LayoutTransform>
<RotateTransform Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource AncestorType={x:Type local:WatermarkUserControl}}}"/>
</TextBlock.LayoutTransform>
</TextBlock>
My UserControl has dependency properties for WatermarkText, WatermarkBrush, WatermarkAngle and WatermarkVisibility (I'll include that below). Notice that the TemplateBindings for WatermarkText, WatermarkBrush and WatermarkVisibility all work fine.
Using TemplateBinding for WatermarkAngle didn't work, because TemplateBinding is a lightweight "binding", so it doesn't support inheritance context. The WatermarkAngle binding that I ended up with (above) actually works; and yet a binding error is reported:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'RelativeSource FindAncestor,
AncestorType='[redacted namespace].WatermarkUserControl', AncestorLevel='1''.
BindingExpression:Path=WatermarkAngle; DataItem=null; target element
is 'RotateTransform' (HashCode=59772470); target property is 'Angle'
(type 'Double')
So is there a way of doing this better, which avoids the error being reported? And why is it reporting an error with the binding, given that the binding is actually working? (If I change the value, it reflects that.)
That concludes the question. Here are all the parts you need, to satisfy MCVE:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
public class WatermarkUserControl : UserControl
{
public static readonly DependencyProperty WatermarkTextProperty =
DependencyProperty.Register(nameof(WatermarkText), typeof(string), typeof(WatermarkUserControl), new PropertyMetadata("Watermark"));
public static readonly DependencyProperty WatermarkBrushProperty =
DependencyProperty.Register(nameof(WatermarkBrush), typeof(Brush), typeof(WatermarkUserControl), new PropertyMetadata(new SolidColorBrush(Colors.LightGray)));
public static readonly DependencyProperty WatermarkAngleProperty =
DependencyProperty.Register(nameof(WatermarkAngle), typeof(double), typeof(WatermarkUserControl), new PropertyMetadata(0d));
public static readonly DependencyProperty WatermarkVisibilityProperty =
DependencyProperty.Register(nameof(WatermarkVisibility), typeof(Visibility), typeof(WatermarkUserControl), new PropertyMetadata(Visibility.Visible));
public string WatermarkText
{
get { return (string)GetValue(WatermarkTextProperty); }
set { SetValue(WatermarkTextProperty, value); }
}
public Brush WatermarkBrush
{
get { return (Brush)GetValue(WatermarkBrushProperty); }
set { SetValue(WatermarkBrushProperty, value); }
}
public double WatermarkAngle
{
get { return (double)GetValue(WatermarkAngleProperty); }
set { SetValue(WatermarkAngleProperty, value); }
}
public Visibility WatermarkVisibility
{
get { return (Visibility)GetValue(WatermarkVisibilityProperty); }
set { SetValue(WatermarkVisibilityProperty, value); }
}
}
ResourceDictionary:
<Style x:Key="WatermarkUserControlBaseStyle" TargetType="local:WatermarkUserControl">
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:WatermarkUserControl">
<Grid>
<local:NoSizeDecorator Visibility="{TemplateBinding WatermarkVisibility}">
<Viewbox>
<TextBlock
Text="{TemplateBinding WatermarkText}"
Foreground="{TemplateBinding WatermarkBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold">
<TextBlock.LayoutTransform>
<RotateTransform Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource AncestorType={x:Type local:WatermarkUserControl}}}"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Viewbox>
</local:NoSizeDecorator>
<ContentPresenter Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DraftWatermarkStyle" TargetType="local:WatermarkUserControl" BasedOn="{StaticResource WatermarkUserControlBaseStyle}">
<Setter Property="WatermarkText" Value="DRAFT"/>
<Setter Property="WatermarkBrush" Value="LightPink"/>
<Setter Property="WatermarkAngle" Value="-20"/>
</Style>
NoSizeDecorator is defined here.
Example of use:
<local:WatermarkUserControl
Style="{StaticResource DraftWatermarkStyle}"
WatermarkVisibility="True">
<StackPanel>
<Label Content="Mr Smith"/>
<Label Content="1 High Street"/>
<Label Content="London"/>
</StackPanel>
</local:WatermarkUserControl>
I've worked out a way of doing it. It has made the error go away, and it still seems to work, and I haven't spotted any side effects yet.
I declared the RotateTransform as a local resource inside the Viewbox in the ControlTemplate, and then bound that as a StaticResource to the LayoutTransform property of the TextBlock. So the new version of the Viewbox from the question looks like this:
<Viewbox>
<Viewbox.Resources>
<RotateTransform x:Key="RotateWatermarkTransform" Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource TemplatedParent}}"/>
</Viewbox.Resources>
<TextBlock
Text="{TemplateBinding WatermarkText}"
Foreground="{TemplateBinding WatermarkBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
LayoutTransform="{StaticResource RotateWatermarkTransform}"/>
</Viewbox>

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.

Cannot set Expression. It is marked as 'NonShareable' and has already been used

iv'e created a custom control deriving from ItemsControl .
public class CustsomItemsControl : ItemsControl
{ }
XAML :
<local:CustsomSelectorControl ItemsSource="{Binding People}">
<local:CustsomSelectorControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}"/>
</DataTemplate>
</local:CustsomSelectorControl.ItemTemplate>
</local:CustsomSelectorControl>
The Control Template :
<Style TargetType="{x:Type local:CustsomItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustsomItemsControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
in DataContext :
public MainWindow()
{
InitializeComponent();
People = new ObservableCollection<Person>();
People.Add(new Person("A"));
People.Add(new Person("B"));
People.Add(new Person("C"));
}
private ObservableCollection<Person> _people;
public ObservableCollection<Person> People
{
get { return _people; }
set
{
_people = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("People"));
}
}
The items are never set , iv'e observed this with snoop ,the ItemsSource property
is marked in RED but there are no BindingErrors , when i delve the BindingExpression
i get an ArgumentExpression:
Cannot set Expression. It is marked as 'NonShareable' and has already been used.
Set the DataContext properly:
public MainWindow()
{
InitializeComponent();
DataContext = this; //This is what you're missing
...
}
Still, you need to have a really strong reason to subclass ItemsControl.

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

WPF Datagrid group expander text - how to bind?

I am using a datagrid with a combox that should change the grouping field. I am using the following xaml to define the general grouping template :
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="NEEDS TO BE BINDED..."/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
I only need to be able to 'reach' that TextBlock within the expander to be able to output the selected grouping applied.
Please help....
If you want to display the common value of the property being grouped by, that will be available as CollectionViewGroup.Name, so you can just do:
<TextBlock Text="{Binding Name}"/>
I have solved my issue by adding a nested class that contains the currently selected grouping (which i manually set ofcourse) + more details i need. Then binding to the class property by using :
<TextBlock Text="{Binding Source={StaticResource GroupingSubject},Path=Name}"/>
Ofcourse that i had to declare the class within the xaml resources as follows :
<local:GroupingName x:Key="GroupingName"/>
My nested class looks as follows :
public class GroupingSubject
{
private static String name = null;
private static Object groupType = null;
public GroupingSubject() { }
public static String Name
{
get { return name; }
set { name = value; }
}
public static Object GroupType
{
get { return groupType; }
set { groupType = value; }
}
}
Now all is well...

Resources