WPF themeing best practices - wpf

This is a best practices question regarding wpf themeing and more specifically skinning.
This is more of an opinion based question since I don't have a problem making this work but more of a general wondering if my conclusions cover all the scenarios, and if any one else came across the same thoughts on the issue and what was their approach .
Some background, Our team is required to define a way to give our system the ability to be themeable.
We broke this ability down to 2 categories :
1) The styles of our controls which we simply call 'Theme'.
2) The resources they use to customize their appearance called 'Skin' this includes Brushes , and all sorts of sizing structs like CornerRadius , BorderThickness etc.
The way which a Skin is set for the system is a simple case of merging the skin dictionary last into our app's resources.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Default.skin.xaml" />
<ResourceDictionary Source="Theme.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
A different skin being merged last into our app.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
string skin = e.Args[0];
if (skin == "Blue")
{ .
ResourceDictionary blueSkin = new ResourceDictionary();
blueSkin.Source = new Uri("Blue.skin.xaml", UriKind.Relative);
Application.Current.Resources.MergedDictionaries.Add(blueSkin);
}
}
Inside Theme.xaml :
<!-- Region TextBox ControlTemplate -->
<ControlTemplate TargetType="{x:Type TextBox}" x:Key="TextBoxTemplate">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{StaticResource TextBoxCornerRadius}" >
<Border x:Name="shadowBorder" BorderBrush="{StaticResource TextBoxShadowBrush}"
CornerRadius="{StaticResource TextBoxInnerShadowCornerRadius}"
BorderThickness="{StaticResource TextBoxInnerShadowBorderThickness}"
Margin="{StaticResource TextBoxInnerShadowNegativeMarginForShadowOverlap}" >
<ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="BorderThickness" Value="0">
<Setter TargetName="shadowBorder" Property="BorderThickness" Value="0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!-- EndRegion -->
<!-- Region TextBox Style -->
<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorderBrush}" />
<Setter Property="Background" Value="{StaticResource TextBoxBackgroundBrush}" />
<Setter Property="BorderThickness" Value="{StaticResource TextBoxBorderThickness}" />
<Setter Property="Padding" Value="{StaticResource TextBoxPadding}" />
<Setter Property="Template" Value="{StaticResource TextBoxTemplate}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource TextBoxIsMouseOverBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource TextBoxIsMouseOverBorderBrush}" />
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background" Value="{StaticResource TextBoxIsMouseWithinBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource TextBoxIsMouseWithinBorderBrush}" />
</Trigger>
</Style.Triggers>
</Style>
<!-- EndRegion -->
In the TextBox ControlTemplate there are elements bound to DependencyProperties using TemplateBinding and some like the CornerRadius and InnerCornerRadius, InnerBorderThickness and InnerBorderBrush which are given their value from resources.
What would be the best approach ?
creating a derived control with the relevant Dependency properties
which would reference the relevant resources and then have the elements in the control template bind to them.
Or
have the elements inside the template reference these resources themselves.
Using the Dependency Property approach :
Advantages :
1) Clarity, we have a clearer API for our control and better understanding of how our control looks and behaves the way it does.
2) The template does not have to change in order to be customizable. Everything is controlled via style.
3) Triggers as well change the look and feel of the control without the need to override the control template, no need for ControlTemplate triggers.
4) "Blendabilty" using blend i can much easily customize my control.
5) Styles themselves are inheritable. so if i want to change just one aspect of the control all i need to do is inherit from the default style.
Disadvantages :
1) Implementing yet another custom control.
2) Implementing numerous dependency properties, some of which do not have much to do with the control and are only there to satisfy something we have in our template.
Just to clarify this means inheriting from TextBox something like InnerShadowTextBox and
implementing dependency properties with in it for all the above.
This will intensify if I have numerous elements inside my template which have to be customizable.
Something like this monstrosity:
<Style x:Key="{x:Type cc:ComplexControl}" TargetType="{x:Type cc:ComplexControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type cc:ComplexControl}">
<Grid>
<Ellipse Fill="Red" Margin="0" Stroke="Black" StrokeThickness="1"/>
<Ellipse Fill="Green" Margin="6" Stroke="Red" StrokeThickness="1"/>
<Ellipse Fill="Blue" Margin="12"/>
<Ellipse Fill="Aqua" Margin="24" />
<Ellipse Fill="Beige" Margin="32"/>
<StackPanel Orientation="Horizontal" Width="25" Height="25"
VerticalAlignment="Center" HorizontalAlignment="Center">
<Rectangle Fill="Black" Width="2" />
<Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/>
<Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/>
<Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Which would require numerous resources :
<SolidColorBrush x:Key="Ellipse1Fill">Red</SolidColorBrush>
<SolidColorBrush x:Key="Ellipse2Fill">Green</SolidColorBrush>
<SolidColorBrush x:Key="Ellipse3Fill">Blue</SolidColorBrush>
<SolidColorBrush x:Key="Ellipse4Fill">Aqua</SolidColorBrush>
<SolidColorBrush x:Key="Ellipse5Fill">Beige</SolidColorBrush>
<SolidColorBrush x:Key="Ellipse1Stroke">Beige</SolidColorBrush>
<sys:Double x:Key="Ellipse1StrokeThickness>1</sys:Double>
......... and many more
I would have a large list of resources either way. But with dependency properties.
I would also need to assign need to find meaning in every little part,Which sometimes isn't much more then "it looks good" and does not have much to do with the control or What if tomorrow I would want to change the template.
Using the approach where the resources are referenced from within the control template.
Advantages :
1) Easy to use, side steps the ugliness describes in the disadvantages described above in the Dp approach while providing a "hack" that enables a theme.
Disadvantages :
1) If I would want to further customize my control like add a trigger that influences the inner border of my TextBox I would simply have to create a new control template.
2) Not a clear API, Lets say I would like to change the BorderBrush of the inner border in a specific view.
<TextBox>
<TextBox.Resources>
<SolidColorBrush x:Key="InnerBorderBrush" Color="Red" />
</TextBox.Resources>
</TextBox>
Which isn't that bad come to think about it…
we sometimes do this with Selector implementations which internally use the specific resources when getting rid of the inactive selection and hightlight colors like so :
<ListBox>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Transparent"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="Transparent"/>
</ListBox.Resources>
</ListBox>
Conclusions :
The hybrid described in the TextBox Style above is the way to go.
1) Dependency properties will be introduced only for aspects of the control which relate to the control's logic including specific template part's .
2) The resource names would be comprised by a clear naming convention and separated in files based on the control they relate to and common usages in views,Like Common brushes used in views in our app.
3) Control templates should aspire to be minimalistic and to use existing Dependency properties. Like Background, Foreground, BorderBrush etc.
I would greatly appreciate your input and thoughts on the matter , thanks in advance.

