Access custom property inside ContentTemplate of custom user control - c#

I have the following custom user control:
namespace MyApp.Controls {
public partial class ArticleButton: UserControl {
public ArticleButton () {
InitializeComponent ();
}
public static readonly DependencyProperty TitleProperty;
static ArticleButton () {
TitleProperty = DependencyProperty.RegisterAttached ("Title",
typeof (String), typeof (ArticleButton));
}
[Description ("The name of the article."), Category ("Common Properties")]
public String Title {
get { return "TEST"; }
}
}
}
And the corresponding XAML:
<UserControl x:Class="MyApp.Controls.ArticleButton"
Name="UC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyApp.Controls">
<Button Name="button" Click="button_Click" Style="{StaticResource defaultButtonStyle}">
<Button.ContentTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Title, ElementName=UC}" />
</Grid>
</DataTemplate>
</Button.ContentTemplate>
</Button>
</UserControl>
Where defaultButtonStyle is defined in App.xaml (there is more than that, but this should be sufficient):
<Style TargetType="Button" x:Key="defaultButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="border"
BorderThickness="1"
Padding="4,2"
BorderBrush="DarkGray"
CornerRadius="3"
Background="{TemplateBinding Background}">
<Grid>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My problem is that the property Title is not displayed, I tried the following which did not work either:
<TextBlock Text="{Binding Path=Title, ElementName=UC}" />
<TextBlock Text="{Binding Path=Title, RelativeSource={RelativeSource TemplatedParent}}" />
<TextBlock Text="{Binding Path=Title, RelativeSource={RelativeSource AncestorType={x:Type local:ArticleButton}}}" />
I found lots of posts with similar issue, but none of them helped... I think the problem is that I try to access a custom property of the custom user control inside the content template of the inner button, but how to do that.

To sum up comments your 3rd RelativeSource binding
<TextBlock Text="RelativeSource={RelativeSource AncestorType={x:Type local:ArticleButton}}"/>
should work fine just use Register instead of RegisterAttached. As for other bindings {Binding Path=Title, ElementName=UC} won't work because ConrtrolTemplate has its own name scope and {Binding Path=Title, RelativeSource={RelativeSource TemplatedParent}}" won't work because you don't set property against Button but UserControl.
Another problem is that you set default value against CLR wrapper and, as mentioned on this MSDN page CLR wrapper is ignored and GetValue/SetValue methods are called directly for TitleProperty
The WPF XAML processor uses property system methods for dependency properties when loading binary XAML and processing attributes that are dependency properties. This effectively bypasses the property wrappers. When you implement custom dependency properties, you must account for this behaviour and should avoid placing any other code in your property wrapper other than the property system methods GetValue and SetValue.
If you want to specify default value and/or property changed callback then when you create DependencyProperty there is another variant of Register method which also takes PropertyMetadata parameter where you can set these values.
Another thing you need to be aware when using dependency properties is that, because its definition is static, default value will be shared between all instances of ArticleButton. So if your type would be list or some other class and you would initialize it in PropertyMetadata to something different then null same instance will be shared as default value
public partial class ArticleButton : UserControl
{
public ArticleButton()
{
InitializeComponent();
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register(
"Title",
typeof(String),
typeof(ArticleButton),
new PropertyMetadata("Test", TitlePropertyChanged));
private static void TitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as ArticleButton).TitlePropertyChanged(e);
}
private void TitlePropertyChanged(DependencyPropertyChangedEventArgs e)
{
//do something when property changed value
}
public String Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
}

Related

WPF - Properties of ItemsSource to Dependency Properties

