Registering font at runtime under Windows XP

S

Sylvain Audet

Hello!
We have a Windows application that is using Crystal Reports reports
containing Barcode fonts. Those reports are called through reflection into
a Crystal Report Viewer and we need to have the Barcode fonts to be
self-installed at runtime whenever a report is viewed.

I tried the following steps:


Public Shared Sub InstallFonts()
Dim fi As System.IO.FileInfo
Try
Dim res As Integer
fi = New System.IO.FileInfo(\\multi316\oasys\Reports\REQUIRED
FONTS\3of9.ttf)
Dim fiTarget As New System.IO.FileInfo("C:\Windows\Fonts\3OF9.TTF")
If fiTarget.Exists Then Return
' copy the font
fi.CopyTo("C:\Windows\Fonts\3OF9.TTF")

' add the font
res = CreateScalableFontResource(0, "c:\windows\Temp\3OF9.FOT",
"c:\windows\fonts\3OF9.TTF", String.Empty)

res = AddFontResource("C:\Windows\Temp\3OF9.FOT")

If res > 0 Then
' alert all windows that a font was added
SendMessage(New System.IntPtr(HWND_BROADCAST), WM_FONTCHANGE, 0,
Nothing)
End If
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub

Everything looks to be ok as my .FOT file is created and both
AddFontResource and CreateScalableFontResource have a return value of 1
meaning the font was correctly added to the font collection.

The problem I am having is with the final part, the line below keep
executing forever:
SendMessage(New System.IntPtr(HWND_BROADCAST), WM_FONTCHANGE, 0,
Nothing)

I also tried different version like :
SendMessage(Me.Handle, WM_FONTCHANGE, 0, Nothing)
and
SendMessage(CrystalReportViewer1.Handle, WM_FONTCHANGE, 0, Nothing)
But with both lines, GetLastError return error "126 - The specified module
could not be found"

Any help would be very appreciated.

Thanks,
 
P

Peter Huang

Hi Sylvain,
The problem I am having is with the final part, the line below keep
executing forever:
SendMessage(New System.IntPtr(HWND_BROADCAST), WM_FONTCHANGE, 0,
Nothing)

What do you mean by executing forever?
Do you mean when you press F5 to run your project, and the code stop at the
code line and did not run through, also your application stops responding?

If you I think you may try the code below.
Please try to declare the SendMessage API as below as try my code.
(The sample runs smoothly on my side, I create a new winform application
and add a button onto the form)


Private Declare Function CreateScalableFontResource Lib "gdi32" Alias
"CreateScalableFontResourceA" (ByVal fHidden As Integer, ByVal
lpszResourceFile As String, ByVal lpszFontFile As String, ByVal
lpszCurrentPath As String) As Integer

Private Declare Function AddFontResource Lib "gdi32" Alias
"AddFontResourceA" (ByVal lpFileName As String) As Integer

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA"
(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer,
ByVal lParam As Integer) As Integer

Private Const WM_FONTCHANGE = &H1D

Private Const HWND_BROADCAST = &HFFFF&

Public Shared Sub InstallFonts()
Dim fi As System.IO.FileInfo
Try
Dim res As Integer
fi = New System.IO.FileInfo("c:\tunga.ttf")
Dim fiTarget As New
System.IO.FileInfo("C:\Windows\Fonts\tunga.ttf")
If fiTarget.Exists Then Return
' copy the font
fi.CopyTo("C:\Windows\Fonts\tunga.ttf")
' add the font
res = CreateScalableFontResource(0, "c:\tunga.fot",
"c:\windows\fonts\tunga.ttf", String.Empty)
Dim rt As Integer =
System.Runtime.InteropServices.Marshal.GetLastWin32Error()
res = AddFontResource("C:\tunga.FOT")
If res > 0 Then
' alert all windows that a font was added
SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0)
End If
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
InstallFonts()
End Sub

You may try my suggestion and let me know the result.

Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

Sylvain Audet

Hi Peter,
Thanks for your reply. By "...keep executing forever", I meant the
application stop responding, like you said. I will try your code and get
back to you with results.

