UserControl within a ControlTemplate - wpf

I have a ControlTemplate for a Telerik Tile and I am overriding like below:
<ControlTemplate TargetType="{x:Type ctrl:Tile}">
<Border>
<local:UserControl>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</local:UserControl>
</Border>
</ControlTemplate>
My user control looks like:
<DockPanel>
<!-- some content -->
<ContentPresenter/>
</DockPanel>
The ControlTemplate does not display the content of the UserControl.
If I change my control template to:
<ControlTemplate TargetType="{x:Type ctrl:Tile}">
<Border>
<StackPanel>
<local:UserControl/>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</StackPanel>
</Border>
</ControlTemplate>
It will find the content and place it appropriately. It seems like the ControlTemplate cannot find the content once it's nested inside my UserControl. Is there anything I could be doing wrong?
Note that these ControlTemplate Items are appearing in an ItemsPresenter.

You're treating the UserControl as if it is a basic ContentControl (like a Button) which is a little different than what it actually is. Using Button as an example, when you add a child (i.e. a TextBlock) into a Button element that's actually setting that TextBlock as the Button's Content property. The way it gets rendered is through the Button's ControlTemplate, which includes a ContentPresenter to inject Content into. The Visual Tree ends up like this:
<Button>
-start Template
<Border>
<ContentPresenter>
-start Content
<TextBlock>
So far that's basically the model your code is following. The problem is that you're using a (still ContentControl derived) UserControl instead, which rather than using a ControlTemplate is most often defined with a XAML+code-behind model, where the XAML defines the Content of the UserControl. (It is possible to switch these models and template a UserControl or make a Button derived class with XAML+code-behind but not common)
If you want to both define the look of your UserControl in XAML as normal and still be able to inject other content you can add another DependencyProperty that mirrors the setup of the Content property and set your content to that. This approach is used with HeaderedContentControl derivatives (i.e. Expander) which essentially has 2 content properties, Content and Header. Using the new property would look like this:
<Border>
<local:UserControl>
<local:UserControl.OtherContent>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</local:UserControl.OtherContent>
</local:UserControl>
</Border>
And then inside the UserControl's XAML you need to explicitly set up the ContentPresenter Bindings (you only get them for free inside templates of ContentControls):
<DockPanel>
<!-- some content -->
<ContentPresenter Content="{Binding Path=OtherContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"/>
</DockPanel>
If you want a ContentTemplate, ContentTemplateSelector, or ContentStringFormat you'll also need to add properties and bindings for those.

Related

Triggers, commands not firing in custom ListBox's ItemTemplate

I've got a custom ListBox control with a style set up in my Themes/Generic.xaml. I then have a button in the ListBox's ItemTemplate, and it's Click event isn't firing and I've got no idea why. Same goes for the button's Commands (I'm confident the Command issue isn't DataContext related) and interaction triggers. While attempting to debug, I noticed that using the default ListBox instead of my own stopped the problem, but I need to use the custom control.
This is essentially what I've got (fluff removed for brevity). The button:
<controls:CustomListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Click=MyHandler/>
</DataTemplate>
</ListBox.ItemTemplate>
</controls:CustomListBox>
And the custom control's style in Themes/Generic:
<Style TargetType="{x:Type controls:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:CustomListBox}">
<Border>
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How can I get this event to fire?
I think this should probably be a usercontrol rather than a custom control.
Are you really going to change the template out of this for something else?
If you use an event handler like that then how are you planning on using the delegate? It's a very inflexible way of working you're headed in.
You mentioned command, which is probably rather more like it.
If you use a button in an item template with a command bound like
<Button Command="{Binding RowCommand}"
Then the datacontext of that Button is the content of the row.
If you bind ItemsSource to a collection Items of ItemVM then it's looking in the ItemVM that is presented to that row.

How is the DataContext of a ControlTemplate set?

I am setting the ControlTemplate in a Tile control (in the Telerik TileList). It looks something like this:
<ControlTemplate TargetType="{x:Type telerik:Tile}">
<Border>
<!-- Some Content that binds to DP on the view models -->
<ContentPresenter Content="{Binding}" />
</Border>
</ControlTemplate>
Elsewhere:
<telerik:RadTileList ItemsSource="{Binding ComponentViewModels}">
And I have DataTemplates defined for the items that would be presented within the Tile's ContentPresenter. The trouble I have is that, when a ComponentViewModel is added to the target of the ItemsSource (ComponentViewModel ObservableCollection) a new Tile appears but it's DataContext is the RadTileList's ViewModel and not the individual component's ViewModel.
Am I missing something regarding the setting of the DataContext in a ControlTemplate?
To bind to a property on the Presenter or ViewModel attached to the DataContext on the parent view or control from inside a DataTemplate you have to use the RelativeSource property with the value “FindAncestor” and the type of the control with the DataContext you are looking for.
The most common mistake I’ve seen using this, is people forgetting to use the {x:Type yourControlType} markup extension for the AcestorType property and using “AncestorType=yourControlType” instead.
Here is an example:
Width="{Binding DataContext.SomeProperty, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
where “SomeProperty” is a property on the Presenter or ViewModel that follows the INotifyPropertyChanged pattern.
Width is the property of the control inside the the ControlTemplate
This seemed to do it. I needed to do TemplateBinding for the ContentTemplate and Content properties.
<ControlTemplate TargetType="{x:Type telerik:Tile}">
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}"/>
</ControlTemplate>