Background
I am making a custom control that has multiple ListBox's. I want to make this control MVVM compliant, so I am keeping any XAML and the code behind agnostic with respect to any ViewModel. One ListBox is simply going to be a list of TextBox's while the other is going to have a canvas as the host to display the data graphically. Both of these ListBox's are children of this custom control.
Pseudo example for the custom control template:
<CustomControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"/>
<ListBox2 Grid.Column="1"/>
</CustomControl>
The code behind for this custrom control would have a dependency property that will serve as the ItemsSource, fairly standard stuff:
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
Where I am stuck
Because the two ListBox's are using the same data source but just display the data differently, I want the ItemsSource defined as one of the the parent view's dependency properties to be the ItemsSource for the two children. From the ViewModel side, this items source can be some sort of ObservableCollection<ChildViewModels>, or IEnumerable, or whatever it wants to be.
How can I point to properties from the ItemsSource's ViewModel to dependency properties of the child views?
I was hoping to get something similar to how it could be done outside of a custom view:
Example Parent ViewModel(omitting a lot, assume all functioning):
public class ParentViewModel
{
public ObservableCollection<ChildViewModel> ChildViewModels;
}
Example ViewModel (omitting INotifyPropertyChanged and associated logic):
public class ChildViewModel
{
public string Name {get; set;}
public string ID {get; set;}
public string Description {get; set;}
}
Example control (ommitting setting the DataContext, assume set properly):
<ListBox ItemsSource="{Binding ChildViewModels}">
<ListBox.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</ListBox.ItemsTemplate>
</ListBox>
How can I do something similar where I can pass the properties from the ItemsSource to the child views on a custom control?
Many thanks
If I understand correctly what you need, then here is an example.
Add properties for element templates in both lists and style for Canvas.
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace Core2022.SO.jgrmn
{
public class TwoListControl : Control
{
static TwoListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TwoListControl), new FrameworkPropertyMetadata(typeof(TwoListControl)));
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(TwoListControl),
new PropertyMetadata((d, e) => ((TwoListControl)d).OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue)));
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
//throw new NotImplementedException();
}
public DataTemplate TemplateForStack
{
get { return (DataTemplate)GetValue(TemplateForStackProperty); }
set { SetValue(TemplateForStackProperty, value); }
}
public static readonly DependencyProperty TemplateForStackProperty =
DependencyProperty.Register(
nameof(TemplateForStack),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public DataTemplate TemplateForCanvas
{
get { return (DataTemplate)GetValue(TemplateForCanvasProperty); }
set { SetValue(TemplateForCanvasProperty, value); }
}
public static readonly DependencyProperty TemplateForCanvasProperty =
DependencyProperty.Register(
nameof(TemplateForCanvas),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public Style StyleForCanvas
{
get { return (Style)GetValue(StyleForCanvasProperty); }
set { SetValue(StyleForCanvasProperty, value); }
}
public static readonly DependencyProperty StyleForCanvasProperty =
DependencyProperty.Register(
nameof(StyleForCanvas),
typeof(Style),
typeof(TwoListControl),
new PropertyMetadata(null));
}
}
In the theme (Themes/Generic.xaml), set bindings to these properties:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:jgrmn="clr-namespace:Core2022.SO.jgrmn">
<Style TargetType="{x:Type jgrmn:TwoListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type jgrmn:TwoListControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForStack}"/>
<ListBox Grid.Column="1"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForCanvas}"
ItemContainerStyle="{TemplateBinding StyleForCanvas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Window with an example of use:
<Window x:Class="Core2022.SO.jgrmn.TwoListWindow"
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:Core2022.SO.jgrmn"
mc:Ignorable="d"
Title="TwoListWindow" Height="250" Width="400">
<FrameworkElement.DataContext>
<CompositeCollection>
<Point>15 50</Point>
<Point>50 150</Point>
<Point>150 50</Point>
<Point>150 150</Point>
</CompositeCollection>
</FrameworkElement.DataContext>
<Grid>
<local:TwoListControl ItemsSource="{Binding}">
<local:TwoListControl.TemplateForStack>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}Point ({0} {1})">
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</local:TwoListControl.TemplateForStack>
<local:TwoListControl.TemplateForCanvas>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Red"/>
</DataTemplate>
</local:TwoListControl.TemplateForCanvas>
<local:TwoListControl.StyleForCanvas>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</local:TwoListControl.StyleForCanvas>
</local:TwoListControl>
</Grid>
</Window>
You must spend all participating controls a ItemsSource property. The idea is to delegate the source collection from the parent to the child controls and finally to the ListBox. The ItemsSource properties should be a dependency property of type IList and not IEnumerable. This way you force the binding source to be of type IList which improves the binding performance.
To allow customization of the actual displayed items, you must either
a) spend every control a ItemTemplate property of type DataTemplate and delegate it to the inner most ListBox.ItemTemplate (similar to the ItemsSource property) or
b) define the template as a resource (implicit template, which is a key less DataTemplate).
The example implements a):
<Window>
<Window.DataContext>
<ParentViewModel />
</Window.DataCOntext>
<CustomControl ItemsSource="{Binding ChildViewModels}">
<CustomControl.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</CustomControl.ItemsTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
<ListBox2 Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</CustomControl>
</Window>
Inside the child controls (ListBox1 and ListBox2):
<UserControl>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</UserControl>

