As a lot of WPF developer, I love the free Metro Studio from Synchrosoft. Most of my WPF projects, professional or not, use it at a moment or another. But, out of the box, the integration between Metro Studio and Visual Studio is not great. At the end of this article you’ll be able to regenerate dynamically your resource file on project build. Also you’ll be able to do this on a build server as we don’t need Metro Studio to be installed on the build machine.
The issue
Here’s a usual situation:
- Create a project in Metro Studio and pick icons.
- Configure some basic styling stuff.
- Copy paste the generated output to visual studio.
- Adapt the templates to match the needs or just extract the path information
- Tweak the styling.
There’s a lot of issues when working that way. If you want theming, if you want to add/remove/update icons, if you want to change the overall style… The generate xaml feature of MetroStudio is nice but it’s a real trap too. In fact, the only useful information from MetroStudio are the path. So our goal will be to retrieve them and manage on our side all the styling.
The solution
First, let’s take a look at how Metro Studio manage his project. Here’s a “.metrop” xml structure :
<IconProject Version="2.0" Name="LogiX"> <Icon Name="AddApplicationPath" GroupName="Office" Data="M21.686602,15.029519C20.406802,... And so on" HasCharacterMap="false" IsDirty="false" ExportCommand="MetroGraphicsPackage.IconCommand"> <Settings FontFamily="Webdings" Character=">" IconBrush="#FFFFFFFF" FlipCommand="MetroGraphicsPackage.IconCommand" FlipX="1" FlipY="1" ContentHeight="26" ContentWidth="26" SizeIndex="4" MainWidth="48" Angle="0" MainHeight="48" Padding="11" CustomSize="48" MaximumPadding="15" BackgroundBrush="#FF67C2EA" IconShape="Square" IsBackgroundVisible="true" /> </Icon> </IconProject>
A metrop file is a basic xml file containing all the informations about an icon. Including the path data.
To retrieve them and produce a resource dictionary with them decided to use XSLT.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0"> <xsl:template match="/"> <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"> <xsl:for-each select="/IconProject/Icon"> <system:String> <xsl:attribute name="x:Key"> <xsl:value-of select="@Name"/> </xsl:attribute> <xsl:value-of select="@Data" /> </system:String> </xsl:for-each> </ResourceDictionary> </xsl:template> </xsl:stylesheet>
The path is stored in a string object, much more flexible than in a path. If you want to generate entire controls or use other attribute from the metrop file, you just need to adapt the xslt file. But you should be careful IMHO. If you decide to generate more complex code with the xslt you’ll need someone to maintain it too in the future. As it’s a WPF application the other persons working on the project will be able to maintain the styles using the path in string but not always able to maintain complex XLST sheets. Also it can be hard to debug and the debug can quickly require additional tools like Oxygen XML Developer or similar.
To apply the stylesheet to the metrop file I use msxsl.exe, a lightweight (25kb) tool from Microsoft allowing you to apply xslt to xml file. And to call the tool I simply add the following prebuild event line to execute it :
"$(ProjectDir)/Resources/Tools/msxsl.exe" "$(ProjectDir)/Resources/Icons/LogiX.metrop" "$(ProjectDir)/Resources/Icons/Generate.xslt" -o "$(ProjectDir)/Resources/Icons/IconsPath.xaml"
In a generic way :
"<Path to msxsl.exe>" "<Metrop file path>" "<Xslt file path>" -o "<Output xaml file>"
Now, everytime you do modification in metro studio you’ll just need to rebuild the project.
Also don’t forget to include your output xaml file in your solution. For my part I usually also include the metrop file and add the generated xaml as a dependent generated item. To do this simply edit your csproj file like the following. For the parent metrop file, just include it as none action (in my case located in “Resources\Icons\LogiX.metrop”):
<None Include="Resources\Icons\LogiX.metrop" />
And here’s the entry for the generated xaml page, changes are in Bold. Basically we say to visual studio that this is an autogenerated item and the parent is LogiX.metrop :
<Page Include="Resources\Icons\IconsPath.xaml"> <SubType>Designer</SubType> <AutoGen>True</AutoGen> <DependentUpon>LogiX.metrop</DependentUpon> <Generator>MSBuild:Compile</Generator> </Page>
And here’s the result in the solution explorer:
Also, to complete this easy integration, you can add metrop studio as a custom editor for the metrop files, allowing you to directly open the project from visual studio. Right click on the metrop file, “Open With…”, “Add”, browse MetroStudio executable, press “Ok”, now select the newly created item in the list and press “Set as default” and then Ok, now when you double click metrop files they’re opened with MetroStudio.
Example
Here’s a template for a button. Basically the button is filled with the icon. To use it, you’ll need to set the style and the path as the content of the button.
<Style TargetType="{x:Type Button}" x:Key="MetroIconButtonStyle"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Viewbox> <Grid Width="48" Height="48"> <Grid> <Rectangle Fill="{DynamicResource IconBackground}" x:Name="bg"/> </Grid> <ContentPresenter x:Name="contentPresenter" Width="30" Height="30" /> </Grid> </Viewbox> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="contentPresenter" Property="Opacity" Value="1" /> <Setter TargetName="bg" Property="Opacity" Value=".7" /> <Setter TargetName="bg" Property="Fill" Value="{DynamicResource IconBackgroundOver}" /> </Trigger> <Trigger Property="IsMouseOver" Value="False"> <Setter TargetName="contentPresenter" Property="Opacity" Value=".7" /> <Setter TargetName="bg" Property="Opacity" Value="1" /> <Setter TargetName="bg" Property="Fill" Value="{DynamicResource IconBackground}" /> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="contentPresenter" Property="Opacity" Value="1" /> <Setter TargetName="bg" Property="Opacity" Value="1" /> <Setter TargetName="bg" Property="Fill" Value="{DynamicResource IconBackgroundPressed}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <Path Data="{Binding}" Stretch="Uniform" Fill="{DynamicResource IconForeground}" RenderTransformOrigin="0.5,0.5" /> </DataTemplate> </Setter.Value> </Setter> </Style>
Usage
<Button Content="{DynamicResource AddApplicationIconPath}" Style="{StaticResource MetroIconButtonStyle}" />
Once it’s done…
Possible improvement would be to create a real MSBuild custom Task and integrate it to be able to use mscsl.exe as a custom generate build instead of a pre build event, but I’m not sure about the real benefit of the solution compared to the actual one, does it worth the price? It’s up to you J
That’s all! If you have questions to not hesitate to contact me.