Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
Ftt_Common
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Ftt_Common
Commits
c0c9960c
Commit
c0c9960c
authored
Jun 30, 2026
by
段廷瑞
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 文本对比功能
parent
a5970404
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
1447 additions
and
0 deletions
+1447
-0
Tools.csproj
Tools/Tools.csproj
+9
-0
wTextDiff.Designer.cs
Tools/UI/wTextDiff.Designer.cs
+483
-0
wTextDiff.cs
Tools/UI/wTextDiff.cs
+834
-0
wTextDiff.resx
Tools/UI/wTextDiff.resx
+120
-0
wExtend.cs
Tools/wExtend.cs
+1
-0
No files found.
Tools/Tools.csproj
View file @
c0c9960c
...
@@ -368,6 +368,12 @@
...
@@ -368,6 +368,12 @@
<Compile
Include=
"UI\wTaskBoard.Designer.cs"
>
<Compile
Include=
"UI\wTaskBoard.Designer.cs"
>
<DependentUpon>
wTaskBoard.cs
</DependentUpon>
<DependentUpon>
wTaskBoard.cs
</DependentUpon>
</Compile>
</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"
>
<Compile
Include=
"UI\wTextFormatter.cs"
>
<SubType>
Form
</SubType>
<SubType>
Form
</SubType>
</Compile>
</Compile>
...
@@ -533,6 +539,9 @@
...
@@ -533,6 +539,9 @@
<EmbeddedResource
Include=
"UI\wTaskBoard.resx"
>
<EmbeddedResource
Include=
"UI\wTaskBoard.resx"
>
<DependentUpon>
wTaskBoard.cs
</DependentUpon>
<DependentUpon>
wTaskBoard.cs
</DependentUpon>
</EmbeddedResource>
</EmbeddedResource>
<EmbeddedResource
Include=
"UI\wTextDiff.resx"
>
<DependentUpon>
wTextDiff.cs
</DependentUpon>
</EmbeddedResource>
<EmbeddedResource
Include=
"UI\wTextFormatter.resx"
>
<EmbeddedResource
Include=
"UI\wTextFormatter.resx"
>
<DependentUpon>
wTextFormatter.cs
</DependentUpon>
<DependentUpon>
wTextFormatter.cs
</DependentUpon>
</EmbeddedResource>
</EmbeddedResource>
...
...
Tools/UI/wTextDiff.Designer.cs
0 → 100644
View file @
c0c9960c
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
;
}
}
Tools/UI/wTextDiff.cs
0 → 100644
View file @
c0c9960c
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
;
}
}
}
Tools/UI/wTextDiff.resx
0 → 100644
View file @
c0c9960c
<?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>
Tools/wExtend.cs
View file @
c0c9960c
...
@@ -50,6 +50,7 @@ namespace Tools
...
@@ -50,6 +50,7 @@ namespace Tools
this
.
CreateModule
(
Tools_imp
,
typeof
(
UI
.
wExcelExportSetting
),
"Excel导出设置"
,
"tr:Excel导出配置图形化版本"
),
this
.
CreateModule
(
Tools_imp
,
typeof
(
UI
.
wExcelExportSetting
),
"Excel导出设置"
,
"tr:Excel导出配置图形化版本"
),
this
.
CreateModule
(
Tools_imp
,
typeof
(
UI
.
wLogViewer
),
"日志查看器"
,
"tr:可以方便的查看系统日志"
),
this
.
CreateModule
(
Tools_imp
,
typeof
(
UI
.
wLogViewer
),
"日志查看器"
,
"tr:可以方便的查看系统日志"
),
this
.
CreateModule
(
Tools_imp
,
typeof
(
UI
.
wTextFormatter
),
"格式化工具"
,
"tr:SQL和JSON文本格式化工具"
),
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_imp
,
typeof
(
DotNetVersionChecker
.
FrmVersion
),
"系统检测"
,
"lxj:检测操作系统运行环境"
),
//-------------------------------
//-------------------------------
this
.
CreateModule
(
Tools_dev
,
typeof
(
Ftt
.
PatchPack
.
FrmMain
),
"自动打包工具"
,
"lxj:用于CS后端程序自动打包成压缩包"
),
this
.
CreateModule
(
Tools_dev
,
typeof
(
Ftt
.
PatchPack
.
FrmMain
),
"自动打包工具"
,
"lxj:用于CS后端程序自动打包成压缩包"
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment