Why does WPF render two identical objects differently? - wpf

Take this Window as an example:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" ResizeMode="NoResize" SizeToContent="WidthAndHeight" SnapsToDevicePixels="True">
<Grid Width="17" Margin="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<RepeatButton Grid.Row="0" SnapsToDevicePixels="True">
<Polyline RenderOptions.EdgeMode="Aliased" Stretch="Uniform" Margin="1" Fill="Red">
<Polyline.Points>
<Point X="0" Y="3" />
<Point X="3" Y="0" />
<Point X="6" Y="3" />
</Polyline.Points>
</Polyline>
</RepeatButton>
<RepeatButton Grid.Row="1" SnapsToDevicePixels="True">
<Polyline RenderOptions.EdgeMode="Aliased" Stretch="Uniform" Margin="1" Fill="Red">
<Polyline.Points>
<Point X="0" Y="3" />
<Point X="3" Y="0" />
<Point X="6" Y="3" />
</Polyline.Points>
</Polyline>
</RepeatButton>
</Grid>
</Window>
Once the application has been ran, the topmost RepeatButton is taller than the bottom one (consequently the top triangle is also bigger than the bottom one). Why?
If I create 4 rows of identical RepeatButtons, then the 1-st and 3-rd RepeatButtons are of equal size and are bigger than the 2-nd and 4-th RepeatButton?!?
I'm thinking this must be a bug in the WPF layout system, but how to work around this problem? I can't use fixed heights (which does solve the problem), because I need the RepeatButtons and triangles to strecth as the container gets bigger (the example I provided is simplifed just to show the issue, I know I can't resize the example window...).
Edit:
In reply to Ben's comments:
Yes, with the added style the triangles do come out as 9px and 8px tall (I could just as well through out the RepeatButtons alltogether and leave only the polylines as the grids children, that would give the same result). Because the triangles are equal sided, then giving the grid a width of 17 will indeed cause the height to become 17 as well, which of course is not enough for two equal height triangles..
What I'm actually trying to do is create a NumericUpDown control. I've found that by default a spinner width of 17 and a UserControl MinHeight of 24 looks very good. The only problem is, that if I drop this UserControl into a Grid, then the top triangle always pushes itself 1px to tall, ruining the look. No matter how I've tried to mingle with the internal Margins and Paddings, the top triangle always makes itself 1px taller than necessary. So in essence what I want is to have a NumericUpDown, that when put into a Grid, doesn't distort itself. By default it should look perfect from the get go (no Grid RowHeight="Auto") and scale properly (no fixed heights). It must be possible, because by looking at the pixels physically then everything can fit into the given dimensions nicely.
Here is my NumericUpDown, I've stripped out all the non essential things to make it more compact:
<UserControl x:Class="HRS.NumericUpDown"
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"
MinWidth="40" MinHeight="24" Name="ucNUPD" Background="White" SnapsToDevicePixels="True">
<UserControl.Resources>
<Style TargetType="{x:Type RepeatButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Name="borderOuter" BorderThickness="1" BorderBrush="Red">
<Border Name="borderInner" BorderThickness="1" BorderBrush="Blue">
<ContentPresenter Margin="2,1" />
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="triangleStyle" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Polyline HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="Green" RenderOptions.EdgeMode="Aliased" Stretch="Uniform">
<Polyline.Points>
<Point X="0" Y="3" />
<Point X="3" Y="0" />
<Point X="6" Y="3" />
</Polyline.Points>
</Polyline>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Border BorderThickness="1" BorderBrush="#ABADB3">
<DockPanel>
<UniformGrid Margin="1" DockPanel.Dock="Right" Rows="2" MinWidth="17" Width="17">
<RepeatButton Name="repeatButtonUp" Grid.Row="0">
<ContentControl Style="{StaticResource triangleStyle}" />
</RepeatButton>
<RepeatButton Name="repeatButtonDown" Grid.Row="1">
<ContentControl Style="{StaticResource triangleStyle}" RenderTransformOrigin="0.5, 0.5">
<ContentControl.RenderTransform>
<ScaleTransform ScaleY="-1" />
</ContentControl.RenderTransform>
</ContentControl>
</RepeatButton>
</UniformGrid>
<TextBox BorderThickness="0" VerticalContentAlignment="Center" Text="0" />
</DockPanel>
</Border>
</UserControl>
Here is a picture of what the end result looks like:
(The image doesn't fit a 100%, but you can still see all the relevant details).
On the right side you can see a zoom-in of the NumericUpDowns. The bottom one looks correct, but only because the grid's row Height is set to Auto. The top one is distorted, but by default I want it to look exactly like the bottom one.
Hmmm...
I might just have found a workable solution:
It seems that by setting the Margin of the ContentPresenter in my NumericUpDown to "3,1", everything looks perfect. Preliminary testing is very promising as everything seems to be exactly the way it should be...
I'll test it some more tommorow and if all goes good will mark Ben's answer as correct :)

With SizeToContent="WidthAndHeight" the height will be 17 as you you set the Grid's Width to 17. But with 17/2=8.5 one row will be 9 (rounding occurs becouse of SnapsToDevicePixels="True") but the other will be 8 pixel tall. If you set the Width to 18 they will be equal.
Proof of my theory:
<Grid Width="17" Margin="0">
<Grid.Resources>
<Style TargetType="{x:Type RepeatButton}">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<ContentPresenter Margin="0"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<RepeatButton Grid.Row="0" SnapsToDevicePixels="True">
<Polyline RenderOptions.EdgeMode="Aliased" Stretch="Uniform" Margin="0" Fill="Red">
<Polyline.Points>
<Point X="0" Y="3" />
<Point X="3" Y="0" />
<Point X="6" Y="3" />
</Polyline.Points>
</Polyline>
</RepeatButton>
<RepeatButton Grid.Row="1" SnapsToDevicePixels="True">
<Polyline RenderOptions.EdgeMode="Aliased" Stretch="Uniform" Margin="0" Fill="Red">
<Polyline.Points>
<Point X="0" Y="3" />
<Point X="3" Y="0" />
<Point X="6" Y="3" />
</Polyline.Points>
</Polyline>
</RepeatButton>
With this snippet you gain a Triangle that has 9 pixel height, and one with 8 pixels.
But if you want for a solution try this:
<RepeatButton Grid.Row="1" SnapsToDevicePixels="True" Height="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth}" >
<Polyline RenderOptions.EdgeMode="Aliased" Stretch="Uniform" Margin="0" Fill="Red">
<Polyline.Points>
<Point X="0" Y="3" />
<Point X="3" Y="0" />
<Point X="6" Y="3" />
</Polyline.Points>
</Polyline>
</RepeatButton>
This way the width and the height of the buttons will be equal.
If think you can write a little converter too, that will can do some nasty things:
<RepeatButton Grid.Row="1" SnapsToDevicePixels="True" Height="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth, Converter={StaticResource TriangleWidthConverter}}" >
<Polyline RenderOptions.EdgeMode="Aliased" Stretch="Uniform" Margin="0" Fill="Red">
<Polyline.Points>
<Point X="0" Y="3" />
<Point X="3" Y="0" />
<Point X="6" Y="3" />
</Polyline.Points>
</Polyline>
</RepeatButton>
public class TriangleWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int width = 0;
if (int.TryParse(value.ToString(), out width))
return width + 1; // Do some fun here.
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I hope these will help.

No idea why you are seeing this behavior, but this might work to fix it: try setting the height on each of your your rows to .5* if you have 2 rows or .25* if you have 4 rows (or .1* if you have 10 rows, etc.).

Related

Adjusting WPF chart toolkit ColumnSeries (System.Windows.Controls.DataVisualization.Charting)

I am using the WPF chart toolkit (System.Windows.Controls.DataVisualization.Charting). All stylings are in the XAML and I only bound the data to chart from my ViewModel. Everything looks alright for the first time I click on a button to show the columnseries. When I click on the button for the second time, the chart becomes bigger/corrupted; It shows only part of the graph.
The graph is drawn for the first time:
And button clicked for the second time:
The code I am using is as below:
<dvc:Chart Cursor="Cross"
Background="#FFFFFCF2"
Title="{Binding Title}"
Height="410"
Width="750"
VerticalAlignment="Top"
Name="ChartContainer">
<dvc:Chart.Series>
<dvc:ColumnSeries ItemsSource="{Binding ChartData,UpdateSourceTrigger=PropertyChanged}"
IndependentValueBinding="{Binding Path= Key}"
DependentValueBinding="{Binding Path= Value}"
Name="SummariesChart"
Margin="0,0,0,0"
IsManipulationEnabled="False">
<dvc:ColumnSeries.DataPointStyle>
<Style TargetType="dvc:ColumnDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="dvc:ColumnDataPoint">
<Grid>
<Rectangle Fill="{TemplateBinding Background}"
Stroke="Black" />
<Grid Margin="0,0, 0, 0"
HorizontalAlignment="Center"
VerticalAlignment="Top">
<TextBlock Text="{TemplateBinding FormattedDependentValue}"
Margin="2" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</dvc:ColumnSeries.DataPointStyle>
</dvc:ColumnSeries >
</dvc:Chart.Series >
<dvc:Chart.Axes>
<dvc:LinearAxis Orientation="X"
Title="{Binding XTitle}"
Interval="1"
Location="Bottom"
ShowGridLines="True" />
<dvc:LinearAxis Orientation="Y"
Title="{Binding YTitle}"
ShowGridLines="True"
Location="Left" />
</dvc:Chart.Axes>
</dvc:Chart>
Also, to give you complete idea, I used a style on top of the page as follow:
<Style TargetType="dvc:Chart">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="dvc:Chart">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<dv:Title Content="{TemplateBinding Title}"
Style="{TemplateBinding TitleStyle}"
Margin="1" />
<Grid Grid.Row="1"
Margin="1,0,1,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<primitives:EdgePanel x:Name="ChartArea">
<Grid Canvas.ZIndex="-1"
Style="{TemplateBinding PlotAreaStyle}" />
<Border Canvas.ZIndex="10"
BorderBrush="#FF919191"
BorderThickness="1" />
</primitives:EdgePanel>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BarDataPointStyle"
TargetType="{x:Type dvc:BarSeries}">
<Setter Property="Background"
Value="Blue"></Setter>
<Setter Property="Opacity"
Value="0" />
</Style>
When I remove <dvc:Chart.Axes> block, it works correctly consistently. But I need X-axe and Y-axes descriptions existing in this code block. Do you know how I can tackle this problem? I appreciate your help.
I found a way to resolve the issue. But still, I am skeptical that something is wrong with the styling or something.
I added Minimum = 0 and Maximum in LinearAxis tag. I bounded Maximum to a variable in my ViewModel. I got the length of the array and added one to the maximum variable(MaxNumberInXAxes).
<dvc:LinearAxis Orientation="X"
Title="{Binding XTitle}" I
nterval="{Binding XAxisInterval}"
Location="Bottom"
ShowGridLines="True"
AllowDrop="False"
Minimum="0"
Maximum="{Binding MaxNumberInXAxes}"/>
The graph becomes something like this:

Change Fill color of Path used as Button Template on MouseOver

I have a button that has it's Template setup to display a Path wrapped in a VisualBrush. The Path has its Fill assigned, but I want to change the Fill color when the button is moused over.
Visual Brush
<VisualBrush x:Key="EditIconBrush" Stretch="Uniform">
<VisualBrush.Visual>
<Canvas Width="24" Height="24">
<Path Fill="White" Data="M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
Button Template
<ControlTemplate x:Key="ButtonIconTemplate"
TargetType="Button">
<Grid>
<Rectangle Fill="Transparent" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Content="{TemplateBinding Content}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
<Button DockPanel.Dock="Right" Margin="0 0 10 0"
Command="{Binding RelativeSource={RelativeSource AncestorType=Expander}, Path=DataContext.AddFieldCommand}"
Template="{StaticResource ButtonIconTemplate}">
<Rectangle Width="20" Height="20" Fill="{DynamicResource EditIconBrush}" />
</Button>
Do I need to have two different versions of my Path, one in each color? Or can this be achieved in a Style? I'd like to increase the size of the Path when moused over and change its color from grey to white, then decrease the size and change the color from white to grey when the mouse leaves.
I tried to bind the Path's Fill property to the Button's Foreground property, using RelativePath as recommend in another post. I basically wanted to achieve the same concept that the original poster in that thread wanted. The difference being that my Path is in a resource dictionary and not part of my Button itself.
Binding to Foreground attempt
<Button DockPanel.Dock="Right" Margin="0 0 10 0"
Foreground="White"
Command="{Binding RelativeSource={RelativeSource AncestorType=Expander}, Path=DataContext.AddFieldCommand}"
Template="{DynamicResource ButtonIconTemplate}">
<Rectangle Width="20" Height="20" Fill="{DynamicResource EditIconBrush}" />
</Button>
<VisualBrush x:Key="EditIconBrush" Stretch="Uniform">
<VisualBrush.Visual>
<Canvas Width="24" Height="24">
<Path Fill="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}"
Data="M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
I'm not sure how to resolve this issue. Any help would be appreciated.
Use the IsMouseOver trigger:
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="NameOfPath" Property="Fill" Value="Colour" />
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="2" ScaleY="2" />
</Setter.Value>
</Setter>
<Setter Property="RenderTransformOrigin" Value="0.5, 0.5" />
</Trigger>
</ControlTemplate.Triggers>
You'll need to name your paths (e.g. <Path x:Name="NameOfPath" />) and substitute the appropriate colour in plus use an appropriate size for the RenderTransform scale

Can I make two containers (e.g. Grid and Canvas) take the same space to combine their abilities?

I have the following layout:
The Rectangles are placed using a Grid. On top of that, I want to add more "fluid" stuff, like Paths and lines that would be located dynamically.
For instance:
the lines between the Rectangles, are stretched from one Rectangle's mid-point to the mid-point of the one below it
The left-manually-drawn-ugly-red-"path" originate from the mid-point of the left-half of the top-Rectangle, and go to the mid-point of the left Rectange below it.
So the question is: the Rectangles match Grid behavior, other stuff, like the lines, match Canvas behavior. How do I use the advantages of both these containers? can I lay one over the other?
You don't need to mix and match your controls at all... you can chose either a Gird or a Canvas control to draw on using a Path element. Clearly, I don't want to do it all for you, so this is just a basic example of drawing in a Grid:
The end result:
The XAML:
<Grid Width="800">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Stroke" Value="Black" />
<Setter Property="Fill" Value="White" />
<Setter Property="StrokeThickness" Value="1" />
<Setter Property="Width" Value="250" />
<Setter Property="Height" Value="100" />
<Setter Property="Rectangle.RadiusX" Value="20" />
<Setter Property="Rectangle.RadiusY" Value="20" />
</Style>
</Grid.Resources>
<Path Grid.Row="1" Grid.RowSpan="2" Grid.Column="0" Data="M0,0 0,100" Stroke="Black" StrokeThickness="2" Fill="{x:Null}" Height="100" VerticalAlignment="Stretch" HorizontalAlignment="Center" />
<Path Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" Data="M0,0 0,100" Stroke="Black" StrokeThickness="2" Fill="{x:Null}" Height="100" VerticalAlignment="Stretch" HorizontalAlignment="Center" />
<Rectangle Grid.Row="0" Grid.ColumnSpan="2" />
<Rectangle Grid.Row="1" Grid.Column="0" />
<Rectangle Grid.Row="1" Grid.Column="1" />
<Rectangle Grid.Row="2" Grid.Column="0" />
<Rectangle Grid.Row="2" Grid.Column="1" />
<Path Grid.Row="0" Grid.RowSpan="2" Grid.ColumnSpan="2" Data="M0,0 A 100,100 90 0 0 -100,100" Stroke="Red" StrokeThickness="2" Fill="{x:Null}" Height="100" VerticalAlignment="Stretch" HorizontalAlignment="Center" Margin="0,0,150,0" />
<Path Grid.Row="0" Grid.RowSpan="2" Grid.ColumnSpan="2" Data="M0,0 A 100,100 90 0 1 100,100" Stroke="Red" StrokeThickness="2" Fill="{x:Null}" Height="100" VerticalAlignment="Stretch" HorizontalAlignment="Center" Margin="0,0,-250,0" />
</Grid>
You can also find the syntax that you need to use in the Path.Data property in the Path Markup Syntax page on MSDN.
Yes you can lay the Canvas on top of a Grid, but you probably don't want to.
<Grid x:Name="container">
<!-- We use this to put the two items in the same location -->
<!-- i.e. Row="0" Column="0" is implicit for both the canvas and the grid below-->
<Grid x:Name="rectangleGrid"/>
<Canvas x:Name="shapeCanvas"/>
</Grid>
It really is that simple, but lets have a look at what we have now.
The shapeCanvas will be in front of the rectangleGrid (and if it isn't just tweak its ZIndex). If it has a non-transparent BackColor then you won't of course see the rectangleGrid, so you will need to sort that out.
If we want to line up the right hand red-line of yours we need to work out where to draw it from. Given that gridColumns don't expose sizes, then that's leftRectangle.ActualWidth + leftRectangle.Margin.Left + leftRectangle.Margin.Right + rightRectangle.Margin.Left + (rightRectangle.ActualWidth/2) and topRectangle.ActualHeight + topRectangle.Margin.Top + someConstantForHowTallThatSpacerRowIs + rightRectangle.Margin.Top + (rightRectangle.Height/2). Ouch
If we resize the container, then the rectangleGrid will also resize, but if you have used start-sizing for your columns then the rectangles all just resized. Now I have to go and recalculate all the sizes again.
So at this point, I'd start asking myself if I really wanted the rectangleGrid to handle the sizing or maybe I should just put everything into the Canvas.
You don't need to resize (although be careful because there are lots of high DPI screens out there now)
If you resize then the sizes are much simpler (e.g. if we assume that our margins are rectangles are 3/4 of the size and the margins 1/8 each side) so that redline top point now becomes shapeCanvas.Size *11/16 and (shapeCanvas.Height - someConstantForHowTallThatSpacerRowIs)/14

SL 4: Strange behavior with templated control

We have some xaml:
<Style TargetType="local:V_RelLine">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:V_RelLine">
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Ellipse x:Name="startArrow" Height="20" Width="60" Fill="Green" Stroke="Blue" Visibility="Visible" />
<Path x:Name="LinePathPart" Visibility="Visible" Stroke="Red" StrokeDashArray="2 2" StrokeThickness="2"
>
<Path.Data>
<PathGeometry x:Name="LinePathGeometry" >
<PathFigure x:Name="linePathBezierFigure" >
<BezierSegment x:Name="linePathBezierSegment" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Ellipse x:Name="endArrow" Height="20" Width="20" Fill="Red" Stroke="Red" Visibility="Visible" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And in the code behind:
LinePathBezierFigure.StartPoint = startPoint;
Canvas.SetLeft(startArrow, startPoint.X);
Canvas.SetTop(startArrow, startPoint.Y);
/* similar for endArrow */
At runtime, startArrow and endArrow end up at the same point (even though they were set to different locations), as though they ended up at 0,0.
In fact, a subsequent call to Canvas.GetLeft(startArrow) show that it is at 0,0.
What is going on? Why are different objects in the same template, assigned the same coordinates, ending up in different locations?
Thanks for any insight in to this....
Just a thought but Canvas.Left and Canvas.Top normally only work well when the elements are placed in a Canvas rather than a Grid like you are using currently.

Style a border with a different brush color for each corner

I have created a static resource defining the border of a specific item in my xaml, but I can't find a good way to define a unique color for each side!
xaml:
<Border Style="{StaticResource SidePanelBorder}">
<!-- rest of the xaml -->
</Border>
StaticResource:
<Style x:Key="SidePanelBorder">
<Setter Property="Control.BorderBrush" Value="#FF363636" />
<Setter Property="Control.BorderThickness" Value="1" />
</Style>
But I want to define one color for each side of the border, and eventually also a different Border thickness.
Any good techniques out there doing this?
Seems very hacky, but you could define borders within borders, and make only 1 side have a thickness. For example
<Border BorderThickness="0,0,0,10" BorderBrush="Green">
<Border BorderThickness="0,0,10,0" BorderBrush="Blue">
<Grid>
<Button>Hello</Button>
</Grid>
</Border>
</Border>
would give a green border on the bottom and a blue border to the right. Isn't the prettiest piece of Xaml though.
Another solution using one Border and a VisualBrush, allowing setting the Border's CornerRadius and BorderThickness:
<Border BorderThickness="10" CornerRadius="10" HorizontalAlignment="Right" Height="150" VerticalAlignment="Bottom" Width="150" Margin="0,0,92.666,42.667">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Path x:Name="ColoredBorderLeft" Data="M0,0 L0,0 1,0.5 L1,0.5 0,1" Fill="Blue" Stretch="Fill" Grid.RowSpan="2"/>
<Path x:Name="ColoredBorderRight" Data="M1,0 L1,0 0,0.5 L0,0.5 1,1" Fill="Red" Stretch="Fill" Grid.Column="1" Grid.RowSpan="2"/>
<Path x:Name="ColoredBorderTop" Data="M0,0 L0,0 0.5,1 L0.5,1 1,0" Fill="Green" Stretch="Fill" Grid.ColumnSpan="2"/>
<Path x:Name="ColoredBorderBottom" Data="M0,1 L0,1 0.5,0 L0.5,0 1,1" Fill="Yellow" Stretch="Fill" Grid.Row="1" Grid.ColumnSpan="2"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
</Border>
The Grid is needed to prevent the tips of the triangle Paths to "push through" into the border.
The Path.Name's can be used for DataBinding or setting the color from code behind.
you can have a DockPanel and can put 4 Borders inside it, each docked to different side.
like:
<DockPanel LastChildFill="true">
<Border DockPanel.Dock="Left" Background="Red"/>
<Border DockPanel.Dock="Top" Background ="Blue"/>
<Border DockPanel.Dock="Right" Background ="Yellow"/>
<Border DockPanel.Dock="Bottom" Background ="Green"/>
<Grid>
...........your control here
</Grid>
</DockPanel>
If you use a Grid you can have Border's overlay on one another to achieve the same affect. Just set the border thickness of the border color you want to show and have the other border thickness be 0.
<UserControl.Resources>
<Style x:Key="GreenBorder" TargetType="Border">
<Setter Property="BorderBrush" Value="Green" />
<Setter Property="BorderThickness" Value="1,1,1,0" />
</Style>
<Style x:Key="RedBorder" TargetType="Border">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.Row="0" Style="{StaticResource GreenBorder}">
<!-- Content goes here -->
</Border>
<Border Grid.Column="0" Grid.Row="0" Style="{StaticResource RedBorder}">
</Border>
</Grid>
This will give a Green border to the left, top and right borders, but a Red border to the bottom border of the Grid cell.
there's no easy way to do this without writing your own control or subclassing border

Resources