get child nodes of node WPF

I have created this UserControl
public partial class Dropdown : UserControl
{
public string Title { get; set; } = string.Empty;
private void loadSuccess(object sender, RoutedEventArgs e)
{
TitleBlock.Text = Title;
}
public Dropdown()
{
Loaded += loadSuccess;
InitializeComponent();
}
}
<UserControl x:Class="Lamprey.UserControls.Dropdown"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Lamprey.UserControls"
mc:Ignorable="d"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="17"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text=">" Grid.Row="0" Grid.Column="0" Foreground="#FFC7C7C7"></TextBlock>
<TextBlock x:Name="TitleBlock" Grid.Row="0" Grid.Column="1"></TextBlock>
<ListBox x:Name="Items" Grid.Column="1" Grid.Row="1" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>
It is meant to be used like this
<usercontrols:Dropdown Title="History">
<ListBoxItem>
<TextBlock>Example</TextBlock>
</ListBoxItem>
</usercontrols:Dropdown>
I am trying to figure out how I can get a list of children of the <Dropdown> so I can move them into the Items <ListBox>.
The end goal is for any children addded to <Dropdown> to be automatically added to the <ListBox> named Items. That way, the entire listbox can be hidden when the dropdown is closed, and shown when the dropdown is opened.
The key is to define a collection dependency property and bind it to the ListBox. This property must be declared as the control's content property in order to be able to add items to it implicitly in XAML.
Define a custom non-generic collection or use an existing one
DropDownItemCollection.cs
public class DropDownItemCollection : List<object>
{ }
Define a dependency property of the non-generic collection type
Declare this property as the content property using the ContentProperty attribute to decorate the property owner
Optionally, define a DataTemplate type property e.g., ItemTemplate to allow templating the items using an explicit DataTemplate
DropDown.xaml.cs
[ContentProperty(nameof(DropDown.DropDownItems))]
public partial class DropDown : UserControl
{
public DropDownItemCollection DropDownItems
{
get => (DropDownItemCollection)GetValue(DropDownItemsProperty);
set => SetValue(DropDownItemsProperty, value);
}
public static readonly DependencyProperty DropDownItemsProperty =
DependencyProperty.Register(
"DropDownItems",
typeof(DropDownItemCollection),
typeof(DropDown),
new PropertyMetadata(default));
public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register(
"ItemTemplate",
typeof(DataTemplate),
typeof(DropDown),
new PropertyMetadata(default));
public DropDown()
{
InitializeComponent();
this.DropDownItems = new DropDownItemCollection();
}
}
Bind the internal ListBox to the new DropDownItems property
DropDown.xaml
<UserControl>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DropDownItems}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</UserControl>
Example
<!--
Since the collection is of type object you can add anything to the collection.
The ItemTemplate property allows to explicitly template the item layout.
-->
<Window>
<Window.Resources>
<DataTemplate x:Key="DroptDownItemTemplate">
<Grid>
<Border Background="OrangeRed" />
<TextBlock Text="{Binding}" />
</Grid>
</DataTemplate>
</Window.Resources>
<DropDown ItemTemplate={StaticResource DroptDownItemTemplate}">
<sys:String>Item 1</sys:String>
<sys:String>Item 2</sys:String>
</DropDown>
</Window>
Remarks
You can skip all this when extending ListBox instead of UserControl. Doing so, your control will support item creation in XAML and templating out of the box.
Hiding and showing the internal ListBox will lead to changes in the layout and therfore result in ugly content resizing. You should consider to host the ListBox (or in case of extending ListBox the ItemsPresenter) inside a Popup to create the flyout that overlays instead of resizing.