Regards,
 
S

Sylvain Audet

Peter,
I tested out your code and it's getting me a bit further. This time the
application does not freeze at the SendMessage(...) line but after this line
execution, GetLastError returns :
Error number 126: The specified module could not be found.

To simplify things a bit, I created a new form with only a label using
my font and still no luck. But the label does show if I start the
application once the font is already installed within \windows\fonts.

Any idea?
 
P

Peter Huang

Hi Sylvain,

Where do you call the GetLastWin32Error()

System.Runtime.InteropServices.Marshal.GetLastWin32Error()
Have you tried to call the GetLastWin32Error after the code line below.
SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0)

Because the return value of GetLastWin32Error will change every time you
call an API.

Returns the error code returned by the last unmanaged function called using
platform invoke that has the DllImportAttribute.SetLastError flag set.

Marshal.GetLastWin32Error Method
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/
frlrfSystemRuntimeInteropServicesMarshalClassGetLastWin32ErrorTopic.asp

Since I can not reproduce the problem, can you send the simpe reproduce
project to me by removing the "online" from my email address.


Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

Sylvain Audet

Hi Peter,
My GetLastError was in my watch window, so I had the value for every
line. I will sure send my project to you later on this morning.

Thanks for your help,
 
S

Sylvain Audet

Peter,
I tried to run the C++ application that you've sent me but
unfortunately, I still experience the same problem. I ran the application
on 4 different computers (one that was a brand new installation using XP, 2
running XP and 1 thinkpad with Windows 2000 on it).

One thing I noticed though is that the opened windows never get the
updated font until I go to the C:\Windows\Fonts directory using windows
explorer to refresh the list and then restart my application. It is doing
the same thing with my own code in VB.NET.

Any idea?
 
P

Peter Huang

Hi Sylvain,

Thanks for your quickly reply!

The behavior is somewhat strange.

Do you mean you still get the 126 error when you call the sendmessage with
the VC++ application I sent to you?

To troubleshoot the problem, please try to follow the steps below.
1. Make sure the 3of9.ttf was not installed.
2. copy the font to c:\windows\temp\3of9.ttf for test.
3. open an notepad.exe application
[NOTE: spy++ will ship with VS6 and VS.NET]
4. open the spy++ utility and Press Ctrl+M to open the Log message dialog,
Drag the find tool drop onto the notepad window(the whole window of notepad
app) so that the spy++ will capture the notepad app, click OK, the spy++
will not log the message for notepad.
5. minimized the notepad, and clear the message log for notepad by pressing
"del" key
6. run the VC++ app I sent to you, click Install Font
7. What result did you get after the four steps.
CopyFile
CreateScalableFontResource
AddFontResource
SendMessage

If all is OK you may get the "***** Succeed", or you will the detailed
error number.

From you description I assume you have got the first three "***** Succeed".
Then did you still get the 126 error after SendMessage, or other error,
please post here.

If all things OK. You will get SendMessage Succeed.
And in the spy++ utility window, you will find WM_FONTCHANGE message, which
means that the notepad has got the message.
Stop the Spy++ by pressing F8, restore the notepad window we minimized just
now and we will find that the font 3 of 9 Barcode can be used now.


Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
P

Peter Huang

Hi Sylvain,

Thanks for posting in the community.

Did my test steps help you?
If you still have any question on this problem , please feel free to post
here.

Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

Sylvain Audet

Hi Peter,
I ran the tests you asked me to do and SPY++ reported that the notepad
application (along with my own application - the one having a label using my
font) are receiving the following messages:

- When copying the file to \windows\fonts -> generated 2 "DEVICE CHANGE"
messages
- SendMessage generated 2 messages "WM_FONTCHANGE"
- all other actions did not generate any messages in SPY++ but I did get the
SUCCEED message for every steps

Yes the font is available within notepad (by selecting Format->Font) but it
is still not available to my own application until I close it, refresh the
\windows\fonts directory and the reopen it. Weird heh!?

What do you suggest next?

Thanks,
 
P

Peter Huang

Hi Sylvain,

Thanks for your quickly reply!

