How to create and use ResourceDictionary of external XAML images - wpf

I would like to use icons from Visual Studio icon set for a WPF app and I would like to do it properly using ResourceDictionaries, but it seems more challenging than I thought.
The example icon is a ViewBox provided in a separate XAML file, it's named home_16x.xaml and the XAML content looks like this:
<Viewbox Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Rectangle Width="16" Height="16">
<Rectangle.Fill>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M7.8786,-0.000199999999999534L-0.000399999999999956,7.8798 -0.000399999999999956,9.4138 0.9996,10.4138 0.9996,15.9998 15.0006,15.9998 15.0006,10.4138 16.0006,9.4138 16.0006,7.8798 8.1206,-0.000199999999999534z" />
<GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M13,14L10,14 10,9 6,9 6,14 3,14 3,7.707 8,2.707 13,7.707z" />
<GeometryDrawing Brush="#FF424242" Geometry="F1M0.6462,8.6461L1.3532,9.3531 2.0002,8.7071 2.0002,15.0001 7.0002,15.0001 7.0002,10.0001 9.0002,10.0001 9.0002,15.0001 14.0002,15.0001 14.0002,8.7071 14.6462,9.3531 15.3532,8.6461 8.0002,1.2931z M3.0002,7.7071L8.0002,2.7071 12.9992,7.7071 12.9992,14.0001 10.0002,14.0001 10.0002,9.0001 6.0002,9.0001 6.0002,14.0001 3.0002,14.0001z" />
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</Viewbox>
If I use the following on my page then it works, but that's not what I am after. It doesn't seem elegant and I would like to keep the option open for switching ResourceDictionaries (for themes).
<Viewbox Width="400" Height="400">
<Frame Source="\Media\Icons\Home_16x.xaml"/>
</Viewbox>
It also shows the following in design view, which is quite confusing:
I would really like to be able to build a dictionary of the icons, that I could call with something like (or cleaner, if possible):
<Viewbox Width="400" Height="400">
<Frame Content="{StaticResource ResourceKey=Icons.Home}"/>
</Viewbox>
My current Icons ResourceDictionary looks like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Viewbox x:Key="Home">
<Viewbox.Resources>
<ResourceDictionary>
<Frame x:Key="Home" Source="\Media\Icons\Home_16x.xaml"/>
</ResourceDictionary>
</Viewbox.Resources>
</Viewbox>
</ResourceDictionary>
I admit I am quite lost. Any help would be much appreciated.

If you use a visualbrush on say a rectangle as fill, that will stretch to whatever size the rectangle is.
Hence you could have a visualbrush in a resource dictionary:
<DrawingBrush x:Key="HomeBrush">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M7.8786,-0.000199999999999534L-0.000399999999999956,7.8798 -0.000399999999999956,9.4138 0.9996,10.4138 0.9996,15.9998 15.0006,15.9998 15.0006,10.4138 16.0006,9.4138 16.0006,7.8798 8.1206,-0.000199999999999534z" />
<GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M13,14L10,14 10,9 6,9 6,14 3,14 3,7.707 8,2.707 13,7.707z" />
<GeometryDrawing Brush="#FF424242" Geometry="F1M0.6462,8.6461L1.3532,9.3531 2.0002,8.7071 2.0002,15.0001 7.0002,15.0001 7.0002,10.0001 9.0002,10.0001 9.0002,15.0001 14.0002,15.0001 14.0002,8.7071 14.6462,9.3531 15.3532,8.6461 8.0002,1.2931z M3.0002,7.7071L8.0002,2.7071 12.9992,7.7071 12.9992,14.0001 10.0002,14.0001 10.0002,9.0001 6.0002,9.0001 6.0002,14.0001 3.0002,14.0001z" />
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
And use that:
<Rectangle Fill="{StaticResource HomeBrush}" Width="16" Height="16"/>
Or you could let your rectangle fill whatever cell of a grid it's in and omit width and height.
You don't need a viewbox to scale vectors, they can stretch.
You can also use a visualbrush within a path. The path can define one shape with a geometry ( which could be from a resource ) and within that your drawingbrush could put another geometry drawing with a different colour.
An example geometry out a resource file I have:
<Geometry x:Key="BeeIcon">
M15.551045,25.144995L16.748029,25.144995 ....
</Geometry>
I removed a big chunk of the geometry there.
That BeeIcon can then be re-used in different drawinggroups which is useful if you want to construct icons with several parts.
Brushes and colours for them can also be from a resource dictionary so you can switch them out for different themes or branding.

Related

XAML icons in menu items look shifted

I would like to use Visual Studio 2022 Image Library in my project. The one that the VS2022 IDE uses (surprise!). But when I add an icon to a menu item, it doesn't look as pretty as it does in a VS menu:
<MenuItem.Icon>
<Rectangle Width="16" Height="16">
<Rectangle.Fill>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="{DynamicResource canvas}" Geometry="F1M16,16H0V0H16Z" />
<DrawingGroup Opacity="{DynamicResource cls-1}">
<GeometryDrawing Brush="{DynamicResource light-defaultgrey-10}" Geometry="F1M14.5,4.5v10H3.5V7.95A4.984,4.984,0,0,0,8,3a4.966,4.966,0,0,0-.254-1.5H11.5Z" />
<GeometryDrawing Brush="{DynamicResource light-defaultgrey}" Geometry="F1M15,4.5v10l-.5.5H3.5L3,14.5V8a4.988,4.988,0,0,0,1-.1V14H14V5H11V2H7.9a4.968,4.968,0,0,0-.321-1H11.5l.354.146,3,3Z" />
</DrawingGroup>
<GeometryDrawing Brush="{DynamicResource light-yellow}" Geometry="F1M3,2,3,0H4L4,2l-.5.5Zm.5,2.5L3,5V7H4V5Zm1-1L5,4,7,4V3L5,3Zm-2,0L2,3,0,3V4L2,4Zm1.708-.7h.708L6.328,1.378,5.62.671,4.208,2.088ZM2.792,4.206H2.085L.673,5.623l.707.706L2.793,4.913Zm1.414,0v.708L5.623,6.328l.706-.708L4.913,4.208ZM2.794,2.792V2.085L1.378.673.671,1.38,2.088,2.793Z" />
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</MenuItem.Icon>
Compare the IDE's and my renders (scaled):
As you can see, my icons seem to be shifted horizontally by a fraction of a pixel. Perhaps it's just because Microsoft guys used PNG instead of XAML.
I've tried adding SnapsToDevicePixels wherever possible, but it doesn't change anything. Obviously, specifying a correct offset will be enough for the solution, but I don't know that offset. And I don't want to look through the Magnifier every time I add a new icon to make sure I need to subtract a hundredth of a pixel from the offset I guessed.
Could you suggest an elegant solution to removing the subpixel effect when rendering these XAML icons?

WPF use Canvas as ImageSource

I am new to WPF and trying to build a basic app with a toolbar and icons. I am testing XamRibbon from Infragistics and ButtonTool that shows on the Ribbon require ImageSource to show an image like so:
<igRibbon:ButtonTool Caption="Edit"
igRibbon:RibbonGroup.MaximumSize="ImageAndTextLarge"
LargeImage="{StaticResource stop}"
SmallImage="{StaticResource stop}" />
I have a XAML icon defined in my dictionary with key 'stop' but the icons I am using are all in Canvas form like this:
<Canvas Width="32" Height="32" Canvas.Left="0" Canvas.Top="0">
<Canvas Clip="M15.82,31.2118l2.0148,-3.8306l-0.0332,-0.0332l0.1068,-0.1068l0.5615,-1.0675l0.253,0.253l8.5884,-8.5885l-0.0112,-0.0111l1.6101,-1.5876c0.4557,-0.4557,0.923,-0.5523,1.234,-0.5523c0.311,0,0.7784,0.0967,1.239,0.5572l0.6169,0.6168V0H0v32h15.7137C15.656,31.7398,15.6889,31.4614,15.82,31.2118z">
<Canvas>
<Canvas.RenderTransform>
<TransformGroup>
<TranslateTransform X="-1" Y="-1"/>
</TransformGroup>
</Canvas.RenderTransform>
<Polygon Points="17,28 17,24.5 15,24.5 15,28 3,28 3,30 29,30 29,28" Fill="#4EA17E" StrokeLineJoin="Miter"/>
<Path Data="M18.475,31h-4.9c-0.3038,0,-0.575,-0.1962,-0.575,-0.5v-2.95c0,-0.3038,0.2712,-0.55,0.575,-0.55h4.9c0.3038,0,0.55,0.2462,0.55,0.55V30.5C19.025,30.8038,18.7788,31,18.475,31z" Fill="#797979"/>
<Path Data="M28,1H4c-0.5523,0,-1,0.4477,-1,1v5c0,0.5523,0.4477,1,1,1h24c0.5523,0,1,-0.4477,1,-1V2C29,1.4477,28.5523,1,28,1zM7,6H5V3h2V6zM28,9H4c-0.5523,0,-1,0.4477,-1,1v5c0,0.5523,0.4477,1,1,1h24c0.5523,0,1,-0.4477,1,-1v-5C29,9.4477,28.5523,9,28,9zM7,14H5v-3h2V14zM28,17H4c-0.5523,0,-1,0.4477,-1,1v5c0,0.5523,0.4477,1,1,1h24c0.5523,0,1,-0.4477,1,-1v-5C29,17.4477,28.5523,17,28,17zM7,22H5v-3h2V22z" Fill="#3E79B4"/>
<Path Data="M7,22H5v-3h2V22zM7,11H5v3h2V11zM7,3H5v3h2V3z" Fill="#FFFFFF"/>
</Canvas>
</Canvas>
<Canvas>
<Path Data="M19.2158,27.348l9.0199,-9.0199l2.1233,2.1233l-9.0199,9.0199L19.2158,27.348zM29.6123,16.9515c0.3525,-0.3525,0.7113,-0.3525,1.0638,0l1.0596,1.0596c0.3525,0.3525,0.3525,0.7113,0,1.0638l-0.8743,0.8743l-2.1388,-2.1202L29.6123,16.9515zM16.7052,31.677l2.0176,-3.8361l2.1233,2.1233l-3.8361,2.0176c-0.061,0.0321,-0.1358,0.0207,-0.1846,-0.028l-0.0923,-0.0923C16.6845,31.8128,16.6732,31.738,16.7052,31.677z" Fill="#3E79B4"/>
</Canvas>
How can I convert Canvas XAML to something that can be used as ImageSource like a DrawingImage for example? I hope this makes sense.
I don't use Infragistics controls, but I believe they follow the standard WPF controls.
I used image control and try to display your canvas path image.
define the Geometry from your canvas path in resource
<Geometry x:Key="StopGeometry">M13,11.3814v18.115c0.0083,0.2845,0.2134,0.5036,0.4896,0.5036h4c0.2807,0,0.5104,-0.2297,0.5104,-0.5104v-18.088l7.6585,7.6695c0.1259,0.1261,0.3415,0.0369,0.3415,-0.1413v-6.2525c0,-0.2125,-0.0845,-0.4162,-0.2349,-0.5663l-9.934,-9.9139c-0.2999,-0.2654,-0.4015,-0.2654,-0.6944,0.0157L5.235,12.0908c-0.1504,0.1501,-0.235,0.3539,-0.235,0.5664v6.2414c0,0.1782,0.2154,0.2674,0.3414,0.1414L13,11.3814z</Geometry>
in XAML
<Image Height="100" Width="100">
<Image.Source>
<DrawingImage >
<DrawingImage.Drawing>
<GeometryDrawing Brush="#3E79B4" Geometry="{StaticResource StopGeometry}"/>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
I used the Blend for Visual studio to convert your Canvas as DrawingBrush. Now I can simply use what Blend Gave me. Only copy and paste I did.
<Image Height="32" Width="32">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup ClipGeometry="M15.82,31.2118L17.8348,27.3812 17.8016,27.348 17.9084,27.2412 18.4699,26.1737 18.7229,26.4267 27.3113,17.8382 27.3001,17.8271 28.9102,16.2395C29.3659,15.7838 29.8332,15.6872 30.1442,15.6872 30.4552,15.6872 30.9226,15.7839 31.3832,16.2444L32.0001,16.8612 32.0001,0 0,0 0,32 15.7137,32C15.656,31.7398,15.6889,31.4614,15.82,31.2118z">
<DrawingGroup>
<DrawingGroup.Transform>
<MatrixTransform Matrix="1,0,0,1,-1,-1" />
</DrawingGroup.Transform>
<GeometryDrawing Brush="#FF4EA17E" Geometry="M17,28 L17,24.5 15,24.5 15,28 3,28 3,30 29,30 29,28 z" />
<GeometryDrawing Brush="#FF797979" Geometry="M18.475,31L13.575,31C13.2712,31,13,30.8038,13,30.5L13,27.55C13,27.2462,13.2712,27,13.575,27L18.475,27C18.7788,27,19.025,27.2462,19.025,27.55L19.025,30.5C19.025,30.8038,18.7788,31,18.475,31z" />
<GeometryDrawing Brush="#FF3E79B4" Geometry="M28,1L4,1C3.4477,1,3,1.4477,3,2L3,7C3,7.5523,3.4477,8,4,8L28,8C28.5523,8,29,7.5523,29,7L29,2C29,1.4477,28.5523,1,28,1z M7,6L5,6 5,3 7,3 7,6z M28,9L4,9C3.4477,9,3,9.4477,3,10L3,15C3,15.5523,3.4477,16,4,16L28,16C28.5523,16,29,15.5523,29,15L29,10C29,9.4477,28.5523,9,28,9z M7,14L5,14 5,11 7,11 7,14z M28,17L4,17C3.4477,17,3,17.4477,3,18L3,23C3,23.5523,3.4477,24,4,24L28,24C28.5523,24,29,23.5523,29,23L29,18C29,17.4477,28.5523,17,28,17z M7,22L5,22 5,19 7,19 7,22z" />
<GeometryDrawing Brush="White" Geometry="M7,22L5,22 5,19 7,19 7,22z M7,11L5,11 5,14 7,14 7,11z M7,3L5,3 5,6 7,6 7,3z" />
</DrawingGroup>
</DrawingGroup>
<GeometryDrawing Brush="#FF3E79B4" Geometry="M19.2158,27.348L28.2357,18.3281 30.359,20.4514 21.3391,29.4713 19.2158,27.348z M29.6123,16.9515C29.9648,16.599,30.3236,16.599,30.6761,16.9515L31.7357,18.0111C32.0882,18.3636,32.0882,18.7224,31.7357,19.0749L30.8614,19.9492 28.7226,17.829 29.6123,16.9515z M16.7052,31.677L18.7228,27.8409 20.8461,29.9642 17.01,31.9818C16.949,32.0139,16.8742,32.0025,16.8254,31.9538L16.7331,31.8615C16.6845,31.8128,16.6732,31.738,16.7052,31.677z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
I think this is the easiest solution.

How do I create vector images for WPF

I want to use vector images in my WPF-application (for buttons and menus). How do I do that? Which tools should I use? Can someone show my a complete example?
##Tools to create XAML vector images##
Probably the best application to create XAML vector images is Microsoft Expression Design. It’s a free tool that could be downloaded from http://expressiondesign4.com/
When you have installed Expression Design, launch it and select Edit -> Options -> Clipboards (XAML). Change Clipboard format to XAML WPF Resource Dictionary. Also change Group By to Document (otherwise each layer will be an image).
Edit your image in Expression Design. When you are done, select everything and open the Edit menu and then Copy XAML. Paste this in appropriate XAML-file. You see in the example below how it should look like. One thing to note is that you need to change DrawingImage tag to DrawingBrush.
When you are drawing the image set the document size to the size you want in your WPF-application (like 32x32 pixels). It not necessary but make the work easier. Before copy the image to XAML you probably want to make a transparent rectangle that has the same size as the document (otherwise the margins could be wrong). Or you could add this manually in the drawing group children:
<GeometryDrawing Brush="#00FFFFFF" Geometry="M 0,0L 32,0L 32,32L 0,32L 0,0 Z " />
###If you are using Inkscape###
Inkscape has support to generate XAML-files. However - this is probably not the format you want! WPF has two different ways to handle graphics in XAML – shapes and geometries. You can find more details about this here: http://www.intertech.com/Blog/WPF-Shapes-vs-WPF-Geometries/.
But in short shapes has support for inputs, while geometries is just pure drawing and therefor more lightweight.
Inkscape generate files in shape-format, which is good for some cases but not for images that should be used in buttons and similar. So what you want is to get your images into Expression Design. You could do that by saving your image as a PDF-file, change the file extension to AI and then in Expression Design use File, Import Adobe Illustrator File. Using EPS is another option.
Most things could be imported to Expression Design. But it might be some issues with borders for instance. When you have got what you want to Expression Design it’s probably better to do all the work in there. If needed you could export your images to SVG which could be used in Inkscape, that normally works without any problems.
#Example#
When you have created XAML-code for the image it’s then quite straight forward. Below is an example where a vector image is used on a menu and two button.
If you want to draw a very thin line (1 pixel), you may want to add RenderOptions.EdgeMode="Aliased" and SnapsToDevicePixels="True" to the attributes to the control that is drawing the image.
Another thing to have in mind is what to do when a button is disabled. In the example below the image looks the same no matter if the button is enabled or not (this is true for ordinary bitmaps too). Changing the opacity to 50% is one approach that looks quite OK. Converting it grey scale is harder but there is solutions for this too.
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Class="VectorGraphicsDemo.MainWindow"
Title="MainWindow"
Height="350"
Width="616">
<Window.Resources>
<!-- Note: When Expression Designed generated the code it was generated
as DrawingBrush. Remember to change this to DrawingImage. -->
<DrawingImage x:Key="TestImage">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#00FFFFFF"
Geometry="M 0,0L 32,0L 32,32L 0,32L 0,0 Z " />
<GeometryDrawing Brush="#FFFF0000"
Geometry="F1 M 6.25,3.97918L 23.125,3.97918L 23.125,16.1458L 6.25,16.1458L 6.25,3.97918 Z ">
<GeometryDrawing.Pen>
<Pen LineJoin="Round"
Brush="#FF000000" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="#FF00C800"
Geometry="F1 M 21.8542,11.0625C 26.399,11.0625 30.0833,14.7468 30.0833,19.2917C 30.0833,23.8365 26.399,27.5208 21.8542,27.5208C 17.3093,27.5208 13.625,23.8365 13.625,19.2917C 13.625,14.7468 17.3093,11.0625 21.8542,11.0625 Z ">
<GeometryDrawing.Pen>
<Pen LineJoin="Round"
Brush="#FF000000" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFFF00"
Geometry="F1 M 16.8731,14.9035L 11.9668,16.2498L 8.58953,12.5761L 8.25831,17.6042L 3.62852,19.7405L 8.33013,21.5017L 8.84603,26.4958L 12.083,22.5562L 17.0316,23.5064L 14.3306,19.3103L 16.8731,14.9035 Z ">
<GeometryDrawing.Pen>
<Pen LineJoin="Round"
Brush="#FF000000" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="TestThinLineImage">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#00FFFFFF"
Geometry="M 0,0L 32,0L 32,32L 0,32L 0,0 Z " />
<GeometryDrawing Geometry="F1 M 2,2L 30,2L 30,30L 2,30L 2,2 Z ">
<GeometryDrawing.Pen>
<Pen LineJoin="Round"
Brush="#FF000000" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="F1 M 7,8L 25,8L 25,24L 7,24L 7,8 Z ">
<GeometryDrawing.Pen>
<Pen LineJoin="Round"
Brush="#FFFF0000" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Window.Resources>
<Grid>
<!-- Menu with image -->
<Menu HorizontalAlignment="Stretch"
VerticalAlignment="Top">
<MenuItem Header="Hello">
<MenuItem Header="World">
<MenuItem.Icon>
<Image Source="{StaticResource TestImage}" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
<!-- Small standard image -->
<Button HorizontalAlignment="Left"
Margin="12,66,0,0"
VerticalAlignment="Top"
Width="142"
Height="43">
<StackPanel Orientation="Horizontal">
<Image x:Name="imageSmall"
Source="{StaticResource TestImage}"
Height="32"
Width="32" />
<Label VerticalAlignment="Center"
Content="Small image" />
</StackPanel>
</Button>
<!-- Large standard image -->
<Button HorizontalAlignment="Left"
Margin="12,149,0,0"
VerticalAlignment="Top"
Width="142"
Height="75">
<StackPanel Orientation="Horizontal">
<Image x:Name="imageLarge"
Source="{StaticResource TestImage}"
Height="64"
Width="64">
</Image>
<Label VerticalAlignment="Center"
Content="Large image" />
</StackPanel>
</Button>
<!-- Small image with thin line with antialising enabled - looks bad! -->
<Button HorizontalAlignment="Left"
Margin="180,67,0,0"
VerticalAlignment="Top"
Width="177"
Height="43">
<StackPanel Orientation="Horizontal">
<Image x:Name="imageSmall1"
Source="{StaticResource TestThinLineImage}"
Height="32"
Width="32" />
<Label VerticalAlignment="Center"
Content="Small thin anti alias" />
</StackPanel>
</Button>
<!-- Large image with thin line with antialising enabled - looks bad! -->
<Button HorizontalAlignment="Left"
Margin="180,149,0,0"
VerticalAlignment="Top"
Width="177"
Height="75">
<StackPanel Orientation="Horizontal">
<Image Source="{StaticResource TestThinLineImage}"
Height="64"
Width="64">
</Image>
<Label VerticalAlignment="Center"
Content="Large thin anti alias" />
</StackPanel>
</Button>
<!-- Small image with thin line with antialising disabled - looks OK! -->
<Button HorizontalAlignment="Left"
Margin="391,67,0,0"
VerticalAlignment="Top"
Width="177"
Height="43">
<StackPanel Orientation="Horizontal">
<Image SnapsToDevicePixels="True"
RenderOptions.EdgeMode="Aliased"
Source="{StaticResource TestThinLineImage}"
Height="32"
Width="32" />
<Label VerticalAlignment="Center"
Content="Small thin alias" />
</StackPanel>
</Button>
<!-- Large image with thin line with antialising disabled - looks OK! -->
<Button HorizontalAlignment="Left"
SnapsToDevicePixels="True"
RenderOptions.EdgeMode="Aliased"
Margin="391,149,0,0"
VerticalAlignment="Top"
Width="177"
Height="75">
<StackPanel Orientation="Horizontal">
<Image Source="{StaticResource TestThinLineImage}"
Height="64"
Width="64" />
<Label VerticalAlignment="Center"
Content="Large thin alias" />
</StackPanel>
</Button>
</Grid>
If you've got VS2013, you should have Blend. If not, you can add it from Add/Remove Programs, by modifying your Studio installation and checking the box.
Once you've got Blend, you can build vector images using the slightly too basic tools it provides; but more useful is its ability to import Adobe Illustrator files. This is still the benchmark vector graphics application to work in. This is great if you have a designer to build the assets, or have the skills to do that yourself.
If you need something between the basics of Blend and the all-singing Illustrator, Expression Design is a decent enough option (as already mentioned by #pek).

Precise OpacityMask

Suppose I need to set an opacity mask on a WPF control that highlights a portion of it in precise position (suppose a 50x50 square at (50;50) position). To do that I create a DrawingGroup containing 2 GeometryDrawing objects: 1 semi-transparent rectangle for the whole actual size of the control and 1 opaque rectangle for highlighted area. Then I create a DrawingBrush from this DrawingGroup, set it's Stretch property to None and set this brush as OpacityMask of the control that needs to be masked.
All this works fine while nothing is "sticking" out of bounds of said control. But if control draws something outside of it's bounds the outer point becomes a starting point from where opacity mask is applied (if the brush is aligned to that side) and the whole mask shifts by that distance resulting in unexpected behavior.
I can't seem to find a way to force mask to be applied from control's bounds or at least get the actual bounds of the control (including sticking parts) so I can adjust my mask accordingly.
Any ideas highly appreciated!
Update: Here's a simple test-case XAML and screenshots demonstrating the issue:
We have 2 nested Borders and Canvas in the last one with the above mentioned square:
<Border Padding="20" Background="DarkGray" Width="240" Height="240">
<Border Background="LightBlue">
<Canvas>
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="50" Height="50"
Stroke="Red" StrokeThickness="2"
Fill="White"
/>
</Canvas>
</Border>
</Border>
Here's how it looks:
(source: ailon.org)
Now we add an OpacityMask to the second border so that every part of it except our square is semi-transparent:
<Border.OpacityMask>
<DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#30000000">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,200,200" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="50,50,50,50" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.OpacityMask>
Everything looks as expected:
(source: ailon.org)
And now we add a line to the canvas that sticks 10 pixels out on the left of our border:
<Line X1="-10" Y1="150" X2="120" Y2="150"
Stroke="Red" StrokeThickness="2"
/>
And the mask shifts 10 pixels to the left:
(source: ailon.org)
Update2: As a workaround I add a ridiculously large transparent rectangle outside of bounds and adjust my mask accordingly but that is a really nasty workaround.
Update3: Note: The canvas with rectangle and line is there just as an example of some object that has something outside of it bounds. In context of this sample it should be treated as some sort of a black box. You can't change it's properties to solve the general issue. This would be the same as just moving the line so it doesn't stick out.
Interesting issue indeed - here's what I've figured: The effect you are experiencing seems to be determined by the Viewport concept/behavior of TileBrush (see Viewbox too for the complete picture). Apparently the implicit bounding box of a FrameworkElement (i.e. the Canvas in your case) is affected/expanded by elements sticking out of bounds in a subtle way, that is, the dimensions of the box expand but the coordinate system of the box does not scale, rather expands too into the out of bounds direction.
It might be easier to illustrate that graphically, but due to time constraints I'll just offer a solution first and will explain the steps I've taken for the moment in order to get you started:
Solution:
<Border Background="LightBlue" Width="198" Height="198">
<Border.OpacityMask>
<DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center"
Viewport="-10,0,222,202" ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#30000000">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="-10,0,220,200" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Black">...</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.OpacityMask>
<Canvas x:Name="myGrid">...</Canvas>
</Border>
Please note that I've adjusted units by +/- 2 pixels here and there for pixel precision without knowing where the offset originates, but I think this can be ignored for the purpose of the example and resolved later if need be.
Explanation:
To simplify the illustration one should usually make all related implied/auto properties explicit first.
The inner border receives auto dimensions of 198 from the outer border (240 - 20 padding - 2 pixels deduced by experiment; don't know their origin, but ignorable right now), that is if you specify this as follows nothing should change, while using other values yields graphical changes:
<Border Background="LightBlue" Width="198" Height="198">...</Border>
Further the default implied Viewport and ViewportUnits like so:
<DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top"
Viewport="0,0,1,1" ViewportUnits="RelativeToBoundingBox">...</DrawingBrush>
You are enforcing the DrawingBrush size by overriding Stretch with None, while keeping the position and dimension of the base tile at default and relative to its bounding box. In addition you (understandably) are overriding AlignmentX/AlignmentY, which determine the placement within the base tile, that is within its bounding box. Resetting those to their defaults of Center is already telling: The mask shifts accordingly, meaning it has to be smaller than the bounding box, else their would be nothing to center within.
This can be taken further by changing ViewportUnits to Absolute, which will yield no graphics at all until the units are properly adjusted of course; again, by experiment the following explicit values are matching the auto ones, while using other values yields graphical changes:
<DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center"
Viewport="0,0,202,202" ViewportUnits="Absolute">...</DrawingBrush>
Now the opacity mask already aligns properly with the control. Obviously there is one problem left though, as the mask is clipping the line now, which is no surprise given its size and the absence of any Stretch effect. Adjusting its size and position accordingly resolves this:
<RectangleGeometry Rect="-10,0,220,200" />
and
<DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center"
Viewport="-10,0,222,202" ViewportUnits="Absolute">...</DrawingBrush>
Finally the opacity mask matches the control bounds as desired!
Supplement:
The required offsets determined by deduction and experiment in the explanation above can be retrieved at runtime by means of the VisualTreeHelper Class:
Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);
Depending on your visual element composition and needs you may need to factor in the LayoutInformation Class and build the union of both to get the all-encompassing bounding box:
Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);
Rect layoutSlot = LayoutInformation.GetLayoutSlot(myGrid);
Rect boundingBox = descendantBounds;
boundingBox.Union(layoutSlot);
See the following links for more details on both topics:
Windows Presentation Foundation
Graphics Rendering Overview,
especially VisualTreeHelper
Class
The Layout System, especially
Element Bounding Boxes
On your Canvas object add ClipToBounds="True".
<Canvas ClipToBounds="True">
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="50" Height="50"
Stroke="Red" StrokeThickness="2"
Fill="White" />
<Line X1="-10" Y1="150" X2="120" Y2="150"
Stroke="Red" StrokeThickness="2"/>
</Canvas>
One workaround that may be more ideal than your current one would be to simply apply the OpacityMask at a higher level. Using this demo code for example, you could remove the mask from the Border and apply it to the Window instead. With a bit of tweaking it fits properly:
<Window.OpacityMask>
<DrawingBrush AlignmentX="Left" AlignmentY="Top" Stretch="None">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#30000000">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,300,300"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="92,82,50,50"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.OpacityMask>
You would have to write some code to move the mask when the Window is resized, and for that reason you may be better off generating the mask dynamically in the code-behind.
My question for you is, why do you need to handle geometries that go outside the bounds of your Canvas?
Since you have parts that stick out from the control, one idea is to separate control image from the control mask.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Border Padding="20" Background="DarkGray" Width="240" Height="240"> <!-- user container -->
<Grid> <!-- the control -->
<Border Background="LightBlue" HorizontalAlignment="Stretch"> <!-- control mask-->
<Canvas>
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="50" Height="50"
Stroke="Red" StrokeThickness="2"
Fill="White"
/>
<Canvas.OpacityMask>
<DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top" TileMode="None">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#30000000">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,200,200" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="50,50,50,50" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Canvas.OpacityMask>
</Canvas>
</Border>
<Canvas> <!-- control image-->
<Line X1="-10" Y1="150" X2="120" Y2="150" Stroke="Red" StrokeThickness="2"/>
</Canvas>
</Grid>
</Border>
</Window>

Preventing deformation in XAML Grid's background when resizing

I am writing a brush in XAML that I can use to paint the background of a Grid to create a banner. It looks like this:
I want the brush to "stretch" with the Grid when the Window resizes, but I do not want the center angles to deform.
I only need to be able to draw the shapes in the background of a Grid. How can I avoid the deformation?
The code I've written looks like this:
<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="60" Width="300">
<Window.Resources>
<DrawingBrush x:Key="GridBackground">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M0,1 0,0 0.4,0 0.45,0.5 0.4,1Z" Brush="#FF6A00" />
<GeometryDrawing Geometry="M0.6,1 0.55,0.5 0.6,0 1,0 1,1Z" Brush="#FF0000" />
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Resources>
<Grid Background="{StaticResource GridBackground}">
<TextBlock Foreground="White" VerticalAlignment="Center">Some text</TextBlock>
</Grid>
</Window>
I would make it two brushes, one anchored to the right, and one anchored to the left. Something like this:
<Grid>
<GeometryXXX Geometry="M0,1 0,0 0.4,0 0.45,0.5 0.4,1Z" Width="300" HorizontalAlignment="Left" Brush="#FF6A00">
<GeometryXXX Geometry="M0,1 0,0 0.4,0 0.45,0.5 0.4,1Z" Width="300" HorizontalAlignment="Right" Brush="#FF0000">
<TextBlock Foreground="White" VerticalAlignment="Center">Some text</TextBlock>
</Grid>
I don't have my compiler open, and I don't remember the name of the Geometry drawing object.
The other way of doing it would be to create a valueconverter, and do something like:
...
<GeometryDrawing Geometry="{Binding Width, ValueConverter=LeftAngledThing}" Brush="#FF6A00" />
<GeometryDrawing Geometry="{Binding Width, ValueConverter=LeftAngledThing}" Brush="#FF0000" />
...
You would need to look up the exact syntax for how to do this though, as I don't remember it right now.

Resources