RenderTargetBitmap is a new feature in Windows 8.1 which allows XAML trees to be rendered to Bitmap:
http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.imaging.rendertargetbitmap.aspx
I've previously written 2 articles about using Direct2D to render 2D drawings in Windows 8 apps and export them to file, which prior to 8.1 was the only way of doing this.
http://geoffwebbercross.blogspot.co.uk/2012/09/exporting-image-from-directx-2d-image.html
http://geoffwebbercross.blogspot.co.uk/2012/09/exporting-image-from-directx-2d-image_15.html
This is how to render the tree using RenderTargetBitmap:
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(myElementTree);
myImage.Source = renderTargetBitmap;
And then output to file:
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.Render(myElementTree);
var pixels = await renderTargetBitmap.GetPixelsAsync();
var picker = new FileSavePicker();
// Picker setup
var file = await picker.PickSaveFileAsync();
// File validation
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite)
{
var encoder = await
BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Rgba8, 100, 0, 96, 96,
await renderTargetBitmap.GetPixelsAsync());
await encoder.FlushAsync();
}
[Samples from here]
This opens up a lot of imaging opportunities for developers wanting to do imaging in apps who don't want to get into Direct2D.
Direct2D however, still has benefits like built-in effects, opacity mask capability (8.1 still doesn't seem to have this but I may be wrong).
Geoff Webber-Cross - .Net, Windows 8, Silverlight and WP developer. Software problem solver. My Website Obelisk - WP7 MVVM Tombstone Library
Friday, 28 June 2013
Using WiX Heat to Harvest Files for a WiX Installer
Overview
Manually adding files to a WiX installer can be fiddly and
error prone. If you have a product with a large number of assemblies you will
not want to type each file element manually and even if you have a small number
of assemblies, there’s a chance you may add an extra one in the future and
forget to add it.
Heat is the harvesting tool for WiX which can take care of
this task by examining a directory and writing the contents to a fragment which
can be incorporated into the WiX setup project.
Getting Heat Running
Heat can be run from the command line, but I like to be able
to build the MSI in Visual Studio while I’m testing and in a single step during
the build process. To do this some work needs to be done on the project
properties in Visual Studio and manual modification to the wixproj file.
To start we need to tell Heat where to get the files from.
To do this we need to define a pre-processor variable:
Be careful to set this for each build configuration you use.
Next we need to manually modify the wixproj file (I normally
use notepad or notepad++). In an on-touched project, the last block with the
Targets in is commented-out:
<!--
To modify your build process, add your task
inside one of the targets below and uncomment it.
Other similar extension points exist, see
Wix.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
We need to uncomment this block and add a HeatDirectory
element containing all the settings for harvesting our files to the BeforeBuild
target so that the files are ready for the build. This link to the
documentation details what each attribute does: http://wix.sourceforge.net/manual-wix3/msbuild_task_reference_heatdirectory.htm
<Target Name="BeforeBuild">
<HeatDirectory
NoLogo="$(HarvestDirectoryNoLogo)"
SuppressAllWarnings="$(HarvestDirectorySuppressAllWarnings)"
SuppressSpecificWarnings="$(HarvestDirectorySuppressSpecificWarnings)
"
ToolPath="$(WixToolPath)"
TreatWarningsAsErrors="$(HarvestDirectoryTreatWarningsAsErrors)"
TreatSpecificWarningsAsErrors="$(HarvestDirectoryTreatSpecificWarningsAsErrors)"
VerboseOutput="$(HarvestDirectoryVerboseOutput)"
AutogenerateGuids="$(HarvestDirectoryAutogenerateGuids)"
GenerateGuidsNow="$(HarvestDirectoryGenerateGuidsNow)"
OutputFile="Components.wxs"
SuppressFragments="$(HarvestDirectorySuppressFragments)"
SuppressUniqueIds="$(HarvestDirectorySuppressUniqueIds)"
Transforms="%(HarvestDirectory.Transforms)"
Directory="$(ProjectDir)Harvest"
ComponentGroupName="C_CommonAssemblies"
DirectoryRefId="INSTALLLOCATION"
KeepEmptyDirectories="false"
PreprocessorVariable="var.SourceDir"
SuppressCom="%(HarvestDirectory.SuppressCom)"
SuppressRootDirectory="true"
SuppressRegistry="%(HarvestDirectory.SuppressRegistry)">
</HeatDirectory>
</Target>
<Target Name="AfterBuild">
</Target>
I’ve highlighted the important bits which need filling in
(for this scenatio):
- Output File – This is the name of the file where the generated fragment goes
- Directory – This is the name of the directory where the file lives which will be written into the fragment
- ComponentGroupName – Because heat generates a fragment with each file having it’s own component, it needs a component group name so they can all be associated and added to a feature.
- DirectoryRefId – This is where in the package directory structure the files go.
- PreprocessorVariable – This is where the variable declared in Visual Studio is linked.
- SuppressRootDirectory – This is needed to stop heat creating a directory element in the fragment
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="Manufacturer" Name="My Company">
<Directory Id="INSTALLLOCATION" Name="My Product" />
</Directory>
</Directory>
</Directory>
</Fragment>
Now, once the project is built, the output file should be
created and can be included in the project. It should look like this (most chopped
out):
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLLOCATION">
<Component Id="cmp7BCDFB601A22996A1076B03EFE0C90F0" Guid="*">
<File Id="fil9009CA87455BA8E51C93294588671D49" KeyPath="yes" Source="$(var.SourceDir)\MyApplication.exe" />
</Component>
<Component Id="cmpD21327D3291243C5AF8B30B1C2B0D068" Guid="*">
<File Id="fil6A58F542D5E381F253E6964ADC0BE44B" KeyPath="yes" Source="$(var.SourceDir)\Common.dll" />
</Component>
</Directory>
</DirectoryRef>
</Fragment>
<Fragment>
<ComponentGroup Id="C_CommonAssemblies">
<ComponentRef Id="cmp7BCDFB601A22996A1076B03EFE0C90F0" />
<ComponentRef Id="cmpD21327D3291243C5AF8B30B1C2B0D068" />
</ComponentGroup>
</Fragment>
</Wix>
Everything is wrapped in a DirectoryRef so it can be hooked
into the main directory structure. Each file had a component and the components
are linked together in a ComponentGroup element at the end.
Adding the ComonentGroup
Now that we have all the components for the files in a
component group, the group needs adding to the required feature (with other
components required):
<Feature Id="F_FullApplication" Title="Full Application" Level="1" Description="All Services" ConfigurableDirectory="INSTALLLOCATION">
<ComponentGroupRef Id="C_CommonAssemblies" />
<ComponentRef Id="C_OtherComponent" />
</Feature>
Now the installer should build with all the required files
included.
Harvesting from a Website
The simplest thing to harvest from is an executable
application as all the binaries are usually in a bin folder. Websites are
different in that they need publishing so that all the required files and
binaries are structured correctly in one place.
To do this manually in Visual Studio, go to Build ->
Publish and setup a File System Publish:
Once published, harvest can be set to the published
directory to collect all the files.
To do this from a command line for a build process, the
following msbuild command can be performed on the web project I took out the
stong naming bit for clarity):
msbuild WebProject.Web.csproj" /t:Clean;Build;_WPPCopyWebApplication
/p:Configuration=Release;WebProjectOutputDir="WebProject.Website"
/clp:summary
Harvesting from Multiple Sources to Single Fragment
If you need to harvest from multiple sources (we had 3
services which we wanted to run from one folder with mostly common assemblies)
you can simply add a script before the harvest step to copy the sources into
one directory. This can’t be done in a pre-build event unfortunately because
this happens after the Target BeforeBuild, so we can put in an exec task like
this:
<Target Name="BeforeBuild">
<Exec Command="
REM
Copy all service build folders into one for harvesting
if
exist $(ProjectDir)Harvest rd $(ProjectDir)Harvest /s /q
md
$(ProjectDir)Harvest
xcopy
/s /i /q /y /c /d $(SolutionDir)App1\bin\$(ConfigurationName)
$(ProjectDir)Harvest\
xcopy
/s /i /q /y /c /d $(SolutionDir)App2\bin\$(ConfigurationName)
$(ProjectDir)Harvest\
xcopy
/s /i /q /y /c /d $(SolutionDir)IridiumManagerService\bin\$(ConfigurationName)
$(ProjectDir)Harvest\">
</Exec>
<HeatDirectory [as before]
</HeatDirectory>
</Target>
The files can then be harvested from the Harvest folder
under the setup project directory.
Removing exe files from the harvested fragment
If you need to manually write a component for the
executables which you will if you want to add ProgId and RegistryValue elements
for file associations etc, or you need to setup a service component, it is
necessary to strip out the exe files from the fragment heat generates.
To do this a transform can be added to the HeatDirectory
block to transform the output. Add a file to the project called Transorm.xsl
and enter that name into the Transform attribute. The file should look like
this:
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi">
<!-- Copy all attributes and elements to the
output. -->
<xsl:template match="@*|*">
<xsl:copy>
<xsl:apply-templates select="@*" />
<xsl:apply-templates select="*" />
</xsl:copy>
</xsl:template>
<xsl:output method="xml" indent="yes" />
<xsl:key name="exe-search" match="wix:Component[contains(wix:File/@Source,
'.exe')]" use="@Id" />
<xsl:template match="wix:Component[key('exe-search', @Id)]" />
<xsl:template match="wix:ComponentRef[key('exe-search', @Id)]" />
</xsl:stylesheet>
Conclusion
I hope all or some of this is helpful, it took a long time
to work out a lot of this stuff from reading various sources.
Wednesday, 19 June 2013
Deleting Images from Storage Displayed in XAML in Windows 8
Overview
It's possible to display images from storage in XAML using the following URI syntax:Property:
public string
StorageName { get { return "ms-appdata:///local/images/image.jpg"; } }
XAML:
<Image Margin="5" Width="50" Height="50" Source="{Binding
StorageName}" />
This can be handy in various scenarios, for me it was to keep a scaled-down copy of a users image for efficiency and to make sure it wasn't deleted.
This is fine but a problem can occur if you want to delete the image when it is displayed on the UI as you get a "System.UnautorizedAccessException" like this:
Solution
I couldn't find a way of stopping this exactly when I wanted to delete the image, so I decided to keep a note of images to delete, then delete them when the application launched and the images were no longer in use.NoteImageForDeletion
Call this when you want to note an image for deletion
private const string NOTED_FOR_DELETION = "NOTED_FOR_DELETION";
public static void NoteImageForDeletion(string name)
{
try
{
List<string> noted = new List<string>();
if (ApplicationData.Current.LocalSettings.Values.ContainsKey(NOTED_FOR_DELETION))
{
noted = new List<string>((string[])ApplicationData.Current.LocalSettings.Values[NOTED_FOR_DELETION]);
}
noted.Add(name);
ApplicationData.Current.LocalSettings.Values[NOTED_FOR_DELETION]
= noted.ToArray();
}
catch (Exception)
{
}
}
DeleteNotedImages
Call this when the app starts
public async static Task DeleteNotedImages()
{
List<string> noted = new List<string>();
if (ApplicationData.Current.LocalSettings.Values.ContainsKey(NOTED_FOR_DELETION))
{
noted = new List<string>((string[])ApplicationData.Current.LocalSettings.Values[NOTED_FOR_DELETION]);
}
foreach (var n in noted)
await DeleteImage(n);
}
public static async Task DeleteImage(string name)
{
try
{
var folder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("images", CreationCollisionOption.OpenIfExists);
StorageFile file = await
folder.GetFileAsync(name);
await file.DeleteAsync();
}
catch (FileNotFoundException)
{
}
}
Conclusion
This is a work-around, but a good way of decoupling the image deletion process from the UI.
Subscribe to:
Posts (Atom)