绘制WinForms TreeView时,状态图像显示明显的像素缺陷

yws3nbqq  于 2022-12-30  发布在  其他
关注(0)|答案(1)|浏览(174)

在我全新的Windows 11桌面上,我在稳定生产应用程序中用来指示"已连接数据库“状态的位图在4K 150%缩放的显示屏上突然看起来很糟糕(显示器还是和以前一样)。这个问题似乎是TreeView特有的,因为同一Form上的同一位图在设置为Label的图像时看起来没有问题。在新机器上运行的Win 10虚拟机上看起来还可以。而且主要影响绿色的那个。奇怪。

无论如何,我不能只是坐在那里哭-我真的需要想出一个新的方法来绘制它,看起来100%正确的时间。所以我尝试了一种新的方法,使用字形字体,它看起来很好,当我把它放在一组标签清晰。

在TableLayoutPanel中看起来不错。

我现在需要做的是生成一个ImageList用于树视图,作为概念验证,我尝试使用Control.DrawToBitmap从标签生成一个运行时ImageList。我添加了一个#DEBUG块来保存位图,我可以在MS Paint中打开它们,它们看起来很好(当然这里放大了很多)。

在.bmp文件中看起来不错。

当然,这会有所改善,但仍有一些明显的像素缺陷,看起来像噪声抗锯齿或调整大小的伪像,即使我很注意对所有东西都使用一致的32 x 32大小。我已经弄乱了ImageListColorDepthImageSize属性。我浪费了几个小时试图理解和修复它。它发生在我的产品代码中。它'这种情况发生在我下面详述的最小可复制样本中。所以,在我把剩下的头发扯掉之前,也许有人能发现我做错了什么,或者告诉我一个更好的方法。

这是我在GitHub上的代码或browse完整示例。

public partial class HostForm : Form
{
    public HostForm()
    {
        InitializeComponent();
#if DEBUG
        _imgFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Paint")!;
        Directory.CreateDirectory(_imgFolder);
#endif
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e); 
        BackColor = Color.Teal;
        var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "database.ttf")!;
        privateFontCollection.AddFontFile(path);

        var fontFamily = privateFontCollection.Families[0];
        var font = new Font(fontFamily, 13.5F);
        var backColor = Color.FromArgb(128, Color.Teal);
        tableLayoutPanel.BackColor = backColor;

        // Stage the glyphs in the TableLayoutPanel.
        setLabelAttributes(label: label0, font: font, text: "\uE800", foreColor: Color.LightGray, backColor: backColor);
        setLabelAttributes(label: label1, font: font, text: "\uE800", foreColor: Color.LightSalmon, backColor: backColor);
        setLabelAttributes(label: label2, font: font, text: "\uE800", foreColor: Color.LightGreen, backColor: backColor);
        setLabelAttributes(label: label3, font: font, text: "\uE800", foreColor: Color.Blue, backColor: backColor);
        setLabelAttributes(label: label4, font: font, text: "\uE800", foreColor: Color.Gold, backColor: backColor);
        setLabelAttributes(label: label5, font: font, text: "\uE801", foreColor: Color.LightGray, backColor: backColor);
        setLabelAttributes(label: label6, font: font, text: "\uE801", foreColor: Color.LightSalmon, backColor: backColor); 
        setLabelAttributes(label: label7, font: font, text: "\uE801", foreColor: Color.LightGreen, backColor: backColor);
        setLabelAttributes(label: label8, font: font, text: "\uE801", foreColor: Color.Blue, backColor: backColor);
        setLabelAttributes(label: label9, font: font, text: "\uE801", foreColor: Color.Gold, backColor: backColor);
        setLabelAttributes(label: label10, font: font, text: "\uE803", foreColor: Color.LightGray, backColor: backColor);
        setLabelAttributes(label: label11, font: font, text: "\uE803", foreColor: Color.LightSalmon, backColor: backColor);
        setLabelAttributes(label: label12, font: font, text: "\uE803", foreColor: Color.LightGreen, backColor: backColor);
        setLabelAttributes(label: label13, font: font, text: "\uE803", foreColor: Color.Blue, backColor: backColor);
        setLabelAttributes(label: label14, font: font, text: "\uE802", foreColor: Color.LightGray, backColor: backColor);
        setLabelAttributes(label: label15, font: font, text: "\uE804", foreColor: Color.LightGreen, backColor: backColor);

        makeRuntimeImageList();
    }        
    private void setLabelAttributes(Label label, Font font, string text, Color foreColor, Color backColor)
    {
        label.UseCompatibleTextRendering = true;
        label.Font = font;
        label.Text = text;
        label.ForeColor = foreColor;
        label.BackColor = Color.FromArgb(200, backColor);
    }
    private void makeRuntimeImageList()
    {
        var imageList22 = new ImageList(this.components);
        imageList22.ImageSize = new Size(32, 32);
        imageList22.ColorDepth = ColorDepth.Depth8Bit;
        foreach (
            var label in 
            tableLayoutPanel.Controls
            .Cast<Control>()
            .Where(_=>_ is Label)
            .OrderBy(_=>int.Parse(_.Name.Replace("label", string.Empty))))
        {
            Bitmap bitmap = new Bitmap(label.Width, label.Height);
            label.DrawToBitmap(bitmap, label.ClientRectangle);
            imageList22.Images.Add(bitmap);
#if DEBUG
            bitmap.Save(Path.Combine(_imgFolder, $"{label.Name}.{ImageFormat.Bmp}"), ImageFormat.Bmp);
#endif
        }
        this.treeView.StateImageList = imageList22;
    }

