Upgrade Nuget
石头 authored at 2024-11-20 14:01:20
18.54 KiB
X
#if __WIN__
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using NewLife;
using NewLife.Log;
using NewLife.Reflection;
using NewLife.Threading;

namespace System.Windows.Forms;

/// <summary>控件助手</summary>
public static class ControlHelper
{
    #region 在UI线程上执行委托
    /// <summary>执行无参委托</summary>
    /// <param name="control"></param>
    /// <param name="method"></param>
    /// <returns></returns>
    public static void Invoke(this Control control, Action method)
    {
        if (control.IsDisposed) return;

        control.BeginInvoke(new Action(() =>
        {
            //using var tc = new TimeCost("Control.Invoke", 500);
            method();
        }));
    }

    ///// <summary>执行仅返回值委托</summary>
    ///// <typeparam name="TResult"></typeparam>
    ///// <param name="control"></param>
    ///// <param name="method"></param>
    ///// <returns></returns>
    //public static TResult Invoke<TResult>(this Control control, Func<TResult> method)
    //{
    //    if (control.IsDisposed) return default(TResult);

    //    return (TResult)control.Invoke(method);
    //}

    /// <summary>执行单一参数无返回值的委托</summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="control"></param>
    /// <param name="method"></param>
    /// <param name="arg"></param>
    /// <returns></returns>
    public static void Invoke<T>(this Control control, Action<T> method, T arg)
    {
        if (control.IsDisposed) return;

        control.BeginInvoke(new Action(() =>
        {
            //using var tc = new TimeCost("Control.Invoke", 500);
            method(arg);
        }));
    }

    ///// <summary>执行单一参数和返回值的委托</summary>
    ///// <typeparam name="T"></typeparam>
    ///// <typeparam name="TResult"></typeparam>
    ///// <param name="control"></param>
    ///// <param name="method"></param>
    ///// <param name="arg"></param>
    ///// <returns></returns>
    //public static TResult Invoke<T, TResult>(this Control control, Func<T, TResult> method, T arg)
    //{
    //    if (control.IsDisposed) return default(TResult);

    //    return (TResult)control.Invoke(method, arg);
    //}

    /// <summary>执行二参数无返回值的委托</summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="T2"></typeparam>
    /// <param name="control"></param>
    /// <param name="method"></param>
    /// <param name="arg"></param>
    /// <param name="arg2"></param>
    public static void Invoke<T, T2>(this Control control, Action<T, T2> method, T arg, T2 arg2)
    {
        if (control.IsDisposed) return;

        control.BeginInvoke(new Action(() =>
        {
            //using var tc = new TimeCost("Control.Invoke", 500);
            method(arg, arg2);
        }));
    }

    ///// <summary>执行二参数和返回值的委托</summary>
    ///// <typeparam name="T"></typeparam>
    ///// <typeparam name="T2"></typeparam>
    ///// <typeparam name="TResult"></typeparam>
    ///// <param name="control"></param>
    ///// <param name="method"></param>
    ///// <param name="arg"></param>
    ///// <param name="arg2"></param>
    ///// <returns></returns>
    //public static TResult Invoke<T, T2, TResult>(this Control control, Func<T, T2, TResult> method, T arg, T2 arg2)
    //{
    //    return (TResult)control.Invoke(method, arg, arg2);
    //}
    #endregion

