Friday, 28 June 2013

Windows 8.1 RenderTargetBitmap

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).

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.