From your description, I assume the sendmessage should work on your side(
the NOTEPAD will get the WM_FONTCHANGE message).

So I think, in your senario, you have a windows form application which host
a crystal report viewer control and you want the crystal report viewer
control to get the WM_FONTCHANGE message.

From the msdn,
[in] Handle to the window whose window procedure will receive the message.
If this parameter is HWND_BROADCAST, the message is sent to all top-level
windows in the system, including disabled or invisible unowned windows,
overlapped windows, and pop-up windows; but the message is not sent to
child windows.

SendMessage Function
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui
/windowsuserinterface/windowing/messagesandmessagequeues/messagesandmessageq
ueuesreference/messagesandmessagequeuesfunctions/sendmessage.asp

We know when you sendmessage to the HWND_BROADCAST, all the toplevel window
will get the message, but not the control as a child window(e.g. crystal
report viewer control ) will get the message.

That's why the notepad will get the WM_FONTCHANGE message in both your
and my test. And I assume the main form of the windows application should
get the message WM_FONTCHANGE. Because they are all toplevel windows.

Also we can sendmessage to the handle of crystal report viewer control, but
the message will not be send to the control's child windows by default. If
you need to do so, I think you need to transvisit all the child window of
the crystal report viewer control to notity them the
WM_FONTCHANGE message explicitly.

Here I write some code snippet on how to transvisit the child control's
handles of the crystal reporter viewer control.
Private Sub RecursiveGetWindow(ByVal ctl As Control, ByVal level As
String)
Debug.WriteLine(level + ctl.Handle.ToString())
If ctl.Controls.Count = 0 Then
Return
End If
level += " + *"
For Each cldcon As Control In ctl.Controls
RecursiveGetWindow(cldcon, level)
Next
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
RecursiveGetWindow(CrystalReportViewer1, "*")
End Sub

You may change the code to meet your request.

If you still have any concern on this issue, please feel free to post here.


Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

Sylvain Audet

Hi Peter,
Thanks for your reply but aren't you missing a line of code within
RecursiveGetWindow sub? It looks like I am only looping through all
controls but what action is peformed other than: level += " + *" ?

Please advise.

Thanks,

--
Sylvain Audet - MCP+SB
Visitez les astuces .NET sur dotNET-fr.org
http://www.dotnet-fr.org/sections.php3?op=listarticles&secid=6
Peter Huang said:
Hi Sylvain,

Thanks for your quickly reply!

From your description, I assume the sendmessage should work on your side(
the NOTEPAD will get the WM_FONTCHANGE message).

So I think, in your senario, you have a windows form application which host
a crystal report viewer control and you want the crystal report viewer
control to get the WM_FONTCHANGE message.

From the msdn,
[in] Handle to the window whose window procedure will receive the message.
If this parameter is HWND_BROADCAST, the message is sent to all top-level
windows in the system, including disabled or invisible unowned windows,
overlapped windows, and pop-up windows; but the message is not sent to
child windows.

SendMessage Function
http://msdn.microsoft.com/library/d.../messagesandmessagequeues/messagesandmessageq
ueuesreference/messagesandmessagequeuesfunctions/sendmessage.asp

We know when you sendmessage to the HWND_BROADCAST, all the toplevel window
will get the message, but not the control as a child window(e.g. crystal
report viewer control ) will get the message.

That's why the notepad will get the WM_FONTCHANGE message in both your
and my test. And I assume the main form of the windows application should
get the message WM_FONTCHANGE. Because they are all toplevel windows.

Also we can sendmessage to the handle of crystal report viewer control, but
the message will not be send to the control's child windows by default. If
you need to do so, I think you need to transvisit all the child window of
the crystal report viewer control to notity them the
WM_FONTCHANGE message explicitly.

Here I write some code snippet on how to transvisit the child control's
handles of the crystal reporter viewer control.
Private Sub RecursiveGetWindow(ByVal ctl As Control, ByVal level As
String)
Debug.WriteLine(level + ctl.Handle.ToString())
If ctl.Controls.Count = 0 Then
Return
End If
level += " + *"
For Each cldcon As Control In ctl.Controls
RecursiveGetWindow(cldcon, level)
Next
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
RecursiveGetWindow(CrystalReportViewer1, "*")
End Sub

