C# WinForm Label 控件拓展—变色字体、超链接

来源:互联网 发布:全球软件行业市场规模 编辑:程序博客网 时间:2024/06/07 17:32

前言:

以前做项目需要实现在文本类控件中加入超链接文字段的功能,在网上查了不少资料基本没有找到比较理想的实现方法,最后无奈只好自己实现了...

需求:

拓展Label控件,使其文本中的文字可以有多种颜色,添加下划线、做成超链、点击变色。变色文字的格局(间距、字体大小、换行)必须与原来一致。(注意做出改变的是部分文字,非整个文本)效果图如下:



图1

  

  图2  


 图3 

   

图1为WinForm原生Label的预览效果,图2、图3为拓展后的预览效果。                            

实现原理:

(1)准确测量文字区域

需要准确测量出文字段的大小才能保证文字格局的不变,GDI+已经自带了这种功能,可以得到指定文字段的System.Drawing.Region对象。但直接通过System.Drawing.Region对象获取的数据进行绘制的效果不太理想(未深入尝试),所以才有了(2)。

(2)异或刷前景

改变局部文字颜色的一种方法:①计算异或相关的颜色值,对局部区域做一次异或填充②绘制所有文字 ③使用与第一步同样的颜色对局部区域再做一次异或填充。

实现上述方法的条件:已知填充的区域、能控制整体文字绘制。

(3)开窗

底部加一层Label,在底部Label做变色,添加下划线效果,上层Label对变色部分开窗。--不算是个好方法,下滑线只能全局设置。

源码:

