I'm trying to draw a grid on my canvas in which:
The lines are always one pixel thick
The size of the square tile are expressed in pixel or in other words the distance between each horizontal and vertical lines is in pixels and maybe binded to the code behind so that I can change it at run time.
This is my first attempt, but maybe I'm using the wrong controls.
<Canvas Panel.ZIndex="0" x:Name="TileCanvas">
<Grid Panel.ZIndex="5">
<Rectangle Width="{Binding ElementName=TileCanvas, Path=ActualWidth}" Height="{Binding ElementName=TileCanvas, Path=ActualHeight}"
Stroke="Black" StrokeThickness="0">
<Rectangle.Fill>
<DrawingBrush ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Viewport>
<MultiBinding Converter="{StaticResource RectConverter}">
<...>
</MultiBinding>
</DrawingBrush.Viewport>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Geometry="M0,0 L1,0 1,0.1, 0,0.1Z" Brush="Green" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<GeometryDrawing Geometry="M0,0 L0,1 0.1,1, 0.1,0Z" Brush="Green" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</DrawingGroup>
</DrawingBrush.Drawing>
<DrawingBrush.Transform>
<ScaleTransform ScaleX="1" ScaleY="1"/>
</DrawingBrush.Transform>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
This solution allows me to vary the size of each tile, but:
I didn't find the way to fix the line thickness to one pixel
When I change the DrawingBrush.Viewport with the converter I do enlarge the tile (Great!) but the thickness of the lines increase (boo).
I had a very similar problem.
You can use a single geometry and if you want to avoid thickness increase\decrease you have to express the thickness in terms of the tile width by a ratio like thickness = 1/tileWidth. If you change that size of your tile with a WPF control you can use a converter to update the thickness accordingly.
<GeometryDrawing Geometry="M10,0 L10,10 0,10 10,10 10,0Z" Brush="Green" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<GeometryDrawing.Pen>
<Pen Thickness="{Binding ElementName=[TILE SIZE CONTROL VAL], Converter={StaticResource RatioConverter}}"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (1 / (double)value);
}
I have a DrawingImage that I use as a Vector image placeholder.
Further on, there are styles that take that respective DrawingImage and use it as an image in my UserControl custom button.
So far everything was fine, but I just came to realize that my current approach has caused my DrawingImages to be non-reusable, since they have their Brush property hard-bound to the control, they are used in, as follows:
<DrawingImage x:Key="addIcon">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="{Binding Path=ImageBrush, ElementName=addButton}" Geometry="M438.2,0H51.6C23.1,0,0,23.2,0,51.6v386.6c0,28.5,23.2,51.6,51.6,51.6h386.6c28.5,0,51.6-23.2,51.6-51.6V51.6
C489.8,23.2,466.6,0,438.2,0z M465.3,438.2c0,14.9-12.2,27.1-27.1,27.1H51.6c-14.9,0-27.1-12.2-27.1-27.1V51.6
c0-14.9,12.2-27.1,27.1-27.1h386.6c14.9,0,27.1,12.2,27.1,27.1V438.2z" />
<GeometryDrawing Brush="{Binding Path=ImageBrush, ElementName=addButton}" Geometry="M337.4,232.7h-80.3v-80.3c0-6.8-5.5-12.3-12.3-12.3s-12.3,5.5-12.3,12.3v80.3h-80.3c-6.8,0-12.3,5.5-12.3,12.2
c0,6.8,5.5,12.3,12.3,12.3h80.3v80.3c0,6.8,5.5,12.3,12.3,12.3s12.3-5.5,12.3-12.3v-80.3h80.3c6.8,0,12.3-5.5,12.3-12.3
C349.7,238.1,344.2,232.7,337.4,232.7z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
Now, I was trying to come up with a way to have them bound not directly to the Element, but to some kind of a generic Element (similairly as interfaces work) so that the code would be sure that anything that is bound to it, has got the Brush Dependency property.
So far, i failed to find it.
I have also tried by searching through an ancestor, still no luck.
Is there a more or less common practice to binding to unknown Elements, that have certain dependency properties, without exposing them directly?
In fact, there is no way to do directly what you ask. The reason is that when you define DrawingImage as a resource and then use it as a source for image, then no copies of the resource are created, but instead of this each image looks at the same resource. Therefore, the DrawingImage can not have a parent in the visual tree at all, so there is just no control to bind to.
There are two options here. One is to use a Geometry as a resource, not a DrawingImage. Then you can create some DrawingImage resources referencing to this Geometry and using different colors. Or do not use DrawingImage at all, but use the Geometry directly (for example, through the Path). Actually, there are so many ways how to use and combine Geometry resources. I give just few examples here:
<Window x:Class="FlipControlApp.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"
Title="MainWindow" Height="300" Width="300">
<Window.Resources>
<PathGeometry x:Key="addIconGeometry" Figures="M337.4,232.7h-80.3v-80.3c0-6.8-5.5-12.3-12.3-12.3s-12.3,5.5-12.3,12.3v80.3h-80.3c-6.8,0-12.3,5.5-12.3,12.2
c0,6.8,5.5,12.3,12.3,12.3h80.3v80.3c0,6.8,5.5,12.3,12.3,12.3s12.3-5.5,12.3-12.3v-80.3h80.3c6.8,0,12.3-5.5,12.3-12.3
C349.7,238.1,344.2,232.7,337.4,232.7z"/>
<PathGeometry x:Key="iconBorderGeometry" Figures="M438.2,0H51.6C23.1,0,0,23.2,0,51.6v386.6c0,28.5,23.2,51.6,51.6,51.6h386.6c28.5,0,51.6-23.2,51.6-51.6V51.6
C489.8,23.2,466.6,0,438.2,0z M465.3,438.2c0,14.9-12.2,27.1-27.1,27.1H51.6c-14.9,0-27.1-12.2-27.1-27.1V51.6
c0-14.9,12.2-27.1,27.1-27.1h386.6c14.9,0,27.1,12.2,27.1,27.1V438.2z"/>
<GeometryGroup x:Key="addIconWithBorderGeometry">
<StaticResource ResourceKey="iconBorderGeometry"/>
<StaticResource ResourceKey="addIconGeometry"/>
</GeometryGroup>
<DrawingImage x:Key="addIconBlack">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Black" Geometry="{StaticResource addIconWithBorderGeometry}" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Window.Resources>
<UniformGrid Columns="2">
<Button Name="addButton0" Width="100" Height="100">
<Image Source="{StaticResource addIconBlack}"/>
</Button>
<Button Width="100" Height="100" Foreground="Blue">
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Control}}"
Geometry="{StaticResource addIconGeometry}" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<Button Width="100" Height="100" Foreground="Green">
<Grid>
<Path Data="{StaticResource iconBorderGeometry}" Fill="Pink" Stretch="Uniform"/>
<Path Data="{StaticResource addIconGeometry}" Fill="Purple" Stretch="Uniform" Margin="15"/>
</Grid>
</Button>
<Button x:Name="addBtn" Width="100" Height="100" Foreground="Green">
<Path Fill="{Binding Foreground, ElementName=addBtn}" Stretch="Uniform">
<Path.Data>
<CombinedGeometry Geometry1="{StaticResource iconBorderGeometry}" Geometry2="{StaticResource addIconGeometry}"/>
</Path.Data>
</Path>
</Button>
</UniformGrid>
</Window>
Another option is to use whole Image as resource with a Brush binding to the some ancestors property. But in this case, it is necessary to use the attribute x:Shared and hence the resource must be declared in the compiled resource dictionary (for details see x:Shared).
Create resource:
<ResourceDictionary>
<Image x:Key="addIconImage2" x:Shared="False">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Control}}"
Geometry="M438.2,0H51.6C23.1,0,0,23.2,0,51.6v386.6c0,28.5,23.2,51.6,51.6,51.6h386.6c28.5,0,51.6-23.2,51.6-51.6V51.6
C489.8,23.2,466.6,0,438.2,0z M465.3,438.2c0,14.9-12.2,27.1-27.1,27.1H51.6c-14.9,0-27.1-12.2-27.1-27.1V51.6
c0-14.9,12.2-27.1,27.1-27.1h386.6c14.9,0,27.1,12.2,27.1,27.1V438.2z" />
<GeometryDrawing Brush="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Control}}"
Geometry="M337.4,232.7h-80.3v-80.3c0-6.8-5.5-12.3-12.3-12.3s-12.3,5.5-12.3,12.3v80.3h-80.3c-6.8,0-12.3,5.5-12.3,12.2
c0,6.8,5.5,12.3,12.3,12.3h80.3v80.3c0,6.8,5.5,12.3,12.3,12.3s12.3-5.5,12.3-12.3v-80.3h80.3c6.8,0,12.3-5.5,12.3-12.3
C349.7,238.1,344.2,232.7,337.4,232.7z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</ResourceDictionary>
Then use it:
<Button Name="addButton3" Width="100" Height="100"
Content="{StaticResource addIconImage}" Foreground="Red"/>
<Button Name="addButton4" Width="100" Height="100"
Content="{StaticResource addIconImage}" Foreground="Green"/>
Each time you reference to it, new copy will be created.
But the first way is more flexible.
I have a DLL that has a ResourceDictionary containing a XAML image:
<DrawingBrush x:Key="imgFoo" ViewboxUnits="Absolute" Viewbox="0,0,128,128">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="#FF111111">
<GeometryDrawing.Pen>
<Pen LineJoin="Miter" StartLineCap="Square" EndLineCap="Square"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry Figures="M 56.5625 ... 64.03125 45.46875 z"
FillRule="NonZero"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
Is it possible to use this DrawingBrush as Window.Icon?
using StaticResource or DynamicResource doesn't work:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Icon="{DynamicResource imgFoo}">
The only thing I found is using Window.Resources like this:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Icon="{DynamicResource imgFoo}">
<Window.Resources>
<DrawingImage x:Key="imgFoo">
<DrawingImage.Drawing>
<GeometryDrawing Brush="#FF111111">
<GeometryDrawing.Pen>
<Pen LineJoin="Miter" StartLineCap="Square"
EndLineCap="Square"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry Figures="M 56.5625 ... 64.03125 45.46875 z"
FillRule="NonZero"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Window.Resources>
But this uses only a part of the DrawingBrush from the DLL file and creates duplicate XAML code.
Any suggestions on how I could use that DrawingBrush directly?
You could split the resource declaration into two parts and declare the GeometryDrawing and the DrawingBrush separately:
<GeometryDrawing x:Key="imgFooDrawing" Brush="#FF111111">
<GeometryDrawing.Pen>
<Pen LineJoin="Miter" StartLineCap="Square" EndLineCap="Square"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry Figures="M10,64 L64,10 118,64 64,118Z" FillRule="NonZero"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<DrawingBrush x:Key="imgFoo" ViewboxUnits="Absolute" Viewbox="0,0,128,128"
Drawing="{StaticResource imgFooDrawing}">
</DrawingBrush>
Now you could directly reuse the GeometryDrawing in a DrawingImage that is used as the Window's Icon:
<Window.Icon>
<DrawingImage Drawing="{DynamicResource imgFooDrawing}"/>
</Window.Icon>
In case you can't change the Resource DLL, you could bind the DrawingImage's Drawing property to the Drawing property of the DrawingBrush.
This would however require an ugly workaround because you can't use a DynamicResource as binding source. You may set the Tag property of the Window to the DrawingBrush, and then create a RelativeSource/FindAncestor binding:
<Window ... Tag="{DynamicResource imgFoo}">
<Window.Icon>
<DrawingImage Drawing="{Binding Tag.Drawing,
RelativeSource={RelativeSource AncestorType=Window}}"/>
</Window.Icon>
</Window>