WPF Custom Control Display the Content - wpf

I'm creating a custom control in WPF, and I would like to be able to display whatever I put inside it.
A good example would be a Grid, StackPanel, DockPanel
Where you may do something like:
<StackPanel>
<TextBox />
<Button/>
</StackPanel>
And the StackPanel knows about the TextBox and the Button displays them and reacts accordingly.
Question:
How can I display what I put inside my control?
I would like to be able to do something like:
<controls:MyControl>
<Grid>
<TextBox />
<Button />
</Grid>
</controls:MyControl>
Update
Code behind looks like:
public class MyControl:ContentControl
{
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl),
new FrameworkPropertyMetadata(typeof(MyControl)));
}
}
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderThickness="1" BorderBrush="Black">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Extend ContentControl:
public class MyControl : ContentControl {}
It has a Content property that you can set to any object, including a Grid or any other Panel.

Related

WPF Style: Generalizing

Following being WPF style, is there a way to generalize the hard-coded column names (Name and Code), so that I could specify them when actually applying this style on a ComboBox? Even better, if I could even modify the number of columns?
<Style TargetType="ComboBox" x:Key="MultiColumnComboBoxStyle">
<Style.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border>
<Grid HorizontalAlignment="Stretch" TextElement.FontWeight="Normal">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="Code" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=Name}" />
<Rectangle Grid.Column="1" Width="1" Fill="Black" />
<TextBlock Grid.Column="2" Text="{Binding Path=Code}" Margin="5,0,5,0" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Grid.IsSharedSizeScope="True" IsItemsHost="True" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
instead of using a style you could consider craeting a custom control with a dependency property for your columns.
a little bit of setup involved but It will better meet your needs, especialy if you want to reuse it.
an example would be something like the following. Some of this is psuedo code you should be able to fill out.
<!-- In your generic.xaml file -->
<Style TargetType="MyCustomComboBox" >
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="MyCustomComboBox" >
<!-- your template code goes here -->
<Grid x:Name="_myCustomGrid />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
//then in a cs file inherit from the combo box
public class MyCustomColumnComboBox : ComboBox //get all the combobox functionality
{
public IList ComboColumns
{
get { return (IList)GetValue(ComboColumnsProperty);}
set { SetValue(ComboColumnsProperty,value);}
}
public static readonly DependencyProperty ComboColumnsProperty = DependencyProperty.RegisterProperty(...);
private Grid _grid;
public override OnApplyTemplate()
{
//pull your template grid info here, then use that when setting the columns.
_grid = GetTemplateChild("_myCustomGrid") as Grid;
//from here you can check to see if you have your list yet,
//if you don't then you maintain the grid for when you do have your list.
// This can behave different depending on if you are in wpf or silverlight,
// and depending on how you were to add the items to the dependency property.
}
}
In Summary, for you, add the custom control with the custom dependency property, then in your theme/generic.xaml drop in your template and name the grid to what you want to pull into your template in the on apply template function. from there you are either ready to set up or can set up your columns that you specified in the dependency property.
NOTE : The dependency property isn't actually necessary but it can help to buy you a little bit more flexibility later on using things like the dependency properties on change callback to update if necessary.

Binding ContentPresenter.Content to TemplatedParent (WPF/Silverlight)

Basically, I would like to overlay, for example: TextBlock over Button, by using ControlTemplate (applied to this Button), but I don't want to get rid of default template of it.
Example:
<Grid Grid.Row="1" Grid.ColumnSpan="2">
<Grid.Resources>
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<ContentPresenter />
<TextBlock Text="textBlock"
Margin="10" Foreground="Red"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Button Style="{StaticResource myStyle}" Content="button1"></Button>
</Grid>
and it gives Button stripped of it's default template:
Rather, I would like to receive something like this:
Is it possible by using ControlTemplate? I was trying to bind TemplatedParent to ContentPresenter.Content like here:
<ContentPresenter Content="{Binding
RelativeSource={RelativeSource Mode=TemplatedParent},
Path=.,
Mode=TwoWay}"/>
or other combinations, but I couldn't make it work.
Edit:
Because I would like to be able to apply this TextBlock not only to a button (it was just an example) but to any Control, I don't want do it by copying default style (to the resources or somewhere), for every Control.
Also, I would prefer not to create UserControl, because I would like to keep xaml clean as much as possible (I mean with system Controls) - and just to turn on/off the overlaying TextBlock by using a ControlTemplate.
You could add the default style on the button and modify it to add your TextBlock. The second option, my preference, is to create a new UserControl that will contain the Button and the TextBlock with IsHitTestVisible=False. You can then add dependency properties to be able to bind to the button and the text block.

Binding within a UserControl not working (templatebinding to a custom dependency property)