As Xavier said, this might be a better question for Code Review. But I will convey some key thoughts on your question, even though a lot of it will come to personal (or team) style and requirements.
After creating several dozen themes, I would recommend against custom controls whenever possible. Over time, the maintainability goes down quite a bit.
If you require minor modifications to a style, it is better to use DataTemplates and Data Triggers if the situation allows. This way you are changing the style in a clean way.
Additionally, you can leverage the BasedOn property. Create your "base" style and have multiple styles that have the attribute BasedOn="{myBaseStyle}. This will allow you lots of options without cluttering your code.
As a rule of thumb, I always recommend having more brushes/colors/resources as opposed to more styles or templates. We usually have our hierarchy set for colors->brushes->styles->templates. This helps reuse the colors while still maintaining separation via brushes.
Using DynamicResource as opposed to StaticResource is also useful in some situations where you load resources dynamically.
Hope this helps. Would love to write more, but some of the parameters for writing a solid theme is very context specific. If you have further examples, I'd be glad to add more information.

Related

Issues with XAML MaterialDesign TextBox style: background when enabled and helpertext

I'm using Material Design library in XAML. I need to re-style some components because I'm using a particular textbox in which I have a clickable icon.
<StackPanel Orientation="Horizontal" Width="328" Grid.Column="0" Background="#3B3A3A">
<TextBox Style="{StaticResource Style}" materialDesign:HintAssist.Hint="Text" Width="300"
TextWrapping="Wrap" materialDesign:HintAssist.HelperText="Text1"/>
<Button Opacity="1" Padding="2,0,0,0"
Height="53.2"
Background="Transparent"
BorderBrush="Transparent"
Command="{x:Static materialDesign:DialogHost.OpenDialogCommand}"
CommandTarget="{Binding ElementName=DialogSelection}">
<materialDesign:PackIcon Kind="ArrowExpand"/>
</Button>
First of all I need to remove the background when the textbox isFocused, so in the style I did something like this:
<Style x:Key="Style" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignFilledTextFieldTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Width="328"
materialDesign:BottomDashedLineAdorner.Thickness="{TemplateBinding Margin}">
<TextBox x:Name="text" TextWrapping="WrapWithOverflow" FontSize="16" FontWeight="Regular" Foreground="White"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="BorderBrush" TargetName="border" Value="#656565"/>
<Setter Property="Background" Value="#3b3a3a"/>
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BorderBrush" TargetName="border" Value="#00B5CE"/>
<Setter Property="Background" Value="#656565"/>
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The issue is that using this code for the style (with the control template) I don't have available the helper text. How can I combine the two things? (I mean my settings for the background when the textbox isFocused and a HelperText available?)
NB: If I don't modify the template and just setting the background it happens that the background when the textbox isFocused isn't as I want but the HelperText is visible
It does not work, because you overwrite the control template of the TextBox and omit most of it that is needed for it to work correctly. A control template defines the visual apprearance of a control, as well as the states and transitions between them. You cannot base a contol template on another as you can with styles. Creating a custom control template means:
Creating it from scratch with all the required states and parts or
Copying the base/default style and adapting it.
For standard WPF controls, you can find required parts and states in the documentation, e.g. for TextBox here. Omitting any of the required components will lead to unexpected behavior or broken visuals states. In case of Material Design, there are even more things necessary to make the controls work correctly, but they are not documented like on MSDN, so you have to create a copy of the default style and adapt it.
You can find the default styles for TextBox here on Github. In newer versions, the styles are renamed to MaterialDesignFilledTextBox because of this issue. The derived style hierarchy is as follows:
MaterialDesignTextBoxBase
MaterialDesignFloatingHintTextBox
MaterialDesignFloatingHintTextBox
MaterialDesignFilledTextFieldTextBox
The control template is defined in MaterialDesignTextBoxBase, which is the base style for all other styles. What you have to do now is to copy the MaterialDesignTextBoxBase style, give it a name and adapt the states as in your provided code. You can merge the setters of the three derived styles above into your custom style. Then you will have a custom style that incorporates all necessary states and parts that will work as you expect it.
You can't "combine" ControlTemplates. You must define a template as a whole.
This means that you should copy the default template in MaterialDesignFilledTextFieldTextBox that includes the helper text and then modify it as per your requirements, i.e. by adding a IsFocused trigger to it.
I am afraid you cannot base a template on another template or add triggers to a template that is defined elsewhere.

