Draw character in center of custom control - Font

2020-04-23 04:30发布

问题:

I'm trying to build custom user control that will display Font Awesome Glyphs inside winforms Button.
I found GitHub repo with similar control, but I would like to use Button as base of my control.
I'm able to show glyph, but I can't align it correctly:

Green dotted line shows button size, blue lines indicates center of control and red lines show rectangle that graphics.MeasureString is returning.

My OnPaint method looks like this:

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        var graphics = e.Graphics;
        // Set best quality
        graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
        graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;

        if(!DesignMode)
        {
            graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
        }

        var letter = char.ConvertFromUtf32((int)_icon);

        Brush b;
        if (!Enabled)
        {
            b = Brushes.LightGray;
        }
        else if (_mouseDown)
        {
            b = new SolidBrush(_clickColor);
        }
        else if (_mouseOver)
        {
            b = new SolidBrush(_hoverColor);
        }
        else
        {
            b = new SolidBrush(ForeColor);
        }

        SizeF s = graphics.MeasureString(letter, new Font(Fonts.Families[0], _iconSize, GraphicsUnit.Point), Width);

        float w = s.Width;
        float h = s.Height;

        // center icon
        float left = Padding.Left + (Width - w)/2;
        float top = Padding.Top + (Height - h)/2;

        if (DesignMode)
        {
            graphics.DrawRectangle(Pens.Red, top, left, w, h);
        }

        graphics.DrawString(letter, new Font(Fonts.Families[0], _iconSize, GraphicsUnit.Point), b, new PointF(left, top));

        if (DesignMode)
        {
            var pen = new Pen(_hoverColor, 1) { DashStyle = DashStyle.Dash, Alignment = PenAlignment.Inset };
            graphics.DrawRectangle(pen, Padding.Left, Padding.Top, Width - Padding.Left - Padding.Right - 1, Height - Padding.Top - Padding.Bottom - 1);
            graphics.DrawLine(Pens.Blue, Padding.Left, Padding.Top, Width - Padding.Left, Height - Padding.Top);
            graphics.DrawLine(Pens.Blue, Width - Padding.Left, Padding.Top, Padding.Left, Height - Padding.Top);
        }
    }

I tried using solution from this question but without any luck.

How can I draw single character (glyph) in my control exact center (center of control and center of glyph should align)

Here is code for my control:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MyControls
{
    class FontButton:Button
    {
        #region Public

        public FontButton()
        {
            base.FlatStyle = FlatStyle.Flat;
            base.FlatAppearance.BorderSize = 0;
            base.FlatAppearance.MouseOverBackColor = Color.Transparent;
            base.FlatAppearance.MouseDownBackColor = Color.Transparent;
            base.Text = "";
            base.MinimumSize = new Size(32,32);
            Size = new Size(32,32);

            _hoverColor = Color.FromArgb(144, 188, 0);
            _clickColor = Color.Green;
            _icon=IconType.Android;
            _iconSize = 40;
        }

        private int _iconSize;

        [DefaultValue(typeof(int), "40"), Category("Appearance"), Description("Icon size in points")]
        public int IconSize
        {
            get { return _iconSize; }
            set
            {
                _iconSize = value;
                Invalidate();
            }
        }


        private Color _hoverColor;

        [DefaultValue(typeof(Color), "144, 188, 0"), Category("Appearance"), Description("Color when mouse over")]
        public Color HoverColor
        {
            get { return _hoverColor; }
            set
            {
                _hoverColor = value;
                Invalidate();
            }
        }

        private Color _clickColor;

        [DefaultValue(typeof(Color), "Green"), Category("Appearance"), Description("Color when mouse click")]
        public Color ClickColor
        {
            get { return _clickColor; }
            set
            {
                _clickColor = value;
                Invalidate();
            }
        }

        private IconType _icon;

        [DefaultValue(typeof(IconType), "Android"), Category("Appearance"), Description("Icon")]
        public IconType Icon
        {
            get { return _icon; }
            set
            {
                _icon = value;
                Invalidate();
            }
        }

        #endregion

        #region Static

        static FontButton()
        {
            InitialiseFont();
        }

        #region NATIVE

        [DllImport("gdi32.dll")]
        private static extern IntPtr AddFontMemResourceEx(byte[] pbFont, int cbFont, IntPtr pdv, out uint pcFonts);

        #endregion

        private static readonly PrivateFontCollection Fonts = new PrivateFontCollection();

        private static void InitialiseFont()
        {
            try
            {
                byte[] fontdata = ImageButtonTest.Properties.Resources.fontawesome_webfont;
                IntPtr ptrFont = Marshal.AllocCoTaskMem(fontdata.Length);
                uint cFonts;
                AddFontMemResourceEx(fontdata, fontdata.Length, IntPtr.Zero, out cFonts);
                Marshal.Copy(fontdata, 0, ptrFont, fontdata.Length);
                Fonts.AddMemoryFont(ptrFont, fontdata.Length);
                Marshal.FreeCoTaskMem(ptrFont);
            }
            catch (Exception)
            {
                Debug.WriteLine("Error");
                throw;
            }
        }

        #endregion

        #region Overrides

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            var graphics = e.Graphics;
            // Set best quality
            graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
            graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;


            if(!DesignMode)
            {
                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
            }

            var letter = char.ConvertFromUtf32((int)_icon);//char.ConvertFromUtf32(0xf190);

            Brush b;
            if (!Enabled)
            {
                b = Brushes.LightGray;
            }
            else if (_mouseDown)
            {
                b = new SolidBrush(_clickColor);
            }
            else if (_mouseOver)
            {
                b = new SolidBrush(_hoverColor);
            }
            else
            {
                b = new SolidBrush(ForeColor);
            }

            SizeF s = graphics.MeasureString(letter, new Font(Fonts.Families[0], _iconSize, GraphicsUnit.Point), Width);

            //SizeF s = TextRenderer.MeasureText(letter, new Font(Fonts.Families[0], _iconSize, GraphicsUnit.Point), new Size(20, 20), TextFormatFlags.NoPadding);

            //Debug.WriteLine(stringSize);
            //Debug.WriteLine(s);

            float w = s.Width;
            float h = s.Height;

            // center icon
            float left = Padding.Left + (Width - w)/2;
            float top = Padding.Top + (Height - h)/2;

            if (DesignMode)
            {
                graphics.DrawRectangle(Pens.Red, top, left, w, h);
            }

            graphics.DrawString(letter, new Font(Fonts.Families[0], _iconSize, GraphicsUnit.Point), b, new PointF(left, top));

            if (DesignMode)//Process.GetCurrentProcess().ProcessName == "devenv")
            {
                var pen = new Pen(_hoverColor, 1) { DashStyle = DashStyle.Dash, Alignment = PenAlignment.Inset };
                graphics.DrawRectangle(pen, Padding.Left, Padding.Top, Width - Padding.Left - Padding.Right - 1, Height - Padding.Top - Padding.Bottom - 1);
                graphics.DrawLine(Pens.Blue, Padding.Left, Padding.Top, Width - Padding.Left, Height - Padding.Top);
                graphics.DrawLine(Pens.Blue, Width - Padding.Left, Padding.Top, Padding.Left, Height - Padding.Top);
            }
        }

        private bool _mouseOver;

        protected override void OnMouseEnter(EventArgs e)
        {
            _mouseOver = true;
            base.OnMouseEnter(e);

        }

        protected override void OnMouseLeave(EventArgs e)
        {
            _mouseOver = false;
            base.OnMouseLeave(e);
        }

        private bool _mouseDown;

        protected override void OnMouseDown(MouseEventArgs e)
        {
            _mouseDown = true;
            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            _mouseDown = false;
            base.OnMouseUp(e);
        }


        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new FlatStyle FlatStyle { get; set; }

        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new FlatButtonAppearance FlatAppearance { get; set; }

        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new string Text { get; set; }

        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new Size MinimumSize { get; set; }

        #endregion
    }
}