WPF Custom Control based on grid or nested grid?

I'm learning WPF but I have a lot of Windows Forms background. I want to convert a WinForms custom control in which I did put a label and a textbox (making a TextField), with a property allowing to set percentage of width allocated to the label.
Now, in WPF, I'm a bit lost. Should I create a custom control that inherits from a grid and expose (how ?) the columns definition properties, or should I create a custom control that will "contain" a grid, and expose two properties "LabelWidth" and "ContentWidth", and bind the two column definitions to these properties ? (Thinking these properties would contain 1* and 3*).
Could someone show me an example of such construction to have a place to start?
You could create a UserControl with two dependency properties.
Please refer to the following sample code.
MyUserControl.xaml:
<UserControl x:Class="WpfApplication3.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding LabelWidth, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<ColumnDefinition Width="{Binding ContentWidth, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Grid.ColumnDefinitions>
<TextBlock Text="..." />
<TextBox Grid.Column="1" />
</Grid>
</UserControl>
MyUserControl.xaml.cs:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty LabelWidthProperty =
DependencyProperty.Register("LabelWidth", typeof(System.Windows.GridLength),
typeof(MyUserControl));
public System.Windows.GridLength LabelWidth
{
get { return (System.Windows.GridLength)GetValue(LabelWidthProperty); }
set { SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty ContentWidthProperty =
DependencyProperty.Register("ContentWidth", typeof(System.Windows.GridLength),
typeof(MyUserControl));
public System.Windows.GridLength ContentWidth
{
get { return (System.Windows.GridLength)GetValue(ContentWidthProperty); }
set { SetValue(ContentWidthProperty, value); }
}
}
Sample usage:
<local:MyUserControl LabelWidth="1*" ContentWidth="5*" />
Dependency Properties Overview: https://msdn.microsoft.com/en-us/library/ms752914(v=vs.110).aspx
I think I managed to achieve what I wanted to do by understanding mm8's code, in particular RelativeSource={RelativeSource AncestorType=UserControl} :
Added a custom control.
FieldText.cs :
public class FieldText : Control
{
static FieldText()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FieldText), new FrameworkPropertyMetadata(typeof(FieldText)));
}
public FieldText()
{
}
public static readonly DependencyProperty LabelLengthProperty =
DependencyProperty.Register("LabelLength", typeof(GridLength),
typeof(FieldText), new UIPropertyMetadata(new GridLength(25, GridUnitType.Star)));
public virtual GridLength LabelLength
{
get { return (GridLength)GetValue(LabelLengthProperty); }
set { SetValue(LabelLengthProperty, value); }
}
public static readonly DependencyProperty ContentLengthProperty =
DependencyProperty.Register("ContentLength", typeof(GridLength),
typeof(FieldText), new UIPropertyMetadata(new GridLength(75, GridUnitType.Star)));
public virtual GridLength ContentLength
{
get { return (GridLength)GetValue(ContentLengthProperty); }
set { SetValue(ContentLengthProperty, value); }
}
}
Generic.xaml :
<Style TargetType="{x:Type controls:FieldText}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:FieldText}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="grd" Margin="3px">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Path=LabelLength, RelativeSource={RelativeSource AncestorType=Control}}" />
<ColumnDefinition Width="{Binding Path=ContentLength, RelativeSource={RelativeSource AncestorType=Control}}" />
</Grid.ColumnDefinitions>
<Label x:Name="label" Grid.Column="0" Content="Field:" />
<TextBox x:Name="textbox" Grid.Column="1" MaxLines="1" TextWrapping="NoWrap" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Sample usage:
<controls:FieldText x:Name="fld1" LabelLength="25*" ContentLength="75*" />

Setting a Button Image to Binding in its Style