namespace MultiColorLabel{    //超链接、变色文字Label拓展控件    //Function:    //(1)Label控件中改变颜色的部分,在这里命名为“分段”。    //(2)一个Label可以有设置多个分段,每个分段可以相互重叠,重叠部分会做颜色融合。    //(3)分段可以改变颜色设置下划线、中划线和响应部分鼠标消息。       //Limitation:不支持Label的Padding属性改变    //Written by xin 2017.3.28    //Reviewed by xin 2017.9.4 This is a test edition, probably has some potential problem.        //Multi Color Label    public class ColorLabel : Label    {        public ColorLabelExtension ColorEx { get { return m_colorEx; } }        ColorLabelExtension m_colorEx;        public ColorLabel()        {            m_colorEx = new ColorLabelExtension(this);        }    }    //main collaboration class    public class ColorLabelExtension    {        public RangeEvents Events { get { return m_rangeEvents; } }//事件集合        public IEnumerable Ranges { get { return m_ranges; } }//分段集合        public bool UnderLine { set { FontStyle = value ? FontStyle | FontStyle.Underline : FontStyle & ~FontStyle.Underline; } get { return ((int)m_rangeStyle & (int)FontStyle.Underline) != 0; } }//下划线        public bool Strikeout { set { FontStyle = value ? FontStyle | FontStyle.Strikeout : FontStyle & ~FontStyle.Strikeout; } get { return ((int)m_rangeStyle & (int)FontStyle.Strikeout) != 0; } }//中划线        FontStyle FontStyle//设置分段的字体风格,其中粗体、斜体、常规对此属性无效。  --取消开放                 {            set            {                m_rangeStyle = value & ~FontStyle.Italic & ~FontStyle.Bold & ~FontStyle.Regular;                OnRangeFontStyleChanged();            }            get            {                return m_rangeStyle;            }        }        Label m_parent;        SubLabel m_subLabel;//显示分段的label        List m_ranges = new List();//分段对象            RangeEvents m_rangeEvents;        FontStyle m_rangeStyle;        int m_lastUpdateRangeCount = 0;//上次更新界面时分段的数量        public ColorLabelExtension(Label parent)        {            m_parent = parent;            //注册事件            m_parent.FontChanged += OnFontChanged;            m_parent.TextChanged += OnTextChanged;            m_parent.SizeChanged += OnSizeChanged;            m_parent.Paint += OnPaint;            //初始化分段控件            m_rangeEvents = new RangeEvents(m_parent);            m_subLabel = new SubLabel(m_ranges, m_rangeEvents);            m_parent.Controls.Add(m_subLabel);            //同步分段控件的设置                 m_subLabel.SuspendLayout();            // PropertiesCopy(m_subLabel,m_parent);            m_subLabel.Cursor = Cursors.Hand;           //  this.UnderLine = true;            m_subLabel.Size = m_parent.Size;            m_subLabel.Region = new Region(new Rectangle(new Point(0, 0), new Size(0, 0)));            m_subLabel.ResumeLayout();        }        //创建分段并添加,返回创建的对象,下标和长度可以超过文本长度,清除文本分段依然保留,清除分段调用Clear即可。        public Range Add(int index, int length)        {            if (index < 0 || length < 0)                throw new IndexOutOfRangeException("下标或长度不能为负!");            Range textRange = new Range(index, length, m_parent.Text);            textRange.ForeColor = m_parent.ForeColor;            textRange.PressedColor = Color.Red;            m_ranges.Add(textRange);            //回调事件            OnRangeCountChanged();            return textRange;        }        //删除分段        public void Remove(Range r)        {            if (m_ranges.Remove(r))            {                //回调事件                OnRangeCountChanged();            }        }        //清除分段        public void Clear()        {            int lastCount = m_ranges.Count;            m_ranges.Clear();            if (lastCount > 0)                //回调事件                OnRangeCountChanged();        }        //字体格式改变时        void OnRangeFontStyleChanged()        {            m_subLabel.Font = new Font(m_parent.Font, m_parent.Font.Style| m_rangeStyle);        }        //父控件字体改变时        void OnFontChanged(object sender, EventArgs e)        {             OnRangeFontStyleChanged();        }        //父控件文本改变时        void OnTextChanged(object sender, EventArgs e)        {            //获取分段文本信息            for (int i = 0; i < m_ranges.Count; i++)                m_ranges[i].ParentText = m_parent.Text;            m_subLabel.Text = m_parent.Text;        }        //尺寸改变时        void OnSizeChanged(object sender, EventArgs e)        {            m_subLabel.Size = m_parent.Size;        }        //绘制控件        void OnPaint(object sender, PaintEventArgs e)        {            //更新分段的位置            updateRangeData(e.Graphics);        }        //当分段数量改变时        void OnRangeCountChanged()        {            //如果控件为可视状态            if (isParentVisualable())            {                //更新分段数据                updateRangeData();            }        }        //控件是否可视        bool isParentVisualable()        {            return m_parent.IsHandleCreated && m_parent.Visible;        }        //计算分段显示的区域        void updateRangeData()        {            Graphics g = m_parent.CreateGraphics();            updateRangeData(g);            g.Dispose();        }        //计算分段显示的区域        void updateRangeData(Graphics g)        {            if (m_ranges.Count < 1)            {                if (m_lastUpdateRangeCount > 0)                {  //区域置0                    m_subLabel.Region = new Region(new Rectangle(new Point(0, 0), new Size(0, 0)));                    m_lastUpdateRangeCount = 0;                }                return;            }            RectangleF layoutRect = m_parent.ClientRectangle;            CharacterRange[] charRanges;            StringFormat strFormat = new StringFormat();            Region[] rangeRegions;            Region lblRegion;            int count = m_ranges.Count;            //获取分段文本信息            charRanges = m_ranges.ConvertAll(o => o.CRange).ToArray();            //测量                        strFormat.SetMeasurableCharacterRanges(charRanges);            rangeRegions = g.MeasureCharacterRanges(m_parent.Text, m_parent.Font, layoutRect, strFormat);            for (int i = 0; i < count; i++)            {                m_ranges[i].Region = rangeRegions[i];            }            //开窗            lblRegion = new Region(new Rectangle(new Point(0, 0), new Size(0, 0)));            for (int i = 0; i < count; i++)            {                lblRegion.Union(rangeRegions[i]);            }            m_subLabel.Region = lblRegion;            m_lastUpdateRangeCount = m_ranges.Count;//记录当前的分段数        }            }    //显示分段的label,用于绘制分段和捕获事件    class SubLabel : Label    {        List m_ranges;        RangeEvents m_textRangeEvents;        Range depressRange = null;//鼠标按下的分段        public SubLabel(List ranges, RangeEvents textRangeEvents)        {            m_ranges = ranges;            m_textRangeEvents = textRangeEvents;        }        protected override void OnMouseDown(MouseEventArgs e)        {            base.OnMouseDown(e);            Range range = null;            if (tryHitRange(e.Location, out range))            {                //触发事件                var tme = new RangeMouseEventArgs();                tme.Range = range;                tme.MouseEventArgs = e;                callEvent(m_textRangeEvents, "MouseDown", m_textRangeEvents.Sender, tme);                if (e.Button != MouseButtons.Left)                    return;                //按下变色 //判断边界:可视,是否被释放,鼠标位置                if (IsRangePressable(range))                {   //颜色交换                    Color temp = range.PressedColor;                    range.PressedColor = range.ForeColor;                    range.ForeColor = temp;                    depressRange = range;                    Invalidate();                }            }        }        bool IsRangePressable(Range range) {            return range.Pressabled&&this.Visible && this.IsHandleCreated && range.Region.IsVisible(this.PointToClient(Form.MousePosition));        }        protected override void OnMouseUp(MouseEventArgs e)        {            base.OnMouseUp(e);            Range range = null;            //还原按下的分段            if (depressRange != null)            {    //颜色交换                Color temp = depressRange.PressedColor;                depressRange.PressedColor = depressRange.ForeColor;                depressRange.ForeColor = temp;                depressRange = null;                Invalidate();            }            //触发事件            if (tryHitRange(e.Location, out range))            {                var tme = new RangeMouseEventArgs();                tme.Range = range;                tme.MouseEventArgs = e;                callEvent(m_textRangeEvents, "MouseUp", m_textRangeEvents.Sender, tme);            }        }        protected override void OnMouseClick(MouseEventArgs e)        {            base.OnMouseClick(e);            Range range = null;            if (tryHitRange(e.Location, out range))            {                //触发事件                var tme = new RangeMouseEventArgs();                tme.Range = range;                tme.MouseEventArgs = e;                callEvent(m_textRangeEvents, "MouseClick", m_textRangeEvents.Sender, tme);            }        }        protected override void OnClick(EventArgs e)        {            base.OnClick(e);            Range range = null;            if (tryHitRange(this.PointToClient(Form.MousePosition), out range))            {                //触发事件                var tme = new RangeMouseEventArgs();                tme.Range = range;                callEvent(m_textRangeEvents, "Click", m_textRangeEvents.Sender, tme);            }        }        //外部触发事件        void callEvent(object p_Object, string p_EventName, params object[] args)        {            System.Reflection.FieldInfo _Field = p_Object.GetType().GetField(p_EventName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static);            if (_Field == null)            {                return;            }            object _FieldValue = _Field.GetValue(p_Object);            if (_FieldValue != null && _FieldValue is Delegate)            {                Delegate _ObjectDelegate = (Delegate)_FieldValue;                _ObjectDelegate.DynamicInvoke(args);            }        }        //绘制分段(做变色处理部分)        protected override void OnPaint(PaintEventArgs e)        {            if (m_ranges.Count < 1)            {                return;            }            Graphics g = e.Graphics;                     int count = m_ranges.Count;            var hrgns = new IntPtr[count];            long[] colors = new long[count];            for (int i = 0; i < count; i++)            {    //调配颜色                colors[i] = m_ranges[i].ForeColor.ToCOLORREF() ^ this.ForeColor.ToCOLORREF();                //将Region转换为Hrgn                hrgns[i] = m_ranges[i].Region.GetHrgn(g);            }            //使用gdi画异或背景            drawXORRegions(g, hrgns, colors);            g.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), this.ClientRectangle);            drawXORRegions(g, hrgns, colors);            //释放资源            for (int i = 0; i < count; i++)            {                m_ranges[i].Region.ReleaseHrgn(hrgns[i]);            }        }        [DllImport("gdi32.dll")]        public static extern int SetROP2(IntPtr hdc, int rop2);        [DllImport("gdi32.dll")]        public static extern int DeleteObject(IntPtr ho);        [DllImport("gdi32.dll")]        public static extern IntPtr CreateSolidBrush(int color);        [DllImport("gdi32.dll ")]        public static extern int FillRgn(IntPtr hdc, IntPtr hrgn, IntPtr hbr);        //使用异或的方式绘制Region        void drawXORRegions(Graphics grp, IntPtr[] region, long[] color)        {               //取得GDI+的设备上下文            IntPtr hdc = grp.GetHdc();            // 设置光栅模式为异或            var oldRop2 = SetROP2(hdc, 7);                      int count = region.Length;            IntPtr[] brushes = new IntPtr[region.Length];            //生成各个颜色的画刷            for (int i = 0; i < count; i++)            {                brushes[i] = CreateSolidBrush((int)color[i]);            }            //开始画区域            for (int i = 0; i < count; i++)            {                FillRgn(hdc, region[i], brushes[i]);            }            //释放资源            for (int i = 0; i < count; i++)            {                DeleteObject(brushes[i]);            }            // 释放hdc            grp.ReleaseHdc(hdc);        }        //查找点所在的分段        bool tryHitRange(Point p, out Range range)        {            range = null;            for (int i = 0; i < m_ranges.Count; i++)            {                if (m_ranges[i].Region.IsVisible(p))                {                    range = m_ranges[i];                    return true;                }            }            return false;        }    }    //文本分段实体    public class Range    {        public string Text//返回分段的文本        {            get            {                if (text == null && parentText != null)                {                    text = parentText.Substring(cRange.First, cRange.Length);                }                return text;            }        }        public int Index { get { return index; } }//开始的位置        public int Length { get { return length; } }//长度        public Color ForeColor { set; get; }//颜色        public Color PressedColor { set; get; }//按下后的颜色 默认为红色        public bool Pressabled { set; get; }//是否可被按下        public object Tag { set; get; }//参见Control的Tag        string parentText;        string text;        int index;        int length;        CharacterRange cRange;        internal string ParentText //父文本        {            set            {                parentText = value;                OnParentTextChanged();            }            get { return parentText; }        }        internal Region Region { set; get; }//本段的区域        internal CharacterRange CRange//字符的范围        {            get { return cRange; }        }        //构造函数        internal Range(int index, int length, string parentText)        {            this.cRange = new CharacterRange();            this.index = index;            this.length = length;            this.ParentText = parentText;        }        //更新子文本内容及CharacterRange对象,主要做边界判断        void OnParentTextChanged()        {            if (parentText == null)            //父文本为空            {                text = null;                cRange.First = 0;                cRange.Length = 0;                return;            }            if (index >= parentText.Length)            //下标超过            {                text = "";                cRange.First = 0;                cRange.Length = 0;            }            else            {                int newLen = length;                int lastLen = parentText.Length - index;                if (length > lastLen)                    //长度超过                    newLen = lastLen;                cRange.First = index;                cRange.Length = newLen;            }        }    }    //分段的事件实体    public class RangeEvents    {        object m_sender;  //事件的sender         internal object Sender { get { return m_sender; } }        public RangeEvents(object sender)        {            m_sender = sender;        }        public event Action Click;//点击事件        public event Action MouseClick;//鼠标点击事件        public event Action MouseDown;//鼠标按下事件        public event Action MouseUp;//鼠标弹起事件    }    //分段的事件参数    public class RangeEventArgs : EventArgs    {        public Range Range { set; get; }    }    //鼠标事件参数      public class RangeMouseEventArgs : RangeEventArgs    {        public MouseEventArgs MouseEventArgs { set; get; }    }    //拓展类    static class ColorExt    {        //获取颜色值        public static long ToCOLORREF(this Color color)        {            return ((long)(((byte)(color.R) | ((long)((byte)(color.G)) << 8)) | (((long)(byte)(color.B)) << 16)));        }    }}//------------------------------到此结束--------------------------------------------------------------------------//示例        ///         /// 应用程序的主入口点。        ///         [STAThread]        static void Main()        {            string exaple = @"It's a truth universally acknowledged,that a single man in possession ofa good fortune must be in want of a wife.凡是有钱的单身汉,总想娶位太太,这已经成了一条举世公认的真理。";                       Form form = new Form();            var cLabel = new ColorLabel();            Font font = new System.Drawing.Font("Times New Roman", 13, FontStyle.Regular);            var colorEx = cLabel.ColorEx; //取得拓展对象            cLabel.Font = font;            cLabel.Text = exaple;            cLabel.Size = cLabel.PreferredSize;            //设置分段的颜色            colorEx.Add(1, 5).ForeColor = System.Drawing.Color.Orange;            colorEx.Add(8, 5).ForeColor = System.Drawing.Color.Blue;            colorEx.Add(47, 8).ForeColor = System.Drawing.Color.Green;            //设置为带下滑线            colorEx.UnderLine = true;            var range = colorEx.Add(60, 80);            range.ForeColor = System.Drawing.Color.Teal;//前景色            range.PressedColor = System.Drawing.Color.Gold;//按下后的颜色            range.Pressabled = true;//设置为可以按下              colorEx.Events.Click += (S, E) =>            {                //TODO:输入测试代码            };            colorEx.Events.MouseClick += (S, E) =>            {                //TODO:输入测试代码            };            colorEx.Events.MouseDown += (S, E) =>            {                //TODO:输入测试代码            };            colorEx.Events.MouseUp += (S, E) =>            {                MessageBox.Show(E.Range.Text);                };            form.Controls.Add(cLabel);             form.StartPosition = FormStartPosition.CenterScreen;            form.Size = form.PreferredSize;            form.ShowDialog();        }//或者可以        ///         /// 应用程序的主入口点。        ///         [STAThread]        static void Main()        {            var label = new Label();            var colorEx = new ColorLabelExtension(label); //外部拓展Label            //TODO:自定义逻辑代码                    }

其他:
限定:不兼容Label的Padding属性。



阅读全文
0 0