    #region 文本控件扩展
    private static readonly Regex _line = new("(?:[^\n])\r", RegexOptions.Compiled);
    /// <summary>附加文本到文本控件末尾。主要解决非UI线程以及滚动控件等问题</summary>
    /// <param name="txt">控件</param>
    /// <param name="msg">消息</param>
    /// <param name="maxLines">最大行数。超过该行数讲清空控件</param>
    /// <returns></returns>
    public static TextBoxBase Append(this TextBoxBase txt, String msg, Int32 maxLines = 1000)
    {
        if (txt.IsDisposed) return txt;

        var func = new Action<String>(m =>
        {
            try
            {
                if (txt.Lines.Length >= maxLines) txt.Clear();

                // 记录原选择
                var selstart = txt.SelectionStart;
                var sellen = txt.SelectionLength;

                // 输出日志
                if (m != null)
                {
                    //txt.AppendText(m);
                    // 需要考虑处理特殊符号
                    //ProcessBell(ref m);
                    //ProcessBackspace(txt, ref m);
                    //ProcessReturn(txt, ref m);

                    m = m.Trim('\0');
                    // 针对非Windows系统到来的数据,处理一下换行
                    if (txt is RichTextBox && Environment.NewLine == "\r\n")
                    {
                        // 合并多个回车
                        while (m.Contains("\r\r")) m = m.Replace("\r\r", "\r");
                        //while (m.Contains("\n\r")) m = m.Replace("\n\r", "\r\n");
                        //m = m.Replace("\r\n", "<TagOfLine>");
                        m = m.Replace("\r\n", "\n");
                        //m = m.Replace("\r", "\r\n");
                        m = m.Replace("\n\r", "\n");
                        // 单独的\r换成\n
                        //if (_line.IsMatch(m))
                        //    m = _line.Replace(m, "\n");
                        m = m.Replace("\r", "\n");
                        //m = m.Replace("\r", null);
                        //m = m.Replace("<TagOfLine>", "\r\n");
                    }
                    if (String.IsNullOrEmpty(m)) return;
                    txt.AppendText(m);
                }

                // 如果有选择,则不要滚动
                if (sellen > 0)
                {
                    // 恢复选择
                    if (selstart < txt.TextLength)
                    {
                        sellen = Math.Min(sellen, txt.TextLength - selstart - 1);
                        txt.Select(selstart, sellen);
                        txt.ScrollToCaret();
                    }

                    return;
                }

                txt.Scroll();
            }
            catch { }
        });

        //txt.Invoke(func, msg);
        var ar = txt.BeginInvoke(func, msg);
        //ar.AsyncWaitHandle.WaitOne(100);
        //if (!ar.AsyncWaitHandle.WaitOne(10))
        //    txt.EndInvoke(ar);

        return txt;
    }

    /// <summary>滚动控件的滚动条</summary>
    /// <param name="txt">指定控件</param>
    /// <param name="bottom">是否底端,或者顶端</param>
    /// <returns></returns>
    public static TextBoxBase Scroll(this TextBoxBase txt, Boolean bottom = true)
    {
        if (txt.IsDisposed) return txt;

        SendMessage(txt.Handle, WM_VSCROLL, bottom ? SB_BOTTOM : SB_TOP, 0);

        return txt;
    }

    static void ProcessBackspace(TextBoxBase txt, ref String? m)
    {
        while (!m.IsNullOrEmpty())
        {
            var p = m.IndexOf('\b');
            if (p < 0) break;

            // 计算一共有多少个字符
            var count = 1;
            while (p + count < m.Length && m[p + count] == '\b') count++;

            // 前面的字符不足,消去前面历史字符
            if (p < count)
            {
                count -= p;
                // 选中最后字符,然后干掉它
                if (txt.TextLength > count)
                {
                    txt.Select(txt.TextLength - count, count);
                    txt.SelectedText = null;
                }
                else
                    txt.Clear();
            }
            else if (p > count)
            {
                // 少输出一个
                txt.AppendText(m[..(p - count)]);
            }

            if (p == m.Length - count)
            {
                m = null;
                break;
            }
            m = m[(p + count)..];
        }
    }

    /// <summary>处理回车,移到行首</summary>
    /// <param name="txt"></param>
    /// <param name="m"></param>
    static void ProcessReturn(TextBoxBase txt, ref String? m)
    {
        while (!m.IsNullOrEmpty())
        {
            var p = m.IndexOf('\r');
            if (p < 0) break;

            // 后一个是
            if (p + 1 < m.Length && m[p + 1] == '\n')
            {
                txt.AppendText(m[..(p + 2)]);
                m = m[(p + 2)..];
                continue;
            }

            // 后一个不是\n,移到行首
            if (p >= 0)
            {
                // 取得最后一行首字符索引
                var lines = txt.Lines.Length;
                var last = lines <= 1 ? 0 : txt.GetFirstCharIndexFromLine(lines - 1);
                if (last >= 0)
                {
                    // 最后一行第一个字符,删掉整行
                    txt.Select(last, txt.TextLength - last);
                    txt.SelectedText = null;
                }
            }

            if (p + 1 == m.Length)
            {
                m = null;
                break;
            }
            m = m[(p + 1)..];
        }
    }

    static void ProcessBell(ref String m)
    {
        var ch = (Char)7;
        var p = 0;
        while (true)
        {
            p = m.IndexOf(ch, p);
            if (p < 0) break;

            if (p > 0)
            {
                var str = m[..p];
                if (p + 1 < m.Length) str += m[(p + 1)..];
                m = str;
            }

            //Console.Beep();
            // 用定时器来控制Beep,避免被堵塞
            _timer ??= new TimerX(Bell, null, 100, 100);
            _Beep = true;
            //SystemSounds.Beep.Play();
            p++;
        }
    }