How to override the ControlTemplate of a UserControl

How can I know the default template element of a usercontrol when I trying to override it?
For example somebody have overrided the TabControl's template like this.
<TabControl>
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled">
<TabPanel x:Name="HeaderPanel"
Panel.ZIndex ="1"
KeyboardNavigation.TabIndex="1"
Grid.Column="0"
Grid.Row="0"
Margin="2,2,2,0"
IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</StackPanel>
</ControlTemplate>
</TabControl.Template>
<TabItem Header="TabItem1">TabItem1 Content</TabItem>
<TabItem Header="TabItem2">TabItem2 Content</TabItem>
</TabControl>
How does he know there is <StackPanel> and <ContentPresenter> in the TabControl's template?
The TabControl class has a [TemplatePart] attribute that indicates mandatory parts of the template:
[StyleTypedPropertyAttribute(Property = "ItemContainerStyle", StyleTargetType = typeof(TabItem))]
[TemplatePartAttribute(Name = "PART_SelectedContentHost", Type = typeof(ContentPresenter))]
public class TabControl : Selector
In this case the template must contain a ContentPresenter named PART_SelectedContentHost. Everything else is optional, you can put anything you like in the template (as long as it makes sense of course).
When you are overriding a ControlTemplate for any control, you are defining how it will look. The <StackPanel> is just the layout control you are using, it could be a grid or any other layout control.
However the is something it needs to be there. If you look at the WPF control hierarchy, you can see several types of controls at the base levels, after Control, FrameworkElement, etc:
<ContentControl>
<HeaderedContentControl>
<ItemsControl>
<HeaderedItemsControl>
Each one of these have specific rendering options and parts. In your case a <TabControl> is an <Selector> which is a special type of an <ItemsControl>. This Selector has a Content and a TabPanel, thus the <TabPanel> and the <ContentPresenter> (which tells WPF where to render the Content).
The best way to aquire this knowledge is by looking at the default WPF templates for each control, for example the TabControl default template for WPF4 is here
See MSDN for default templates and styles.

Binding the button Content to the userControl Content

I have some userControl that contain simple button.
I want to bind the button Content to the userControl Content - How to do it?
Set a name for the user control (for example x:Name="self") and in the Button
<Button Content={Binding ElementName=self}" />
Do you mean this or something else?
If the Button is inside the UserControl it is part of the UserControl's Content and can't recursively contain itself. The whole purpose of a UserControl is that you're explicitly defining a fixed set of Content. If you want variable Content then you should use a templated ContentControl something like this:
<ContentControl Content="{Binding SomeVariableValue}">
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Border>
<!-- Other content from your user control -->
<Button Content="{TemplateBinding Content}"/>
</Border>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>

How to change the style of a combobox to a label or hyperlink in WPF?

I want to change the style of a combo box control to look like a hyperlink.
When user clicks on the hyperlink ( combo box) it show options in the combobox to select.
The idea is that I want combo box control to display as a plain text ( more readable form).
If anybody created this type of sytle please let me know.
You could edit the ComboBox template and replace the ContentPresenter with a Hyperlink-style button. This should work pretty well, and it is just a bit of XAML coding. You can find the original ComboBox template here, or using Expression Blend.
EDIT:
OK, well, you have a ComboBox template which looks something like this (extremely simplified!):
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<!-- The popup that is displayed after you clicked on the ComboBox. -->
<Popup IsOpen="{TemplateBinding IsDropDownOpen}"
Placement="Bottom"/>
<!-- The button that is used to open the drop down. -->
<ToggleButton x:Name="btnOpenDropDown"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
<!-- The control which displays the currently selected item. -->
<ContentPresenter x:Name="contentPres"
Content="{TemplateBinding SelectionBoxItem}"/>
</Grid>
</ControlTemplate>
Actually, it is a bit more complicated because the ToggleButton must occupy the whole width (since the drop down should open whereever you click on the ComboBox), but it should display only on the right of the content. However, for your scenario, we can neglect this since you will not have a drop down button.
Now, since you only want to display the content as a hyperlink and no button besides it, you no longer need a distinction between ContentPresenter and ToggleButton. Therefore, instead of using a separate ContentPresenter, you could use the ToggleButton to present the content since it also has a Content property. Something like that:
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<!-- The popup that is displayed after you clicked on the ComboBox. -->
<Popup IsOpen="{TemplateBinding IsDropDownOpen}"
Placement="Bottom"/>
<!-- The button that is used to open the drop down AND to display the content (now). -->
<ToggleButton x:Name="btnOpenDropDown"
Content="{TemplateBinding SelectionBoxItem}"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</ControlTemplate>
Of course, there are some additional properties to move from the ContentPresenter to the ToggleButton.
Now, all you have to do is define another template for the ToggleButton which looks like a Hyperlink (and then assign this template to the ToggleButton above). Actually, this should not be difficult assuming that your content is always a string (again, simplified!):
<Style x:Key="hyperlinkButtonStyle" TargetType="{x:Type ButtonBase}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<TextBlock Text="{TemplateBinding Content}"
TextDecorations="Underline"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This simplified code shows how you could do it. There are certainly other ways, and it still involves some work for you as the example was simplified. However, I cannot offer you the complete code.

Resources