Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
108 views
in Technique[技术] by (71.8m points)

c# - InstalledFontCollection does not actualize when adding font with AddFontResourceW followed by SendMessage

I am developping an application with wpf, .net and devexpress. I mainly use the devexpress for the report designer and the grid. I need to load custom fonts in the session for the report designer and cannot install them in the system as user may not have administrative priviledges. The report designer seems to rely on "InstalledFontCollection" to present available fonts to user.

As MSDN documentation indicates here, I dynamically load fonts with "AddFontResourceW" followed by "SendMessage". However, calling "InstalledFontCollection" subsequently is not registering newly loaded fonts. However, it will list them if I restart the app. The same phenomena appears using RemoveFontResourceW followed by SendMessage. Here is part of the program I built to test it.

1 - The class responsible for font related stuff :

public static class FontManager
{
    static public IEnumerable<string> ListInstalledFonts()
    {
        var installedFonts = new InstalledFontCollection();
        var result = from f in installedFonts.Families select f.Name;
        installedFonts.Dispose();
        return result;
    }

    [DllImport("gdi32.dll", EntryPoint = "AddFontResourceW", SetLastError = true)]
    public static extern int AddFontResource([In][MarshalAs(UnmanagedType.LPWStr)] string lpFileName);

    [DllImport("gdi32.dll", EntryPoint = "RemoveFontResourceW", SetLastError = true)]
    public static extern int RemoveFontResource([In][MarshalAs(UnmanagedType.LPWStr)] string lpFileName);

    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, uint wMsg, IntPtr wParam, IntPtr lParam);

    static IntPtr HWND_BROADCAST = new IntPtr(0xffff);
    const uint WM_FONTCHANGE = 0x001D;
    static public void LoadFont(string path)
    {
        Console.WriteLine("Loading Font : " + path);
        if (AddFontResource(path) > 0)
        {
            Console.WriteLine(string.Format("Font {0} installed successfully.", path));
            SendMessage(HWND_BROADCAST, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
        }
        else
        {
            int error = Marshal.GetLastWin32Error();
            if (error != 0)
                Console.WriteLine(string.Format("Error Installing font : {0} : {1}", path, new Win32Exception(error).Message));
        }
    }

    static public void UnloadFont(string path)
    {
        Console.WriteLine("Removing Font : " + path);
        if (RemoveFontResource(path) > 0)
        {
            Console.WriteLine(string.Format("Font {0} removed successfully.", path));
            SendMessage(HWND_BROADCAST, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
        }
        else
        {
            int error = Marshal.GetLastWin32Error();
            if (error != 0)
                Console.WriteLine(string.Format("Error Removing Font : {0} : {1}", path, new Win32Exception(error).Message));
        }
    }
}

2 - the viewmodel presenting the font list to the ui :

public class MainWindowVM : INotifyPropertyChanged
{
    #region ctor
    public MainWindowVM()
    {
        RefreshAvailableFonts();
        RefreshVisibleFonts();
        CmdRefreshAvailableFonts = new RelayCommand(RefreshAvailableFonts);
        CmdRefreshVisibleFonts = new RelayCommand(RefreshVisibleFonts);
        CmdLoadCustomFonts = new RelayCommand(LoadCustomFonts);
        CmdUnloadCustomFonts = new RelayCommand(UnloadCustomFonts);
    }
    #endregion

    #region propertyNotifier

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion propertyNotifier

    // property used to present font list to an ui control, for instance a grid
    private IEnumerable<string> visibleFonts = null;
    public IEnumerable<string> VisibleFonts
    {
        get => visibleFonts;
        set
        {
            visibleFonts = value;
            NotifyPropertyChanged();
        }
    }

    // property used to cache font list available in system
    private IEnumerable<string> availableFonts = null;
    public IEnumerable<string> AvailableFonts
    {
        get => availableFonts;
        set
        {
            availableFonts = value;
            NotifyPropertyChanged();
            RefreshVisibleFonts();
        }
    }

    // property used with textbox to filter visible fonts in grid
    private string filter = null;
    public string Filter
    {
        get => filter;
        set
        {
            filter = value;
            NotifyPropertyChanged();
            RefreshVisibleFonts();
        }
    }

    #region Commands
    // command used to refresh visible font list manually from ui
    public ICommand CmdRefreshVisibleFonts { get; private set; }
    public void RefreshVisibleFonts()
    {
        Console.WriteLine("=> RefreshVisibleFonts()");
        if (filter == null)
            VisibleFonts = from fn in AvailableFonts select fn;
        else VisibleFonts = from fn in AvailableFonts where fn.ToLower().Contains(filter.ToLower()) select fn;
    }
    // command used to refresh available font list manually from ui
    public ICommand CmdRefreshAvailableFonts { get; private set; }
    public void RefreshAvailableFonts()
    {
        Console.WriteLine("=> RefreshAvailableFonts()");
        AvailableFonts = FontManager.ListInstalledFonts();
    }

    // command used to load custom fonts manually in the session 
    public ICommand CmdLoadCustomFonts { get; private set; }
    public void LoadCustomFonts()
    {
        Console.WriteLine("=> LoadCustomFonts()");
        var docs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        string fonts = Path.Combine(docs, "fonts");
        FontManager.LoadFont(Path.Combine(fonts, @"Pancho-Semibold.ttf"));
        FontManager.LoadFont(Path.Combine(fonts, @"Pancho-Medium.ttf"));
        FontManager.LoadFont(Path.Combine(fonts, @"Pancho-Regular.ttf"));
    }

    // command used to unload custom fonts manually from the session
    public ICommand CmdUnloadCustomFonts { get; private set; }
    public void UnloadCustomFonts()
    {
        Console.WriteLine("=> UnloadCustomFonts()");
        var docs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        string fonts = Path.Combine(docs, "fonts");
        FontManager.UnloadFont(Path.Combine(fonts, @"Pancho-Semibold.ttf"));
        FontManager.UnloadFont(Path.Combine(fonts, @"Pancho-Medium.ttf"));
        FontManager.UnloadFont(Path.Combine(fonts, @"Pancho-Regular.ttf"));
    }

    #endregion Commands
}

3 - mainwindow code behind : I have also added a winproc hook to check if the message is effectively broadcasted when adding or removing fonts using sendMessage :

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
        source.AddHook(WndProc);
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == 0x1D)
        {
            Console.WriteLine("Fontchange message received");
            if (DataContext is ViewModels.MainWindowVM vm)
                vm.RefreshAvailableFonts();
        }
        return IntPtr.Zero;
    }
}

related links : https://social.msdn.microsoft.com/Forums/en-US/53e22f9e-40b1-4bbb-b5d2-fd3d7431c783/installedfontcollection

question from:https://stackoverflow.com/questions/65858045/installedfontcollection-does-not-actualize-when-adding-font-with-addfontresource

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...