在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
作为资源。之后,每当您需要其中之一时,就使用缓存将其退还给您。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:使用随 'AddFontMemResourceEx'一起安装的字体仅用于处理,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50964801/