and IconType enum:

namespace MyControls
{
    public enum IconType
    {
        Adjust = 0xf042,
        Adn = 0xf170,
        AlignCenter = 0xf037,
        AlignJustify = 0xf039,
        AlignLeft = 0xf036,
        AlignRight = 0xf038,
        Ambulance = 0xf0f9,
        Anchor = 0xf13d,
        Android = 0xf17b,
        ArrowCircleDown = 0xf0ab,
        ArrowCircleLeft = 0xf0a8,
        ArrowCircleODown = 0xf01a,
        ArrowCircleOLeft = 0xf190,
        ArrowCircleORight = 0xf18e,
        ArrowCircleOUp = 0xf01b,
        ArrowCircleRight = 0xf0a9,
        ArrowCircleUp = 0xf0aa,
        ArrowDown = 0xf063,
        ArrowLeft = 0xf060,
        ArrowRight = 0xf061,
        ArrowUp = 0xf062,
        Arrows = 0xf047,
        ArrowsAlt = 0xf0b2,
        ArrowsH = 0xf07e,
        ArrowsV = 0xf07d,
        User = 0xf007,
        UserMd = 0xf0f0,
        Users = 0xf0c0,
        Stop = 0xf04d
    }
}

All it needs is to include fontawesome_webfont.ttf in resources.

回答1:

This is from MSDN :

The MeasureString method is designed for use with individual strings and includes a small amount of extra space before and after the string to allow for overhanging glyphs. Also, the DrawString method adjusts glyph points to optimize display quality and might display a string narrower than reported by MeasureString. To obtain metrics suitable for adjacent strings in layout (for example, when implementing formatted text), use the MeasureCharacterRanges method or one of the MeasureString methods that takes a StringFormat and pass GenericTypographic. Also ensure the TextRenderingHint for the Graphics is AntiAlias.

So to make the MeasureString leave out all extra space pass in the GenericTypographic like this:

SizeF s = graphics.MeasureString(letter, new Font(Fonts.Families[0], 
          _iconSize, GraphicsUnit.Point), Width, StringFormat.GenericTypographic);

I found this to help a lot, although I haven't had to do it quite as exact as you..



回答2:

I've got best result using GraphicsPath:

var path = new GraphicsPath()
path.AddString(_letter, font.FontFamily, (int) font.Style, font.Size, new Point(0, 0), StringFormat.GenericTypographic);
Rectangle area = Rectangle.Round(path.GetBounds());