How to set specific control element settings that override a global style in WPF?

I have defined a global style for Buttons in my application via an application-wide Resource Dictionary. The style looks like this (followed from another SO Example):
<Style TargetType="Button">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="{DynamicResource BaseButtonBG}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<!-- Triggers here -->>
</Style.Triggers>
It works. But I need to assign specific values directly to some of my buttons, like margin and padding to align them. I also would like to have the ability to override the color properties from the style in individual buttons.
It appears that any properties I set directly on specific buttons get completely ignored and only the properties from the global style are used. How do I overcome this?
UPDATE: To clarify what I want:
In the HTML/CSS world (which is older than dirt), you can add a style class to an element, but you can also assign properties directly to the element that override the class values. That's what I want to accomplish in WPF.
UPDATE 2
It's possible people think this question is stupid because the solution should be obvious. However, from my personal testing, there appears to be a bug with Padding not changing at all unless you specifically bind it in a control template. This behavior seems to change from property to property. Since my original attempt to override a property specifically involved Padding and it didn't work, I had to build this workaround.
Ok, form in Design:
XAML code for form:
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Button Content="Button" x:Name="btnNo1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="120"/>
<Button Content="Button" x:Name="btnNo2" HorizontalAlignment="Left" Margin="135,10,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
and in RunTime we going to change Margin by using this code in CS file:
public MainWindow()
{
InitializeComponent();
btnNo2.Margin = new Thickness(100, 100, 100, 100);
}
Result will be:
Can you create and use new style for button where you need custom margin/padding?
<Style x:Key="SpecialButtonType1" BasedOn="{StaticResource ResourceKey=CommonButtonStyle}">
...
</Style>
and change
<Style TargetType="Button">
to
<Style TargetType="Button" x:Key="CommonButtonStyle">
YES: It's completely doable, You can assign overriding properties directly on an element without doing the ugly process many are using of creating a special one-off dictionary entry just for the specific element in question.
I don't know if its caused by a bug in WPF, but there's an initial requirement...
Your dictionary-referenced base style might need to include any properties that you want to be overridable. For some reason different properties seem to exhibit different behavior. But at least in the case of Padding, if you don't include Padding on your ControlTemplate TemplateBinding, you won't be able to override it on your element.
Additionally, in the case of margin, there seems to be some kind of "doubling" effect that happens if you include Margin in the ControlTemplate TemplateBinding. If you don't templateblind the margin, you can still override margin but the behavior changes.
STEP 1
Define a base style with a ControlTemplate. Make sure that your ControlTemplate includes a TemplateBinding for all properties that you may want to customize/override on individual elements.
<Style TargetType="Button">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="{StaticResource BaseButtonBG}"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
Margin="{TemplateBinding Margin}"
>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource BaseButtonBG_IsMouseOver}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource BaseButtonBG_IsPressed}"/>
</Trigger>
</Style.Triggers>
</Style>
I've defined a few StaticResource keys for my property colors so that I can put them altogether in another place for cleaner skinning. Here are those keys:
<SolidColorBrush x:Key="BaseButtonBG" Color="#5f636c"/>
<SolidColorBrush x:Key="BaseButtonBG_IsMouseOver" Color="#898C94"/>
<SolidColorBrush x:Key="BaseButtonBG_IsPressed" Color="#484B51"/>
STEP 2
Implement actual button like this:
<Button Content="New" />
And the result of this makes a button that looks like this:
STEP 3
Now let's say I want all of my buttons to look squashed like that, except one. I want to add some vertical padding to make one specific button look taller. I could alter the example button like this:
<Button Content="New" Padding="0,30"/>
And the result:
Alternatively, you could implement the button override as follows, which gives you the ability to override Triggers or other special Style options.
<Button Content="New">
<Button.Style >
<Style TargetType="Button" BasedOn="{DynamicResource {x:Type Button}}">
<Setter Property="Padding" Value="0,30"/>
</Style>
</Button.Style>
</Button>
TADA! We've assigned a one-off style tweak directly to the element WHERE IT BELONGS! We didn't have to create a style in a dictionary and reference it for this one case.
Important Points
In order to make this work "Padding" MUST BE defined in the ControlTemplate with the TemplateBinding code. If you don't do that, Padding directly applied to the button just gets ignored completely. Again, I'm not sure why its like this, but that seems to be the magic fix.
Further Reading: I was able to figure this out from some helpful info on this blog article:
explicit-implicit-and-default-styles-in-wpf
Interestingly, the last part of that article suggests creating a custom control with your own default style and properties that you can override similarly to how I've done here. This seems a bit overkill to me, but it might eliminate the weird bugginess problem with padding and margin behaving differently. Haven't tried that yet, though.

