WPF: Styling ContentControl - c#

I would like to create a style that could be applied to any ContentControl which would take the ToolTip and add an image to the ContentControl and apply the tool tip text from the object to the image. I have about a hundred of these that need to be done (in various projects) so being able to create a style would save a lot of typing.
What I am trying to recreate is this (ToolTip text is on the blue 'i' and not the 'Reload Employee Data':
which is accomplished via the following:
<StackPanel Orientation="Horizontal">
<CheckBox Content="Reload Employee Data"
IsChecked="{Binding AdjustmentSettings.ReloadEmployeeData}"
Grid.Row="0"
Grid.Column="0">
</CheckBox>
<Image Source="/DelphiaLibrary;Component/Resources/info.ico"
ToolTip="Check if you want to re-upload ..."/>
</StackPanel>
What I am trying to avoid is creating a new stack panel each time I want to add the blue 'i' with the tool tip text on the 'i' and not on the text of the object.
I was able to create the following that works for a Label:
<!-- Works for just Label -->
<Style x:Key="LabelToolTipStyle"
TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{TemplateBinding Content}" />
<Image Source="info.ico" ToolTip="{TemplateBinding ToolTip}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I can call this simply by adding the style to the label like so:
<Label Content="First Text" Style="{StaticResource LabelToolTipStyle}" ToolTip="Label with LabelToolTipStyle" />
I then tried to make this more generalized by creating a style targeting ContentControl but obviously doesn't work because this overrides the entire template (in the case of CheckBox control, the checkbox is missing):
<!-- Works on Label but not CheckBox -->
<Style x:Key="ContentToolTipStyle"
TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{TemplateBinding Content}" />
<Image Source="info.ico" ToolTip="{TemplateBinding ToolTip}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Is there a way that I could create a style for ContentControls that would allow me to ADD to the template without redefining the entire template? If it cannot be done to ContentControl I wouldn't be opposed to creating a separate style for each control type but would like to avoid redefining the entire template to do so.

You are almost there. You need to create a custom control template for a ContentControl:
<Style x:Key="ToolTipWrapper" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<StackPanel Orientation="Horizontal">
<StackPanel.ToolTip>
<ToolTip Visibility="Hidden" />
</StackPanel.ToolTip>
<ContentPresenter />
<Image Source="info.ico" ToolTip="{TemplateBinding ToolTip}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then wrap your elements in a ContentControl and apply the style:
<ContentControl Style="{StaticResource ToolTipWrapper}" ToolTip="Hello world">
<CheckBox Content="I am a check box" />
</ContentControl>
What you can't do is to automatically apply the custom style to all "content" controls: you will always need the extra ContentControl wrapped around each element you want to style in this way.

Related

Reste telerik Theme after add <Style>

I want to change my WPF & C# code to telerik. Before change i have HeaderContentControl with some Workspaces
My XAML code
<HeaderedContentControl
Content="{Binding Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Style="{StaticResource MainHCCStyle}"
/>
My Resources
<Style x:Key="MainHCCStyle" TargetType="{x:Type HeaderedContentControl}>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedContentControl}>
<DockPanel>
<ContentPresenter
ContentSource="Content"
ContentTemplate="{TemplateBinding ContentTemplate}"
/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
After modify code to Telerik my code looks like
My XAML code
<telerik:RadTabbedWindow
Content="{Binding Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
telerik:StyleManager.Theme="Office2016"
Style="{StaticResource MainHCCStyle}"
/>
My Resources
<Style x:Key="MainHCCStyle" TargetType="{x:Type telerik:RadTabbedWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type telerik:RadTabbedWindow}">
<DockPanel>
<ContentPresenter
ContentSource="Content"
ContentTemplate="{TemplateBinding ContentTemplate}"
/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
Workspaces wors ok, but Telerik theme doesn't work ( telerik:StyleManager.Theme="Office2016"). Styles only activate if I delete them Style="{StaticResource MainHCCStyle}", however then workspaces doesn't work
The custom Style that targets RadTabbedWindow is overriding its ControlTemplate (via the Template property). This means that the default look and feel of the control is replaced with the Dock panel defined in the Style.
To make this work, set the ContentTemplate of RadTabbedWindow, instead of its Template property.

Auto scrolling on TextBox ControlTemplate

I'm using the TextBox below, which in order to apply a DropShadowEffect on its text, uses a ControlTemplate. I managed to get the TextWrapping to work, but once the TextBox fills up, it's content goes out of view. How do I replicate the Auto scrolling to the bottom feature of a native TextBox?
<TextBox TextWrapping="Wrap"
Foreground="LimeGreen"
Background="Black"
Margin="10,40,10,40"
FontSize="40"
HorizontalAlignment="Stretch"
x:Name="Inp"
FontFamily="Courier New"
CaretBrush='LimeGreen'>
<TextBox.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Grid x:Name="RootElement">
<ScrollViewer>
<ContentPresenter Content="{TemplateBinding Text}">
<ContentPresenter.Effect>
<DropShadowEffect ShadowDepth="4"
Direction="330"
Color="LimeGreen"
Opacity="1"
BlurRadius="5" />
</ContentPresenter.Effect>
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property='TextWrapping'
Value='Wrap' />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</ScrollViewer>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TextBox.Resources>
</TextBox>
This solution is a bit different that what you might expect. I think using the ContentPresenter is the wrong way because in the end you still want the functionality of the TextBox. So my solution focuses on getting rid of the border and focus indicator that mess up the drop shadow effect:
<TextBox x:Name="Inp"
Height="100"
HorizontalAlignment="Stretch"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
CaretBrush="LimeGreen"
FontFamily="Courier New"
FontSize="40"
Foreground="LimeGreen"
TextWrapping="Wrap">
<TextBox.Effect>
<DropShadowEffect BlurRadius="5"
Direction="330"
Opacity="1"
ShadowDepth="4"
Color="LimeGreen" />
</TextBox.Effect>
<TextBox.FocusVisualStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate/>
</Setter.Value>
</Setter>
</Style>
</TextBox.FocusVisualStyle>
</TextBox>
I set the Background, BorderBrush to be transparent (=> no shadow). I removed the ContentPresenter; it's a 'regular textbox now. And to remove the focus border I set the FocusVisualStyle to an empty Template.

ContentPresenter steals resources?

In a simple view I want to display two buttons, that have as contents images, that are provided via resources in an extra style.xaml that is loaded in the App.xaml.
<BitmapImage x:Key="AddIcon" UriSource="pack://application:,,,/WpfTestBench;component/Images/plus.png"></BitmapImage>
<BitmapImage x:Key="RemoveIcon" UriSource="pack://application:,,,/WpfTestBench;component/Images/minus.png"></BitmapImage>
<Style x:Key="AddButtonWithIconStyle" TargetType="{x:Type Button}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{DynamicResource AddIcon}" Stretch="None"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RemoveButtonWithIconStyle" TargetType="{x:Type Button}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{DynamicResource RemoveIcon}" Stretch="None"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Setter.Value>
</Setter>
</Style>
In my view I use the styles like this:
<DockPanel Margin="0,0,5,0" DockPanel.Dock="Bottom" LastChildFill="False" >
<Button Command="{Binding DeleteCommand}" DockPanel.Dock="Right"
Style="{StaticResource RemoveButtonWithIconStyle}"/>
<Button Command="{Binding AddCommand}" DockPanel.Dock="Right" Margin="5,0"
Style="{StaticResource AddButtonWithIconStyle}"/>
</DockPanel>
So far this looks good.
But when I add another view into this view via a ContentPresenter, that has basically the same content (again with the above described button styles) the display of the first two buttons (of the parent view) does not work anymore. All I get ist two small circles with the button functionality.
<ContentPresenter Content="{Binding SomeEmbeddedViewModel, UpdateSourceTrigger=PropertyChanged}"/>
Why does this happen? Does the ContentPresenter somehow prevent to share the resources?
As explained in the other answer, you should have the Image control in the ContentTemplate of the Button.
Declare a simple Image Button Style with an Image that has its Source property bound to the actual Content:
<Style x:Key="ImageButtonStyle" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
...
<Button Style="{StaticResource ImageButtonStyle}"
Content="{DynamicResource RemoveIcon}" .../>
You may also declare the Style as default Button Style, probably inside the DockPanel:
<DockPanel>
<DockPanel.Resources>
<Style TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding}" Stretch="None"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DockPanel.Resources>
<Button Command="{Binding DeleteCommand}" DockPanel.Dock="Right"
Content="{DynamicResource RemoveIcon}"/>
<Button Command="{Binding AddCommand}" DockPanel.Dock="Right" Margin="5,0"
Content="{DynamicResource AddIcon}"/>
</DockPanel>
Diagnosis
The core reason of this behavior is that an element can only appear once in the visual tree.
First of all, resources in a ResourceDictionary are by default shared, i.e. every time you fetch a resource with a particular key, you always get the same instance. In your case, you always get the same instance of Style, which also means that there's always only one instance of Image (for each style).
So if you apply the same style to two buttons, the framework tries to put the same Image instance in two different places, which is not allowed. To avoid that, upon attempt to load the image into the visual tree, if it already is in the visual tree, it is unloaded from the previous location first.
That's why the image is only visible in the last button (in order in which they were loaded).
Solution
There are basically two solutions to this problem.
1. Disable resource sharing
You can disable the resource sharing so that each time you fetch a resource from a ResourceDictionary you get a new instance of the resource. In order to do that, you set x:Shared="False" on your resource:
<Style x:Key="AddButtonWithIconStyle" x:Shared="False" TargetType="{x:Type Button}">
(...)
</Style>
2. Use ContentTemplate in your style
Instead of putting the image as the content of the button, you could define a ContentTemplate, which is realized separately for each button it is applied to (so each button gets its own instance of the image):
<Style x:Key="RemoveButtonWithIconStyle" TargetType="{x:Type Button}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{DynamicResource RemoveIcon}" Stretch="None"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Personally I'd advise you to use the second solution, since that's exactly the purpose of templates in WPF.

