Monday, 28 June 2010

ClickOnce does not install fonts - Install fonts using C#

ClickOnce does not install fonts. ClickOnce is designed to be a non-impactful install, and installing fonts is considered impactful to a user's computer. ClickOnce is only a deployment technology, it does not offer any support for font installation, so this operation requires extra coding. We have to code the font installation logic in the application. To install font on client machines, we have to create code just like the following.

ClickOnce deloyment normally does not touch the system directories. This is an important difference with MSI deployment technology. ClickOnce deployment does not cover:
· "Install to GAC"(GAC is another system directory)
· "Write to Registry"
· "Install for All Users" etc... While these can all be done with MSI:

Take a look to "Key Differences" between ClickOnce and MSI:
http://msdn.microsoft.com/en-us/library/142dbbz4(VS.90).aspx

So we have to code ourselves for the font installation, first: write file from embedded resource, second: add font to windows resources, third: add registry entry so the font is also available next session, finally: solve GDI+ bug, where GDI+ must be notified with this new added font, so, a scalable font resource file needs to be created, this scalable font resource file stores the name of a TrueType font file so that GDI knows where to find the file. To create a scalable font resource file, call the GDI function CreateScalableFontResource with an integer flag, the name of the font resource file to be generated, an existing TrueType font file name, and the path to the files if they do not contain a complete path.

First: Special thanks for following threads:

http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic52983.aspx
http://www.daniweb.com/forums/thread231795.html
http://www.dotnetmonster.com/Uwe/Forum.aspx/winform/18215/ClickOnce-deployment-include-font

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Drawing.Text;
using System.Windows.Forms;
namespace myNameSpace

{
class InstallFonts
{
// PInvoke to look up fonts path
[DllImport("shfolder.dll", CharSet = CharSet.Auto)]
private static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken, int dwFlags, StringBuilder lpszPath);

private const int CSIDL_FONTS = 0x0014;
private const int MAX_PATH = 260;
private static string GetFontsPath()
{
StringBuilder sb = new StringBuilder(MAX_PATH);
SHGetFolderPath(IntPtr.Zero, CSIDL_FONTS, IntPtr.Zero, 0, sb);
return sb.ToString();
}

// PInvoke to 'register' fonts and broadcast addition

[DllImport("gdi32.dll")]
private static extern int AddFontResource(string lpszFilename);

[DllImport("gdi32", EntryPoint = "RemoveFontResource")]
private static extern bool RemoveFontResourceW(string lpFileName);

[DllImport("gdi32.dll")]
private static extern int CreateScalableFontResource(uint fdwHidden, string lpszFontRes, string lpszFontFile, string lpszCurrentPath);

private static IntPtr HWND_BROADCAST = new IntPtr(0xffff);
private const uint WM_FONTCHANGE = 0x001D;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

public static void InstallFont()
{

string fontsPath = GetFontsPath();
string ttfFile9 = System.IO.Path.Combine(fontsPath, "V100009_.TTF"); ;
string fotFile9 = System.IO.Path.Combine(fontsPath, "V100009_.FOT"); ;

int ret;
if (!System.IO.File.Exists(ttfFile9))
{
//Write file from embedded resource
System.IO.File.WriteAllBytes(ttfFile9, MyFonts.V100009_);
//Allow GDI+ to be notified and determines the new fonts
//to install a TrueType font, a scalable font resource file needs to be
//created. This scalable font resource file stores the name of a TrueType
//font file so that GDI knows where to find the file. To create a scalable
//font resource file, call the GDI function CreateScalableFontResource with
//an integer flag, the name of the font resource file to be generated, an
//existing TrueType font file name, and the path to the files if they do not
//contain a complete path.
ret = CreateScalableFontResource(0, fotFile9, ttfFile9, String.Empty);
//Add font resource
ret = AddFontResource(fotFile9);
//Add registry entry so the font is also available next session
Registry.SetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts", "C39HrP36DlTt (TrueType)", "V100009_.TTF", RegistryValueKind.String);

//Broadcast to let all top-level windows know about change
ret = SendMessage(HWND_BROADCAST, WM_FONTCHANGE, new IntPtr(0), new IntPtr(0));
//Work around to use font after installing without restarting the installing application
//It is GDI+ bug not .NET bug,
PrivateFontCollection oPFC9 = new PrivateFontCollection();
oPFC9.AddFontFile(ttfFile9);
}
}
}
}

No comments:

Post a Comment