ViewFile 和 Head 工具






4.82/5 (10投票s)
本文是对早期版本的重大修订,并引入了新版本的ViewFile。
目录
1. 引言
本文是 ViewFile 工具早期版本的最后一次计划修订。促成这次修订的原因是我正在创建一个大文件,并且需要检查该文件中的特定字节。通过滚动文件直到找到所需的字节似乎不是最佳的用户体验。
修订包括
- 添加了一个“起始位置”文本框,允许用户指定显示开始的字节位置。
- 将只读对象的背景色设置为 GradientActiveCaption 系统颜色。
- 增加用户界面的字体大小。
- 移除 Head 应用程序
2. 背景
在开发过程中,了解应用程序提供的非默认格式的文件内容通常很有用。例如,.txt 文件通常由记事本显示为一系列文本行;.png 文件由图像查看器显示为图片。通常,默认的 Windows 工具不提供以字节顺序显示文件内容的选项。ViewFile 旨在弥补这一疏漏。
3. 关于排版和图片的说明
在以下讨论中,软件内部使用的对象将显示为斜体文本;用户访问的对象将显示为粗斜体文本。对于内容难以辨认的图片,图片为缩略图。点击图片后,将使用默认图像查看器显示更大的版本。
4. ViewFile
ViewFile 是一个直观的文件转储工具。在初始执行时,ViewFile 在用户点击浏览按钮时接受输入文件的名称。
ViewFile 提供上次访问目录功能,可以“记住”上次检查的文件的路径。该路径被保存在一个名为 last_directory.dat 的文件中,供以后引用。由于 last_directory.dat 可能在 ViewFile 执行期间被修改,因此它被保存在用户的配置文件文件夹中。在 Windows 7 中,路径为C:\Users\<Username>\ViewFile\last_directory.dat。如果该文件不存在,它将被创建,并且上次访问的目录将设置为 C:\Users\<Username>\ViewFile 并保存在新创建的文件中。
ViewFile 是可重入的,这意味着可以再次点击浏览按钮来提供新的输入文件名。
当用户提供了输入文件名后,ViewFile 将该文件的目录路径保存在 last_directory.dat 中。然后,Viewfile 显示文件的名称、一个用户可以指定显示开始的字节的文本框,以及一个启动显示的开始按钮。
当用户点击开始按钮时,文件内容将被显示,从“起始位置”文本框中的值开始。
请注意,在这种情况下,内容正在显示的文件是一个便携式网络图形文件(不仅通过其名称 .png 识别,还通过其文件签名 89 50 4E 47 0D 0A 1A 0A 识别)。有关文件签名的良好来源,请参阅 Gary Kessler 的文件签名表 [^]。
用户可以使用窗口右侧的垂直滚动条上下滚动文件内容。
ViewFile 还响应以下键盘按键。
- Home
- Page Up
- End
- Page Down
- 向上箭头
- 向下箭头
ViewFile 会响应按住的键(Home 和 End 键除外)。
用户可以选择
- 将左侧格式从十六进制更改为十进制。
- 使显示保持在所有其他桌面窗口之上。
- 用字符“<SP>”替换右侧的空格。当右侧的空格数量可能不明显时,这很有用。
5. 实现
ViewFile 构造函数是
:
// ******************************************* class constants
:
const int TIMER_REPEAT_DELAY = 400;
:
// ************************************************** ViewFile
public ViewFile ( )
{
InitializeComponent ( );
Application.ApplicationExit += new EventHandler (
OnApplicationExit );
OnApplicationExit ( this, EventArgs.Empty );
// TextBox MouseWheel
if ( SystemInformation.MouseWheelPresent )
{
contents_TB.MouseWheel += new MouseEventHandler (
TB_MouseWheel );
}
// repeat timer
timer = new Timer ( );
timer.Interval = TIMER_REPEAT_DELAY;
timer.Tick += new EventHandler ( TIMER_Tick );
timer.Enabled = false;
initialize_global_variables ( contents_TB );
signon ( );
initialize_GUI ( contents_TB );
} // ViewFile
contents_TB 是显示用户选择的文件内容的文本框。它在 Visual Studio 设计器 [^] 中声明。TIMER_REPEAT_DELAY 是一个全局常量,包含计时器 Tick 事件再次触发之前的毫秒数(400)。该值不能小于一。400 毫秒似乎是计时器重复间隔的合理选择。
在完成这些任务后,ViewFile 完全由事件驱动。
5.1. ViewFile 事件处理程序
5.1.1. OnApplicationExit 事件处理程序
虽然此事件处理程序不是严格必需的,但它被声明用于在应用程序退出时关闭和释放要显示的 FileStream [^],并停止和释放 Windows 计时器 [^]。
using System;
:
using System.IO;
:
using System.Windows.Forms;
:
// ******************************************* class variables
:
FileStream file_stream = null;
:
Timer timer = null; // Windows Timer
:
// ***************************************** OnApplicationExit
void OnApplicationExit ( object sender,
EventArgs e )
{
if ( file_stream != null )
{
file_stream.Close ( );
file_stream.Dispose ( );
file_stream = null;
}
if ( timer != null )
{
if ( timer.Enabled )
{
timer.Stop ( );
}
timer.Dispose ( );
timer = null;
}
}
5.1.2. BUT_Click 事件处理程序
BUT_Click 事件处理程序处理浏览、开始和退出按钮的按钮 [^] 点击事件 [^]。
// ************************************************* BUT_Click
void BUT_Click ( object sender,
EventArgs e )
{
Button button = ( Button ) sender;
string name = button.Name.Trim ( );
switch ( name )
{
case "browse_BUT":
if ( browse ( ) )
{
if ( open_file_stream ( ) )
{
if ( get_file_statistics ( ) )
{
file_size_TB.Text =
file_bytes.ToString ( );
start_at = 0;
start_at_TB.Text =
start_at.ToString ( );
start_at_and_go_GB.Visible = true;
}
}
}
break;
case "go_BUT":
if ( start_at_valid ( ) )
{
display_file ( );
contents_TB.Focus ( );
}
break;
case "exit_BUT":
Application.Exit ( );
break;
default:
throw new ApplicationException (
String.Format (
"{0} is not a recognized Button name",
name ) );
}
} // BUT_Click
browse 使用 last_directory 获取要显示的文件的文件名;open_file_stream 尝试打开用户指定的文件;get_file_statistics 获取输入文件的字节长度。
start_at_valid 确定用户提供的值是否有效。如果没有错误,则调用 display_file。请注意,display_file 每个文件仅调用一次。
:
// ******************************************* class variables
:
VScrollBar textbox_VSB = null;
:
// ********************************************** display_file
/// <summary>
/// performs initialization of GUI variables; invokes
/// display_input_file
/// </summary>
/// <note>
/// display_file is invoked at the beginning of the display of
/// a file; thereafter display_input_file is invoked directly
/// </note>
void display_file ( )
{
new_value = start_at / MAXIMUM_ENTRIES_PER_LINE;
textbox_VSB.Minimum = 0;
textbox_VSB.Maximum = file_lines;
textbox_VSB.Value = 0;
// set display_changed to true
// forcing display_input_file
// to fill the textbox
display_changed = true;
options_and_size_GB.Visible = true;
display_input_file ( );
} // display_file
当文件特定变量初始化后,display_file 调用 display_input_file。
display_changed 必须在调用 display_input_file 之前设置为 true。在这种情况下,new_value 和 textbox_VSB.Value 都设置为零。通常,在 display_input_file 中,这种情况会被解释为“无需重绘”。然而,当要显示新打开的文件时,会调用 display_file。因此,有必要强制 display_input_file 重绘显示。其他需要将 display_changed 设置为 true 的情况是当选项十六进制或使用 <SP> 表示空格改变显示本身时。在这些情况下,new_value 或 textbox_VSB.Value 不会发生变化。除非将 display_changed 设置为 true,否则显示将不会反映用户的意愿。
display_input_file 是 ViewFile 的主要工作函数。
:
// ******************************************* class constants
const int MAXIMUM_BUFFER = MAXIMUM_LINES *
MAXIMUM_ENTRIES_PER_LINE;
const int MAXIMUM_ENTRIES_PER_LINE = 8;
const int MAXIMUM_LINES = 20;
:
// ******************************************* class variables
:
FileStream file_stream = null;
:
int new_value = 0;
VScrollBar textbox_VSB = null;
:
// **************************************** display_input_file
/// <summary>
/// retrieves MAXIMUM_BUFFER bytes from the input file and
/// formats them into the text_box display
/// </summary>
/// <param name="text_box">
/// TextBox into which file bytes will be formatted
/// </param>
void display_input_file ( )
{
byte [ ] buffer = new byte [ MAXIMUM_BUFFER ];
int buffer_index = 0;
int byte_offset = 0;
int bytes_read = 0;
StringBuilder ch_buffer = new StringBuilder ( );
StringBuilder digits_buffer = new StringBuilder ( );
int lines_read = 0;
int remainder = 0;
int starting_byte = 0;
// new_value units is lines
new_value = Math.Max (
0,
Math.Min ( new_value,
( textbox_VSB.Maximum -
MAXIMUM_LINES ) ) );
if ( display_changed ) // force redisplay
{
display_changed = false;
}
else if ( new_value == textbox_VSB.Value )
{
return; // nothing to do if both equal
}
// refill the screen
contents_TB.Suspend ( ); // see ControlExtensions.cs
contents_TB.Clear ( );
// read a screen-full of data
// byte_offset is the location
// of the first byte in the
// new_value line
byte_offset = new_value * MAXIMUM_ENTRIES_PER_LINE;
textbox_VSB.Value = new_value;
read_data ( file_stream,
byte_offset,
buffer,
ref bytes_read );
lines_read = bytes_read / MAXIMUM_ENTRIES_PER_LINE;
// if remainder is > 0, a
// partial line is at the end
// of the buffer
remainder = bytes_read % MAXIMUM_ENTRIES_PER_LINE;
// dd dd dd dd dd dd dd dd | sb | chchchchchchchch
// ^------- digits_buffer ------^ ^-- ch_buffer -^
// starting_byte ^
// clear the buffers
ch_buffer.Length = 0;
digits_buffer.Length = 0;
buffer_index = 0;
starting_byte = byte_offset;
start_at_TB.Text = starting_byte.ToString ( );
// process whole lines
for ( int line = 0; ( line < lines_read ); line++ )
{
for ( int j = 0;
( j < MAXIMUM_ENTRIES_PER_LINE );
j++ )
{
insert_a_byte ( ref ch_buffer,
ref digits_buffer,
buffer [ buffer_index++ ] );
}
complete_line ( ref ch_buffer,
ref digits_buffer,
ref starting_byte,
ref contents_TB );
}
// process the remainder
if ( remainder > 0 )
{
int empty_entries = 0;
for ( int j = 0; ( j < remainder ); j++ )
{
insert_a_byte ( ref ch_buffer,
ref digits_buffer,
buffer [ buffer_index++ ] );
}
empty_entries = MAXIMUM_ENTRIES_PER_LINE -
remainder;
// pad end of ch_buffer
for ( int j = 0; ( j < empty_entries ); j++ )
{
ch_buffer.Append ( " " );
}
complete_line ( ref ch_buffer,
ref digits_buffer,
ref starting_byte,
ref contents_TB );
}
contents_TB.Select( 0, 0 ); // contents_TB first byte
contents_TB.ScrollToCaret ( );
contents_TB.Resume ( ); // see ControlExtensions.cs
contents_TB.Visible = true;
textbox_VSB.Visible = true;
options_and_size_GB.Visible = true;
} // display_input_file
如果全局变量 display_changed 为 true,则 contents_TB 将被重绘,否则,全局变量 new_value 的值控制 display_input_file 的执行。最初,其值设置为 0,display_changed 的值设置为 true。这些值确保 display_input_file 使用输入文件的前 160 个字节填充 contents_TB。此后,new_value 会响应事件而修改。
一旦 display_input_file 从文件中检索到数据,它就会填充 contents_TB。如果读取的字节数不能被 contents_TB 行中的字节数(MAXIMUM_ENTRIES_PER_LINE)整除。填充 contents_TB 分两步进行:第一步用“完整”行填充 contents_TB;第二步用剩余字节填充 contents_TB。
请注意,display_input_file 读取的字节数始终小于或等于 160(如果 filestream 在文件末尾遇到 EOF,则会更少)。因为 ViewFile 与用户交互,所以进行任何优化都没有优势。任何性能的提高都会被 ViewFile 的用户界面所抵消。
当 display_input_file 完成执行后,ViewFile 会进入空闲状态,直到按下键盘按键、鼠标滚轮滚动或 contents_TB 的垂直滚动条被修改。
5.1.3. TIMER_Tick 事件处理程序
在讨论键盘事件处理程序之前,我先介绍 TIMER_Tick 事件处理程序,因为它间接由 TB_KeyDown 事件处理程序调用,并且其操作由 TB_KeyUp 事件处理程序修改。
TIMER_Tick 相对简单。当 timer.Enabled 设置为 true 并且 timer.Interval 过期时,它会被调用。
:
// ******************************************* class constants
:
const int TIMER_REPEAT_DELAY = 400;
:
// ******************************************* class variables
:
bool key_down = false;
:
int lines_to_scroll = 0;
:
int new_value = 0;
:
// ************************************************ TIMER_Tick
void TIMER_Tick ( object sender,
EventArgs e )
{
timer.Interval = TIMER_REPEAT_DELAY;
if ( key_down )
{
if ( lines_to_scroll != 0 )
{
new_value = textbox_VSB.Value + lines_to_scroll;
display_input_file ( );
}
}
} // TIMER_Tick
它的第一个操作是将 timer.Interval 重置为 TIMER_REPEAT_DELAY 的值。我认为 400 毫秒足以提供合理的重复速率。在 TIMER_Tick 继续执行之前,必须满足两个额外条件:key_down 必须为 true 且 lines_to_scroll 必须非零。如果满足这些条件,new_value 将被修改并调用 display_input_file。
5.1.4. TB_KeyDown 和 TB_KeyUp 事件处理程序
在这两个事件处理程序中,TB_KeyUp 远为简单。
// ************************************************** TB_KeyUp
void TB_KeyUp ( object sender,
KeyEventArgs e )
{
key_down = false;
timer.Enabled = false;
}
当按键释放时,会调用 TB_KeyUp。其目的是取消 TIMER_Tick 的任何进一步重复操作。通过将 TIMER_Tick 执行所需的两个条件(即 timer.Enabled 和 key_down)设置为 false,TIMER_Tick 将停止执行。进而,contents_TB 的进一步修改也将停止。
尽管 TB_KeyUp 更复杂,但其逻辑相对简单。其目的是确定是否按下了感兴趣的键盘按键,如果按下了,则分派相应的操作。
// ************************************************ TB_KeyDown
void TB_KeyDown ( object sender,
KeyEventArgs e )
{
Keys key = e.KeyCode;
bool trigger_timer = true;
// compute lines_to_scroll
if ( key == Keys.Down )
{
lines_to_scroll = textbox_VSB.SmallChange;
}
else if ( key == Keys.Up )
{
lines_to_scroll = -textbox_VSB.SmallChange;
}
else if ( key == Keys.PageDown )
{
lines_to_scroll = textbox_VSB.LargeChange;
}
else if ( key == Keys.PageUp )
{
lines_to_scroll = -textbox_VSB.LargeChange;
}
else if ( key == Keys.Home )
{
trigger_timer = false;
lines_to_scroll = int.MinValue;
}
else if ( key == Keys.End )
{
trigger_timer = false;
lines_to_scroll = int.MaxValue;
}
else
{
lines_to_scroll = 0;
}
// either start timer or
// directly dispatch refill
if ( lines_to_scroll != 0 )
{
new_value = textbox_VSB.Value + lines_to_scroll;
if ( trigger_timer )
{
timer.Interval = 1; // cannot be zero
key_down = true;
timer.Enabled = true;
}
else
{
display_input_file ( );
}
}
} // TB_KeyDown
TB_KeyDown 的第一个任务是确定按下了哪个键,并将 lines_to_scroll 设置为相应的值。除了 Home 和 End 键之外,分配给 lines_to_scroll 的值与等效的 contents_TB 垂直滚动条控件相关联的值相同。
对于 Home 和 End 键,lines_to_scroll 分别设置为 int.MinValue [^] 和 int.MaxValue [^]。将 lines_to_scroll 设置为这些值将确保 new_value 被赋予一个超出 contents_TB 垂直滚动条允许范围的值。
请注意,如果按下的键不是我们感兴趣的键,lines_to_scroll 将为零;因此,TB_KeyDown 将返回,不再执行进一步操作。否则,TB_KeyDown 将计算 new_value。如果识别的键不是 Home 或 End(即,trigger_timer 为 true),则 timer.Interval 设置为一,并且 key_down 和 timer.Enabled 都设置为 true。将 timer.Interval 设置为一会导致 TIMER_Tick 几乎立即执行。
对于 Home 和 End 键,一旦计算出 new_value,就可以调用 display_input_file。
5.1.5. TB_MouseWheel 事件处理程序
当鼠标滚轮移动时,会引发鼠标滚轮事件 [^]。此事件由 TB_MouseWheel 事件处理程序处理。
// ********************************************* TB_MouseWheel
void TB_MouseWheel ( object sender,
MouseEventArgs e )
{
int lines_to_move = 0;
lines_to_move =
( e.Delta * mouse_wheel_scroll_lines ) /
DELTA_UNITS_OF_WHEEL_MOVEMENT;
new_value = lines_to_move + textbox_VSB.Value;
display_input_file ( );
} // TB_MouseWheel
e.Delta 包含鼠标滚轮旋转的“咔哒”数的有符号计数。一个“咔哒”是鼠标滚轮的一个档位。
来自 MSDN
鼠标滚轮结合了滚轮和鼠标按钮的功能。滚轮具有离散的、等距的档位。当您旋转滚轮时,每遇到一个档位就会发送一个滚轮消息。一个滚轮档位,即一个“咔哒”,由 Windows 常量 WHEEL_DELTA [在 SystemInformation.MouseWheelScrollLines 中找到] 定义,值为 120。正值表示滚轮向前旋转,远离用户;负值表示滚轮向后旋转,朝向用户。
当前,120 的值是一个档位的标准。如果引入更高分辨率的鼠标,WHEEL_DATA 的定义可能会变小。大多数应用程序应检查正值或负值,而不是总和。
因为 TB_MouseWheel 在鼠标滚轮每次移动时都会被调用,所以 lines_to_move 的计算非常直接。全局常量 DELTA_UNITS_OF_WHEEL_MOVEMENT 定义为 120,从而考虑了 WHEEL_DELTA 的值。
lines_to_move 将是正值或负值,取决于鼠标滚轮的移动方向。由此,可以计算出 new_value 并调用 display_input_file。
5.1.6. CHKBX_CheckedChanged 事件处理程序
应用程序所有复选框的更改事件都通过 CHKBX_CheckedChanged 事件处理程序路由。
:
// ******************************************* class variables
:
bool display_changed = true;
:
bool hexadecimal = true;
:
bool keep_on_top = false;
:
bool use_SP = false;
:
// ************************************** CHKBX_CheckedChanged
void CHKBX_CheckedChanged ( object sender,
EventArgs e )
{
CheckBox check_box = ( CheckBox ) sender;
bool is_checked = check_box.Checked;
string name = check_box.Name.ToString ( );
switch ( name )
{
case "hexadecimal_CHKBX":
hexadecimal = is_checked;
display_changed = true;
display_input_file ( );
break;
// display does not change,
// it is just placed on top
case "keep_on_top_CHKBX":
keep_on_top = is_checked;
this.TopMost = keep_on_top;
break;
case "use_SP_for_spaces_CHKBX":
use_SP = is_checked;
display_changed = true;
display_input_file ( );
break;
default:
throw new ApplicationException (
String.Format (
"{0} is not a recognized CheckBox name",
name ) );
}
} // CHKBX_CheckedChanged
复选框 [^] 提供了用户更改 ViewFile 显示数据方式的能力。十六进制和使用 <SP> 表示空格会改变显示本身;保持在顶部使 ViewFile 显示为桌面最顶层的窗口。所有复选框都是切换开关:可以选中它们来激活显示选项,或清除它们来禁用显示选项。
当选项的更改需要重绘 ViewFile 显示时,会设置 display_changed。
5.1.7. numeric_TB_KeyPress 事件处理程序
添加 start_at_TB 文本框时,也增加了确保只输入数字值的要求。numeric_TB_KeyPress 事件处理程序满足了这一要求。
// *************************************** numeric_TB_KeyPress // Matt Hamilton response in // https://stackoverflow.com/questions/463299/ // how-do-i-make-a-textbox-that-only-accepts-numbers private void numeric_TB_KeyPress ( object sender, KeyPressEventArgs e ) { if ( !( char.IsControl ( e.KeyChar ) || char.IsDigit ( e.KeyChar ) ) ) { e.Handled = true; } } // numeric_TB_KeyPress
当 start_at_TB 文本框具有焦点时按下按键,就会触发该处理程序。将 e.Handled 设置为 true 会告知操作系统该事件已被处理。接受的是数字以及控制键。通过接受控制键,可以编辑 start_at_TB 文本框的内容。
5.2. 初始化应用程序全局变量
:
// ******************************************* class variables
:
bool hexadecimal = true;
string input_file_name = String.Empty;
bool keep_on_top = false;
string last_initial_directory_filename = String.Empty;
:
bool use_SP = false;
string user_viewfile_directory = String.Empty;
:
// ******************************* initialize_global_variables
void initialize_global_variables ( TextBox text_box )
{
determine_textbox_geometry ( text_box );
hexadecimal = true;
input_filename = String.Empty;
keep_on_top = false;
start_at = 0;
use_SP = false;
user_viewfile_directory =
( String.Format (
"{0}/{1}",
Environment.GetEnvironmentVariable (
"USERPROFILE" ),
Application.ProductName.
Replace ( " ", "_" ) ) ).
Replace ( @"\", "/" );
if ( !Directory.Exists ( user_viewfile_directory ) )
{
Directory.CreateDirectory ( user_viewfile_directory );
}
last_initial_directory_filename =
String.Format ( "{0}/last_directory.dat",
user_viewfile_directory );
initialize_tooltips ( );
} // initialize_global_variables
initialize_global_variables 确定 contents_TB 的几何形状,以便 maximum_textbox_lines 可供应用程序使用。然后它将全局变量设置为初始值。设置后,用户可以通过用户界面更改它们。initialize_global_variables 还建立将在点击浏览按钮时使用的 last_initial_directory_filename。最后,initialize_global_variables 为每个用户可访问的控件设置一个工具提示 [^]。
5.2.1. determine_textbox_geometry
:
// ******************************************* class variables
:
int maximum_textbox_lines = 0;
:
// ******************************** determine_textbox_geometry
void determine_textbox_geometry ( TextBox text_box )
{
int character_height = 0;
Font font = text_box.Font;
Size proposed_size = new Size ( int.MaxValue,
int.MaxValue );
// for each printing character
// determine its size and
// revise character_height
for ( int i = SPACE; ( i <= TILDE ); i++ )
{
char ch = Convert.ToChar ( i );
Size size;
string str = ch.ToString ( );
size = TextRenderer.MeasureText (
str,
font,
proposed_size,
TextFormatFlags.Default );
if ( size.Height > character_height )
{
character_height = size.Height;
}
}
maximum_textbox_lines = text_box.Size.Height /
character_height;
} // determine_textbox_geometry
determine_textbox_geometry 确定 contents_TB 文本框的 maximum_textbox_lines。它使用 TextRenderer.MeasureText [^] 来获取可打印字符的最大 character_height。然后,将 character_height(以像素为单位)除以 contents_TB 文本框的 Height。请注意,这是整数除法,因此商会被截断。
5.3. signon
// **************************************************** signon
void signon ( )
{
string [ ] pieces;
pieces = this.ProductVersion.Split (
new char [ ] { '.' },
StringSplitOptions.RemoveEmptyEntries );
this.Text = String.Format ( "{0} V{1}.{2}",
this.ProductName,
pieces [ 0 ],
pieces [ 1 ] );
this.Icon = Properties.Resources.ViewFileIcon;
this.StartPosition = FormStartPosition.CenterScreen;
}
ProductName 和 ProductVersion 的值在 AssemblyInfo.cs 中定义
5.4. 初始化 GUI
:
// ******************************************* class variables
:
VScrollBar textbox_VSB = null;
:
// ******************************************** initialize_GUI
void initialize_GUI ( TextBox text_box )
{
if ( textbox_VSB != null )
{
text_box.Controls.Remove ( textbox_VSB );
textbox_VSB.Dispose ( );
textbox_VSB = null;
}
textbox_VSB = initialize_VScrollBar ( text_box );
text_box.Controls.Add ( textbox_VSB );
textbox_VSB.Visible = false;
text_box.Visible = false;
browse_BUT.Visible = true;
input_file_LAB.Visible = true;
input_file_TB.ForeColor = Color.Gray;
input_file_TB.Text = BROWSE_PROMPT;
input_file_TB.Visible = true;
file_size_LAB.Visible = true;
file_size_TB.Visible = true;
file_bytes = 0;
file_size_TB.Text = file_bytes.ToString ( );
start_at_LAB.Visible = true;
start_at_TB.Visible = true;
start_at = 0;
start_at_TB.Text = start_at.ToString ( );
go_BUT.Visible = true;
start_at_and_go_GB.Visible = false;
hexadecimal_CHKBX.Checked = hexadecimal;
hexadecimal_CHKBX.Visible = true;
keep_on_top_CHKBX.Checked = keep_on_top;
keep_on_top_CHKBX.Visible = true;
use_SP_for_spaces_CHKBX.Checked = use_SP;
use_SP_for_spaces_CHKBX.Visible = true;
options_and_size_GB.Visible = false;
exit_BUT.Visible = true;
} // initialize_GUI
到目前为止,除了一个控件之外,所有 GUI 元素都已定义。最后一个控件是 contents_TB 内部的垂直滚动条(textbox_VSB)。
5.4.1. 初始化垂直滚动条
一个垂直滚动条 [^] 由一个带阴影的滑杆组成,两端各有一个箭头按钮,在箭头按钮之间有一个滚动框(有时称为拇指)。
Minimum 指定滚动条顶部的滚动条值
点击向上箭头会使拇指向上移动 SmallChange 属性中指定的行数(默认为 1)
点击向上翻页区域会使拇指向上移动 LargeChange 属性中指定的行数(默认为 10)
Thumb 是当前位置(在 Value 属性处)
点击向下翻页区域会使拇指向下移动 LargeChange 属性中指定的行数(默认为 10)
点击向下箭头会使拇指向下移动 SmallChange 属性中指定的行数(默认为 1)
Maximum 指定滚动条底部的滚动条值
在运行时,垂直滚动条放置在 contents_TB 的右侧。这允许命名滚动条并指定其某些属性。垂直滚动条由 initialize_VScrollBar 创建。
// ************************************* initialize_VScrollBar
VScrollBar initialize_VScrollBar ( TextBox text_box )
{
VScrollBar vsb = new VScrollBar ( )
{
Cursor = Cursors.Arrow,
LargeChange =
( maximum_textbox_lines / 2 ),
Location = new Point (
( text_box.Width - VSB_WIDTH ),
0 ),
Maximum = int.MaxValue,
Minimum = 0,
Name = "textbox_VSB",
Size = new Size (
VSB_WIDTH,
( text_box.Height - 3 ) ),
SmallChange = 1,
Value = int.MaxValue
};
vsb.Scroll += new ScrollEventHandler ( TB_VSB_Scroll );
return ( vsb );
} // initialize_VScrollBar
定义了 textbox_VSB 后,我们只需要收到垂直滚动条控件(向上或向下滚动、向上或向下翻页)更改的通知。提供这些通知的事件处理程序是 TB_VSB_Scroll。
5.4.2. TB_VSB_Scroll 事件处理程序
// ********************************************* TB_VSB_Scroll
void TB_VSB_Scroll ( Object sender,
ScrollEventArgs e )
{
new_value = e.NewValue;
display_input_file ( );
} // TB_VSB_Scroll
6. 结论
本文介绍了 ViewFile 的修订版,ViewFile 是一个提供文件内容字节导向显示的工具。下图概述了其功能。
尽管 ViewFile 是一个有用的工具,但本文试图说明如何让多个事件处理程序协同工作以提供良好的用户体验。
7. 参考文献
- 按钮 [^]
- 复选框 [^]
- 点击事件 [^]
- 文件签名表 [^]
- FileStream [^]
- int.MaxValue [^]
- int.MinValue [^]
- 鼠标滚轮事件 [^]
- TextRenderer.MeasureText [^]
- 工具提示 [^]
- 垂直滚动条 [^]
- Visual Studio 设计器 [^]
- Windows 计时器 [^]
8. 开发环境
本文介绍的软件是在以下环境中开发的
Microsoft Windows 7 Professional Service Pack 1 |
Microsoft Visual Studio 2008 Professional |
Microsoft .Net Framework Version 3.5 SP1 |
Microsoft Visual C# 2008 |
9. 历史记录
ViewFile V4.0 | 12/16/2022 | 修订工具并删除 Head |
ViewFile V3.1 | 08/11/2017 | 修订文章和软件 |
ViewFile V1.1 和 Head V1.2 | 06/23/2014 | 原始文章 |