ContentControl does not show content in ControlTemplate - c#

I need to create a custom control similar to WPF GroupBox. I started from the standard WPF Custom Control Library template in VS 2015 and defined my CustomGroupBox control like this:
public class CustomGroupBox : ContentControl
{
static CustomGroupBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomGroupBox), new FrameworkPropertyMetadata(typeof(CustomGroupBox)));
}
}
Then added the following minimal set of lines to develop my custom GroupBox according to the specification:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomGroupBox">
<Style TargetType="{x:Type local:CustomGroupBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomGroupBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="Gray" BorderThickness="3" CornerRadius="3">
<ContentControl />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
To debug and develop, I created a test form for my custom control:
<Window x:Class="CustomGroupBoxClient.MainWindow"
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:CustomGroupBoxClient"
xmlns:ctrl="clr-namespace:CustomGroupBox;assembly=CustomGroupBox"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ctrl:CustomGroupBox Margin="5">
<StackPanel Orientation="Vertical">
<TextBlock>Text Block #1</TextBlock>
<TextBlock>Text Block #2</TextBlock>
</StackPanel>
</ctrl:CustomGroupBox>
</Grid>
</Window>
However, when I launch this form, I see the border of my custom control but not the content (2 TextBlock's):
I've re-read many manuals and articles related to this topic, but still can't figure out why ContentControl in my ControlTemplate does not display the specified content. How to solve my problem?

Try using ContentPresenter instead of ContentControl in your template:
<ContentPresenter />
By default, ContentPresenter finds the Content property of its templated parent and displays whatever it finds there. You could change the name of the property it looks for by changing its ContentSource value...
<ContentPresenter ContentSource="FooBar" />
...but since you're inheriting from ContentControl, the default is probably what you want.

Related

Header property is not working in UWP CustomTextBox

In UWP I have created CustomTextBox which is derived from TextBox control. In my CustomTextBox, I have used various properties but some properties are working and some are not working.
Below properties are working fine,
Width Height BorderBrush, etc,.
Below properties are not working,
Header Text, etc,. Please find code snippet on below,
MyTextBox.cs
public sealed class MyTextBox : TextBox
{
public MyTextBox()
{
this.DefaultStyleKey = typeof(MyTextBox);
}
}
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SfMaskedEdit_header">
<Style TargetType="local:MyTextBox" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyTextBox">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainPage.xaml
<Page
x:Class="SfMaskedEdit_header.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SfMaskedEdit_header"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="grid1">
<local:MyTextBox x:Name="myTextBox" Text="Hello" Header="MyTextBox" Width="200" Height="40"
BorderBrush="Blue" BorderThickness="2" Background="Pink"/>
</Grid>
</Page>
Please use the below sample for further information,
Sample: MyTextBox
The reason is that you have overridden the default template and hence removed all the elements that normally make up a TextBox.
To understand this, it is important to make it clear that UWP controls usually just provide a behavior, but are themselves made up of multiple other controls in their template (the terminology is usually that XAML controls are look-less by default). And these controls are providing the visual representation of the control. For example, in the case of TextBox, the template is made up of Border for the borders around it, TextBlock for the placeholder text, Button for clearing out the content, a ScrollViewer where the Text input is rendered and a ContentPresenter for the header and so on.
Your custom TextBox is in fact just the Border itself, which means you have essentially lost the capabilities you mentioned because there are no controls to surface them visually.
To fix your problem, I suggest starting with the default TextBox template (by copying and pasting it to your own style. And then just editing the parts that you want to change. This way you will start from the fully functional state and can decide what to change while retaining the capabilities of the control.
You can find the default template in C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\{version}\Generic\generic.xaml. You can search for <Style TargetType="TextBox"> to find the default TextBox style and template.

WPF Custom Toggle button with ContentControl

It seems very hard to achieve something rather trivial in WPF...
I need to design a toggle button with a specific look (and feel). I made a small project to demonstrate the problem.
"ToggleButton user control" :
<UserControl x:Class="WpfApp4.UserControl1"
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"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"
Name="Bla">
<UserControl.Resources>
<Style TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Width="300" Height="300" Fill="Yellow"/>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ToggleButton
Width="300" Height="300">
<ContentControl Content="{Binding ElementName=Bla, Path=MainContent}"/>
</ToggleButton>
</UserControl>
Dependency property:
public static DependencyProperty MainContentProperty = DependencyProperty.Register(
"MainContent",
typeof(object),
typeof(UserControl1),
null);
public object MainContent
{
get => GetValue(MainContentProperty);
set => SetValue(MainContentProperty, value);
}
The way I want to use the control:
<local:UserControl1>
<TextBlock>Whatever</TextBlock>
</local:UserControl1>
When I run the program, the textbox appears "Whatever", but the style is not applied, the ellipse won't show.
What's the correct way of doing this?
=== Update ===
OK, getting somewhere... finally...
Now I got this as user control:
<UserControl x:Class="WpfApp4.UserControl1"
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:wpfApp4="clr-namespace:WpfApp4"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"
Name="Bla">
<UserControl.Resources>
<Style TargetType="wpfApp4:UserControl1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ToggleButton>
<Grid>
<Ellipse Width="300" Height="300" Fill="Yellow"/>
<ContentPresenter Content="{Binding ElementName=Bla, Path=MainContent}" />
</Grid>
</ToggleButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ContentPresenter/>
</UserControl>
And this is how I use it:
<local:UserControl1>
<local:UserControl1.MainContent>
<TextBlock>Whatever</TextBlock>
</local:UserControl1.MainContent>
</local:UserControl1>
That finally gives me a toggle button with the style applied (the ellipse shows up) and the textbox is shown as well.
So, this works. Is this the way you mean it should work? Or can it be simplified?
It should be more like
<local:UserControl1>
<local:UserControl1.MainContent>
<TextBlock>Whatever</TextBlock>
</local:UserControl1.MainContent>
</local:UserControl1>
But you should look forward overriding ContentControl which would be more adequate rather then using UserControl.
By the way why did you put a ContentControl inside ToggleButton? ToggleButton by itself is a ContentControl it has it's own Content property.
Update:
All depends on what you whant to do. If it is only change the visual of the toggle button, then just create a toggle button style like this:
<ToggleButton>
<TextBlock>Whatever</TextBlock>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Ellipse Width="300" Height="300" Fill="Yellow"/>
<ContentPresenter Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
Ofcorse if you want to use your style across the application it is better to define the style in a resource dictionnary (for exemple in App.xaml), give it a key and call it on each toggle button using {StaticResource key}.
If on the other hand, you want to add some logic, you have to create a control class inheriting from ToggleButton and add the logic inside.

Custom Control BorderThickness

I've created a custom WPF user control. The problem is, that sometimes I need a BorderThickness of 0 and sometimes a BorderThickness of 1.
<UserControl ...>
<clay:TextBox x:Name="ClayTextBox"
BorderThickness="1" >
</clay:TextBox>
</UserControl>
If I'm using the control in a xaml document like this:
<clay:TextBox x:Name="ClayTextBox"
BorderThickness="0" >
</clay:TextBox>
... the Border is always 1. How can I solve that?
In your custom control template style, you should set the parent container control as border and then use template binding to bind the border thickness. Here I've assumed that your CustomControl inherits a control that has BorderThickness as a property.
<ControlTemplate TargetType="{x:Type clay:TextBox}">
<Border BorderThickness="{TemplateBinding BorderThickness}">
//Remaining xaml that makes up your custom control.
</Border>
</ControlTemplate>
Have your border Bind its BorderThickness propety to the UserControls one like this:
<UserControl x:Class="UseRcontrolWithProperty.UserControl1"
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"
mc:Ignorable="d" x:Name="this"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Border BorderThickness="{Binding ElementName=this, Path=BorderThickness}"></Border>
</Grid>
</UserControl>
that way changing the BorderBrush on the UserControl will change the border brush of the internal border.

Access `DependencyProperty` via XAML and evaluate it?

I just created a DependencyProperty for my custom button.
The property is called IsChecked.
If IsChecked == true my button shall change its background color to something else and keep this color until IsChecked == false.
Here is my DependencyProperty:
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(MainMenuButton), new PropertyMetadata(false));
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
Next I have a ControlTemplate for this button:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="MainMenuButtonStyle" TargetType="{x:Type UserControl}">
<ControlTemplate.Resources>
/* Some storyboards */
</ControlTemplate.Resources>
<Grid x:Name="grid">
<Grid.Background>
<SolidColorBrush Color="{DynamicResource MainUI_MainMenuButtonBackground}"/>
</Grid.Background>
</Grid>
<ControlTemplate.Triggers>
/* Some triggers */
</ControlTemplate.Triggers>
</ControlTemplate>
My problem is now how I can access IsChecked and based on the current value of it change the background of grid? Haven't done that before only tried it some time ago and totally failed.
Thanks in advance :)
Edit:
Here is the UserControl aswell:
<UserControl
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"
mc:Ignorable="d"
x:Class="IFCSMainInterface.MainMenuButton"
x:Name="UserControl"
d:DesignWidth="640" d:DesignHeight="480" Width="272" Height="110" Template="{DynamicResource MainMenuButtonStyle}">
<Grid x:Name="LayoutRoot"/>
I think the problem is here:
<ControlTemplate x:Key="MainMenuButtonStyle" TargetType="{x:Type UserControl}">
You must set the correct type in order to use its dependency property.
Write in your ResourceDictionary the namespace of the class, and put your type correctly in control template, eg:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ProjectName.NameSpace">
<ControlTemplate TargetType="my:MainMenuButton" x:Key="MainMenuButtonStyle">
<!-- ... -->
</ControlTemplate>
</ResourceDictionary>
Edit (now that was explained):
You're trying to define a template in xaml own control
That does not seem quite right to do, I suggest looking for other approaches, such as creating a control that uses a Generic.xaml
( http://utahdnug.org/blogs/xamlcoder/archive/2007/12/13/building-custom-template-able-wpf-controls.aspx )
but if you want to use the dependency properties (the way you're doing) you can try the following code (for example):
<UserControl x:Class="SamplesStack.Controls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:SamplesStack.Controls">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid x:Name="LayoutRoot">
<ContentPresenter />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsChecked}" Value="True">
<Setter TargetName="LayoutRoot" Property="Background" Value="Red"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Template>
This will make your control work as expected. Though not think very cool use DataTrigger.

mysterious runtime error in applying a template to wpf window

i have a hard problem dealing with template. please help me.
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">
//Note i didn't set a StartupURI in Application tag please.
<Application.Resources>
<Style TargetType="Window" x:Key="myWindowStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Rectangle Fill="gray" RadiusX="30" RadiusY="30"/>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</Application>
App.xaml.cs
using System;
using System.Windows;
namespace WpfApplication1
{
public partial class App : Application
{
CMainWindow winMain;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
winMain = new CMainWindow();
winMain.ShowDialog();
}
}
}
CMainWindow.xaml
<Window x:Class="WpfApplication2.CMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Style="{StaticResource myWindowStyle}" Background="Red">
</Window>
=====================
question #1
when run this program, ide occure a runtime error : XmlParseException.
so i add a line in app.xaml, it runs properly. that line is : StartupUri="CMainWindow.xaml".
what is this? what relationship between template and startupuri? please tell me about this.
question #2
when i add control to CMainWindow, it didn't apeear even i set a in window's template.
how can i add control properly in this situation?
thanks.
question #1
A WPF application is always centered around a window. You're override of OnStartup is unnecessary. By setting the StartupURI the application will automatically start by displaying the window.
There is no actual relationship between template and startupuri. You just happen to be using App.xaml to store global styles.
question #2
The magic field to add is "TargetType" on the control template. You have to explicitly say its for the window type.
<Application x:Class="SimpleWPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="Window" x:Key="myWindowStyle">
<Setter Property="Template">
<Setter.Value>
<!-- Explicitly setting TargetType to Window -->
<ControlTemplate TargetType="Window">
<Grid>
<Rectangle Fill="gray" RadiusX="30" RadiusY="30"/>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</Application>

Categories