#if DEBUG
    readonly string _imgFolder;
#endif
    PrivateFontCollection privateFontCollection = new PrivateFontCollection();
}
3xiyfsfu

3xiyfsfu1#

如果要使用TreeView.StateImageList而不是TreeView.ImageList,则需要具有/create 16x16图像。将ImageSize属性的大小设置为更大或更小的大小只会输出模糊、扭曲的重叠像素图像。因为使用TreeView.StateImageList时,非16x16图像将调整大小以适应分配的空间,复选框的边界,因为TreeView.StateImageList的通常用途是使用列表的第一(未选中)和第二(选中)图像来指示TreeView的节点选中状态,其中CheckBoxes属性被设置为true
从文件上看
默认情况下,TreeView中显示的状态图像为16 x 16像素。设置StateImageList的ImageSize属性不会影响图像的显示方式。但是,当app.config文件包含以下项时,将根据系统DPI设置调整状态图像的大小:

<appSettings>  
  <add key="EnableWindowsFormsHighDpiAutoResizing" value="true" />  
</appSettings>

...以及
当TreeView的CheckBoxes属性设置为true并设置了StateImageList属性时,TreeView中包含的每个TreeNode将显示StateImageList中的第一个和第二个图像,以分别指示未选中或选中状态。
考虑到这一点,将字体字形转换为图像将产生可接受的质量。请确保选择一个适当的字体大小,以锐化字形的细节。

    • 示例**

下面是一个简单的helper类,用于将一些Segoe MDL2 Assets字体字形转换为图像。

public static class SegoeMDL2AssetsFont
{
    public enum Glyph
    {
        Video = 0xE714,
        Search = 0xE721,
        FavoriteStar = 0xE734,
        FavoriteStarFill = 0xE735,
        GripperTool = 0xE75E,
        ContactPresence = 0xE8CF,
        Like = 0xE8E1,
        FeedbackApp = 0xE939,
        Robot = 0xE99A
    }

    public static Bitmap CreateGlyphImage(
        Glyph glyph, 
        Size imgSize,
        float fontSize, 
        FontStyle fontStyle,
        Color foreColor,
        Color backColor)
    {            
        var bmp = new Bitmap(imgSize.Width, imgSize.Height);
        var str = ((char)(int)glyph).ToString();
        var rec = new Rectangle(Point.Empty, imgSize);
            
        using (var g = Graphics.FromImage(bmp))
        using (var fnt = GetFont(fontSize, fontStyle))
        using (var sf = new StringFormat(StringFormat.GenericTypographic))
        using (var br = new SolidBrush(foreColor))
        {               
            sf.Alignment = sf.LineAlignment = StringAlignment.Center;

            g.Clear(backColor);
            g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
            g.DrawString(str, fnt, br, rec, sf);
        }

        return bmp;
    }

    public static string FontName => "Segoe MDL2 Assets";

    public static Font GetFont(float fontSize, FontStyle style) =>
        new Font(FontName, fontSize, style);        
}

......以及实施。

private void SomeCaller()
{
    imgList.Images.Clear();
    imgList.ColorDepth = ColorDepth.Depth32Bit;
    imgList.ImageSize = new Size(16, 16);
    imgList.TransparentColor = Color.Teal;

    var sz = imgList.ImageSize;
    var fs = 12f;
    var style = FontStyle.Regular;
    var bkColor = imgList.TransparentColor;

    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.FavoriteStar,
        sz, fs, style, Color.LightGray, bkColor));
    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.FavoriteStarFill,
        sz, fs, style, Color.LightSalmon, bkColor));
    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.GripperTool,
        sz, fs, style, Color.LightGreen, bkColor));
    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.Search,
        sz, fs, style, Color.Blue, bkColor));
    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.Video,
        sz, fs, style, Color.Gold, bkColor));
    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.ContactPresence,
        sz, fs, style, Color.DarkRed, bkColor));
    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.Like,
        sz, fs, style, Color.Cyan, bkColor));
    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.Robot,
        sz, fs, style, Color.Maroon, bkColor));
    imgList.Images.Add(SegoeMDL2AssetsFont.CreateGlyphImage(
        SegoeMDL2AssetsFont.Glyph.FeedbackApp,
        sz, fs, style, Color.DarkOrange, bkColor));

    treeView1.StateImageList = imgList;
    treeView1.BeginUpdate();
    treeView1.Nodes.Clear();

    for (int i = 0; i < imgList.Images.Count; i++)
    {
        var tn = new TreeNode($"Node {i + 1}")
        {
            StateImageIndex = i
        };
        treeView1.Nodes.Add(tn);
    }

    treeView1.EndUpdate();
}

...结果

相关问题