I am finding that I am creating the same Button style for multiple buttons but only changing one part - the image that is used on the Button. An example;
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="pack://application:,,,/Resources/MainWindowIcons/Staff.ico" Height="20"/>
<TextBlock Style="{StaticResource HoverUnderlineStyle}" Text="Staff" Margin="5,0,0,0"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
This is the code for the staff Button. If I wanted to add another button I'd replicate the whole style but just change the Source of the Image.
Is there a way I can have on style and then set this on the Button itself - meaning that I don't have to replicate the style multiple times?
You could implement two attached properties - one for the Image source and one for the text - that you can set on any Button:
public class ButtonProperties
{
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.RegisterAttached("ImageSource", typeof(Uri), typeof(ButtonProperties));
public static Uri GetImageSource(Button button)
{
return (Uri)button.GetValue(ImageSourceProperty);
}
public static void SetImageSource(Button button, Uri value)
{
button.SetValue(ImageSourceProperty, value);
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached("Text", typeof(Uri), typeof(ButtonProperties));
public static string GetText(Button button)
{
return (string)button.GetValue(ImageSourceProperty);
}
public static void SetText(Button button, string value)
{
button.SetValue(ImageSourceProperty, value);
}
}
Then you only need to define the ContentTemplate once as a resource, for example in your App.xaml:
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
StartupUri="MainWindow.xaml">
<Application.Resources>
<DataTemplate x:Key="dataTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=(local:ButtonProperties.ImageSource), RelativeSource={RelativeSource AncestorType=Button}}" Height="20"/>
<TextBlock Text="{Binding Path=(local:ButtonProperties.Text), RelativeSource={RelativeSource AncestorType=Button}}" Margin="5,0,0,0"/>
</StackPanel>
</DataTemplate>
</Application.Resources>
</Application>
Usage:
<Button local:ButtonProperties.Text="Staff"
local:ButtonProperties.ImageSource="pack://application:,,,/Resources/MainWindowIcons/Staff.ico"
ContentTemplate="{StaticResource dataTemplate}" />

How to bind an element defined inside the control template for a custom control to a property defined in a subclass (of custom control class)

I would like to bind an element inside a control template for a custom control to a property defined in a child class defined inside the custom control class. What might be the syntax for doing that (see {Binding ???})?
Some code...
C# code:
public class CustmCntrl : Control
{
// blablabla
public class SubChildClass: INotifyPropertyChanged
{
public double X { get; private set; }
public string info { get; private set; }
}
}
XAML:
<Style TargetType="{x:Type local:CustmCntrl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustmCntrl}">
<Grid> ... </Grid>
<ItemsControl
ItemsSource="{Binding stuffToDisplay, RelativeSource={RelativeSource TemplatedParent}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<Grid> ... </Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:X
ToolTip="{Binding info ???}">
<local:X.RenderTransform>
<TranslateTransform X="{Binding X ???}"/>
</local:X.RenderTransform>
</local:X>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I can't test this at the moment, but you should be able to bind by adding the name of the SubChildClass class. Try something like this:
<TextBlock Text="{Binding SubChildClass.PropertyName, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourCustomControl}}}">
If that doesn't work, try this:
<TextBlock Text="{Binding (SubChildClass.PropertyName), RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourCustomControl}}}">
You can find out more from the Property Path Syntax page on MSDN.
UPDATE >>>
Yeah, after getting a chance to test this out in a project, it seems that you can't data bind directly with class properties like that. However, if you just wanted to declare the class there as a container for some basic data items in your control then that is fine. As long as you define some DependencyPropertys to hold them, you can use that class just fine:
private static DependencyProperty ItemsProperty = DependencyProperty.Register("Items",
typeof(ObservableCollection<SubChildClass>), typeof(CustomControl1));
public ObservableCollection<SubChildClass> Items
{
get { return (ObservableCollection<SubChildClass>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
In Generic.xaml:
<ListBox ItemsSource="{Binding Items,
RelativeSource={RelativeSource AncestorType={x:Type local:CustmCntrl}}}" />

Categories