<

[.NET] Change a Font's properties

Published on
9,881 Points
3,781 Views
1 Endorsement
Last Modified:
Approved
So, looking to -truly- change the FontFamily of your .NET Font, are you? For whatever reason, I've seen this problem confronted and abandoned several times now. Because of this, I decided to delve into the issue, and finally came out with a solution using Reflection. It's actually rather simple.

Because the System.Drawing.Font class is sealed, we can't inherit from it, so there goes that possiblity. On top of that, there is no interface or anything (that I know of) that can offer the same versatile functionality--all the System.Windows.Forms.Control deriven classes require a Font for the Font property.

On top of this fact, all of the Font object's properties are get-only (aka, read-only; the "set" accessor has been omitted), and there is no method used to substitute this functionality. However, using an extremely invaluable tool, I've found that the Font class internally implements a small number of methods that may be used to aid us in the process. The problem is, these methods are private. To invoke a private method or "mess with" a private field, there are a couple steps we have to take.

First, get an instance of the Font Type (System.Type) through the typeof operator, like the following.

 
//C#
Type fontType = typeof(Font);
//MC  
Type^ t = Font::typeid;

Open in new window


Through this Type object, we can invoke any method or change any member of the Font type, given an instance to apply the change or invocation to. There are private fields within the Font type, with the following names (I'll give descriptions, as well). I'll only highlight the important ones:

fontFamily : FontFamily - Represents the managed wrapper of the Font's FontFamily.
fontSize : Single - The size in of the font, measured in whatever the private fontUnit field is.
fontStyle : FontStyle - The FontStyle value of the font.
fontUnit : GraphicsUnit - The GraphicsUnit value used to size the font.
gdiCharSet : Byte - Unimportant; usually is a value of 1.
gdiVerticalFont : Boolean - True for vertical; otherwise, false.
nativeFont : IntPtr - Handle to the FontFamily's native Font (accessed through the FontFamily's internal instance property "NativeHandle")

The rest are as follows, and are (up until this point, I believe) not useful:
LogFontCharSetOffset : Int32
LogFontNameOffset : Int32
originalFontName : String
systemFontName : String

Now, on to actually changing the font's properties. To change any of the fields (except the fontFamily and nativeFont fields, which I will go into in a minute) in the Font, you simple use the following method call, or something like it:

 
//C#:
typeof(Font).InvokeMember("fieldName", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField, null, fontInstanceToModify, new Object[] { /*Value to assign*/ });
 
//MC  :
Font::typeid->InvokeMember(L"fieldName", BindingFlags::NonPublic | BindingFlags::Instance | BindingFlags::SetField, nullptr, fontInstanceToModify, gcnew cli::array<Object^>^ { /*Value to assign*/ });

Open in new window


For example, to change the Size to 16, you could do the following.

// C#:
typeof(Font).InvokeMember("fontSize", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField, null, fontInstanceToModify, new Object[] { 16f });
 
// MCPP:
Font::typeid->InvokeMember(L"fontSize", BindingFlags::NonPublic | BindingFlags::Instance | BindingFlags::SetField, nullptr, fontInstanceToModify, gcnew cli::array<Object^>^ { 16.0f });

Open in new window


That should work for any of the items (except nativeFont and fontFamily, which might cause problems if you do this) in the FIRST list above (the second list is something I haven't experimented with, though it is there for your reference). So, how do we change the FontFamily, and what's up with the nativeFont field, too? They actually go hand in hand. When a GDI+ method is called, a handle to the Font, and the font's Native

FontFamily handle must be passed, so simply changing one or the other just does not suffice. Getting the handle to a native font is kind of a pain, and involves a few API calls, but luckily, the .NET Framework left us a nice little present we can use to get the handle anyway.This method actually takes care of all of the nativeFont portion just on invocation, so all we need to do is make sure that we've changed the FontFamily beforehand. We could just change the field, but we'd probably have to do more API calls and such, and we don't want that. Instead, call the private method called "SetFontFamily"*, which takes a single FontFamily as a parameter and returns void, like this:


// C#:
typeof(Font).InvokeMember("SetFontFamily", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, fontInstanceToModify, new Object[] { yourNewFontFamily });
 
// MC  :
Font::typeid->InvokeMember(L"SetFontFamily", BindingFlags::NonPublic | BindingFlags::Instance | BindingFlags::InvokeMethod, nullptr, fontInstanceToModify, gcnew cli::array<Object^>^ { yourNewFontFamily });

Open in new window


However, this isn't quite enough, as the NativeFont handle doesn't get updated unfortunately. This is quickly solved by invoking a simple parameterless void-returning method called "CreateNativeFont". A call to this method would look like the following.

// C#
typeof(Font).InvokeMember("CreateNativeFont", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, fontInstanceToModify, null);
 
// MC  :
Font::typeid->InvokeMember(L"CreateNativeFont", BindingFlags::NonPublic | BindingFlags::Instance | BindingFlags::InvokeMethod, nullptr, fontInstanceToModify, nullptr);

Open in new window


Yeup, that's it. Just remember that if you change the FontFamily of your font (whether it be thorugh the method SetFontFamily or changing the field directly), you need to call CreateNativeFont sometime afterward (as long as you do so before using the font).

There is yet another thing to look out for, however: many fonts don't actually support a lot of the FontStyle values (including Regular!). I suggest you use the instance method FontFamily.IsStyleSupported(FontStyle) to determine this when changing fonts like I've shown; be sure to change to a supported FontStyle.

Lastly, for the impatient and/or lazy ones, I've included the real beauty in this article below. You can import the methods directly into your code, if you wish. It may even be a good idea to create your own customizeable Font with a Font object as a private field (make it implicitly convertible to a Font to "fake" inheritance, because Font is sealed).

C#:
public static void SetFontFamily(this Font myFont, FontFamily myFamily
{
    typeof(Font).InvokeMember("SetFontFamily", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, 
          myFont, new Object[] { myFamily });
     typeof(Font).InvokeMember("CreateNativeFont", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, 
          myFont, null);
}
public static void SetSize(this Font myFont, Single mySize)
{
     typeof(Font).InvokeMember("fontSize", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField, null, myFont,
          new Object[] { mySize });
}
// Returns false if style wasn't supported
public static Boolean SetFontStyle(this Font myFont, FontStyle myStyle)
{
     if(myFont.FontFamily.IsStyleSupported(myStyle))
     {
          typeof(Font).InvokeMember("fontSize", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField, null, myFont,
               new Object[] { mySize });
          return true;
     }
     return false;
}

Open in new window


Hope this has helped, or at least been an interesting read!
Nate

*You can probably just change the field directly, but I would just invoke the method anyway.
1
Comment
1 Comment
 

Expert Comment

by:Raj2009
can you please let me know how to set Font Size/Style for selected controls through reflection
0

Featured Post

Upgrade your Question Security!

Your question, your audience. Choose who sees your identity—and your question—with question security.

Join & Write a Comment

Wrapper-1-Query. Use an Excel function to calculate a column for an Access query. Part 1. Shows a query in Access that has a calculated column with the results of an Excel worksheet function. See how to call a wrapper function from a query, and …
If you, like me, have a dislike for using Online Subscription anti-spam services, then this video series is for you. I have an inherent dislike of leaving decisions such as what is and what isn't spamming to other people or services for me and insis…
Other articles by this author
Next Article:

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month