I am attempting to create a user control in WPF that allows the user to select specific regions of a shoe (heel, edge, sole etc)
The idea is that you have an image (drawing) of a shoe which you can click on individual parts and select the regions.
I am using a set of check boxes which are templated.
There is a single check box with a path that defines the edge and then a set of rectangles which define the individual areas.
This works well except obviously it doesn't look good. To make it look better I want to hide everything that is not inside the original shoe path.
The rectangles are all on a seperate row in a grid and the background shoe spans all of the rows.
I tried to set the Clip property of the parent grid to the same path as the background shoe edge but got some weird results:
I am pretty sure I am on the right lines with the Grid clipping but I dont understand what is happening here.
If anybody can help with this issue or suggest a better way of doing the same task I would be grateful.
<Geometry x:Key="ShoeEdgeGeometry">M26.25,0.5 C40.471332,0.5 52,17.625107 52,38.75 52,51.292905 47.935695,62.425729 41.656635,69.401079 L41.349452,69.733874 42.012107,70.457698 C45.421829,74.364614 47.5,79.554564 47.5,85.25 47.5,97.400265 38.042015,107.25 26.375,107.25 14.707984,107.25 5.2499995,97.400265 5.2499991,85.25 5.2499995,79.554564 7.3281701,74.364614 10.737891,70.457698 L11.276058,69.869853 10.843364,69.401079 C4.5643053,62.425729 0.49999952,51.292905 0.5,38.75 0.49999952,17.625107 12.028667,0.5 26.25,0.5 z</Geometry>
<Grid Margin="0"
Clip="{StaticResource ShoeEdgeGeometry}">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="4*" />
<RowDefinition Height="2*" />
<RowDefinition Height="2*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<!-- The edge check box-->
<CheckBox x:Name="ShoeEdgeRegion"
Grid.Row="0"
Grid.RowSpan="5">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Path x:Name="MainPath"
Data="{StaticResource ShoeEdgeGeometry}"
Fill="{StaticResource TransparentBrush}"
Stroke="Black"
Stretch="Fill" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The Toe check box-->
<CheckBox x:Name="ShoeToeRegion"
Grid.Row="0">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The Sole check box-->
<CheckBox x:Name="ShoeSoleRegion"
Grid.Row="1"
Margin="0,-1,0,0">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The Instep check box-->
<CheckBox x:Name="ShoeInstepRegion"
Margin="0,-1,0,0"
Grid.Row="2">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The Lower heel check box-->
<CheckBox x:Name="ShoeLowerHeelRegion"
Grid.Row="3"
Margin="0,-1,0,0">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The heel check box-->
<CheckBox x:Name="ShoeHeelRegion"
Grid.Row="4"
Margin="0,-1,0,0">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
</Grid>
Basing on the code you've provided I've reproduced the issue you described. We'll assume that the grid is of size 100x200. First of all, here's how the grid looks without and with clip set:
Obviously the result is the same. But here's how it looks if we add some background to the grid (red is the background of the grid, and blue is the background "behind" the grid):
Now we can clearly see what happened here - the geometry (roughly of size 50x100) was not automatically scaled to the size of the grid, but rather it's original size was maintained, thus clipping whole lot more that we'd like. Here's how it looks like if we resize the grid to match the size of the geometry:
In order to make our work a bit easier I've extracted the geometry figure definition to a resource and normalized it so that it is of size 1x1, which will make scaling a lot easier. So here are the resources:
<PathFigureCollection x:Key="ShoeEdgeFigures">
M 0.5048,0
C 0.7783,0 1,0.164 1,0.361 1,0.478 0.9218,0.582 0.8011,0.647
L 0.7952,0.65 0.8079,0.657
C 0.8735,0.693 0.9135,0.742 0.9135,0.795 0.9135,0.908 0.7316,1 0.5072,1
0.2828,1 0.101,0.908 0.101,0.795 0.101,0.742 0.1409,0.693 0.2065,0.657
L 0.2168,0.651 0.2085,0.647
C 0.0878,0.582 0,0.478 0,0.361 0,0.164 0.2313,0 0.5048,0
Z
</PathFigureCollection>
<PathGeometry x:Key="ShoeEdgeGeometry" Figures="{StaticResource ShoeEdgeFigures}" />
In order to make the grid look as we expect we'll need to scale the geometry - this can be done by means of Geometry.Transform
property. I think it is beneficial to define new geometry for that purpose. Then we only need to set ScaleTransform
on the geometry with ScaleX
and ScaleY
equal to the width and height of the grid respectively. That's because the initial size of the geometry is 1x1. Here's the code:
<Grid Width="100" Height="200" (...)>
<Grid.Resources>
<PathGeometry x:Key="StaticClipGeometry" Figures="{StaticResource ShoeEdgeFigures}">
<PathGeometry.Transform>
<ScaleTransform ScaleX="100" ScaleY="200" />
</PathGeometry.Transform>
</PathGeometry>
</Grid.Resources>
<Grid.Clip>
<StaticResource ResourceKey="StaticClipGeometry" />
</Grid.Clip>
(...)
</Grid>
This however is somewhat limiting, because the size of the grid needs to be constant and known at compile-time.
To make the clip geometry adjust to the size of the grid dynamically we'll need to bind the Geometry.Transform
property, and because we will bind to both Grid.ActualWidth
and Grid.ActualHeight
, we are going to need a simple converter:
public class SizeToScaleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new ScaleTransform
{
ScaleX = (double)values[0],
ScaleY = (double)values[1],
};
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then we define the grid:
<Grid (...)>
<Grid.Resources>
<PathGeometry x:Key="DynamicClipGeometry" Figures="{StaticResource ShoeEdgeFigures}">
<PathGeometry.Transform>
<MultiBinding>
<MultiBinding.Converter>
<local:SizeToScaleConverter />
</MultiBinding.Converter>
<Binding RelativeSource="{RelativeSource AncestorType=Grid}"
Path="ActualWidth" />
<Binding RelativeSource="{RelativeSource AncestorType=Grid}"
Path="ActualHeight" />
</MultiBinding>
</PathGeometry.Transform>
</PathGeometry>
</Grid.Resources>
<Grid.Clip>
<StaticResource ResourceKey="DynamicClipGeometry" />
</Grid.Clip>
(...)
</Grid>
And the end result looks like this:
It seems the Dimensions of your path are quite not fitting, but as an acceptable workaround i'd suggest you to surround your Grid
with an ViewBox
.
Full Code
<Window.Resources>
<Geometry x:Key="ShoeEdgeGeometry" >
M26.25,0.5 C40.471332,0.5 52,17.625107 52,38.75 52,51.292905 47.935695,62.425729 41.656635,69.401079 L41.349452,69.733874 42.012107,70.457698 C45.421829,74.364614 47.5,79.554564 47.5,85.25 47.5,97.400265 38.042015,107.25 26.375,107.25 14.707984,107.25 5.2499995,97.400265 5.2499991,85.25 5.2499995,79.554564 7.3281701,74.364614 10.737891,70.457698 L11.276058,69.869853 10.843364,69.401079 C4.5643053,62.425729 0.49999952,51.292905 0.5,38.75 0.49999952,17.625107 12.028667,0.5 26.25,0.5 z
</Geometry>
<SolidColorBrush x:Key="RedBrush" Color="Red"></SolidColorBrush>
<SolidColorBrush x:Key="TransparentBrush" Color="Transparent"></SolidColorBrush>
</Window.Resources>
<Viewbox Stretch="Fill">
<Grid Margin="0" Clip="{StaticResource ShoeEdgeGeometry}" >
<Grid.RowDefinitions>
<RowDefinition Height="0.2*" />
<RowDefinition Height="0.4*" />
<RowDefinition Height="0.2*" />
<RowDefinition Height="0.2*" />
<RowDefinition Height="0.2*" />
</Grid.RowDefinitions>
<!-- The edge check box-->
<CheckBox x:Name="ShoeEdgeRegion" Grid.Row="0" Grid.RowSpan="5">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Path x:Name="MainPath"
Data="{StaticResource ShoeEdgeGeometry}"
Fill="{StaticResource TransparentBrush}"
Stroke="Black"
Stretch="Fill" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The Toe check box-->
<CheckBox x:Name="ShoeToeRegion"
Grid.Row="0">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The Sole check box-->
<CheckBox x:Name="ShoeSoleRegion"
Grid.Row="1"
Margin="0,-1,0,0">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The Instep check box-->
<CheckBox x:Name="ShoeInstepRegion"
Margin="0,-1,0,0"
Grid.Row="2">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The Lower heel check box-->
<CheckBox x:Name="ShoeLowerHeelRegion"
Grid.Row="3"
Margin="0,-1,0,0">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<!-- The heel check box-->
<CheckBox x:Name="ShoeHeelRegion"
Grid.Row="4"
Margin="0,-1,0,0">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Rectangle x:Name="MainPath"
StrokeThickness="1"
Fill="{StaticResource TransparentBrush}"
Stroke="Black" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Stroke"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="MainPath"
Property="Fill"
Value="{StaticResource RedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
</Grid>
</Viewbox>
Outcome
Hope this'll work for you
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With