    static TimerX? _timer;
    static Boolean _Beep;
    static void Bell(Object? state)
    {
        if (_Beep)
        {
            _Beep = false;
            Console.Beep();
        }
    }

    [DllImport("user32.dll")]
    static extern Int32 SendMessage(IntPtr hwnd, Int32 wMsg, Int32 wParam, Int32 lParam);
    private const Int32 SB_TOP = 6;
    private const Int32 SB_BOTTOM = 7;
    private const Int32 WM_VSCROLL = 0x115;
    #endregion

    #region 设置控件样式
    /// <summary>设置默认样式,包括字体、前景色、背景色</summary>
    /// <param name="control">控件</param>
    /// <param name="size">字体大小</param>
    /// <returns></returns>
    public static Control SetDefaultStyle(this Control control, Int32 size = 10)
    {
        control.SetFontSize(size);
        control.ForeColor = Color.White;
        control.BackColor = Color.FromArgb(42, 33, 28);
        return control;
    }

    /// <summary>设置字体大小</summary>
    /// <param name="control"></param>
    /// <param name="size"></param>
    /// <returns></returns>
    public static Control SetFontSize(this Control control, Int32 size = 10)
    {
        control.Font = new Font(control.Font.FontFamily, size, GraphicsUnit.Point);
        return control;
    }
    #endregion

    #region 文本控件着色
    //Int32 _pColor = 0;
    static readonly Color _Key = Color.FromArgb(255, 170, 0);
    static readonly Color _Num = Color.FromArgb(255, 58, 131);
    static readonly Color _KeyName = Color.FromArgb(0, 255, 255);

    static readonly String[] _Keys = [
        "(", ")", "{", "}", "[", "]", "*", "->", "+", "-", "*", "/", "\\", "%", "&", "|", "!", "=", ";", ",", ">", "<",
        "void", "new", "delete", "true", "false"
    ];

    /// <summary>采用默认着色方案进行着色</summary>
    /// <param name="rtb">文本控件</param>
    /// <param name="start">开始位置</param>
    public static Int32 ColourDefault(this RichTextBox rtb, Int32 start)
    {
        if (start > rtb.TextLength) start = 0;
        if (start == rtb.TextLength) return start;

        // 有选择时不着色
        if (rtb.SelectionLength > 0) return start;

        //var color = Color.Yellow;
        //var color = Color.FromArgb(255, 170, 0);
        //ChangeColor("Send", color);
        foreach (var item in _Keys)
        {
            ChangeColor(rtb, start, item, _Key);
        }

        ChangeCppColor(rtb, start);
        ChangeKeyNameColor(rtb, start);
        ChangeNumColor(rtb, start);

        // 移到最后,避免瞬间有字符串写入,所以减去100
        start = rtb.TextLength;
        if (start < 0) start = 0;

        return start;
    }

    static void ChangeColor(RichTextBox rtb, Int32 start, String text, Color color)
    {
        var s = start;
        //while ((-1 + text.Length - 1) != (s = text.Length - 1 + rtx.Find(text, s, -1, RichTextBoxFinds.WholeWord)))
        while (true)
        {
            if (s >= rtb.TextLength) break;
            s = rtb.Find(text, s, -1, RichTextBoxFinds.WholeWord);
            if (s < 0) break;
            if (s > rtb.TextLength - 1) break;
            s++;

            rtb.SelectionColor = color;
            //rtx.SelectionFont = new Font(rtx.SelectionFont.FontFamily, rtx.SelectionFont.Size, FontStyle.Bold);
        }
        //rtx.Select(0, 0);
        rtb.SelectionLength = 0;
    }

    // 正则匹配,数字开头的词。支持0x开头的十六进制
    static readonly Regex _reg = new(@"(?i)\b(0x|[0-9])([0-9a-fA-F\-]*)(.*?)\b", RegexOptions.Compiled);
    static void ChangeNumColor(RichTextBox rtb, Int32 start)
    {
        //var ms = _reg.Matches(rtb.Text, start);
        //foreach (Match item in ms)
        //{
        //    rtb.Select(item.Groups[1].Index, item.Groups[1].Length);
        //    rtb.SelectionColor = _Num;

        //    rtb.Select(item.Groups[2].Index, item.Groups[2].Length);
        //    rtb.SelectionColor = _Num;

        //    rtb.Select(item.Groups[3].Index, item.Groups[3].Length);
        //    rtb.SelectionColor = _Key;
        //}
        //rtb.SelectionLength = 0;

        rtb.Colour(_reg, start, _Num, _Num, _Key);
    }