You may change the code to meet your request.

If you still have any concern on this issue, please feel free to post here.


Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
P

Peter Huang

Hi Sylvain,

Thanks for your quickly reply!

I am sorry for confusion. My code in my last post is just to demostrating
how to enum all the descent window's handle of Crystal report viewer
control. As I said, the sendmessage will not be sent to one window's child
window, we have to do it ourselves.

That is to say, we need to sendmessage to the handles of the child windows
of Crystal report viewer, so that all the child windows will get the
WM_FONTCHANGE message.

e.g.
I have confirmed with spy++, the child windows of Crystal report viewer
will get the WM_FONTCHANGE notification, you may have a try and let me know
the result.



Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

Sylvain Audet

Hi Peter,
Well....it didn't get further. Here is the path I used to test your
code:

1- Start the app with your code
-> 44 fonts into the System.Drawing.Text.InstalledFontCollection of my
computer

2- Install the font and broadcast the font change through SendMessage using
your code
-> still 44 fonts into the System.Drawing.Text.InstalledFontCollection of my
computer

3- Restart the same app
-> This time there are "45" fonts into the
System.Drawing.Text.InstalledFontCollection of my computer

4- Start-up my crystal viewer's app
-> The barcode font is not showing-up

5- Close my crystal viewer's app

6- Refresh my C:\windows\fonts directory using Windows explorer

7- Restart my crystal viewer's app
-> this time the barcode font is showing-up properly

It looks that the problem may not be with the SendMessage but with the
action performed internally by Windows when the C:\Windows\Fonts directory
is refreshed!

Any idea?
 
S

Sylvain Audet

Please note that I also tried to refresh the C:\Windows\Fonts directory
within code but it didn't work. The font was still unusable until I
manually go to this directory using Windows Explorer.
 
P

Peter Huang

Hi Sylvain,

Thanks for your quickly reply!

I am researching the issue, I will get back here and update you with new
information ASAP.

Have a nice day!

Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
P

Peter Huang

Hi Sylvain,

After I further researching, we know that this is a known issue. Because
the Framework caches a list of the installed fonts at startup. So far the
only way to refresh this list is to restart the .Net application. Now we
are involved more persons on this issue to see if there is another way to
workaround the problem.

Thanks for your understanding.

Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

Sylvain Audet

Peter,
Do you guys have any ETA? (yeah! I know, this is a tricky question, but
heh! I've got answers to give on my side too! ;-) )

Depending on the time you guys take to find a workaround, I may no more
monitor this thread so I would appreciate if you could keep me informed on
this issue at my personal email address along as this newsgroup thread if
possible?

Thanks again Peter for all your efforts!
 
Y

Yan-Hong Huang[MSFT]

Hi Sulvain,

First, the font must be installed in the fonts folder for this to work
since that is where the InstalledFontCollection is derived. If the font is
not moved to the Fonts folder to install it, the InstalledFontCollection
will not list it.

So the first question is a known problem. In order to workaround it, we
need to use PInvoke to call EnumFontFamiliesEx to query installed fonts.

For more information on it, please refer to:
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&threadm=nggF4aSf
BHA.1272%40cpmsftngxa09&rnum=1&prev=/groups%3Fhl%3Den%26lr%3D%26ie%3DUTF-8%2
6oe%3DUTF-8%26q%3DGDI%2BInstalledFontCollection
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&threadm=ACe2PqRA
EHA.1268%40cpmsftngxa06.phx.gbl&rnum=4&prev=/groups%3Fhl%3Den%26lr%3D%26ie%3
DUTF-8%26oe%3DUTF-8%26q%3DGDI%2BInstalledFontCollection

For the second refresh folder question, when using text box, we don't need
to refresh folder to make fonts shown. Currently we are testing on crystal
viewer and will reply here with more information as soon as possible.

Thanks very much.

Best regards,
Yanhong Huang
Microsoft Community Support

Get Secure! ¨C www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top