Using a generic style as base vs custom user control - wpf

I'm creating a resource dictionary to my application, where I'll have some "icon+text" buttons. Since they will all look the same (except for the icon and the text), I've created a generic style to serve as base to the others:
<!-- Generic ActionButtonStyle -->
<Style x:Key="ActionButtonStyle" TargetType="{x:Type Button}">
<!-- some setter properties -->
<Setter Property="ContentTemplate" Value="{DynamicResource ButtonDataTemplate}"/>
</Style>
<DataTemplate x:Key="ButtonDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding Source}"
Stretch="Uniform"
Grid.Column="0"
Margin="2"/>
<TextBlock Text="{Binding text}"
TextWrapping="Wrap"
Grid.Column="1"
Margin="2"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
And I have some images for the icons:
<!-- Icons -->
<ImageSource x:Key="textToSpeech">Images/GreyIcons/TextToSpeech.png</ImageSource>
<ImageSource x:Key="play">Images/GreyIcons/Play.png</ImageSource>
<ImageSource x:Key="playSound">Images/GreyIcons/PaySound.png</ImageSource>
.
.
.
.
<ImageSource x:Key="group">Images/GreyIcons/Goup1.png</ImageSource>
And I'd like to create individual styles for each button (corresponding to each icon). Something like this:
<!-- Specific ActionButtonStyles -->
<Style x:Key="TextToSpeechButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource ActionButtonStyle}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource textToSpeech}"
</Setter.Value>
</Setter>
</Style>
I know that this doesn't work.. How should I do it? Should I create a custom user control for the generic button? The text will be binding to an object in my Model, and so will the command (to an action).

The example of what you are looking for seems to be missing, but it seems that you may be looking for "BasedOn" - which allows you to inherit, but still override a previously defined style. You can implement it like this:
<Style x:Key="MyButtonStyle" BasedOn="{StaticResource ActionButtonStyle}">
<Setter.../>
</Style>

You need to create a derived class from Button that adds two new DependancyProperties. They would be called something like Text and ImageSource. Your derived class would also set the ContentTemplate as you have indicated. This ContentTemplate would bind against the Text and ImageSource dependancy properties.
You can then create your custom control in XAML like this...
<app:CustomButton Text="Play" Source="{Binding play}"/>
...but if you want the same button over and over again you could create a style that is applied to the CustomButton and sets those two properties as required.

Related

Can't set both ContentTemplateSelector and Template properties on a DataGridColumnHeader

In short, the question title says it all. For those that want more detail, here is the crux of my problem: I need to apply a custom ControlTemplate to the DataGridColumnHeader elements in my DataGrid control, but I also need to style them differently, depending on the cell data nearest the header. However, when I set both the ContentTemplateSelector and Template properties on a DataGridColumnHeader element, the DataTemplateSelector that is set as the value of the ContentTemplateSelector property is not called. Commenting out the Template property setting confirms this to be the case, as the DataTemplateSelector element will now be called.
Yes, I know that you guys love to see some code, but I have completely templated the whole DataGrid control to look like Excel, so as you can imagine, I have far too much code to display here. But just to please you code hungry devs, I've recreated my problem in a much simpler example... let's first see the XAML:
<Window x:Class="WpfApp1.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:WpfApp1"
xmlns:System="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.Items>
<System:String>One</System:String>
<System:String>Two</System:String>
<System:String>Three</System:String>
</DataGrid.Items>
<DataGrid.Resources>
<Local:StringDataTemplateSelector x:Key="StringDataTemplateSelector" />
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="ContentTemplateSelector" Value="{StaticResource StringDataTemplateSelector}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" />
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
</Window>
Now the most simple DataTemplateSelector class:
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public class StringDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Debugger.Break();
return null;
}
}
}
In the XAML, we see a DataGrid, with just one DataGridTemplateColumn and three string values, one on each row, and some resources. There is a Style for the DataGridColumnHeader element in the Resource section, with the most simple ControlTemplate set up for it, that only includes the required named parts from the default ControlTemplate.
If you run the application as it is, then it will NOT currently break at the Debugger.Break() method in the StringDataTemplateSelector class. This is unexpected. If you now comment out the setting of the Template property in the Style and run the application again, then you will now see that program execution will now break at the Debugger.Break() method, as expected.
Further information:
In the Remarks section of the ContentControl.ContentTemplateSelector Property page of MSDN, it states that
If both the ContentTemplateSelector and the ContentTemplate properties are set, then this property is ignored.
However, it does not mention the Template property and there is also no mention of this on the Control.Template Property page on MSDN.
Furthermore, I tried this same setup using a simple Button control and can confirm that setting both the ContentTemplateSelector and the ContentTemplate properties on that does NOT stop the StringDataTemplateSelector class from being called:
<ItemsControl>
<ItemsControl.Resources>
<Local:StringDataTemplateSelector x:Key="StringDataTemplateSelector" />
<Style TargetType="{x:Type Button}">
<Setter Property="ContentTemplateSelector" Value="{StaticResource StringDataTemplateSelector}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Stroke="Red" StrokeThickness="1" Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding Height}" />
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Resources>
<Button Content="One" />
<Button Content="Two" />
<Button Content="Three" />
</ItemsControl>
So, what I'm after is a way to apply a custom ControlTemplate element to the DataGridColumnHeader objects, yet still be able to have the DataTemplateSelector class called during the rendering process.
add a content presenter in your controltemplate?
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" />
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" />
<ContentPresenter></ContentPresenter>
</Grid>
</ControlTemplate>

Rational creation of XAML markup for readibility and sanity

Disclaimer: Not sure what to put in the title to make it clear as the words to be used are the ones that I don't know (yet) and am asking about. Feel free to correct.
Imagine a scenario with GUI consisting of 4x3 inputs, where every input consists of a label and a textbox. At the moment, it's done by explicitly declaring all the components and each component has the for as follows.
<Label x:Name="Label1"
Content="Text1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10,210,0,0" />
<TextBox x:Name="TextBox1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
Height="23" Margin="10,241,0,0"
TextWrapping="Wrap" Text="TextBox" />
Is there a recommended way to generate those from "something else", like a template or such, that's governing all the common attributes in it, eliminating the need for me to type them in over and over again (well, those were autogenerated but still...)? The alignments and sizes are tedious...
As for the margins, perhaps there's a layouting functionality? I've googled it but the hits related to XAML I've got were either suspiciously weird or relying on code behind. Is that the way to go or is it doable from XAML straight off?
To properly configure your layout, you should use WPF Layout Controls. In order to make the grid layout, you can use Grid, UniformGrid, etc., depending on your needs.
In order to apply several properties to the all controls inside the layout control, you can define the Style in the Resources of that control, as was mentioned already:
<Grid>
<Grid.Resources>
<Style TargetType="TextBox">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="25" />
<!-- etc... -->
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<!-- Row definitions here. -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<!-- Column definitions here. -->
</Grid.ColumnDefinitions>
<!-- controls ... -->
<TextBox Text="{Binding YourProperty}"
Grid.Row="1"
Grid.Column="2"
/>
<!-- controls ... -->
</Grid>
Here the style will be applied to all TextBox's controls.
You're referring to a WPF "Style." With styles, you define a set of properties that will be the same between all instances of a control which use that style.
<Style x:Key="MyTextBoxStyle" TargetType="TextBox">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="23" />
<Setter Property="TextWrapping" Value="Wrap" />
<!-- etc... -->
</Style>
<!-- This textbox will default its property values to those defined above -->
<TextBox Style="{StaticResource MyTextBoxStyle}" />

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.

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