    static readonly Regex _reg2 = new(@"(?i)(\b\w+\b)(\s*::\s*)(\b\w+\b)", RegexOptions.Compiled);
    /// <summary>改变C++类名方法名颜色</summary>
    static void ChangeCppColor(RichTextBox rtb, Int32 start)
    {
        var color = Color.FromArgb(30, 154, 224);
        var color3 = Color.FromArgb(85, 228, 57);

        //var ms = _reg2.Matches(rtx.Text, start);
        //foreach (Match item in ms)
        //{
        //    rtx.Select(item.Groups[1].Index, item.Groups[1].Length);
        //    rtx.SelectionColor = color;

        //    rtx.Select(item.Groups[2].Index, item.Groups[2].Length);
        //    rtx.SelectionColor = _Key;

        //    rtx.Select(item.Groups[3].Index, item.Groups[3].Length);
        //    rtx.SelectionColor = color3;
        //}
        //rtx.SelectionLength = 0;

        rtb.Colour(_reg2, start, color, _Key, color3);
    }

    static readonly Regex _reg3 = new(@"(?i)(\b\w+\b)(\s*[=:])[^:]\s*", RegexOptions.Compiled);
    static void ChangeKeyNameColor(RichTextBox rtb, Int32 start)
    {
        //var ms = _reg3.Matches(rtx.Text, _pColor);
        //foreach (Match item in ms)
        //{
        //    rtx.Select(item.Groups[1].Index, item.Groups[1].Length);
        //    rtx.SelectionColor = _KeyName;

        //    rtx.Select(item.Groups[2].Index, item.Groups[2].Length);
        //    rtx.SelectionColor = _Key;
        //}
        //rtx.SelectionLength = 0;

        rtb.Colour(_reg3, start, _KeyName, _Key);
    }

    /// <summary>着色文本控件的内容</summary>
    /// <param name="rtb">文本控件</param>
    /// <param name="reg">正则表达式</param>
    /// <param name="start">开始位置</param>
    /// <param name="colors">颜色数组</param>
    /// <returns></returns>
    public static RichTextBox Colour(this RichTextBox rtb, Regex reg, Int32 start, params Color[] colors)
    {
        var ms = reg.Matches(rtb.Text, start);
        foreach (Match item in ms)
        {
            //rtx.Select(item.Groups[1].Index, item.Groups[1].Length);
            //rtx.SelectionColor = _KeyName;

            //rtx.Select(item.Groups[2].Index, item.Groups[2].Length);
            //rtx.SelectionColor = _Key;

            // 如果没有匹配组,说明作为整体着色
            if (item.Groups.Count <= 1)
            {
                rtb.Select(item.Groups[0].Index, item.Groups[0].Length);
                rtb.SelectionColor = colors[0];
            }
            else
            {
                // 遍历匹配组,注意0号代表整体
                for (var i = 1; i < item.Groups.Count; i++)
                {
                    rtb.Select(item.Groups[i].Index, item.Groups[i].Length);
                    rtb.SelectionColor = colors[i - 1];
                }
            }
        }
        rtb.SelectionLength = 0;

        return rtb;
    }

    /// <summary>着色文本控件的内容</summary>
    /// <param name="rtb">文本控件</param>
    /// <param name="reg">正则表达式</param>
    /// <param name="start">开始位置</param>
    /// <param name="colors">颜色数组</param>
    /// <returns></returns>
    public static RichTextBox Colour(this RichTextBox rtb, String reg, Int32 start, params Color[] colors)
    {
        var r = new Regex(reg, RegexOptions.Compiled);
        return Colour(rtb, r, start, colors);
    }
    #endregion

    #region DPI修复
    /// <summary>当前Dpi</summary>
    public static Int32 Dpi { get; set; }

    /// <summary>修正ListView的Dpi</summary>
    /// <param name="lv"></param>
    public static void FixDpi(this ListView lv)
    {
        if (Dpi == 0) Dpi = (Int32)lv.CreateGraphics().DpiX;

        foreach (ColumnHeader item in lv.Columns)
        {
            item.Width *= Dpi / 96;
        }
    }

    /// <summary>修正窗体的Dpi</summary>
    /// <param name="frm"></param>
    public static void FixDpi(this Form frm)
    {
        // 只要重新设置一次字体,就可以适配高Dpi,不晓得为啥
        frm.Font = new Font("宋体", 9F, FontStyle.Regular, GraphicsUnit.Point, 134);

        foreach (var fi in frm.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
        {
            if (fi.FieldType == typeof(ListView) && frm.GetValue(fi) is ListView lv)
                lv.FixDpi();
        }
    }
    #endregion
}
#endif