在WPF中,我们希望使用ttf字体作为嵌入式资源,而无需将其复制或安装到系统中,也无需将其实际写入磁盘。没有内存泄漏问题。

没有以下解决方案中详细介绍的解决方案:

How to include external font in WPF application without installing it

由于以下情况导致WPF内存泄漏,因此在这种情况下可用:

WPF TextBlock memory leak when using Font

仅可以通过AddFontMemResourceEx 在内存中从内存安装字体并进行处理。由于这将为该过程安装字体,因此它也应适用于WPF,但是在通过FontFamily安装字体后,我们在AddFontMemResourceEx周围似乎存在问题。例如。:

var font = new FontFamily("Roboto");

这样做的原因是它不会产生任何错误,但是实际上并没有更改字体,更改了某些行距和其他度量,但是由于某种原因,字体看起来与Segoe UI完全一样。

然后的问题是,在WPF中如何使用随AddFontMemResourceEx安装的字体?

PS:这是P/调用代码:
const string GdiDllName = "gdi32";
[DllImport(GdiDllName, ExactSpelling= true)]
private static extern IntPtr AddFontMemResourceEx(byte[] pbFont, int cbFont, IntPtr pdv, out uint pcFonts);

public static void AddFontMemResourceEx(string fontResourceName, byte[] bytes, Action<string> log)
{
    var handle = AddFontMemResourceEx(bytes, bytes.Length, IntPtr.Zero, out uint fontCount);
    if (handle == IntPtr.Zero)
    {
        log?.Invoke($"Font install failed for '{fontResourceName}'");
    }
    else
    {
        var message = $"Font installed '{fontResourceName}' with font count '{fontCount}'";
        log?.Invoke(message);
    }
}

这段代码成功接收了以下日志消息:
Font installed 'Roboto-Regular.ttf' with font count '1'

支持将嵌入式资源加载为字节数组的支持代码:
public static byte[] ReadResourceByteArray(Assembly assembly, string resourceName)
{
    using (var stream = assembly.GetManifestResourceStream(resourceName))
    {
        var bytes = new byte[stream.Length];
        int read = 0;
        while (read < bytes.Length)
        {
            read += stream.Read(bytes, read, bytes.Length - read);
        }
        if (read != bytes.Length)
        {
            throw new ArgumentException(
                $"Resource '{resourceName}' has unexpected length " +
                $"'{read}' expected '{bytes.Length}'");
        }
        return bytes;
    }
}

这意味着安装嵌入式字体可以像这样完成,assembly是包含嵌入式字体资源的程序集,EMBEDDEDFONTNAMESPACE是嵌入式资源的 namespace ,例如SomeProject.Fonts:
var resourceNames = assembly.GetManifestResourceNames();

string Prefix = "EMBEDDEDFONTNAMESPACE" + ".";
var fontFileNameToResourceName = resourceNames.Where(n => n.StartsWith(Prefix))
    .ToDictionary(n => n.Replace(Prefix, string.Empty), n => n);

var fontFileNameToBytes = fontFileNameToResourceName
    .ToDictionary(p => p.Key, p => ReadResourceByteArray(assembly, p.Value));

foreach (var fileNameBytes in fontFileNameToBytes)
{
    AddFontMemResourceEx(fileNameBytes.Key, fileNameBytes.Value, log);
}

最佳答案

