448 lines
15 KiB
C#
448 lines
15 KiB
C#
using AntdUI;
|
|||
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.ComponentModel;
|
||
|
|
using System.Diagnostics;
|
||
|
|
using System.Drawing;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Runtime.InteropServices;
|
||
|
|
using System.Text;
|
||
|
|
using System.Threading.Tasks;
|
||
|
|
using System.Windows.Forms;
|
||
|
|
|
||
|
|
namespace JinYuan.ControlLib
|
||
|
|
{
|
||
|
|
|
||
|
|
public partial class CustomRichTextBox : RichTextBox
|
||
|
|
{
|
||
|
|
private Color darkBackColor = Color.Black;
|
||
|
|
private Color lightBackColor = Color.White;
|
||
|
|
private Color darkForeColor = Color.White;
|
||
|
|
private Color lightForeColor = Color.Black;
|
||
|
|
private Color darkBorderColor = Color.FromArgb(64, 64, 64);
|
||
|
|
private Color lightBorderColor = Color.FromArgb(217, 217, 217);
|
||
|
|
private bool isDarkTheme;
|
||
|
|
private const int BatchSize = 5000;
|
||
|
|
private bool isUpdatingTheme = false;
|
||
|
|
private bool isInitialized = false;
|
||
|
|
|
||
|
|
public CustomRichTextBox()
|
||
|
|
{
|
||
|
|
BorderStyle = BorderStyle.None;
|
||
|
|
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
|
||
|
|
|
||
|
|
// 在句柄创建后初始化主题
|
||
|
|
HandleCreated += CustomRichTextBox_HandleCreated;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void CustomRichTextBox_HandleCreated(object sender, EventArgs e)
|
||
|
|
{
|
||
|
|
if (!isInitialized)
|
||
|
|
{
|
||
|
|
isInitialized = true;
|
||
|
|
InitializeThemeAsync();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async void InitializeThemeAsync()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await UpdateThemeAsync(Config.IsDark);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
System.Diagnostics.Debug.WriteLine($"Theme initialization failed: {ex.Message}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task UpdateThemeAsync(bool isDark)
|
||
|
|
{
|
||
|
|
if (isDarkTheme == isDark || isUpdatingTheme || !IsHandleCreated) return;
|
||
|
|
|
||
|
|
isUpdatingTheme = true;
|
||
|
|
isDarkTheme = isDark;
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
if (InvokeRequired)
|
||
|
|
{
|
||
|
|
await Task.Run(() =>
|
||
|
|
{
|
||
|
|
Invoke((MethodInvoker)(() => UpdateThemeInternal()));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
UpdateThemeInternal();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
System.Diagnostics.Debug.WriteLine($"Theme update failed: {ex.Message}");
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
isUpdatingTheme = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateThemeInternal()
|
||
|
|
{
|
||
|
|
if (!IsHandleCreated) return;
|
||
|
|
|
||
|
|
BackColor = isDarkTheme ? darkBackColor : lightBackColor;
|
||
|
|
ForeColor = isDarkTheme ? darkForeColor : lightForeColor;
|
||
|
|
|
||
|
|
SuspendLayout();
|
||
|
|
try
|
||
|
|
{
|
||
|
|
int visibleStart = GetCharIndexFromPosition(new Point(0, 0));
|
||
|
|
int visibleEnd = GetCharIndexFromPosition(new Point(ClientSize.Width, ClientSize.Height));
|
||
|
|
|
||
|
|
// Update visible text first
|
||
|
|
UpdateTextColors(visibleStart, visibleEnd - visibleStart);
|
||
|
|
|
||
|
|
// Then update the rest in batches
|
||
|
|
for (int i = 0; i < TextLength; i += BatchSize)
|
||
|
|
{
|
||
|
|
if (i >= visibleStart && i < visibleEnd) continue;
|
||
|
|
int length = Math.Min(BatchSize, TextLength - i);
|
||
|
|
UpdateTextColors(i, length);
|
||
|
|
if (i % (BatchSize * 10) == 0) Application.DoEvents();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
ResumeLayout();
|
||
|
|
}
|
||
|
|
|
||
|
|
Invalidate();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateTextColors(int start, int length)
|
||
|
|
{
|
||
|
|
if (start < 0 || length <= 0 || start + length > TextLength) return;
|
||
|
|
|
||
|
|
Select(start, length);
|
||
|
|
SelectionColor = ForeColor;
|
||
|
|
SelectionBackColor = BackColor;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void OnPaint(PaintEventArgs e)
|
||
|
|
{
|
||
|
|
base.OnPaint(e);
|
||
|
|
using (var pen = new Pen(isDarkTheme ? darkBorderColor : lightBorderColor))
|
||
|
|
{
|
||
|
|
e.Graphics.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void OnTextChanged(EventArgs e)
|
||
|
|
{
|
||
|
|
base.OnTextChanged(e);
|
||
|
|
|
||
|
|
if (!isUpdatingTheme && TextLength > 0 && IsHandleCreated)
|
||
|
|
{
|
||
|
|
int start = TextLength - 1;
|
||
|
|
int length = 1;
|
||
|
|
if (start < 0) start = 0;
|
||
|
|
if (start + length > TextLength) length = TextLength - start;
|
||
|
|
|
||
|
|
UpdateTextColors(start, length);
|
||
|
|
SelectionLength = 0;
|
||
|
|
SelectionStart = TextLength;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[Browsable(true)]
|
||
|
|
[Category("Appearance")]
|
||
|
|
[DefaultValue(typeof(Color), "30, 30, 30")]
|
||
|
|
public Color DarkBackColor
|
||
|
|
{
|
||
|
|
get => darkBackColor;
|
||
|
|
set
|
||
|
|
{
|
||
|
|
if (darkBackColor != value)
|
||
|
|
{
|
||
|
|
darkBackColor = value;
|
||
|
|
if (isDarkTheme && IsHandleCreated) SafeRefreshTheme();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[Browsable(true)]
|
||
|
|
[Category("Appearance")]
|
||
|
|
[DefaultValue(typeof(Color), "White")]
|
||
|
|
public Color LightBackColor
|
||
|
|
{
|
||
|
|
get => lightBackColor;
|
||
|
|
set
|
||
|
|
{
|
||
|
|
if (lightBackColor != value)
|
||
|
|
{
|
||
|
|
lightBackColor = value;
|
||
|
|
if (!isDarkTheme && IsHandleCreated) SafeRefreshTheme();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[Browsable(true)]
|
||
|
|
[Category("Appearance")]
|
||
|
|
[DefaultValue(typeof(Color), "White")]
|
||
|
|
public Color DarkForeColor
|
||
|
|
{
|
||
|
|
get => darkForeColor;
|
||
|
|
set
|
||
|
|
{
|
||
|
|
if (darkForeColor != value)
|
||
|
|
{
|
||
|
|
darkForeColor = value;
|
||
|
|
if (isDarkTheme && IsHandleCreated) SafeRefreshTheme();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[Browsable(true)]
|
||
|
|
[Category("Appearance")]
|
||
|
|
[DefaultValue(typeof(Color), "Black")]
|
||
|
|
public Color LightForeColor
|
||
|
|
{
|
||
|
|
get => lightForeColor;
|
||
|
|
set
|
||
|
|
{
|
||
|
|
if (lightForeColor != value)
|
||
|
|
{
|
||
|
|
lightForeColor = value;
|
||
|
|
if (!isDarkTheme && IsHandleCreated) SafeRefreshTheme();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[Browsable(true)]
|
||
|
|
[Category("Appearance")]
|
||
|
|
[DefaultValue(typeof(Color), "64, 64, 64")]
|
||
|
|
public Color DarkBorderColor
|
||
|
|
{
|
||
|
|
get => darkBorderColor;
|
||
|
|
set
|
||
|
|
{
|
||
|
|
if (darkBorderColor != value)
|
||
|
|
{
|
||
|
|
darkBorderColor = value;
|
||
|
|
if (isDarkTheme && IsHandleCreated) Invalidate();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[Browsable(true)]
|
||
|
|
[Category("Appearance")]
|
||
|
|
[DefaultValue(typeof(Color), "217, 217, 217")]
|
||
|
|
public Color LightBorderColor
|
||
|
|
{
|
||
|
|
get => lightBorderColor;
|
||
|
|
set
|
||
|
|
{
|
||
|
|
if (lightBorderColor != value)
|
||
|
|
{
|
||
|
|
lightBorderColor = value;
|
||
|
|
if (!isDarkTheme && IsHandleCreated) Invalidate();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async void SafeRefreshTheme()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await RefreshThemeAsync();
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
System.Diagnostics.Debug.WriteLine($"Theme refresh failed: {ex.Message}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task RefreshThemeAsync()
|
||
|
|
{
|
||
|
|
if (!IsHandleCreated) return;
|
||
|
|
await UpdateThemeAsync(isDarkTheme);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//public partial class CustomRichTextBox : RichTextBox
|
||
|
|
//{
|
||
|
|
// private Color darkBackColor = Color.Black;
|
||
|
|
// private Color lightBackColor = Color.White;
|
||
|
|
// private Color darkForeColor = Color.White;
|
||
|
|
// private Color lightForeColor = Color.Black;
|
||
|
|
// private Color darkBorderColor = Color.FromArgb(64, 64, 64);
|
||
|
|
// private Color lightBorderColor = Color.FromArgb(217, 217, 217);
|
||
|
|
// private bool isDarkTheme;
|
||
|
|
|
||
|
|
// private const int BatchSize = 5000; // 每批处理的字符数
|
||
|
|
// private bool isUpdatingTheme = false;
|
||
|
|
|
||
|
|
// public CustomRichTextBox()
|
||
|
|
// {
|
||
|
|
// BorderStyle = BorderStyle.None;
|
||
|
|
// SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
|
||
|
|
|
||
|
|
// InitializeThemeAsync();
|
||
|
|
// }
|
||
|
|
|
||
|
|
|
||
|
|
// private async void InitializeThemeAsync()
|
||
|
|
// {
|
||
|
|
// await UpdateThemeAsync(Config.IsDark);
|
||
|
|
// }
|
||
|
|
|
||
|
|
// public async Task UpdateThemeAsync(bool isDark)
|
||
|
|
// {
|
||
|
|
// if (isDarkTheme == isDark || isUpdatingTheme) return;
|
||
|
|
|
||
|
|
// isUpdatingTheme = true;
|
||
|
|
// isDarkTheme = isDark;
|
||
|
|
|
||
|
|
// try
|
||
|
|
// {
|
||
|
|
// await Task.Run(async () =>
|
||
|
|
// {
|
||
|
|
// await InvokeAsync(() =>
|
||
|
|
// {
|
||
|
|
// BackColor = isDark ? darkBackColor : lightBackColor;
|
||
|
|
// ForeColor = isDark ? darkForeColor : lightForeColor;
|
||
|
|
|
||
|
|
// SuspendLayout();
|
||
|
|
// try
|
||
|
|
// {
|
||
|
|
// int visibleStart = GetCharIndexFromPosition(new Point(0, 0));
|
||
|
|
// int visibleEnd = GetCharIndexFromPosition(new Point(ClientSize.Width, ClientSize.Height));
|
||
|
|
|
||
|
|
// // Update visible text first
|
||
|
|
// UpdateTextColors(visibleStart, visibleEnd - visibleStart);
|
||
|
|
|
||
|
|
// // Then update the rest in batches
|
||
|
|
// for (int i = 0; i < TextLength; i += BatchSize)
|
||
|
|
// {
|
||
|
|
// if (i >= visibleStart && i < visibleEnd) continue; // Skip already updated visible part
|
||
|
|
// int length = Math.Min(BatchSize, TextLength - i);
|
||
|
|
// UpdateTextColors(i, length);
|
||
|
|
// if (i % (BatchSize * 10) == 0) Application.DoEvents();
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
// finally
|
||
|
|
// {
|
||
|
|
// ResumeLayout();
|
||
|
|
// }
|
||
|
|
|
||
|
|
// Invalidate();
|
||
|
|
// });
|
||
|
|
// });
|
||
|
|
// }
|
||
|
|
// finally
|
||
|
|
// {
|
||
|
|
// isUpdatingTheme = false;
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// private void UpdateTextColors(int start, int length)
|
||
|
|
// {
|
||
|
|
// Select(start, length);
|
||
|
|
// SelectionColor = ForeColor;
|
||
|
|
// SelectionBackColor = BackColor;
|
||
|
|
// }
|
||
|
|
|
||
|
|
// protected override void OnPaint(PaintEventArgs e)
|
||
|
|
// {
|
||
|
|
// base.OnPaint(e);
|
||
|
|
// using (var pen = new Pen(isDarkTheme ? darkBorderColor : lightBorderColor))
|
||
|
|
// {
|
||
|
|
// e.Graphics.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// protected override void OnTextChanged(EventArgs e)
|
||
|
|
// {
|
||
|
|
// base.OnTextChanged(e);
|
||
|
|
|
||
|
|
// if (!isUpdatingTheme && TextLength > 0)
|
||
|
|
// {
|
||
|
|
// int start = TextLength - 1;
|
||
|
|
// int length = 1;
|
||
|
|
// if (start < 0) start = 0;
|
||
|
|
// if (start + length > TextLength) length = TextLength - start;
|
||
|
|
|
||
|
|
// UpdateTextColors(start, length);
|
||
|
|
// SelectionLength = 0;
|
||
|
|
// SelectionStart = TextLength;
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// [Browsable(true)]
|
||
|
|
// [Category("Appearance")]
|
||
|
|
// [DefaultValue(typeof(Color), "30, 30, 30")]
|
||
|
|
// public Color DarkBackColor
|
||
|
|
// {
|
||
|
|
// get => darkBackColor;
|
||
|
|
// set { darkBackColor = value; if (isDarkTheme) RefreshThemeAsync(); }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// [Browsable(true)]
|
||
|
|
// [Category("Appearance")]
|
||
|
|
// [DefaultValue(typeof(Color), "White")]
|
||
|
|
// public Color LightBackColor
|
||
|
|
// {
|
||
|
|
// get => lightBackColor;
|
||
|
|
// set { lightBackColor = value; if (!isDarkTheme) RefreshThemeAsync(); }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// [Browsable(true)]
|
||
|
|
// [Category("Appearance")]
|
||
|
|
// [DefaultValue(typeof(Color), "White")]
|
||
|
|
// public Color DarkForeColor
|
||
|
|
// {
|
||
|
|
// get => darkForeColor;
|
||
|
|
// set { darkForeColor = value; if (isDarkTheme) RefreshThemeAsync(); }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// [Browsable(true)]
|
||
|
|
// [Category("Appearance")]
|
||
|
|
// [DefaultValue(typeof(Color), "Black")]
|
||
|
|
// public Color LightForeColor
|
||
|
|
// {
|
||
|
|
// get => lightForeColor;
|
||
|
|
// set { lightForeColor = value; if (!isDarkTheme) RefreshThemeAsync(); }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// [Browsable(true)]
|
||
|
|
// [Category("Appearance")]
|
||
|
|
// [DefaultValue(typeof(Color), "64, 64, 64")]
|
||
|
|
// public Color DarkBorderColor
|
||
|
|
// {
|
||
|
|
// get => darkBorderColor;
|
||
|
|
// set { darkBorderColor = value; if (isDarkTheme) Invalidate(); }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// [Browsable(true)]
|
||
|
|
// [Category("Appearance")]
|
||
|
|
// [DefaultValue(typeof(Color), "217, 217, 217")]
|
||
|
|
// public Color LightBorderColor
|
||
|
|
// {
|
||
|
|
// get => lightBorderColor;
|
||
|
|
// set { lightBorderColor = value; if (!isDarkTheme) Invalidate(); }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// public async Task RefreshThemeAsync()
|
||
|
|
// {
|
||
|
|
// await UpdateThemeAsync(isDarkTheme);
|
||
|
|
// }
|
||
|
|
|
||
|
|
// private Task InvokeAsync(Action action)
|
||
|
|
// {
|
||
|
|
// return Task.Factory.FromAsync(BeginInvoke(action), EndInvoke);
|
||
|
|
// }
|
||
|
|
//}
|