Commit c0c9960c authored by 段廷瑞's avatar 段廷瑞

feat: 文本对比功能

parent a5970404
......@@ -368,6 +368,12 @@
<Compile Include="UI\wTaskBoard.Designer.cs">
<DependentUpon>wTaskBoard.cs</DependentUpon>
</Compile>
<Compile Include="UI\wTextDiff.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\wTextDiff.Designer.cs">
<DependentUpon>wTextDiff.cs</DependentUpon>
</Compile>
<Compile Include="UI\wTextFormatter.cs">
<SubType>Form</SubType>
</Compile>
......@@ -533,6 +539,9 @@
<EmbeddedResource Include="UI\wTaskBoard.resx">
<DependentUpon>wTaskBoard.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\wTextDiff.resx">
<DependentUpon>wTextDiff.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\wTextFormatter.resx">
<DependentUpon>wTextFormatter.cs</DependentUpon>
</EmbeddedResource>
......
namespace Tools.UI
{
partial class wTextDiff
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tabControl = new System.Windows.Forms.TabControl();
this.tabCompare = new System.Windows.Forms.TabPage();
this.tlpCompare = new System.Windows.Forms.TableLayoutPanel();
this.panelToolbar = new System.Windows.Forms.Panel();
this.lblShortcut = new System.Windows.Forms.Label();
this.btnSideBySide = new System.Windows.Forms.Button();
this.btnTopBottom = new System.Windows.Forms.Button();
this.btnCompare = new System.Windows.Forms.Button();
this.btnClear = new System.Windows.Forms.Button();
this.splitContainer = new System.Windows.Forms.SplitContainer();
this.panelLeft = new System.Windows.Forms.Panel();
this.rtbOriginal = new Tools.UI.ScrollSyncRichTextBox();
this.panelLeftHeader = new System.Windows.Forms.Panel();
this.lblOriginal = new System.Windows.Forms.Label();
this.btnCopyLeft = new System.Windows.Forms.Button();
this.panelRight = new System.Windows.Forms.Panel();
this.rtbModified = new Tools.UI.ScrollSyncRichTextBox();
this.panelRightHeader = new System.Windows.Forms.Panel();
this.lblModified = new System.Windows.Forms.Label();
this.btnCopyRight = new System.Windows.Forms.Button();
this.panelStats = new System.Windows.Forms.Panel();
this.lblStats = new System.Windows.Forms.Label();
this.tabUnified = new System.Windows.Forms.TabPage();
this.tlpUnified = new System.Windows.Forms.TableLayoutPanel();
this.panelUnifiedToolbar = new System.Windows.Forms.Panel();
this.lblUnifiedTitle = new System.Windows.Forms.Label();
this.btnCopyUnified = new System.Windows.Forms.Button();
this.rtbUnifiedDiff = new System.Windows.Forms.RichTextBox();
this.tabControl.SuspendLayout();
this.tabCompare.SuspendLayout();
this.tlpCompare.SuspendLayout();
this.panelToolbar.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
this.splitContainer.Panel1.SuspendLayout();
this.splitContainer.Panel2.SuspendLayout();
this.splitContainer.SuspendLayout();
this.panelLeft.SuspendLayout();
this.panelLeftHeader.SuspendLayout();
this.panelRight.SuspendLayout();
this.panelRightHeader.SuspendLayout();
this.panelStats.SuspendLayout();
this.tabUnified.SuspendLayout();
this.tlpUnified.SuspendLayout();
this.panelUnifiedToolbar.SuspendLayout();
this.SuspendLayout();
//
// tabControl
//
this.tabControl.Controls.Add(this.tabCompare);
this.tabControl.Controls.Add(this.tabUnified);
this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl.Location = new System.Drawing.Point(0, 0);
this.tabControl.Name = "tabControl";
this.tabControl.SelectedIndex = 0;
this.tabControl.Size = new System.Drawing.Size(900, 620);
this.tabControl.TabIndex = 0;
//
// tabCompare
//
this.tabCompare.Controls.Add(this.tlpCompare);
this.tabCompare.Location = new System.Drawing.Point(4, 26);
this.tabCompare.Name = "tabCompare";
this.tabCompare.Padding = new System.Windows.Forms.Padding(3);
this.tabCompare.Size = new System.Drawing.Size(892, 590);
this.tabCompare.TabIndex = 0;
this.tabCompare.Text = "文本对比";
this.tabCompare.UseVisualStyleBackColor = true;
//
// tlpCompare
//
this.tlpCompare.ColumnCount = 1;
this.tlpCompare.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpCompare.Controls.Add(this.panelToolbar, 0, 1);
this.tlpCompare.Controls.Add(this.splitContainer, 0, 2);
this.tlpCompare.Controls.Add(this.panelStats, 0, 3);
this.tlpCompare.Dock = System.Windows.Forms.DockStyle.Fill;
this.tlpCompare.Location = new System.Drawing.Point(3, 3);
this.tlpCompare.Name = "tlpCompare";
this.tlpCompare.Padding = new System.Windows.Forms.Padding(6, 3, 6, 6);
this.tlpCompare.RowCount = 5;
this.tlpCompare.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 3F));
this.tlpCompare.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 34F));
this.tlpCompare.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpCompare.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tlpCompare.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 3F));
this.tlpCompare.Size = new System.Drawing.Size(886, 584);
this.tlpCompare.TabIndex = 0;
//
// panelToolbar
//
this.panelToolbar.Controls.Add(this.btnSideBySide);
this.panelToolbar.Controls.Add(this.btnTopBottom);
this.panelToolbar.Controls.Add(this.lblShortcut);
this.panelToolbar.Controls.Add(this.btnCompare);
this.panelToolbar.Controls.Add(this.btnClear);
this.panelToolbar.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelToolbar.Location = new System.Drawing.Point(9, 6);
this.panelToolbar.Name = "panelToolbar";
this.panelToolbar.Size = new System.Drawing.Size(868, 28);
this.panelToolbar.TabIndex = 0;
//
// btnSideBySide
//
this.btnSideBySide.Location = new System.Drawing.Point(0, 2);
this.btnSideBySide.Name = "btnSideBySide";
this.btnSideBySide.Size = new System.Drawing.Size(90, 25);
this.btnSideBySide.TabIndex = 1;
this.btnSideBySide.Text = "⬅ ➡ 左右布局";
this.btnSideBySide.UseVisualStyleBackColor = true;
this.btnSideBySide.Click += new System.EventHandler(this.btnSideBySide_Click);
//
// btnTopBottom
//
this.btnTopBottom.Location = new System.Drawing.Point(96, 2);
this.btnTopBottom.Name = "btnTopBottom";
this.btnTopBottom.Size = new System.Drawing.Size(90, 25);
this.btnTopBottom.TabIndex = 2;
this.btnTopBottom.Text = "⬆ ⬇ 上下布局";
this.btnTopBottom.UseVisualStyleBackColor = true;
this.btnTopBottom.Click += new System.EventHandler(this.btnTopBottom_Click);
//
// lblShortcut
//
this.lblShortcut.AutoSize = true;
this.lblShortcut.ForeColor = System.Drawing.SystemColors.GrayText;
this.lblShortcut.Location = new System.Drawing.Point(195, 6);
this.lblShortcut.Name = "lblShortcut";
this.lblShortcut.Size = new System.Drawing.Size(109, 17);
this.lblShortcut.TabIndex = 5;
this.lblShortcut.Text = "Ctrl+Enter 对比";
this.lblShortcut.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// btnCompare
//
this.btnCompare.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCompare.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnCompare.Location = new System.Drawing.Point(698, 2);
this.btnCompare.Name = "btnCompare";
this.btnCompare.Size = new System.Drawing.Size(75, 25);
this.btnCompare.TabIndex = 3;
this.btnCompare.Text = "对比";
this.btnCompare.UseVisualStyleBackColor = true;
this.btnCompare.Click += new System.EventHandler(this.btnCompare_Click);
//
// btnClear
//
this.btnClear.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnClear.Location = new System.Drawing.Point(779, 2);
this.btnClear.Name = "btnClear";
this.btnClear.Size = new System.Drawing.Size(75, 25);
this.btnClear.TabIndex = 4;
this.btnClear.Text = "清空";
this.btnClear.UseVisualStyleBackColor = true;
this.btnClear.Click += new System.EventHandler(this.btnClear_Click);
//
// splitContainer
//
this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer.Location = new System.Drawing.Point(9, 40);
this.splitContainer.Name = "splitContainer";
//
// splitContainer.Panel1
//
this.splitContainer.Panel1.Controls.Add(this.panelLeft);
this.splitContainer.Panel1.Padding = new System.Windows.Forms.Padding(0);
//
// splitContainer.Panel2
//
this.splitContainer.Panel2.Controls.Add(this.panelRight);
this.splitContainer.Panel2.Padding = new System.Windows.Forms.Padding(0);
this.splitContainer.Size = new System.Drawing.Size(868, 508);
this.splitContainer.SplitterDistance = 434;
this.splitContainer.SplitterWidth = 6;
this.splitContainer.TabIndex = 1;
//
// panelLeft
//
this.panelLeft.Controls.Add(this.rtbOriginal);
this.panelLeft.Controls.Add(this.panelLeftHeader);
this.panelLeft.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelLeft.Location = new System.Drawing.Point(0, 0);
this.panelLeft.Name = "panelLeft";
this.panelLeft.Size = new System.Drawing.Size(434, 508);
this.panelLeft.TabIndex = 0;
//
// rtbOriginal
//
this.rtbOriginal.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.rtbOriginal.DetectUrls = false;
this.rtbOriginal.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtbOriginal.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtbOriginal.Name = "rtbOriginal";
this.rtbOriginal.Size = new System.Drawing.Size(434, 478);
this.rtbOriginal.TabIndex = 1;
this.rtbOriginal.Text = "";
this.rtbOriginal.WordWrap = false;
this.rtbOriginal.ScrollChanged += new System.EventHandler(this.rtbOriginal_VScroll);
//
// panelLeftHeader
//
this.panelLeftHeader.Controls.Add(this.lblOriginal);
this.panelLeftHeader.Controls.Add(this.btnCopyLeft);
this.panelLeftHeader.Dock = System.Windows.Forms.DockStyle.Top;
this.panelLeftHeader.Location = new System.Drawing.Point(0, 0);
this.panelLeftHeader.Name = "panelLeftHeader";
this.panelLeftHeader.Size = new System.Drawing.Size(434, 26);
this.panelLeftHeader.TabIndex = 0;
//
// lblOriginal
//
this.lblOriginal.AutoSize = true;
this.lblOriginal.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblOriginal.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.lblOriginal.Location = new System.Drawing.Point(0, 4);
this.lblOriginal.Name = "lblOriginal";
this.lblOriginal.Size = new System.Drawing.Size(68, 17);
this.lblOriginal.TabIndex = 0;
this.lblOriginal.Text = "原始文本:";
this.lblOriginal.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// btnCopyLeft
//
this.btnCopyLeft.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCopyLeft.Location = new System.Drawing.Point(368, 1);
this.btnCopyLeft.Name = "btnCopyLeft";
this.btnCopyLeft.Size = new System.Drawing.Size(65, 24);
this.btnCopyLeft.TabIndex = 1;
this.btnCopyLeft.Text = "复制";
this.btnCopyLeft.UseVisualStyleBackColor = true;
this.btnCopyLeft.Click += new System.EventHandler(this.btnCopyLeft_Click);
//
// panelRight
//
this.panelRight.Controls.Add(this.rtbModified);
this.panelRight.Controls.Add(this.panelRightHeader);
this.panelRight.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelRight.Location = new System.Drawing.Point(0, 0);
this.panelRight.Name = "panelRight";
this.panelRight.Size = new System.Drawing.Size(428, 508);
this.panelRight.TabIndex = 0;
//
// rtbModified
//
this.rtbModified.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.rtbModified.DetectUrls = false;
this.rtbModified.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtbModified.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtbModified.Name = "rtbModified";
this.rtbModified.Size = new System.Drawing.Size(428, 478);
this.rtbModified.TabIndex = 1;
this.rtbModified.Text = "";
this.rtbModified.WordWrap = false;
this.rtbModified.ScrollChanged += new System.EventHandler(this.rtbModified_VScroll);
//
// panelRightHeader
//
this.panelRightHeader.Controls.Add(this.lblModified);
this.panelRightHeader.Controls.Add(this.btnCopyRight);
this.panelRightHeader.Dock = System.Windows.Forms.DockStyle.Top;
this.panelRightHeader.Location = new System.Drawing.Point(0, 0);
this.panelRightHeader.Name = "panelRightHeader";
this.panelRightHeader.Size = new System.Drawing.Size(428, 26);
this.panelRightHeader.TabIndex = 0;
//
// lblModified
//
this.lblModified.AutoSize = true;
this.lblModified.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblModified.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(100)))), ((int)(((byte)(0)))));
this.lblModified.Location = new System.Drawing.Point(0, 4);
this.lblModified.Name = "lblModified";
this.lblModified.Size = new System.Drawing.Size(68, 17);
this.lblModified.TabIndex = 0;
this.lblModified.Text = "对比文本:";
this.lblModified.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// btnCopyRight
//
this.btnCopyRight.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCopyRight.Location = new System.Drawing.Point(362, 1);
this.btnCopyRight.Name = "btnCopyRight";
this.btnCopyRight.Size = new System.Drawing.Size(65, 24);
this.btnCopyRight.TabIndex = 1;
this.btnCopyRight.Text = "复制";
this.btnCopyRight.UseVisualStyleBackColor = true;
this.btnCopyRight.Click += new System.EventHandler(this.btnCopyRight_Click);
//
// panelStats
//
this.panelStats.Controls.Add(this.lblStats);
this.panelStats.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelStats.Location = new System.Drawing.Point(9, 554);
this.panelStats.Name = "panelStats";
this.panelStats.Size = new System.Drawing.Size(868, 21);
this.panelStats.TabIndex = 2;
//
// lblStats
//
this.lblStats.AutoSize = true;
this.lblStats.Location = new System.Drawing.Point(0, 2);
this.lblStats.Name = "lblStats";
this.lblStats.Size = new System.Drawing.Size(236, 17);
this.lblStats.TabIndex = 0;
this.lblStats.Text = "差异统计: 点击「对比」开始分析差异";
this.lblStats.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// tabUnified
//
this.tabUnified.Controls.Add(this.tlpUnified);
this.tabUnified.Location = new System.Drawing.Point(4, 26);
this.tabUnified.Name = "tabUnified";
this.tabUnified.Padding = new System.Windows.Forms.Padding(3);
this.tabUnified.Size = new System.Drawing.Size(892, 590);
this.tabUnified.TabIndex = 1;
this.tabUnified.Text = "差异结果";
this.tabUnified.UseVisualStyleBackColor = true;
//
// tlpUnified
//
this.tlpUnified.ColumnCount = 1;
this.tlpUnified.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpUnified.Controls.Add(this.panelUnifiedToolbar, 0, 1);
this.tlpUnified.Controls.Add(this.rtbUnifiedDiff, 0, 2);
this.tlpUnified.Dock = System.Windows.Forms.DockStyle.Fill;
this.tlpUnified.Location = new System.Drawing.Point(3, 3);
this.tlpUnified.Name = "tlpUnified";
this.tlpUnified.Padding = new System.Windows.Forms.Padding(6, 3, 6, 6);
this.tlpUnified.RowCount = 4;
this.tlpUnified.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 3F));
this.tlpUnified.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
this.tlpUnified.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpUnified.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 3F));
this.tlpUnified.Size = new System.Drawing.Size(886, 584);
this.tlpUnified.TabIndex = 0;
//
// panelUnifiedToolbar
//
this.panelUnifiedToolbar.Controls.Add(this.lblUnifiedTitle);
this.panelUnifiedToolbar.Controls.Add(this.btnCopyUnified);
this.panelUnifiedToolbar.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelUnifiedToolbar.Location = new System.Drawing.Point(9, 6);
this.panelUnifiedToolbar.Name = "panelUnifiedToolbar";
this.panelUnifiedToolbar.Size = new System.Drawing.Size(868, 24);
this.panelUnifiedToolbar.TabIndex = 0;
//
// lblUnifiedTitle
//
this.lblUnifiedTitle.AutoSize = true;
this.lblUnifiedTitle.Location = new System.Drawing.Point(0, 4);
this.lblUnifiedTitle.Name = "lblUnifiedTitle";
this.lblUnifiedTitle.Size = new System.Drawing.Size(103, 17);
this.lblUnifiedTitle.TabIndex = 0;
this.lblUnifiedTitle.Text = "统一差异视图 (git diff 风格):";
this.lblUnifiedTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// btnCopyUnified
//
this.btnCopyUnified.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCopyUnified.Location = new System.Drawing.Point(775, 0);
this.btnCopyUnified.Name = "btnCopyUnified";
this.btnCopyUnified.Size = new System.Drawing.Size(90, 25);
this.btnCopyUnified.TabIndex = 1;
this.btnCopyUnified.Text = "复制结果";
this.btnCopyUnified.UseVisualStyleBackColor = true;
this.btnCopyUnified.Click += new System.EventHandler(this.btnCopyUnified_Click);
//
// rtbUnifiedDiff
//
this.rtbUnifiedDiff.BackColor = System.Drawing.SystemColors.ControlLightLight;
this.rtbUnifiedDiff.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.rtbUnifiedDiff.DetectUrls = false;
this.rtbUnifiedDiff.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtbUnifiedDiff.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtbUnifiedDiff.Location = new System.Drawing.Point(9, 36);
this.rtbUnifiedDiff.Name = "rtbUnifiedDiff";
this.rtbUnifiedDiff.ReadOnly = true;
this.rtbUnifiedDiff.Size = new System.Drawing.Size(868, 539);
this.rtbUnifiedDiff.TabIndex = 1;
this.rtbUnifiedDiff.Text = "请先在「文本对比」标签页点击「对比」按钮";
this.rtbUnifiedDiff.WordWrap = false;
//
// wTextDiff
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(900, 620);
this.Controls.Add(this.tabControl);
this.MinimumSize = new System.Drawing.Size(700, 450);
this.Name = "wTextDiff";
this.Text = "文本对比工具";
this.Resize += new System.EventHandler(this.wTextDiff_Resize);
this.tabControl.ResumeLayout(false);
this.tabCompare.ResumeLayout(false);
this.tlpCompare.ResumeLayout(false);
this.panelToolbar.ResumeLayout(false);
this.panelToolbar.PerformLayout();
this.splitContainer.Panel1.ResumeLayout(false);
this.splitContainer.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
this.splitContainer.ResumeLayout(false);
this.panelLeft.ResumeLayout(false);
this.panelLeftHeader.ResumeLayout(false);
this.panelLeftHeader.PerformLayout();
this.panelRight.ResumeLayout(false);
this.panelRightHeader.ResumeLayout(false);
this.panelRightHeader.PerformLayout();
this.panelStats.ResumeLayout(false);
this.panelStats.PerformLayout();
this.tabUnified.ResumeLayout(false);
this.tlpUnified.ResumeLayout(false);
this.panelUnifiedToolbar.ResumeLayout(false);
this.panelUnifiedToolbar.PerformLayout();
this.ResumeLayout(false);
}
#endregion
// ---- 顶层控件 ----
private System.Windows.Forms.TabControl tabControl;
private System.Windows.Forms.TabPage tabCompare;
private System.Windows.Forms.TabPage tabUnified;
// ---- Tab1: 文本对比 ----
private System.Windows.Forms.TableLayoutPanel tlpCompare;
private System.Windows.Forms.Panel panelToolbar;
private System.Windows.Forms.Label lblShortcut;
private System.Windows.Forms.Button btnSideBySide;
private System.Windows.Forms.Button btnTopBottom;
private System.Windows.Forms.Button btnCompare;
private System.Windows.Forms.Button btnClear;
private System.Windows.Forms.SplitContainer splitContainer;
private System.Windows.Forms.Panel panelLeft;
private System.Windows.Forms.Panel panelLeftHeader;
private System.Windows.Forms.Label lblOriginal;
private System.Windows.Forms.Button btnCopyLeft;
private Tools.UI.ScrollSyncRichTextBox rtbOriginal;
private System.Windows.Forms.Panel panelRight;
private System.Windows.Forms.Panel panelRightHeader;
private System.Windows.Forms.Label lblModified;
private System.Windows.Forms.Button btnCopyRight;
private Tools.UI.ScrollSyncRichTextBox rtbModified;
private System.Windows.Forms.Panel panelStats;
private System.Windows.Forms.Label lblStats;
// ---- Tab2: 差异结果 ----
private System.Windows.Forms.TableLayoutPanel tlpUnified;
private System.Windows.Forms.Panel panelUnifiedToolbar;
private System.Windows.Forms.Label lblUnifiedTitle;
private System.Windows.Forms.Button btnCopyUnified;
private System.Windows.Forms.RichTextBox rtbUnifiedDiff;
}
}
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//
using Tools.MyCommon;
using Tools.MyUtils;
namespace Tools.UI
{
/// <summary>
/// 支持滚动事件的 RichTextBox 子类
/// </summary>
public class ScrollSyncRichTextBox : RichTextBox
{
private const int WM_PASTE = 0x0302;
private const int WM_VSCROLL = 0x115;
private const int WM_HSCROLL = 0x114;
private const int WM_MOUSEWHEEL = 0x020A;
private const int WM_KEYDOWN = 0x0100;
public event EventHandler ScrollChanged;
protected override void WndProc(ref Message m)
{
// 拦截粘贴:只粘贴纯文本,去除所有富文本格式(语法高亮等)
if (m.Msg == WM_PASTE)
{
if (Clipboard.ContainsText())
{
this.SelectedText = Clipboard.GetText(TextDataFormat.UnicodeText);
}
return;
}
base.WndProc(ref m);
if (m.Msg == WM_VSCROLL || m.Msg == WM_HSCROLL || m.Msg == WM_MOUSEWHEEL)
{
ScrollChanged?.Invoke(this, EventArgs.Empty);
}
else if (m.Msg == WM_KEYDOWN)
{
int key = m.WParam.ToInt32();
if (key == (int)Keys.Up || key == (int)Keys.Down ||
key == (int)Keys.PageUp || key == (int)Keys.PageDown ||
key == (int)Keys.Home || key == (int)Keys.End)
{
this.BeginInvoke(new Action(() =>
{
ScrollChanged?.Invoke(this, EventArgs.Empty);
}));
}
}
}
}
public partial class wTextDiff : wBasic
{
// ==================== 布局模式 ====================
private enum LayoutMode { SideBySide, TopBottom }
private LayoutMode _currentLayout = LayoutMode.SideBySide;
// ==================== 差异行类型 ====================
private enum DiffLineType { Unchanged, Added, Removed, Modified, Placeholder }
// ==================== 字符级差异范围 ====================
private sealed class CharDiffRange
{
public int Start { get; set; }
public int Length { get; set; }
}
// ==================== 差异行信息 ====================
private sealed class DiffLineInfo
{
public string Text { get; set; }
public DiffLineType Type { get; set; }
public int? OriginalLineNumber { get; set; }
public int? ModifiedLineNumber { get; set; }
public List<CharDiffRange> CharDiffs { get; set; }
}
// ==================== 差异结果缓存 ====================
private List<DiffLineInfo> _leftDiffLines;
private List<DiffLineInfo> _rightDiffLines;
private List<DiffLineInfo> _unifiedDiffLines;
// ==================== 状态标志 ====================
private bool _isSyncingScroll;
private volatile bool _isComputing; // 防止重入
// ==================== 颜色定义 ====================
// 行级背景色:绿色=新增 橙色=修改 红色=删除
private static readonly Color AddedBackColor = Color.FromArgb(220, 255, 220);
private static readonly Color RemovedBackColor = Color.FromArgb(255, 220, 220);
private static readonly Color ModifiedBackColor = Color.FromArgb(255, 235, 210);
private static readonly Color ModifiedCharBackColor = Color.FromArgb(255, 195, 120);
private static readonly Color AddedForeColor = Color.FromArgb(0, 100, 0);
private static readonly Color RemovedForeColor = Color.FromArgb(180, 0, 0);
// ==================== 安全限制 ====================
private const int MaxTotalChars = 5_000_000;
private const int MaxTotalLines = 100_000;
private const int MaxCharDiffLineLength = 500;
private const int MaxLineLcsCells = 50_000_000;
// ==================== 辅助方法 ====================
private static DiffLineInfo CreatePlaceholder() =>
new DiffLineInfo { Text = "", Type = DiffLineType.Placeholder };
private static bool IsPlaceholder(DiffLineInfo line) =>
line != null && line.Type == DiffLineType.Placeholder;
private static string TrimTrailingNewline(string text)
{
if (string.IsNullOrEmpty(text)) return text;
if (text.EndsWith("\r\n")) return text.Substring(0, text.Length - 2);
if (text.EndsWith("\n")) return text.Substring(0, text.Length - 1);
return text;
}
/// <summary>
/// 创建并初始化离屏 RichTextBox,确保与可见控件属性一致。
/// 关键:字体必须匹配,否则 RTF 的 \fonttbl 会覆盖可见控件的字体设置;
/// DetectUrls 必须关闭,否则 URL 检测会添加额外格式干扰高亮渲染。
/// </summary>
private static RichTextBox CreateOffscreenRtb(string plainText)
{
var rtb = new RichTextBox
{
DetectUrls = false,
WordWrap = false,
Font = new Font("Consolas", 9.75f, FontStyle.Regular)
};
IntPtr h = rtb.Handle; // 强制创建窗口句柄
rtb.Text = plainText;
return rtb;
}
// ==================== 构造函数 ====================
public wTextDiff()
{
InitializeComponent();
ApplyLayoutMode(_currentLayout);
}
// ==================== 布局切换 ====================
private void btnSideBySide_Click(object sender, EventArgs e)
{
if (_currentLayout != LayoutMode.SideBySide)
{
_currentLayout = LayoutMode.SideBySide;
ApplyLayoutMode(_currentLayout);
UpdateLayoutButtons();
ReapplyHighlighting();
}
}
private void btnTopBottom_Click(object sender, EventArgs e)
{
if (_currentLayout != LayoutMode.TopBottom)
{
_currentLayout = LayoutMode.TopBottom;
ApplyLayoutMode(_currentLayout);
UpdateLayoutButtons();
ReapplyHighlighting();
}
}
private void ApplyLayoutMode(LayoutMode mode)
{
this.splitContainer.SuspendLayout();
if (mode == LayoutMode.SideBySide)
{
this.splitContainer.Orientation = Orientation.Vertical;
this.splitContainer.SplitterDistance = this.splitContainer.Width / 2;
}
else
{
this.splitContainer.Orientation = Orientation.Horizontal;
this.splitContainer.SplitterDistance = this.splitContainer.Height / 2;
}
this.splitContainer.ResumeLayout(true);
}
private void UpdateLayoutButtons()
{
bool isSideBySide = _currentLayout == LayoutMode.SideBySide;
this.btnSideBySide.Enabled = !isSideBySide;
this.btnTopBottom.Enabled = isSideBySide;
this.btnSideBySide.BackColor = isSideBySide ? SystemColors.GradientInactiveCaption : SystemColors.ButtonFace;
this.btnTopBottom.BackColor = !isSideBySide ? SystemColors.GradientInactiveCaption : SystemColors.ButtonFace;
}
private void ReapplyHighlighting()
{
if (_leftDiffLines != null && _rightDiffLines != null)
{
this.rtbOriginal.Rtf = BuildFormattedRtf(_leftDiffLines);
this.rtbModified.Rtf = BuildFormattedRtf(_rightDiffLines);
}
}
// ==================== 对比逻辑(异步 + 防重入) ====================
private async void btnCompare_Click(object sender, EventArgs e)
{
// 防重入
if (_isComputing) return;
_isComputing = true;
string original = this.rtbOriginal.Text;
string modified = this.rtbModified.Text;
if (string.IsNullOrEmpty(original))
{
new wMsgBox().Show("原始文本不能为空,请输入文本后再对比");
_isComputing = false;
return;
}
if (string.IsNullOrEmpty(modified))
{
new wMsgBox().Show("对比文本不能为空,请输入文本后再对比");
_isComputing = false;
return;
}
if (original.Length > MaxTotalChars || modified.Length > MaxTotalChars)
{
new wMsgBox().Show(string.Format(
"输入文本过大(单侧限制 {0:N0} 字符,约 {1:N0} MB),请精简后再对比",
MaxTotalChars, MaxTotalChars / 1_000_000));
_isComputing = false;
return;
}
// UI 状态切换
SetUiEnabled(false);
this.lblStats.Text = "正在对比中…";
this.Cursor = Cursors.WaitCursor;
string originalCopy = original;
string modifiedCopy = modified;
try
{
// 后台线程:纯数据计算
await Task.Run(() => ComputeDiffData(originalCopy, modifiedCopy));
// UI 线程:渲染结果
ApplyDiffResults();
}
catch (OutOfMemoryException)
{
ClearResults();
new wMsgBox().Show("文本过大导致内存不足,请减少文本量后重试");
}
catch (Exception ex)
{
ClearResults();
new wMsgBox().Show("文本对比失败:" + ex.Message);
}
finally
{
this.Cursor = Cursors.Default;
SetUiEnabled(true);
_isComputing = false;
}
}
private void SetUiEnabled(bool enabled)
{
this.btnCompare.Enabled = enabled;
this.btnClear.Enabled = enabled;
this.btnCopyLeft.Enabled = enabled;
this.btnCopyRight.Enabled = enabled;
this.btnCopyUnified.Enabled = enabled;
}
/// <summary>
/// 后台线程:纯数据计算,不访问任何控件
/// </summary>
private void ComputeDiffData(string original, string modified)
{
string[] originalLines = original.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
string[] modifiedLines = modified.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
if (originalLines.Length > MaxTotalLines || modifiedLines.Length > MaxTotalLines)
throw new InvalidOperationException(string.Format(
"输入行数过多(单侧限制 {0:N0} 行),请精简后再对比", MaxTotalLines));
long cells = (long)originalLines.Length * modifiedLines.Length;
if (cells > MaxLineLcsCells)
throw new InvalidOperationException(string.Format(
"文本规模过大({0:N0} × {1:N0}),请减少对比行数", originalLines.Length, modifiedLines.Length));
var diffResult = ComputeLineDiff(originalLines, modifiedLines);
_leftDiffLines = diffResult.LeftLines;
_rightDiffLines = diffResult.RightLines;
_unifiedDiffLines = BuildUnifiedDiff(_leftDiffLines, _rightDiffLines);
}
/// <summary>
/// UI 线程:将计算结果渲染到控件。
/// 使用离屏 RichTextBox 构建格式化 RTF,然后一次性赋值,避免数千次 Select 调用阻塞 UI。
/// </summary>
private void ApplyDiffResults()
{
if (_leftDiffLines == null || _rightDiffLines == null) return;
// 离屏构建 RTF,再一次性应用到可见控件
this.rtbOriginal.Rtf = BuildFormattedRtf(_leftDiffLines);
this.rtbModified.Rtf = BuildFormattedRtf(_rightDiffLines);
this.rtbOriginal.Select(0, 0);
this.rtbModified.Select(0, 0);
UpdateUnifiedDiffView();
UpdateStatistics();
}
private void ClearResults()
{
_leftDiffLines = null;
_rightDiffLines = null;
_unifiedDiffLines = null;
}
// ==================== RTF 格式化渲染 ====================
/// <summary>
/// 使用离屏 RichTextBox 构建带高亮的 RTF 文本。
/// 所有 Select/BackColor 操作在不可见控件上完成,无绘制开销。
/// 使用行索引而非手动 char 位置追踪,避免 \r\n 内部转换导致的偏移错误。
/// </summary>
private static string BuildFormattedRtf(List<DiffLineInfo> diffLines)
{
var sb = new StringBuilder();
foreach (var line in diffLines)
sb.AppendLine(line.Text);
string plainText = TrimTrailingNewline(sb.ToString());
using (var offscreen = CreateOffscreenRtb(plainText))
{
// 使用 RichTextBox 原生行索引,自动适配内部 \r\n 格式
for (int lineIdx = 0; lineIdx < diffLines.Count; lineIdx++)
{
var line = diffLines[lineIdx];
int lineLength = line.Text.Length;
if (lineLength == 0) continue;
int lineStart = offscreen.GetFirstCharIndexFromLine(lineIdx);
if (lineStart < 0) continue; // 安全守卫
if (line.Type == DiffLineType.Added)
{
offscreen.Select(lineStart, lineLength);
offscreen.SelectionBackColor = AddedBackColor;
}
else if (line.Type == DiffLineType.Removed)
{
offscreen.Select(lineStart, lineLength);
offscreen.SelectionBackColor = RemovedBackColor;
}
else if (line.Type == DiffLineType.Modified)
{
offscreen.Select(lineStart, lineLength);
offscreen.SelectionBackColor = ModifiedBackColor;
if (line.CharDiffs != null && line.CharDiffs.Count > 0)
{
foreach (var cd in line.CharDiffs)
{
if (cd.Length > 0 && cd.Start + cd.Length <= lineLength)
{
offscreen.Select(lineStart + cd.Start, cd.Length);
offscreen.SelectionBackColor = ModifiedCharBackColor;
}
}
}
}
}
return offscreen.Rtf;
}
}
// ==================== LCS 行级 Diff 算法 ====================
private sealed class DiffResult
{
public List<DiffLineInfo> LeftLines { get; set; }
public List<DiffLineInfo> RightLines { get; set; }
}
private static DiffResult ComputeLineDiff(string[] originalLines, string[] modifiedLines)
{
int m = originalLines.Length;
int n = modifiedLines.Length;
int[,] lcs = new int[m + 1, n + 1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
if (originalLines[i - 1] == modifiedLines[j - 1])
lcs[i, j] = lcs[i - 1, j - 1] + 1;
else
lcs[i, j] = Math.Max(lcs[i - 1, j], lcs[i, j - 1]);
int oi = m, mi = n;
var leftStack = new Stack<DiffLineInfo>();
var rightStack = new Stack<DiffLineInfo>();
while (oi > 0 || mi > 0)
{
if (oi > 0 && mi > 0 && originalLines[oi - 1] == modifiedLines[mi - 1])
{
leftStack.Push(new DiffLineInfo { Text = originalLines[oi - 1], Type = DiffLineType.Unchanged, OriginalLineNumber = oi, ModifiedLineNumber = mi });
rightStack.Push(new DiffLineInfo { Text = modifiedLines[mi - 1], Type = DiffLineType.Unchanged, OriginalLineNumber = oi, ModifiedLineNumber = mi });
oi--; mi--;
}
else if (mi > 0 && (oi == 0 || lcs[oi, mi - 1] >= lcs[oi - 1, mi]))
{
rightStack.Push(new DiffLineInfo { Text = modifiedLines[mi - 1], Type = DiffLineType.Added, ModifiedLineNumber = mi });
mi--;
}
else if (oi > 0 && (mi == 0 || lcs[oi, mi - 1] < lcs[oi - 1, mi]))
{
leftStack.Push(new DiffLineInfo { Text = originalLines[oi - 1], Type = DiffLineType.Removed, OriginalLineNumber = oi });
oi--;
}
}
var leftRaw = new List<DiffLineInfo>(leftStack);
var rightRaw = new List<DiffLineInfo>(rightStack);
return AlignDiffLines(leftRaw, rightRaw);
}
/// <summary>
/// 将 LCS 回溯产生的原始差异列表对齐为左右等长列表。
/// 连续 Removed+Added 配对为 Modified,孤立 Removed/Added 在对侧补 Placeholder 占位行。
/// </summary>
private static DiffResult AlignDiffLines(List<DiffLineInfo> leftRaw, List<DiffLineInfo> rightRaw)
{
var leftLines = new List<DiffLineInfo>();
var rightLines = new List<DiffLineInfo>();
int CountConsecutive(List<DiffLineInfo> list, int start, DiffLineType type)
{
int count = 0;
while (start + count < list.Count && list[start + count].Type == type)
count++;
return count;
}
int lPtr = 0, rPtr = 0;
while (lPtr < leftRaw.Count || rPtr < rightRaw.Count)
{
// 1) 两侧当前位置都是 Unchanged → 对齐输出
if (lPtr < leftRaw.Count && rPtr < rightRaw.Count
&& leftRaw[lPtr].Type == DiffLineType.Unchanged
&& rightRaw[rPtr].Type == DiffLineType.Unchanged)
{
leftLines.Add(leftRaw[lPtr]);
rightLines.Add(rightRaw[rPtr]);
lPtr++; rPtr++;
}
// 2) 左侧 Removed 且右侧 Added → 按连续数配对为 Modified
else if (lPtr < leftRaw.Count && leftRaw[lPtr].Type == DiffLineType.Removed
&& rPtr < rightRaw.Count && rightRaw[rPtr].Type == DiffLineType.Added)
{
int remCount = CountConsecutive(leftRaw, lPtr, DiffLineType.Removed);
int addCount = CountConsecutive(rightRaw, rPtr, DiffLineType.Added);
int pairCount = Math.Min(remCount, addCount);
for (int i = 0; i < pairCount; i++)
{
string leftText = leftRaw[lPtr].Text;
string rightText = rightRaw[rPtr].Text;
leftLines.Add(new DiffLineInfo
{
Text = leftText,
Type = DiffLineType.Modified,
OriginalLineNumber = leftRaw[lPtr].OriginalLineNumber,
CharDiffs = ComputeCharDiff(leftText, rightText)
});
rightLines.Add(new DiffLineInfo
{
Text = rightText,
Type = DiffLineType.Modified,
ModifiedLineNumber = rightRaw[rPtr].ModifiedLineNumber,
CharDiffs = ComputeCharDiff(rightText, leftText)
});
lPtr++; rPtr++;
}
}
// 3) 左侧 Removed(右侧当前不是 Added)→ 右侧补占位行以保持对齐
else if (lPtr < leftRaw.Count && leftRaw[lPtr].Type == DiffLineType.Removed)
{
leftLines.Add(leftRaw[lPtr]);
rightLines.Add(CreatePlaceholder());
lPtr++;
}
// 4) 右侧 Added(左侧当前不是 Removed)→ 左侧补占位行以保持对齐
else if (rPtr < rightRaw.Count && rightRaw[rPtr].Type == DiffLineType.Added)
{
leftLines.Add(CreatePlaceholder());
rightLines.Add(rightRaw[rPtr]);
rPtr++;
}
}
return new DiffResult { LeftLines = leftLines, RightLines = rightLines };
}
// ==================== 字符级 Diff 算法 ====================
private static List<CharDiffRange> ComputeCharDiff(string source, string reference)
{
var result = new List<CharDiffRange>();
if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(reference))
{
if (!string.IsNullOrEmpty(source))
result.Add(new CharDiffRange { Start = 0, Length = source.Length });
return result;
}
int m = source.Length;
int n = reference.Length;
if (m > MaxCharDiffLineLength || n > MaxCharDiffLineLength)
return result;
int[,] lcs = new int[m + 1, n + 1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
if (source[i - 1] == reference[j - 1])
lcs[i, j] = lcs[i - 1, j - 1] + 1;
else
lcs[i, j] = Math.Max(lcs[i - 1, j], lcs[i, j - 1]);
var diffPositions = new bool[m];
int si = m, ri = n;
while (si > 0 || ri > 0)
{
if (si > 0 && ri > 0 && source[si - 1] == reference[ri - 1]) { si--; ri--; }
else if (ri > 0 && (si == 0 || lcs[si, ri - 1] >= lcs[si - 1, ri])) { ri--; }
else if (si > 0 && (ri == 0 || lcs[si, ri - 1] < lcs[si - 1, ri])) { diffPositions[si - 1] = true; si--; }
}
int pos = 0;
while (pos < m)
{
if (diffPositions[pos])
{
int start = pos;
while (pos < m && diffPositions[pos]) pos++;
result.Add(new CharDiffRange { Start = start, Length = pos - start });
}
else pos++;
}
return result;
}
// ==================== 统一 Diff 视图 ====================
/// <summary>
/// 从已对齐的左右差异列表生成统一差异视图(git diff 风格)。
/// 左右列表长度已由 AlignDiffLines 保证相等,单索引遍历。
/// </summary>
private static List<DiffLineInfo> BuildUnifiedDiff(List<DiffLineInfo> leftLines, List<DiffLineInfo> rightLines)
{
var result = new List<DiffLineInfo>
{
new DiffLineInfo { Text = "--- 原始文本", Type = DiffLineType.Unchanged },
new DiffLineInfo { Text = "+++ 对比文本", Type = DiffLineType.Unchanged },
new DiffLineInfo { Text = "", Type = DiffLineType.Unchanged }
};
for (int i = 0; i < leftLines.Count; i++)
{
var left = leftLines[i];
var right = rightLines[i];
if (IsPlaceholder(left) && IsPlaceholder(right))
{
continue; // 双占位,不应出现,安全跳过
}
else if (IsPlaceholder(right))
{
// 右侧占位 → 左侧是孤立删除行
if (left.Type == DiffLineType.Removed)
result.Add(new DiffLineInfo { Text = "- " + left.Text, Type = DiffLineType.Removed });
}
else if (IsPlaceholder(left))
{
// 左侧占位 → 右侧是孤立新增行
if (right.Type == DiffLineType.Added)
result.Add(new DiffLineInfo { Text = "+ " + right.Text, Type = DiffLineType.Added });
}
else if (left.Type == DiffLineType.Unchanged && right.Type == DiffLineType.Unchanged)
{
result.Add(new DiffLineInfo { Text = " " + left.Text, Type = DiffLineType.Unchanged });
}
else if (left.Type == DiffLineType.Modified && right.Type == DiffLineType.Modified)
{
result.Add(new DiffLineInfo { Text = "- " + left.Text, Type = DiffLineType.Removed });
result.Add(new DiffLineInfo { Text = "+ " + right.Text, Type = DiffLineType.Added });
}
}
return result;
}
private void UpdateUnifiedDiffView()
{
if (_unifiedDiffLines == null || _unifiedDiffLines.Count == 0)
{
this.rtbUnifiedDiff.Clear();
this.rtbUnifiedDiff.Text = "请先在「文本对比」标签页点击「对比」按钮";
return;
}
// 统一 diff 视图也使用离屏 RTF 构建,避免 Select 阻塞
var sb = new StringBuilder();
foreach (var line in _unifiedDiffLines)
sb.AppendLine(line.Text);
string plainText = TrimTrailingNewline(sb.ToString());
using (var offscreen = CreateOffscreenRtb(plainText))
{
for (int lineIdx = 0; lineIdx < _unifiedDiffLines.Count; lineIdx++)
{
var line = _unifiedDiffLines[lineIdx];
int lineLength = line.Text.Length;
if (lineLength == 0) continue;
int lineStart = offscreen.GetFirstCharIndexFromLine(lineIdx);
if (lineStart < 0) continue;
if (line.Type == DiffLineType.Added)
{
offscreen.Select(lineStart, lineLength);
offscreen.SelectionBackColor = AddedBackColor;
offscreen.SelectionColor = AddedForeColor;
}
else if (line.Type == DiffLineType.Removed)
{
offscreen.Select(lineStart, lineLength);
offscreen.SelectionBackColor = RemovedBackColor;
offscreen.SelectionColor = RemovedForeColor;
}
}
this.rtbUnifiedDiff.Rtf = offscreen.Rtf;
}
this.rtbUnifiedDiff.Select(0, 0);
}
// ==================== 统计信息 ====================
private void UpdateStatistics()
{
int added = 0, removed = 0, modified = 0, unchanged = 0;
if (_leftDiffLines != null)
{
foreach (var line in _leftDiffLines)
{
if (IsPlaceholder(line)) continue;
switch (line.Type)
{
case DiffLineType.Removed: removed++; break;
case DiffLineType.Modified: modified++; break;
case DiffLineType.Unchanged: unchanged++; break;
}
}
}
if (_rightDiffLines != null)
{
foreach (var line in _rightDiffLines)
{
if (IsPlaceholder(line)) continue;
if (line.Type == DiffLineType.Added) added++;
}
}
this.lblStats.Text = string.Format(
"差异统计: +{0} 行新增 -{1} 行删除 ~{2} 行修改 ={3} 行未变",
added, removed, modified, unchanged);
}
// ==================== 辅助功能 ====================
private void btnClear_Click(object sender, EventArgs e)
{
this.rtbOriginal.Clear();
this.rtbModified.Clear();
this.rtbUnifiedDiff.Clear();
this.rtbUnifiedDiff.Text = "请先在「文本对比」标签页点击「对比」按钮";
this.lblStats.Text = "差异统计: 点击「对比」开始分析差异";
ClearResults();
}
private void btnCopyUnified_Click(object sender, EventArgs e)
{
if (_unifiedDiffLines == null || _unifiedDiffLines.Count == 0)
{
new wMsgBox().Show("暂无差异结果,请先执行对比");
return;
}
var sb = new StringBuilder();
foreach (var line in _unifiedDiffLines)
sb.AppendLine(line.Text);
Clipboard.SetText(sb.ToString());
new wMsgBox().Show("已复制到剪贴板");
}
private void btnCopyLeft_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(this.rtbOriginal.Text))
{
new wMsgBox().Show("原始文本为空");
return;
}
Clipboard.SetText(this.rtbOriginal.Text);
new wMsgBox().Show("已复制到剪贴板");
}
private void btnCopyRight_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(this.rtbModified.Text))
{
new wMsgBox().Show("对比文本为空");
return;
}
Clipboard.SetText(this.rtbModified.Text);
new wMsgBox().Show("已复制到剪贴板");
}
// ==================== 同步滚动 ====================
// RichEdit 消息:获取/设置滚动位置(像素级,同时覆盖水平和垂直方向)
private const int EM_GETSCROLLPOS = 0x04DD;
private const int EM_SETSCROLLPOS = 0x04DE;
[StructLayout(LayoutKind.Sequential)]
private struct POINTAPI
{
public int x;
public int y;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref POINTAPI lParam);
private void rtbOriginal_VScroll(object sender, EventArgs e)
{
if (_isSyncingScroll) return;
_isSyncingScroll = true;
SyncScrollPosition(this.rtbOriginal, this.rtbModified);
_isSyncingScroll = false;
}
private void rtbModified_VScroll(object sender, EventArgs e)
{
if (_isSyncingScroll) return;
_isSyncingScroll = true;
SyncScrollPosition(this.rtbModified, this.rtbOriginal);
_isSyncingScroll = false;
}
/// <summary>
/// 同步两个 RichTextBox 的水平和垂直滚动位置(像素级)。
/// 使用 RichEdit 原生 EM_GETSCROLLPOS / EM_SETSCROLLPOS 消息,
/// 确保底部(水平)与侧边(垂直)滚动条双向绑定。
/// </summary>
private static void SyncScrollPosition(RichTextBox source, RichTextBox target)
{
var srcPos = new POINTAPI();
SendMessage(source.Handle, EM_GETSCROLLPOS, IntPtr.Zero, ref srcPos);
var tgtPos = new POINTAPI();
SendMessage(target.Handle, EM_GETSCROLLPOS, IntPtr.Zero, ref tgtPos);
// 只有位置实际不同时才同步,避免无效消息
if (srcPos.x != tgtPos.x || srcPos.y != tgtPos.y)
{
SendMessage(target.Handle, EM_SETSCROLLPOS, IntPtr.Zero, ref srcPos);
}
}
// ==================== 快捷键支持 ====================
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == (Keys.Control | Keys.Enter))
{
btnCompare_Click(this, EventArgs.Empty);
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
private void wTextDiff_Resize(object sender, EventArgs e)
{
if (this.splitContainer.Width <= 0 || this.splitContainer.Height <= 0) return;
if (_currentLayout == LayoutMode.SideBySide)
this.splitContainer.SplitterDistance = this.splitContainer.Width / 2;
else
this.splitContainer.SplitterDistance = this.splitContainer.Height / 2;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
......@@ -50,6 +50,7 @@ namespace Tools
this.CreateModule(Tools_imp, typeof(UI.wExcelExportSetting), "Excel导出设置", "tr:Excel导出配置图形化版本"),
this.CreateModule(Tools_imp, typeof(UI.wLogViewer), "日志查看器", "tr:可以方便的查看系统日志"),
this.CreateModule(Tools_imp, typeof(UI.wTextFormatter), "格式化工具", "tr:SQL和JSON文本格式化工具"),
this.CreateModule(Tools_imp, typeof(UI.wTextDiff), "文本对比工具", "tr:行级文本差异对比,支持左右/上下布局"),
this.CreateModule(Tools_imp, typeof(DotNetVersionChecker.FrmVersion), "系统检测", "lxj:检测操作系统运行环境"),
//-------------------------------
this.CreateModule(Tools_dev, typeof(Ftt.PatchPack.FrmMain), "自动打包工具", "lxj:用于CS后端程序自动打包成压缩包"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment