Using SVG image in WPF project as Canvas background [duplicate] - wpf

Can someone describe a recommended Step by Step procedure for doing this?
Step1. Convert SVG to XAML... thats easy
Step2. Now what?

Your technique will depend on what XAML object your SVG to XAML converter produces. Does it produce a Drawing? An Image? A Grid? A Canvas? A Path? A Geometry? In each case your technique will be different.
In the examples below I will assume you are using your icon on a button, which is the most common scenario, but note that the same techniques will work for any ContentControl.
Using a Drawing as an icon
To use a Drawing, paint an approriately-sized rectangle with a DrawingBrush:
<Button>
<Rectangle Width="100" Height="100">
<Rectangle.Fill>
<DrawingBrush>
<DrawingBrush.Drawing>
<Drawing ... /> <!-- Converted from SVG -->
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</Button>
Using an Image as an icon
An image can be used directly:
<Button>
<Image ... /> <!-- Converted from SVG -->
</Button>
Using a Grid as an icon
A grid can be used directly:
<Button>
<Grid ... /> <!-- Converted from SVG -->
</Button>
Or you can include it in a Viewbox if you need to control the size:
<Button>
<Viewbox ...>
<Grid ... /> <!-- Converted from SVG -->
</Viewbox>
</Button>
Using a Canvas as an icon
This is like using an image or grid, but since a canvas has no fixed size you need to specify the height and width (unless these are already set by the SVG converter):
<Button>
<Canvas Height="100" Width="100"> <!-- Converted from SVG, with additions -->
</Canvas>
</Button>
Using a Path as an icon
You can use a Path, but you must set the stroke or fill explicitly:
<Button>
<Path Stroke="Red" Data="..." /> <!-- Converted from SVG, with additions -->
</Button>
or
<Button>
<Path Fill="Blue" Data="..." /> <!-- Converted from SVG, with additions -->
</Button>
Using a Geometry as an icon
You can use a Path to draw your geometry. If it should be stroked, set the Stroke:
<Button>
<Path Stroke="Red" Width="100" Height="100">
<Path.Data>
<Geometry ... /> <!-- Converted from SVG -->
</Path.Data>
</Path>
</Button>
or if it should be filled, set the Fill:
<Button>
<Path Fill="Blue" Width="100" Height="100">
<Path.Data>
<Geometry ... /> <!-- Converted from SVG -->
</Path.Data>
</Path>
</Button>
How to data bind
If you're doing the SVG -> XAML conversion in code and want the resulting XAML to appear using data binding, use one of the following:
Binding a Drawing:
<Button>
<Rectangle Width="100" Height="100">
<Rectangle.Fill>
<DrawingBrush Drawing="{Binding Drawing, Source={StaticResource ...}}" />
</Rectangle.Fill>
</Rectangle>
</Button>
Binding an Image:
<Button Content="{Binding Image}" />
Binding a Grid:
<Button Content="{Binding Grid}" />
Binding a Grid in a Viewbox:
<Button>
<Viewbox ...>
<ContentPresenter Content="{Binding Grid}" />
</Viewbox>
</Button>
Binding a Canvas:
<Button>
<ContentPresenter Height="100" Width="100" Content="{Binding Canvas}" />
</Button>
Binding a Path:
<Button Content="{Binding Path}" /> <!-- Fill or stroke must be set in code unless set by the SVG converter -->
Binding a Geometry:
<Button>
<Path Width="100" Height="100" Data="{Binding Geometry}" />
</Button>

Install the SharpVectors library
Install-Package SharpVectors
Add the following in XAML
<UserControl xmlns:svgc="http://sharpvectors.codeplex.com/svgc">
<svgc:SvgViewbox Source="/Icons/icon.svg"/>
</UserControl>

Windows 10 build 15063 "Creators Update" natively supports SVG images (though with some gotchas) to UWP/UAP applications targeting Windows 10.
If your application is a WPF app rather than a UWP/UAP, you can still use this API (after jumping through quite a number of hoops): Windows 10 build 17763 "October 2018 Update" introduced the concept of XAML islands (as a "preview" technology but I believe allowed in the app store; in all cases, with Windows 10 build 18362 "May 2019 Update" XAML islands are no longer a preview feature and are fully supported) allowing you to use UWP APIs and controls in your WPF applications.
You need to first add the references to the WinRT APIs, and to use certain Windows 10 APIs that interact with user data or the system (e.g. loading images from disk in a Windows 10 UWP webview or using the toast notification API to show toasts), you also need to associate your WPF application with a package identity, as shown here (immensely easier in Visual Studio 2019). This shouldn't be necessary to use the Windows.UI.Xaml.Media.Imaging.SvgImageSource class, though.
Usage (if you're on UWP or you've followed the directions above and added XAML island support under WPF) is as simple as setting the Source for an <Image /> to the path to the SVG. That is equivalent to using SvgImageSource, as follows:
<Image>
<Image.Source>
<SvgImageSource UriSource="Assets/svg/icon.svg" />
</Image.Source>
</Image>
However, SVG images loaded in this way (via XAML) may load jagged/aliased. One workaround is to specify a RasterizePixelHeight or RasterizePixelWidth value that is double+ your actual height/width:
<SvgImageSource RasterizePixelHeight="300" RasterizePixelWidth="300" UriSource="Assets/svg/icon.svg" /> <!-- presuming actual height or width is under 150 -->
This can be worked around dynamically by creating a new SvgImageSource in the ImageOpened event for the base image:
var svgSource = new SvgImageSource(new Uri("ms-appx://" + Icon));
PrayerIcon.ImageOpened += (s, e) =>
{
var newSource = new SvgImageSource(svgSource.UriSource);
newSource.RasterizePixelHeight = PrayerIcon.DesiredSize.Height * 2;
newSource.RasterizePixelWidth = PrayerIcon.DesiredSize.Width * 2;
PrayerIcon2.Source = newSource;
};
PrayerIcon.Source = svgSource;
The aliasing may be hard to see on non high-dpi screens, but here's an attempt to illustrate it.
This is the result of the code above: an Image that uses the initial SvgImageSource, and a second Image below it that uses the SvgImageSource created in the ImageOpened event:
This is a blown up view of the top image:
Whereas this is a blown-up view of the bottom (antialiased, correct) image:
(you'll need to open the images in a new tab and view at full size to appreciate the difference)

After various searches and attempts I managed to find the method without having to use external libraries.
First you will need to use Inkscape to open the SVG file to prepare, then follow the procedure according to the following list:
Open the SVG file with Inkscape;
Press Ctrl + A to select everything;
Go to Edit > Resize page to selection;
Press Ctrl + C;
Press Ctrl + S then close Inkscape;
Open the SVG file a file editor then go to <path>, you could view several paths. This is an example:
<path d="..." fill="..." id="path2"/>
<path d="..." fill="..." id="path4"/>
<path d="..." fill="..." id="path6"/>
In your XAML file you have to create a ViewBox element, then insert a Grid element and then Path elements for the number of times when in the SVG file see the paths:
<Viewbox Stretch="Fill">
<Grid>
<Path Fill="..." Data="..."/>
<Path Fill="..." Data="..."/>
<Path Fill="..." Data="..."/>
</Grid>
</Viewbox>
Where in Fill property on your XAML you have to insert the fill property in the SVG file and in Data property on your XAML you have to insert the d property in the SVG file.
You should get a result like this:

Option 1: Use SVG icons directly using "SharpVectors" nuget package
Add SharpVectors nuget package to your project.
Add SVG files to your project, for example, in a "Icons" subfolder and set their Build Action property to Resource
Use it in your code:
<Window x:Class="WpfApp.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"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<Button Height="100">
<svgc:SvgViewbox Source="/Icons/Checkmark_16x.svg"/>
</Button>
<ContentControl Height="100">
<svgc:SvgViewbox Source="/Icons/CollapseAll_16x.svg"/>
</ContentControl>
<Label Height="100">
<svgc:SvgViewbox Source="/Icons/Refresh_16x.svg"/>
</Label>
</StackPanel>
</Grid>
</Window>
Option 2: Convert SVG to XAML using "SvgToXaml" tool
SvgToXaml. Download the latest release (this answer was tested with the "Ver_1.3.0")
Place all your SVG icons into a folder and execute the following command:
SvgToXaml.exe BuildDict /inputdir "c:\Icons" /outputdir "c:\MyWpfApp" /outputname IconsDictionary
Add generated IconsDictionary.xaml file to your project and use it in your code:
<Window x:Class="WpfApp.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"
xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="IconsDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel>
<Button Height="100">
<Image Source="{StaticResource Refresh_16xDrawingImage}"/>
</Button>
<ContentControl Height="100">
<Image Source="{StaticResource CollapseAll_16xDrawingImage}"/>
</ContentControl>
<Label Height="100">
<Image Source="{StaticResource Checkmark_16xDrawingImage}"/>
</Label>
</StackPanel>
</Grid>
</Window>
Option 3: Use IValueConverter for some already generated XAML files
If you already have generated XAML files and you want to use them, for some types of them it is possible to create a custom ValueConverter class. Please refer to the following answers for more information:
Option 2: Use .xaml icon files directly
https://stackoverflow.com/a/21588195/7585517

You can use the resulting xaml from the SVG as a drawing brush on a rectangle. Something like this:
<Rectangle>
<Rectangle.Fill>
--- insert the converted xaml's geometry here ---
</Rectangle.Fill>
</Rectangle>

Use the SvgImage or the SvgImageConverter extensions, the SvgImageConverter supports binding.
See the following link for samples demonstrating both extensions.
https://github.com/ElinamLLC/SharpVectors/tree/master/TutorialSamples/ControlSamplesWpf

We can use directly the path's code from the SVG's code:
<Path>
<Path.Data>
<PathGeometry Figures="M52.8,105l-1.9,4.1c ...

Another alternative is dotnetprojects SVGImage
This allows native use of .svg files directly in xaml.
The nice part is, it is only one assembly which is about 100k. In comparision to sharpvectors which is much bigger any many files.
Usage:
...
xmlns:svg1="clr-namespace:SVGImage.SVG;assembly=DotNetProjects.SVGImage"
...
<svg1:SVGImage Name="mySVGImage" Source="/MyDemoApp;component/Resources/MyImage.svg"/>
...
That's all.
See:
https://www.nuget.org/packages/DotNetProjects.SVGImage/
https://github.com/dotnetprojects/SVGImage

I found this tutorial extremely helpful: https://msadowski.github.io/WPF-vector-graphics-tutorial/
Download the zip file for the program from github.
Use program to convert the SVG to XAML.
Copy/paste the .xaml file into your folder of choice in your project.
Add the application resource to App.xaml for your file.
Reference your vector image in your xaml page with Source="{StaticResource }"
The tutorial explains all steps very well with an example shown. I've tried it and my svg image is showing up great in my application now.

Related

WPF UserControl as Polygon

I am very new at this WPF world, i have some experience in the classic Windows Desktop applications.
I am trying to create a custom UserControl polygon shaped.
I have tryied creating a Path Data and then setting the UserControl Opacity property to "0", but it makes transparent the whole UserControl.
For example, i have build this Polygon inside the user control
<UserControl x:Class="WindowsFormsApp2.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480" Opacity="100">
<Grid x:Name="LayoutRoot">
<Path Data="M-70.616296,46.859802 L7.3270039,-1.2587545 174.31959,52.958763 168.71134,98.185567 z" Fill="#FF2121D6" HorizontalAlignment="Left" Height="100" Margin="138,114,0,0" Stretch="Fill" Stroke="Black" Opacity ="100" VerticalAlignment="Top" Width="246"/>
</Grid>
As you can see the user control is 640x480 so when i add code to the UserControl_MouseLeftButtonDown event, it fires if clicking in ay position inside the 640x480 while i want only to fire when clicking inside the polygon.
I have been googling for a solution but i can't found any solution, ¿maybe what i want it's not possible?
You may template a UserControl to look like a polygon:
<UserControl x:Class="WpfApp1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Path Data="M-70.616296,46.859802 L7.3270039,-1.2587545 174.31959,52.958763 168.71134,98.185567 z"
Fill="#FF2121D6" HorizontalAlignment="Left" Height="100" Margin="138,114,0,0"
Stretch="Fill" Stroke="Black" Opacity ="100" VerticalAlignment="Top" Width="246"/>
</ControlTemplate>
</UserControl.Template>
</UserControl>
Or you may just get rid of the UserControl and use the Path element directly. It also has a MouseLeftButtonDown event that you can handle.

How to share vector icon while being able to change Fill brush

In .Net 4.6, I use shared vector icons implemented as Path in Canvas in a global xaml like this:
<ResourceDictionary ...>
<Canvas x:Key="mSearchCanvas" x:Shared="False" Width="24" Height="24">
<Path Data="M9.5,3A6.5,6.5 0 0,1 16,9.5..." Fill="Black" />
</Canvas>
</ResourceDictionary
I can then reuse these icons in several applications using:
<Viewbox Width="16" Height="16" Child="{StaticResource mPencilCanvas}"/>
It works well but I'd like to use the same icon/Canvas with different Fill brushes.
Is there a simple way to override Fill property with new value?

Setting Canvas to a control template?

I have a Canvas in a ResourceDictionary xaml file like so:
<Canvas x:Key="Icon">
<Path ... />
<Path ... />
</Canvas>
In my code-behind I load this icon using
LayoutGrid.Children.Add(FindResource("Icon") as Canvas);
And this works fine. Now I want to create a button that uses the same icon as a template. So I create a control template:
<ControlTemplate x:Key="IconTemplate">
...
</ControlTemplate>
Now here's the problem: How would I put the "Icon" resource canvas into the control template? As far as I know, Canvas does not have a Style or Template property. It has a Children property, but it's not accessible via XAML. How would I go about using my canvas in a template?
When you create a type such as canvas as a resource, then you are creating ONE instance of the type. This means that you cannot place that resource in more than one location in your application (an element can only be in one place at a time). You should look at using control templates, I think.
You don't need any code behind for this.
Something like this:
<ControlTemplate x:Key="Icon">
<Canvas>
<Path ... />
<Path ... />
</Canvas>
</ControlTemplate>
Then elsewhere you do something like this:
<Button>
<Control Template="{StaticResource Icon}" />
</Button>
This constructs a regular looking button with your icon as it's content. The content of that button is your icon.
If, however, you want to completely redefine the template of your button, then you would do so
<ControlTemplate x:Key="Icon" TargetType="Button">
<Canvas>
<Path ... />
<Path ... />
</Canvas>
</ControlTemplate>
Then elsewhere you do something like this:
<Button Template="{StaticResource Icon}" />
Note that this isn't a great style for a button. Look at this example from Microsoft for an example of a more fully featured button template.
EDIT
Unless you have a ContentPresenter in your ControlTemplate, then there's no need to assign the template to a content control. Note that any class derived from Control can be templated, including Control itself. So in order to place an item into your view, then you can just use:
<Control Template="{StaticResource Icon}" />
This uses the widest applicable type in the hierarchy, which is also the lightest.
A good way to do define an icon for a button is to use a DrawingBrush and set it as the fill of a Rectangle that you embed in the Button:
<Button>
<Rectangle
Width="32"
Height="32"
Fill={Background myDrawingBrush}
/>
</Button>
myDrawingBrush must be defined in resources like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options">
<DrawingBrush x:Key="dialogerror" Stretch="Uniform">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing>
... define geometry here ...
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</ResourceDictionary>

ProgressBar in Silverlight Custom Splash Screen

How does one embed the Silverlight ProgressBar control in a a custom splash screen? I put the following into loose xaml (simplified for brevity):
<Grid xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ProgressBar />
</Grid>
The error message is as follows:
Error: Unhandled Error in Silverlight Application
Code: 2007
Category: ParserError
Message: Unknown element: ProgressBar.
etc
Isn't the ProgressBar a standard control defined in the http://schemas.microsoft.com/winfx/2006/xaml/presentation namespace?
I'm assuming SL2 or SL3 since the presence of a progress bar - it got added in SL2.
The /winfx/2006/xaml/presentation name space isn't declared - but certainly in SL2/3 that is where it resides. You have used the alternative http://schemas.microsoft.com/client/2007, this is a legacy namespace for SL1.0
The code given will work, but to reproduce I have to have the root element as a user control and the namespaces have to reside in there, otherwise the usercontrol tag itself is not recognised.
<UserControl x:Class="myApp.scratchpad"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<ProgressBar />
</Grid>
</UserControl>
Worked fine in SL3.
Can you post more of the page, root element, update the reference to the SL2/3 namespace and also say which verison of SL is being used?
If this is for the SplashScreenSource property of the object tag, then the XAML must be Silverlight 1+JavaScript (non-managed XAML) Here is how you would do that: (Taken from the Silverlight SDK here)
<Canvas Background="Black" Width="302" Height="52">
<Rectangle Canvas.Left="1" Canvas.Top="1" Width="300" Height="30" Fill="White"/>
<Rectangle x:Name="progressBarFill" Canvas.Left="1" Canvas.Top="1" Height="30" Fill="Blue"/>
</Canvas>
And then use a JS function to update the progress:
function onProgressChanged(sender, eventArgs)
{
var slPlugin = sender.getHost();
slPlugin.content.findName("progressBarFill").width = eventArgs.progress * 300;
}

How to store and retrieve multiple shapes in XAML/WPF?

Seem to be having a lot of problems doing what should be simple things with XAML / WPF - I have created some XAML-based images using shapes like Rectangle and Ellipse to create icons which I need other parts of my application to use - but I cannot seem to find out how to do this - I seem to be able to store a Canvas in the Resource Dictionary but no way of using it in any other Window. How is this done - these are simple images just two or three shapes I want to use throughout my project!
The images must also be resizable - I know how to store paths, however these shapes contain gradient styles I want preserved plus I don't know how the rectangle could convert to path and colour data.
Thanks!
You should use a Drawing and display it using a DrawingBrush like KP Adrian suggested or a DrawingImage and an Image control, but if you can't use a drawing you can use a Canvas inside a VisualBrush.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<VisualBrush x:Key="Icon">
<VisualBrush.Visual>
<Canvas Width="10" Height="10">
<Ellipse Width="5" Height="5" Fill="Red"/>
<Ellipse Width="5" Height="5" Fill="Blue" Canvas.Left="5" Canvas.Top="5"/>
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</Page.Resources>
<Rectangle Width="100" Height="100" Fill="{StaticResource Icon}"/>
</Page>
You don't want to be using a Canvas to store these resources in a Resource Dictionary. The root of your geometry is probably something like a DrawingBrush (especially if you used Expression Design to create the images), and those are the items that would need to be added to a Resource Dictionary like so:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DrawingBrush x:Key="YourResourceKey">
<DrawingBrush.Drawing>
<DrawingGroup>
<!-- This can change a lot, but a typical XAML file exported from a Design image would have the geometry of the image here as a bunch of Paths or GeometryDrawings -->
</DrawingGroup>
</DrawingBrush.Drawing>
</ResourceDictionary>
I'll assume you know how to get this Resource Dictionary referenced in your application.
To use the Resources, you simply would assign them to the appropriate properties. For shape-type images, you can assign them to something like the Fill property of a Rectangle (there are plenty of other ways, but this is a simple one). Here's one example:
<Button>
<Grid>
<Rectangle Fill="{StaticResource YourResourceKey}" />
</Grid>
</Button>

Resources