Create a visual template for grid

I am trying to create a visual template (saved in resource dictionary as xaml code) for a grid object, that I will apply to various grid objects created later in runtime.
I need a simple style with border and background.
What would be the best way of doing this?
Simple working examples would be greatly appreciated.
Ok, so, after searching for examples, I tried something like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="TestStyle">
<Setter Property="Background" Value="#FF873507" />
<Setter.Value>
<ControlTemplate>
<Grid>
<Border BorderThickness="7" CornerRadius="4">
<Border.BorderBrush>
<SolidColorBrush Color="#73B2F5"/>
</Border.BorderBrush>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Style>
</ResourceDictionary>
WPF doesn't really work like that... because the Grid class has no Template property, you cannot define a new ControlTemplate for it. The nearest thing that you can do is to create a UserControl with the UI elements that you want to use and then display the UserControl wherever you want to display those controls.
Alternatively, you could define your content inside a ControlTemplate if the inner controls will always be the same:
<ControlTemplate x:Key="StaticGrid">
<Grid>
<Border BorderThickness="7" CornerRadius="4">
<Border.BorderBrush>
<SolidColorBrush Color="#73B2F5"/>
</Border.BorderBrush>
<!--Add your inner elements here-->
</Border>
</Grid>
</ControlTemplate>
You could then display it like this:
<ContentControl Template="{StaticResource StaticGrid}" />
However, you wouldn't be able to add different inner elements using this method. If you used the UserControl method, you could potentially replace the word UserControl with Grid so that you were in fact extending the Grid class, but you still wouldn't be able to add different elements to it.
The best that you could do in a Style would be to set the Background property.
After more tries and frustrations, I found a much simpler solution, that worked for me.
Instead of trying to apply the style to the grid, I applied it to the border around the grid.
So, my dictionary looks like this:
<Style x:Key="TestStyle" TargetType="{x:Type Border}">
<Setter Property="Background" Value="#FFBDACA2" />
<Setter Property="BorderBrush" Value="#FFFF5E00" />
<Setter Property="CornerRadius" Value="30,30,30,30" />
<Setter Property="BorderThickness" Value="10" />
</Style>
And my main frame xaml:
<Border Style="{StaticResource TestStyle}" >
<Grid>
</Grid>
</Border>