I have a user control which I'd like to be used like so:
// MainPage.xaml
<my:MyControl Data="10" />
<!-- or -->
<my:MyControl Data="{Binding SomeData}" />
It's codebind is this:
public partial class MyControl : UserControl
{
public MyControl() {
InitializeComponent();
}
public const string DataPropertyName = "Data";
public int Data
{
get
{
return (int)GetValue(DataProperty);
}
set
{
SetValue(DataProperty, value);
}
}
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
DataPropertyName,
typeof(int),
typeof(MyControl),
new PropertyMetadata(10);
}
It's xaml portion is this:
<UserControl>
<!-- omitted namespaces etc. -->
<Grid x:Name="LayoutRoot">
<Button x:Name="myButton" Content="{Binding Data}">
<Button.Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<TextBlock Text="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Button.Style>
</Button>
</Grid>
</UserControl>
The crucial line, in usercontrol's xaml part is:
<Button x:Name="myButton" Content="{Binding Data}">
I'd like to bind this Button's Content property to the UserControl's property (Data), while still retaining the ability to set values on it from outside (<my:MyControl Data="10" />)
The problem is, that when I use binding - <Button x:Name="myButton" Content="{Binding Data}"> - it doesn't work (The templatebinding doesnt pick any values)
It works however, if I set the values manually i.e - <Button x:Name="myButton" Content="12">
If you want to bind to your "own" dependency property inside a UserControl you need to add a x:Name to your UserControl and use it as the ElementName in your binding.
<UserControl x:Name="myControl">
<!-- omitted namespaces etc. -->
<Grid x:Name="LayoutRoot">
<Button x:Name="myButton"
Content="{Binding Data, ElementName=myControl}">
</Button>
</Grid>
</UserControl>
To make the Template also work:
Instead of the TemplateBinding you need to use the RelativeSource TemplatedParent sysntax, because you need to set the Mode=OneWay (TemplateBinding uses Mode=OneTime for performance reasons by default but in your scenario you need Mode=OneWay)
<Style TargetType="Button">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<TextBlock Text="{Binding Path=Content, Mode=OneWay,
RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>

Specify ControlTemplate for ItemsControl.ItemContainerStyle

The following is similar to what I'm trying to accomplish. However, I get the error
Invalid PropertyDescriptor value.
on the Template Setter. I suspect it's because I didn't specify a TargetType for the Style; however, I don't know the container type for ItemsControl.
<ItemsControl>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<TextBlock Text="Some Content Here" />
<ContentPresenter />
<Button Content="Edit" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<!-- heterogenous controls -->
<ItemsControl.Items>
<Button Content="Content 1" />
<TextBox Text="Content 2" />
<Label Content="Content 3" />
</ItemsControl.Items>
</ItemsControl>
You can qualify the property name with the type name:
<Setter Property="Control.Template">
The container for ItemsControl is normally a ContentPresenter, but if the child is a UIElement then it won't use a container. In this case, all of the children are Controls, so the ItemContainerStyle will apply to them directly. If you added an item other than a UIElement, that setter would set the Control.Template property on the ContentPresenter, which would succeed but have no effect.
Actually, it sounds like what you want is to wrap each child in a container, even if they are already a UIElement. To do that, you will have to use a subclass of ItemsControl. You could use an existing one like ListBox, or you could subclass ItemsControl and override GetContainerForItemOverride and IsItemItsOwnContainerOverride to wrap the items in your own container. You could wrap them in a ContentControl and then use that as the TargetType for the Style.
public class CustomItemsControl
: ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ContentControl();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
// Even wrap other ContentControls
return false;
}
}
You will also need to set the TargetType on the ControlTemplate so that the ContentPresenter will bind to the Content property:
<ControlTemplate TargetType="ContentControl">
Also if you only want to do all of it with XAML you can simply use ListBox instead of ItemsControl and define a style for ListBoxItem:
<ListBox ItemsSource="{Binding Elements.ListViewModels}">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel>
<TextBlock>Some Content Here</TextBlock>
<ContentPresenter Content="{TemplateBinding Content}" />
<Button>Edit</Button>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Note that because I am using ListBox the container is ListBoxItem(Generally the container for WPF's default list control is always named the Item) so we create a style for ListBoxItem:
<Style TargetType="ListBoxItem">
Then we create a new ControlTemplate for ListBoxItem. Please note that ContentPresenter is not used as it always appears in articles and tutorials, you need to template-bind it to Content property of ListBoxItem, so it will show the content for that item.
<ContentPresenter Content="{TemplateBinding Content}" />
I just had the same problem and fixed it this way. I dont wanted some functionalities of ListBox ( item selection ) and by using this technique the item selection does not work anymore.

WPF: Inheriting from HeaderedContentControl

I would like to create a simple control that inherits from HeaderedContentControl, and has some basic dependency properties called Title, Subtitle, Icon. I would like to be able to provide a default header template that databinds these properties. For this example, I have named this class HeaderedView.
I am having trouble in providing a default header template that can bind to the properties defined on the HeaderedView. I am experimenting with markup like the following:
<Style TargetType="{x:Type local:HeaderedView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedContentControl}">
<StackPanel>
<Grid>
<ContentPresenter ContentSource="Header"/>
</Grid>
<Grid>
<ContentPresenter ContentSource="Content"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<TextBlock Text="{TemplateBinding local:HeaderedView.Title}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Unfortunately, the Title is not being displayed.
The header template must be replaceable (which is why I want to utilize the HeaderedContentControl).
Every time I seem to want to inherit from this control, I seem to struggle with the implementation. Any help would be greatly appreciated!
In your template, you are using a ContentPresenter to display the Header, but you're not telling the ContentPresenter that it needs to use the HeaderTemplate. You should be able to do this in order to see your custom HeaderTemplate applied:
<ContentPresenter ContentSource="Header" ContentTemplate="{TemplateBinding HeaderTemplate}" />
Also, if you're only planning on changing the HeaderTemplate, then you don't need to override the Template in the first place. The default HeaderedContentControl will apply your HeaderTemplate appropriately.

Resources