Thursday, 7 June 2007

Creating a Hatched / Patterned Brush in WPF

I recently wanted to create a patterned brush in WPF. Of course, WPF is more than capable of doing hatching patterns, but it took me a little while to work out how to do it, especially since the MSDN2 website seems to have lost all example XAML snippets at the moment.
The approach I chose is to use a VisualBrush, although a DrawingBrush would probably work equally well. In the Visual property of the VisualBrush you add your graphics. So for my examples I have one which just has a circle, allowing you to fill an object with dots, and one with two diagonal lines, to create cross-hatching. Then you need to set some additional properties on the VisualBrush:

  • TileMode would be set to Tile as this is a repeating pattern
  • Stretch is set to None
  • The ViewBox rectangle specifies a window onto the Visual allowing you to select just a part of it. Both my fill pattern visuals were 10x10, but I made the ViewBox for the dot fill 12x12 which effectively added some spacing between the dots.
  • The ViewPort rectangle gives more flexibility by specifying the position and dimensions of the first "tile". Normally you would simply set this to the same as the ViewBox, but you could use it to scale the tiled image in X or Y dimensions, or effectively slide the tiles across.

Here's some example XAML:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:sys="clr-namespace:System;assembly=mscorlib" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
  <VisualBrush 
    x:Key="DotFillBrush" 
    TileMode="Tile" 
    Viewport="0,0,10,10" 
    ViewportUnits="Absolute" 
    Viewbox="0,0,12,12"
    ViewboxUnits="Absolute">
    <VisualBrush.Visual>
      <Ellipse 
        Fill="#00C0FF" 
        Width="10" Height="10" />
      </VisualBrush.Visual>
    </VisualBrush>      
    <VisualBrush 
      x:Key="HatchBrush" 
      TileMode="Tile" Viewport="0,0,10,10" 
      ViewportUnits="Absolute" Viewbox="0,0,10,10"    
      ViewboxUnits="Absolute">
      <VisualBrush.Visual>
        <Canvas>
           <Rectangle Fill="Azure" Width="10" Height="10" />
           <Path Stroke="Purple" Data="M 0 0 l 10 10" />
           <Path Stroke="Purple" Data="M 0 10 l 10 -10" />
        </Canvas>
      </VisualBrush.Visual>
   </VisualBrush>      
</Page.Resources>

<Canvas>
<Rectangle Canvas.Top="20" Canvas.Left="20" 
  Width="80" Height="40" 
  Fill="{StaticResource DotFillBrush}"/>
<Rectangle Canvas.Top="20" Canvas.Left="120" 
  Width="80" Height="40" 
  Fill="{StaticResource HatchBrush}"/>
<TextBlock Canvas.Top="80" Canvas.Left="20" 
  Text="Hello" FontSize="80" 
  Foreground="{StaticResource DotFillBrush}"/>
<TextBlock Canvas.Top="80" Canvas.Left="220" 
  Text="World" FontSize="80" 
  Foreground="{StaticResource HatchBrush}"/>
</Canvas>
</Page>

And here's what it looks like:

WPF Patterned & Hatched Brushes

9 comments:

Anonymous said...

Unfortunately, Silverlight doesn't have the DrawingBrush or the VisualBrush, which means that so far I have been unable to figure out how to fill in a rectangle with the simplest of patterns instead of a solid color.

Unknown said...

Hi Steve, yes it is a shame that Silverlight doesn't have all the features of WPF. Hopefully this will be rectified in future version

Anonymous said...

When creating diagonal hashes, you are stuck with empty pixels at the corners with this approach. It is more evident if you change your visualbrush to use just a single path at a diagonal (0,10 to 10,-10). You will see what appears to be small gaps at the corners. An alternative way would be to always create the lines vertical or horizontal and set the transform on the brush to be rotate the entire brush.

Anonymous said...

Hi Mark,

Thanks for this tutorial but unfortunately i couldn't get it working. Maybe i have missed some functions but things like GetNoteYPosition or NoteSeperateBrush variable don't seem to be recognized. You never specified where these were meant to go.

Since i am currently doing some research into how midi works within C#, do you think that it would be possible to put a compressed solution version of the tutorials underneath the tutorial names? If this isn't possible then will you be able to paste the whole xaml and the window code?

Thanks, i would of dropped you an e-mail but it appears that your website doesn't have it on.

Mark

Anonymous said...

Hi, I was able to do a dot pattern starting from your example and it worked perfectly :D.

VisualBrush vb = new VisualBrush();

vb.TileMode = TileMode.Tile;

vb.Viewport = new Rect(0, 0, 10, 10);
vb.ViewportUnits = BrushMappingMode.Absolute;

vb.Viewbox = new Rect(0, 0, 12, 12);
vb.ViewboxUnits = BrushMappingMode.Absolute;

Ellipse ellipse = new Ellipse();
ellipse.Fill = Brushes.Black;
ellipse.Width = 10;
ellipse.Height = 10;
vb.Visual = ellipse;

return vb;


Thanks!

Neal said...

Thanks Mark - I was looking for a crosshatch and came across your article - it helped a lot!

geFrank said...

I was looking for a hatched example to "hatch" a datagrid row based on a DataTrigger and this worked great!!

Thanks...

Fernando said...

Also in VB.NET we have it:


Dim MyBrush As New VisualBrush With {.TileMode = TileMode.Tile, .Viewport = New Rect(0, 0, 10, 10), .ViewportUnits = BrushMappingMode.Absolute, .Viewbox = New Rect(0, 0, 12, 12), .ViewboxUnits = BrushMappingMode.Absolute}
MyBrush.Visual = New Ellipse With {.Fill = Brushes.Black, .Width = 10, .Height = 10}

Envisioning God said...

Extremely simple and extremely helpful. Thank you! And my UI thanks you too!