Why must I use "Resources" in WPF?

I'm just diving into WPF and find this a little odd and frustrating to my style: Why can the value of something be a Resource but you can't set the value directly to what the Resource represents? Example:
This is valid:
<ToggleButton>
<ToggleButton.Resources>
<Image x:Key="cancelImage" Source="cancel.png" />
</ToggleButton.Resources>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Content" Value="{DynamicResource cancelImage}" />
</Style>
</ToggleButton.Style>
</ToggleButton>
But this is not:
<ToggleButton>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Content">
<Setter.Value>
<Image Source="cancel.png" />
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
What is the difference? Why don't both work? I don't like having to create a "Resource" for some things because it divides my code up and can make it more difficult to read.
And yes, I do know my example can be simplified like this
<ToggleButton>
<Image Source="cancel.png" />
</ToggleButton>
but that's not the point.
When working with setters, the value for the setter has to support the ability to be "frozen". That can be a value type (including structs and enums), or a class that derives from Freezable.
It may not help your aesthetic sense much, but it is often cleaner to declare all your resources at the top of the Resources section of the file (or ResourceDictionary), as opposed to adding them to the Resources of an individual style or control.

Styling a WPF layout grid background (of each cell, row, column)

I would like to know if there is any way to style a WPF layout grid's cells, rows and columns. I've been trying to find any information and the few mentions I've found have not been that informative.
I would like to style the grid to look like the one in the linked screenshot.
If the actual control does not support it, can I inherit it somehow and do it then? I am quite new to WPF so any help would be very appreciated.
One other thing, I know I can style each and every control within the grid, but it seems like overkill. I would like to have a grid that does it itself.
screenshot http://img21.imageshack.us/img21/2842/capturehz8.png
#Dan recommends WPF Unleashed, which I'm currently reading. Just this morning, I come across a section addressing your question.
Chapter 6, Page 161:
FAQ: How can I give Grid cells background colors, padding, and borders like I can with cells of a HTML Table?
There is no intrinsic mechanism to give Grid cells such properties, but you can simulate them pretty easily thanks to the fact that multiple elements can appear in any Grid cell. To give a cell a background color, you can simply plop in a Rectangle with the appropriate Fill, which stretches to fill the cell by default. To give a cell padding, you can use auto sizing and set the Margin on the appropriate child element. For borders, you can again use a Rectangle but give it an explicit Stroke of the appropriate color, or you can simply use a Border element instead.
Just be sure to add such Rectangles or Borders to the Grid before any of the other children (or explicitly mark them with the ZIndex attached property), so their Z order puts them behind the main content.
Btw, WPF Unleashed rocks. Its very well written, and the print in full color makes it even more easier to read.
Here's a quick (very rough sample) that you could hack around to get the format you want (if you're serious about working with WPF, you'll find Blend an enormous help in getting your layouts looking good):
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="CustomerDefinition" TargetType="TextBlock">
<Setter Property="Control.FontFamily" Value="Tahoma"/>
<Setter Property="Control.FontSize" Value="12"/>
<Setter Property="Control.Foreground" Value="Red"/>
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Width" Value="100"/>
</Style>
<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="MinWidth" Value="120"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Width" Value="200"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<Border
Name="Border"
Background="#FFEBE9E9"
BorderBrush="#FF8B8787"
BorderThickness="1"
CornerRadius="2"
Padding="3">
<ScrollViewer x:Name="PART_ContentHost" Margin="0"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background"
Value="#EEEEEE"/>
<Setter TargetName="Border" Property="BorderBrush"
Value="#EEEEEE"/>
<Setter Property="Foreground" Value="#888888"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<LinearGradientBrush x:Key="NormalBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Offset="0.0" Color="#FFF0EDED"/>
<GradientStop Offset="1.0" Color="#FFE1E0E0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
</Page.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="26"/>
<RowDefinition Height="23"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<TextBlock
Grid.ColumnSpan="2"
Grid.Row="0"
Style="{StaticResource CustomerDefinition}"
Text="Customer Definition"/>
<Border
Grid.Column="0"
Grid.Row="1"
Background="#FFEBE9E9"
BorderBrush="#FF8B8787"
BorderThickness="1">
<StackPanel Background="{StaticResource NormalBrush}" Orientation="Horizontal">
<Label Content="Customer Code"/>
<TextBox Text="SMITHA 098 (normally I'd bind here)"/>
</StackPanel>
</Border>
<Border
Grid.Column="1"
Grid.Row="1"
Background="#FFEBE9E9"
BorderBrush="#FF8B8787"
BorderThickness="1">
<StackPanel Background="{StaticResource NormalBrush}" Orientation="Horizontal">
<Label Content="Customer Type"/>
<TextBox Text="PRIVATE INDIVIDUAL"/>
</StackPanel>
</Border>
</Grid> </Page>
The WPF Grid doesn't have visible cells as such. Think of them as invisible grid lines against which you can have child elements be aligned.
So, to style the grid's cells, you have to style the items that are aligned inside the grid.
It is confusing to think of the Grid as being anything like a WinForms DataGrid. I guess its closest WinForms equivalent is the TableLayout control.
Check out some 3rd party grid controls. I used the DevExpress one while it was in beta and found it pretty straightforward.
I would recommend using borders for your styling.
You could recreate that layout pretty easily by creating borders for each row and each column and set the rowspans and colspans accordingly.
You will have 5 borders with colspan 2, these borders will take care of your gradient backgrounds for each row and the borders along the top and bottom of each row. Then you will have 2 borders with rowspan 5 these will handle the column borders. Imagine that you are overlaying the borders to form the visual grid effect you are after.
For the header and outer border, just wrap the entire grid with a border and style as needed.
I would recommend storing your styles as resources so you can keep all your styling info in one place.
Take care to learn how the styling works because it is pretty powerful, but there is a learning curve as it is quite different to the way CSS works. I would recommend reading WPF Unleashed if you can.
I found this post when looking for method for setting margin (or padding) for DataGrid cells. My problem was solved thanks to example xaml code posted at (near the end) -- pretty minimalistic.
http://forums.silverlight.net/forums/p/16842/55997.aspx

Resources