我不知道这是否正是您想要的,但是我有一个解决方案,您可以在解决方案中将字体用作Resource

  • 将所需的所有fonts声明为Resource
  • 制作一个称为MarkupExtension的自定义FontExplorer
  • 尝试我的XAML示例

  • application启动并且首次使用FontExplorer时,它将缓存您拥有的所有fonts作为资源。之后,每当您需要其中之一时,就使用缓存将其退还给您。

    c# - WPF:使用随 &#39;AddFontMemResourceEx&#39;一起安装的字体仅用于处理-LMLPHP
    public class FontExplorer : MarkupExtension
    {
        // ##############################################################################################################################
        // Properties
        // ##############################################################################################################################
    
        #region Properties
    
        // ##########################################################################################
        // Public Properties
        // ##########################################################################################
    
        public string Key { get; set; }
    
        // ##########################################################################################
        // Private Properties
        // ##########################################################################################
    
        private static readonly Dictionary<string, FontFamily> _CachedFonts = new Dictionary<string, FontFamily>();
    
        #endregion
    
    
        // ##############################################################################################################################
        // Constructor
        // ##############################################################################################################################
    
        #region Constructor
    
        static FontExplorer()
        {
            foreach (FontFamily fontFamily in Fonts.GetFontFamilies(new Uri("pack://application:,,,/"), "./Fonts/"))
            {
                _CachedFonts.Add(fontFamily.FamilyNames.First().Value, fontFamily);
            }
        }
    
        #endregion
    
        // ##############################################################################################################################
        // methods
        // ##############################################################################################################################
    
        #region methods
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return ReadFont();
        }
    
        private object ReadFont()
        {
            if (!string.IsNullOrEmpty(Key))
            {
                if (_CachedFonts.ContainsKey(Key))
                    return _CachedFonts[Key];
            }
    
            return new FontFamily("Comic Sans MS");
        }
    
        #endregion
    }
    
    <Window x:Class="WpfApp1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            d:DataContext="{d:DesignInstance local:MainWindow}"
            Title="MainWindow" Height="450" Width="800">
        <Window.Style>
            <Style TargetType="local:MainWindow">
                <Setter Property="FontFamily" Value="{local:FontExplorer Key='Candle Mustard'}"/>
                <Style.Triggers>
                    <Trigger Property="Switch" Value="True">
                        <Setter Property="FontFamily" Value="{local:FontExplorer Key=Roboto}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Window.Style>
        <Grid x:Name="grid">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0">
                <TextBlock Text="Hello World" FontFamily="{local:FontExplorer Key='Candle Mustard'}"/>
                <TextBlock Text="Hello World" FontFamily="{local:FontExplorer Key=Roboto}"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
                <TextBlock Text="Hello World"/>
            </StackPanel>
            <StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="1" x:Name="Panel"/>
        </Grid>
    </Window>
    
    public partial class MainWindow : Window
    {
        public bool Switch
        {
            get => (bool)GetValue(SwitchProperty);
            set => SetValue(SwitchProperty, value);
        }
    
        /// <summary>
        /// The <see cref="Switch"/> DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty SwitchProperty = DependencyProperty.Register("Switch", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
    
    
        private readonly DispatcherTimer _Timer;
    
        public MainWindow()
        {
            InitializeComponent();
            _Timer = new DispatcherTimer();
            _Timer.Interval = TimeSpan.FromMilliseconds(50);
            _Timer.Tick += (sender, args) =>
            {
                Switch = !Switch;
                Panel.Children.Add(new TextBlock {Text = "I'm frome code behind"});
                if(Panel.Children.Count > 15)
                    Panel.Children.Clear();
            };
            _Timer.Start();
        }
    
    
        // ##############################################################################################################################
        // PropertyChanged
        // ##############################################################################################################################
    
        #region PropertyChanged
    
        /// <summary>
        /// The PropertyChanged Eventhandler
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
    
        /// <summary>
        /// Raise/invoke the propertyChanged event!
        /// </summary>
        /// <param name="propertyName"></param>
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        #endregion
    }
    

    预习

    如您在预览中所见,memory usage完成工作后,GC从83,2MB减少到82.9 MB。

    c# - WPF:使用随 &#39;AddFontMemResourceEx&#39;一起安装的字体仅用于处理-LMLPHP

    关于c# - WPF:使用随 'AddFontMemResourceEx'一起安装的字体仅用于处理,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50964801/

    10-17 01:11