在 WPF 中,我们希望将ttf字体用作嵌入资源,而无需将它们复制或安装到系统中,也无需实际将它们写入磁盘。没有内存泄漏问题。
没有详细说明的解决方案:
由于 WPF 内存泄漏,在这种情况下可用:
只能通过AddFontMemResourceEx在 GDI 中从内存和进程安装字体。由于这会为该过程安装字体,因此它也应该适用于 WPF,但是在FontFamily通过AddFontMemResourceEx. 例如:
var font = new FontFamily("Roboto");
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为它不会给出任何错误,但字体实际上并没有改变,一些行间距和其他指标发生了变化,但由于Segoe UI某种原因字体看起来完全一样。
那么问题是它以及如何使用AddFontMemResourceExWPF 中安装的字体?
PS:这里是 P/Invoke 代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
此代码成功处理日志消息,例如:
Font installed 'Roboto-Regular.ttf' with font count '1'
Run Code Online (Sandbox Code Playgroud)
支持将嵌入资源加载为字节数组的代码:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
这意味着可以像这样安装嵌入字体,assembly作为包含嵌入字体资源的程序集和嵌入资源EMBEDDEDFONTNAMESPACE的命名空间,例如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);
}
Run Code Online (Sandbox Code Playgroud)
我不知道这是否正是您想要的,但我得到了一个解决方案,您可以在Resource解决方案中使用字体。
fonts将您想要的一切声明为Resource.MarkupExtension名为FontExplorerXAML例子当application启动并FontExplorer第一次使用时,它会缓存fonts您拥有的所有资源。此后,每当您需要其中之一时,都会使用缓存将其返还。

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
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)
正如您在预览中看到的,完成工作memory usage后,大小从 83.2MB 减少到 82.9MB 。GC