WPF Style: Generalizing - wpf

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.

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>

Using a generic style as base vs custom user control

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.

WPF: How to make a Expander overflow and fill window

I'm trying to create a expander that has a togglebutton/header as a slim bar to the left but when expanded fills over the rest of the window, even over material that's already there.
I'm not really sure what the best way to do it is. I thought perhaps of a grid with 2 columns. First would have the expander, second the other material. Next I would have a trigger that would set the second column width to zero when the Expander IsExpanded.
I'm not really sure how to get that to work or even how to do it properly.
Here is some code example:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Name="SecondColumn" Width="*" />
</Grid.ColumnDefinitions>
<Expander ExpandDirection="Right" IsExpanded="True">
<Expander.Resources>
<Style TargetType="Expander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Expander" >
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True" >
<Setter TargetName="SecondColumn" Property="ColumnDefinition.Width" Value="0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Expander.Resources>
<ListBox >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Expander>
<TabControl Grid.Column="1" />
</Grid>
I wan't the listbox to be seen when expanded, otherwise the TabControl
Any ideas?
It sounds like you're wanting to do something similar to Karl Shifflett's example here. He's just modifying the z-index of the content control in this case and setting the row height manually to give the illusion of a popup, so you'd want to make sure you're not trying to ZIndex other visual elements similarly.
You will want to make sure you're setting ColumnSpan and RowSpan on your Expander so that when it does expand it covers the content of those rows.

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.

WPF Custom Control for Side by Side Layout

I want to create a custom control so that I can do something like this:
<SideBySide>
<StackPanel SideBySide.Left="True">...</StackPanel>
<StackPanel SideBySide.Right="False">...</StackPanel>
</SideBySide>
I'm going to be using this all over the place, with obviously more options (sizing, etc.).
I've considered using a Panel subclass, but that doesn't seem right (there's a notion of a selected item between the left and the right).
So, I'm trying to use a ItemsControl subclass -- now, does anyone know how to put the items in a control template for an ItemsControl?
This is an abbreviated template for the SideBySide:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<Style TargetType="{x:Type local:SideBySideControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SideBySideControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Margin"
Value="5" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0"
VerticalAlignment="Stretch">
<!-- PART_LeftContent goes here -->
</Grid>
<GridSplitter Width="3"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
ShowsPreview="False">
</GridSplitter>
<Grid Grid.Column="2">
<!-- PART_RightContent goes here -->
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The direct answer is that you need an ItemsPresenter in your ControlTemplate, which would look something like this:
<ItemsControl x:Class="ItemsControlExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Border SnapsToDevicePixels="True">
<!-- Collection items are displayed by the ItemsPresenter. -->
<ItemsPresenter SnapsToDevicePixels="True" />
</Border>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- Replace the default vertical StackPanel with horizontal. -->
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="...">
<!-- The same container style applies to all items so where do you put the splitter? -->
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
But it should be obvious now that ItemsControl doesn't align to your use case. However, you can implement it as a Control using the ControlTemplate you already have with a ContentControl the PART_LeftContent and PART_RightContent grid cells:
<!-- LeftSideContent is a DependencyProperty of type object -->
<ContentControl x:Name="LeftContentControl" Content="{TemplateBinding LeftSideContent}" />
Then extend your code to handle the ContentControl mouse events in order to select and add style triggers for the selected appearance, but that's pretty straightforward stuff. If you haven't implemented lookless controls before you should be aware that you can't define event callbacks in the template, but instead have to hook them in your code:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ContentControl lc = (ContentControl)base.GetTemplateChild("LeftContentControl"));
// check for null in case the active template doesn't have a 'LeftContentControl' element
if (lc != null)
{
// Use these events to set SelectedItem DependencyProperty and trigger selected item
// highlight style. Don't forget to capture the mouse for proper click behavior.
lc.MouseDown += new MouseButtonEventHandler(LeftSide_MouseDown);
lc.MouseUp += new MouseButtonEventHandler(LeftSide_MouseUp);
}
}

Resources