Multiple Foreground Colours In ControlTemplate

I have a Style for an Expander. I want the Foreground property of the Header to be different from the Content.
<Style TargetType="Expander">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Expander">
<StackPanel>
<ContentControl Content="{TemplateBinding Header}" Foreground="{TemplateBinding Foreground}"/>
<ContentControl Content="{TemplateBinding Content}" Foreground="Blue" TextBlock.Foreground="Blue"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Expander>
<Expander.Header>
<TextBlock Text="Header"/>
</Expander.Header>
<Expander.Content>
<TextBlock Text="Content"/>
</Expander.Content>
</Expander>
However, once the Style is applied, both the Header and the Content come back as Red i.e. the Expander style Foreground colour.
How can I get the Expander style to contain multiple Foreground colours.
First, in template is better to use the ContentPresenter, instead of ContentControl, which is a common practice. Quote reply Nir (Question: What's the difference between ContentControl and ContentPresenter?):
ContentControl is a base class for controls that contain other elements and have a Content-property (for example, Button).
ContentPresenter is used inside control templates to display content.
ContentControl, when used directly (it's supposed to be used as a base class), has a control template that uses ContentPresenter to display it's content.
Inside ControlTemplate use ContentPresenter;
Outside of ControlTemplate (including DataTemplate and outside templates) try not to use any of them, if you need to, you must prefer ContentPresenter;
Subclass ContentControl if you are creating a custom "lookless" control that host content and you can't get the same result by changing an existing control's template (that should be extremely rare).
Second, the type of construction:
<Expander Header="MyHeader">
<Expander.Content>
<TextBlock Text="Content"/>
</Expander.Content>
</Expander>
You automatically overwrites your the template (style), so the color is the same everywhere, because Red is set, taken from the Style setter.
So, a slightly modified example:
<Style TargetType="{x:Type Expander}">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<StackPanel>
<Border BorderThickness="{TemplateBinding BorderThickness}" TextBlock.Foreground="{TemplateBinding Foreground}">
<ContentPresenter Content="{TemplateBinding Header}" />
</Border>
<Border Name="Content" BorderThickness="{TemplateBinding BorderThickness}" TextBlock.Foreground="Blue">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Grid>
<!-- Work -->
<Expander Header="MyHeader" Content="MyContent" />
<!-- Not work -->
<!--<Expander Header="MyHeader">
<Expander.Content>
<TextBlock Text="Content"/>
</Expander.Content>
</Expander>-->
</Grid>
To study the work style, you can take a predefined style (such as MSDN), and change it from there.
Note: It is better to design a universal template to the content, because if you set the content other than TextBlock, color for TextBlock will be useless. Perhaps it is better to set the color in style for the TextBlock.

How to read first 5 characters from string using StringFormat in Windows Phone XAML?

Basically in my Windows Phone application, i used to display the Name in the List through Binding. In this case, i want to restrict the name to display only the first 5 set of characters to avoid the unnecessary wrapping.
In my view, We can achieve this using the Converter option while binding the Name to the TextBox. But is there any other option to achieve this through XAML itself using StringFormat option while binding. Could you please anyone help me on this ?
<TextBox Text="{Binding Path=Name, StringFormat=??}" TextWrapping="NoWrap"/>
You could use the TextTrimming property of a TextBlock and create a style which contains a TextBlock inside your TextBox, like so:
Style 1
<!-- Trims text but shows all on-focus -->
<Style TargetType="TextBox" x:Key='TrimmingStyle1'>
<Style.Triggers>
<DataTrigger Binding="{Binding IsKeyboardFocused, RelativeSource={RelativeSource Self}}" Value="false">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border BorderThickness='1' Background='#ffefefef' BorderBrush='LightBlue'>
<TextBlock Text="{TemplateBinding Text}" TextTrimming="None" Margin='4,1' />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Style 2
<!-- Trims text always, non editable -->
<Style TargetType="TextBox" x:Key='TrimmingStyle2'>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border BorderThickness='1' Background='#ffefefef' BorderBrush='LightBlue'>
<TextBlock Text="{TemplateBinding Text}" TextTrimming="None" Margin='4,1' />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage
<TextBox Style="{StaticResource TrimmingStyle1}" ... />
<TextBox Style="{StaticResource TrimmingStyle2}" ... />
Be sure to change the text bindings to match your application data. Also note that the trimming depends on the size of your textbox.
Source: Using a Style to Simulate TextTrimming on TextBox

Categories