Click here to Skip to main content
13,259,976 members (50,684 online)
Click here to Skip to main content
Add your own
alternative version

Stats

10.8K views
818 downloads
20 bookmarked
Posted 23 Jun 2014

ViewFile and Head Tools

, 13 Aug 2017
Rate this:
Please Sign up or sign in to vote.
This article is a significant revision to the earlier version and introduces a new version of ViewFile.

1. Introduction Table of Contents

This article is a significant revision to the earlier version of this article. The revisions include:

  • A user interface that uses a TextBox [^] in place of a RichTextBox [^]
  • Simplified event handlers
  • Repair of a Utility method that would not allow multiple files to be viewed
  • Depreciation of the Head application

2. Background Table of Contents

Often, during the development process, it is useful to know the contents of a file in a format that is not the default format provided by applications. For example, a .txt file is usually displayed by Notepad as a series of lines of text; a .png file by an image viewer as a picture. Normally, default Windows tools do not display the contents of a file in a byte-ordered display.

3. A Note on Typography and Images Table of Contents

In the following discussions, properties that are specified by the developer are displayed in bold text; objects, used internally by the software are displayed in italicized text; objects that are accessed by the user are displayed in italicized bold text.

For those images whose contents are difficult to read, the image is a thumbnail. Upon clicking on the image, a larger version will be displayed.

4. ViewFile Table of Contents

ViewFile

ViewFile is a straight-forward file dump facility. On its initial execution, ViewFile accepts the name of an input file when the user clicks on the Browse button. </p

 

ViewFile Start

ViewFile provides a last directory visited feature that "remembers" the path of the last file examined. This path is placed into a file, named last_directory.dat, for later reference. Because last_directory.dat may be revised during the execution of ViewFile, it is placed into the user's profile folder. A typical path in Windows 7 is C:\Users\<Username>\ViewFile\last_directory.dat. If that file does not exist, it will be created and the last directory visited will be set to C:\Users\<Username>\ViewFile and saved in the newly created file.

When the user has provided the name of the input file, ViewFile saves its path in last_directory.dat and displays the file's contents in its display area. In this revised version, ViewFile is reentrant. This means that the Browse button may be clicked again to supply a new input file name.

Default ViewFile

Note that, in this case, the file whose contents are being displayed is a Bitmap file (recognized not only by its name, .bmp, but also by its file signature, BM). For a good source of file signatures see Gary Kessler's File Signatures Table [^].

The user may scroll up and down through the file contents using the vertical scroll bar to the right of the window.

Keyboard Keys

ViewFile also responds to the following keyboard keys.

  • Home
  • Page Up
  • End
  • Page Down
  • Up Arrow
  • Down Arrow

ViewFile responds to a key that is being held down (with the exception of Home and End).

 

The user may choose to:

  • Change the left-hand format from hexadecimal to decimal.
  • Cause the display to stay on top of all other desktop windows.
  • Replace spaces on the right-hand side by the characters "<SP>". This is useful when the number of space characters on the right may not be immediately apparent.

5. Head Table of Contents

Head

Head, named after the Unix head command, performs in much the same manner as ViewFile except that, like its Unix cousin, it displays only the first 80 bytes of the file.

After considerable thought, I have decided to depreciate Head. Its display format is the same as ViewFile. Both load in about the same amount of time. The only major difference (other than the 80 byte limit of Head) is that Head does not have a scroll bar and does not recognize any keyboard actions.

Although it is depreciated, it is included in the solution download. (I do like its icon.)

 

6. Implementation Table of Contents

ViewFile and Head share much in common. But because Head is depreciated, this discussion of implementation addresses only ViewFile.

:
// ******************************************* class constants
:
const int       TIMER_REPEAT_DELAY = 400;
:
// ************************************************** ViewFile

public ViewFile ( )
    {

    InitializeComponent ( );

    initialize_event_handlers_and_timer (
                                        contents_TB,
                                        TIMER_REPEAT_DELAY );
    initialize_global_variables ( contents_TB );
    signon ( );
    initialize_GUI ( contents_TB );
    }

contents_TB is the TextBox that will display the contents of the user-chosen file. It is declared in the Visual Studio Designer [^]. TIMER_REPEAT_DELAY is a global constant that contains the number of milliseconds (400) before the timer Tick event is raised again. The value cannot be less than one. The value of 400 milliseconds appears to be a reasonable choice for the timer repeat interval.

After these tasks complete, ViewFile is wholly event driven.

6.1. initialize_event_handlers_and_timer Table of Contents

This method's first parameter is the TextBox that will contain the display; the second is the time in milliseconds by which the timer will delay between re-executing.

:
// *********************** initialize_event_handlers_and_timer

void initialize_event_handlers_and_timer (
                                TextBox text_box,
                                int     timer_repeat_delay )
    {
                                // application exit
    Application.ApplicationExit += new EventHandler (
                                        OnApplicationExit );
                                // Button Clicks
    browse_BUT.Click += new EventHandler ( BUT_Click );
    exit_BUT.Click += new EventHandler ( BUT_Click );
                                // Keyboard keys
    text_box.KeyDown += new KeyEventHandler( TB_KeyDown );
    text_box.KeyUp += new KeyEventHandler ( TB_KeyUp );
                                // TextBox MouseWheel
    if ( mouse_wheel_present )
        {
        text_box.MouseWheel += new MouseEventHandler (
                                        TB_MouseWheel );
        }
                                // CheckBox CheckedChanged
    hexadecimal_CHKBX.CheckedChanged +=
        new EventHandler ( CHKBX_CheckedChanged );
    keep_on_top_CHKBX.CheckedChanged +=
        new EventHandler ( CHKBX_CheckedChanged );
    use_SP_for_spaces_CHKBX.CheckedChanged +=
        new EventHandler ( CHKBX_CheckedChanged );
                                // repeat timer
    timer = new Timer ( );
    timer.Interval = timer_repeat_delay;
    timer.Tick += new EventHandler ( TIMER_Tick );
    timer.Enabled = false;
    }

Each of the event handlers will be discussed below.

6.1.1. OnApplicationExit Event Handler Table of Contents

Although this event handler is not strictly required, it is declared so that the FileStream [^], whose contents are to be displayed, will be closed and disposed and the Windows Timer [^] will be stopped and disposed when the application exits.

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;
            }
        }

6.1.2. BUT_Click Event Handler Table of Contents

The BUT_Click event handler fields the Button [^] Click Events [^] of the Browse and Exit buttons. Processing the Exit button is straight forward. The Browse button, on the other hand, actually initiates the display.

// ************************************************* BUT_Click

void BUT_Click ( object     sender,
                 EventArgs  e )
    {
    Button  button = ( Button ) sender;
    string  message = String.Empty;
    string  name = button.Name;

    switch ( name )
        {
        case "browse_BUT":
            if ( browse_click_processed ( ref message ) )
                {
                if ( display_file (     input_file_name,
                                    ref contents_TB,
                                    ref message ) )
                    {
                    contents_TB.Focus ( );
                    }
                }
            if ( message.Length > 0)
                {
                MessageBox.Show (
                    message,
                    "Failed to retrieve input file name" );
                }
            break;

        case "exit_BUT":
            Application.Exit ( );
            break;

        default:
            throw new ApplicationException (
                String.Format (
                    "{0} is not a recognized Button name",
                    name ) );
        }
    }

browse_click_processed obtains the filename of the file that is to be displayed. If there were no errors, BUT_Click invokes display_file to setup the initial display. Note that display_file is invoked once for each file.

:
// ******************************************* class variables
:
VScrollBar  textbox_VSB = null;
:
// ********************************************** display_file

/// <summary>
/// performs initialization of the file stream for reading and
/// the GUI for display, finally invoking display_input_file;
/// display_file is invoked at the beginning of the display of
/// a file; thereafter display_input_file is invoked directly
/// </summary>
/// <param name="filename">
/// name of the file whose contents are to be displayed
/// </param>
/// <param name="text_box">
/// TextBox into which the file's contents are placed
/// </param>
/// <param name="error_message">
/// if no errors were encountered, an empty string; otherwise,
/// a string containing a description of the error
/// </param>
/// <returns>
/// true, if processing was successful; otherwise false
/// </returns>
bool display_file (     string  filename,
                    ref TextBox text_box,
                    ref string  error_message )
    {
    bool  successful = false;

    error_message = String.Empty;

    if ( open_file_stream (     filename,
                            ref error_message ) )
        {
        try
            {
            int         file_bytes = 0;
            FileInfo    file_info = null;
            int         file_lines = 0;

            file_info = new FileInfo ( filename );
            file_bytes = ( int ) file_info.Length;
            file_size_TB.Text = file_bytes.ToString ( );
                                // round up (integer divide)
            file_lines = ( file_bytes +
                           MAXIMUM_ENTRIES_PER_LINE ) /
                         MAXIMUM_ENTRIES_PER_LINE;

            new_value = 0;

            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 ( ref text_box );

            error_message = String.Empty;
            successful = true;
            }
        catch ( Exception ex )
            {
            error_message = ex.Message;
            successful = false;
            }
        }

    return ( successful );
    }

When the file specific variables have been initialized, display_file invokes display_input_file.

display_changed must be set true before display_input_file is invoked. In this case, both new_value and textbox_VSB.Value are set to zero. Normally, in display_input_file, this situation would be interpreted as "no need to redraw." However, display_file is invoked when a newly opened file is to be displayed. Therefore it is necessary to force display_input_file to redraw the display. Other instances where display_changed must be set to true are when the options Hexadecimal or Use <SP> for Spaces change the display itself. In those cases no change to new_value or textbox_VSB.Value occurs. Unless display_changed is set true, the display will not reflect the user's wishes.

display_input_file is the workhorse of 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 ( ref TextBox  text_box )
        {
        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
        text_box.Suspend ( );       // see ControlExtensions.cs
        text_box.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;
                                    // 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 text_box );
            }
                                    // 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 text_box );
            }

        text_box.Select( 0, 0 );    // first byte of text_box
        text_box.ScrollToCaret ( ); 
        text_box.Resume ( );        // see ControlExtensions.cs
        text_box.Visible = true;
        textbox_VSB.Visible = true;

        options_and_size_GB.Visible = true;
        }

If the global variable display_changed is true, the contents of the TextBox will be redrawn, otherwise, the value of the global variable new_value controls the execution of display_input_file. Initially, its value is set to 0 and the value in display_changed is set to true. These values insure that display_input_file fills the TextBox with the first 160 bytes of the input file. Thereafter, new_value is modified in response to events.

Once display_input_file has retrieved the data from the file, it fills the TextBox. If the number of bytes read is not evenly divisible by the number of bytes in a TextBox line (MAXIMUM_ENTRIES_PER_LINE), filling the TextBox occurs in two steps: the first filling the TextBox with "whole" lines, and the second filling the remaining bytes.

Note that the number of bytes read by display_input_file is always 160 or less (less if the end of file is encountered on filestream). Because ViewFile interacts with the user, there is no advantage to performing any kind of optimizations. Any performance gained would be offset by ViewFile's user interface.

When display_input_file completes execution, ViewFile goes idle until a keyboard key is pressed, the mouse wheel is rotated, or until the TextBox vertical scroll bar is modified.

6.1.3. TIMER_Tick Event Handler Table of Contents

Prior to discussing the keyboard event handlers, I address the TIMER_Tick event handler because it is indirectly invoked by the TB_KeyDown event handler and its actions are modified by the TB_KeyUp event handler.

TIMER_Tick is relatively simple. It is invoked when timer.Enabled is set true and the timer.Interval expires.

:
// ******************************************* 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 ( ref contents_TB );
            }
        }
    }

Its first action is to reset the timer.Interval to the value of TIMER_REPEAT_DELAY. I believe that 400 milliseconds is sufficient to provide a reasonable repeat rate. Two additional conditions must be met before TIMER_Tick continues its execution: key_down must be true and lines_to_scroll must be non-zero. If these conditions are met, new_value is revised and display_input_file is invoked.

6.1.4. TB_KeyDown and TB_KeyUp Event Handlers Table of Contents

Of these two event handlers, TB_KeyUp is by far the simplest.

// ************************************************** TB_KeyUp

void TB_KeyUp ( object       sender,
                KeyEventArgs e )
    {

    key_down = false;
    timer.Enabled = false;
    }

When a key is released, TB_KeyUp is invoked. Its purpose is to cancel any further actions by TIMER_Tick. By setting the two conditions required for TIMER_Tick to execute (i.e., timer.Enabled and key_down) to false, TIMER_Tick stops executing. In turn, further revisions to the TextBox display are halted.

Although TB_KeyUp is more complex, its logic is relatively simple. Its purpose is to determine if a keyboard key of interest has been pressed and, if so, dispatch the appropriate action.

// ************************************************ TB_KeyDown

void TB_KeyDown ( object       sender,
                  KeyEventArgs e )
    {
    Keys    key = e.KeyCode;
    TextBox text_box = ( TextBox ) sender;
    bool    trigger_timer = true;
                                // compute lines_to_scroll
                                // and either start timer or
                                // directly dispatch refill
    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;
        }

    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 ( ref text_box );
            }
        }
    }

The first task of TB_KeyDown is to determine what key was pressed and set lines_to_scroll to its appropriate value. With the exception of the Home and End keys, the values assigned to lines_to_scroll are those associated with the equivalent TextBox's vertical scroll bar controls.

For the Home and End keys, lines_to_scroll is set to int.MinValue [^] and int.MaxValue [^] respectively. Setting lines_to_scroll to these values will insure that new_value will be assigned a value outside of the permissible range of the TextBox vertical scroll bar.

Note that lines_to_scroll will be zero if the key pressed is not one of those in which we are interested; thus causing TB_KeyDown to return, taking no further action. Otherwise, TB_KeyDown will compute new_value. If a key other than Home or End was recognized (i.e., trigger_timer is true), the timer.Interval is set to one and both key_down and timer.Enabled are set true. Setting timer.Interval to one causes TIMER_Tick to execute almost immediately.

For the Home and End keys, once new_value is computed, display_input_file can be invoked.

6.1.5. TB_MouseWheel Event Handler Table of Contents

The MouseWheel Event [^] is raised when the mouse wheel moves. This event is handled by the TB_MouseWheel event handler.

// ********************************************* 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 ( ref contents_TB );
    }

e.Delta contains a signed count of the number of detents the mouse wheel has rotated. A detent is one notch of the mouse wheel.

From MSDN:

The mouse wheel combines the features of a wheel and a mouse button. The wheel has discrete, evenly spaced notches. When you rotate the wheel, a wheel message is sent as each notch is encountered. One wheel notch, a detent, is defined by the windows constant WHEEL_DELTA [which in turn is found in SystemInformation.MouseWheelScrollLines], which is 120. A positive value indicates that the wheel was rotated forward, away from the user; a negative value indicates that the wheel was rotated backward, toward the user.

Currently, a value of 120 is the standard for one detent. If higher resolution mice are introduced, the definition of WHEEL_DATA might become smaller. Most applications should check for a positive or negative value rather than an aggregate total.

Because TB_MouseWheel is invoked each time that the mouse wheel moves, the computation of lines_to_move is straight-forward. The global constant DELTA_UNITS_OF_WHEEL_MOVEMENT is defined as 120, thus taking into effect the value of WHEEL_DELTA.

lines_to_move will be positive or negative, depending upon the direction in which the mouse wheel was moved. From that value, new_value can be computed and display_input_file invoked.

6.1.6. CHKBX_CheckedChanged Event Handler Table of Contents

All change events for the application's CheckBoxes are routed through the CHKBX_CheckedChanged event handler.

:
// ******************************************* 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 ( ref contents_TB );
            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 ( ref contents_TB );
            break;

        default:
            throw new ApplicationException (
                String.Format (
                    "{0} is not a recognized CheckBox name",
                    name ) );
        }
    }

CheckBoxs [^] provide the user with the ability to change the way in which ViewFile displays its data. Hexadecimal and Use <SP> for Spaces change the display itself; Keep on Top causes ViewFile to be displayed as the topmost form on the desktop. All CheckBoxes are toggles: they can be checked to activate the display option or cleared to deactivate the display option.

display_changed is set when the change in an option requires that the ViewFile display be redrawn.

6.2. Initialize application global variables Table of Contents

:
// ******************************************* 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;                       // default
    input_file_name = String.Empty;
    keep_on_top = false;                      // default
    use_SP = false;                           // default

    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 determines the TextBox geometry so that the maximum_textbox_lines is available to the application. Then it sets global variables to their initial values. Once set, they may be changed by the user through the user interface. initialize_global_variables also establishes the last_initial_directory_filename that will be used when the Browse button is clicked. Lastly, initialize_global_variables sets a ToolTip [^] on each user accessible control.

6.2.1. determine_textbox_geometry Table of Contents

:
// ******************************************* 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 determines the maximum_textbox_lines of the TextBox. It uses TextRenderer.MeasureText [^] to obtain the largest character_height of the printable characters. character_height (in pixels) is then divided into the Height of the TextBox. Note that this is integer division, so the quotient is truncated.

6.3. signon Table of Contents

signon is presented without further comment.

// **************************************************** 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;
    }

6.4. Initialize the GUI Table of Contents

:
// ******************************************* 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;

    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;
    }

Up until now, with the exception of one, all GUI elements have been defined. This last control is the vertical scroll bar (textbox_VSB) inside the TextBox.

6.4.1. Initialize the Vertical Scroll Bar Table of Contents

A Vertical ScrollBar [^] consists of a shaded shaft with an arrow button at each end and a scroll box (sometimes called a thumb) between the arrow buttons.

Vertical Scroll Bar

Minimum specifies the scrollbar value at the top of the scrollbar
Clicking the Line up arrow moves the thumb up the number of lines specified in the SmallChange property (defaults to 1)
Clicking in the Page up area moves the thumb up the number of lines specified in the LargeChange property (defaults to 10)
Thumb is the current position (at the property Value)
Clicking in the Page down area moves the thumb down the number of lines specified in the LargeChange property (defaults to 10)
Clicking the Line down arrow moves the thumb down the number of lines specified in the SmallChange property (defaults to 1)
Maximum specifies the scrollbar value at the bottom of the scrollbar

 

At run-time, the vertical scrollbar is placed on the right side of the TextBox. This allows naming the scrollbar as well as specifying some of its properties. The vertical scrollbar is created by 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 );
    }

With textbox_VSB defined, all we need is to be notified of changes to the Vertical Scroll Bar controls (Line up or down, Page up or down). The event handler that provides these notifications is TB_VSB_Scroll.

6.4.2. TB_VSB_Scroll Event Handler Table of Contents

// ********************************************* TB_VSB_Scroll

void TB_VSB_Scroll ( Object          sender,
                     ScrollEventArgs e )
    {

    new_value = e.NewValue;
    display_input_file ( ref contents_TB );
    }

7. Conclusion Table of Contents

This article has presented the revision to ViewFile, a tool that provides a byte-oriented display of file contents. The following figure presents an overview.

Viewfile Overview

 

Although ViewFile is a useful tool, this article has attempted to illustrate how a number of event handlers can work together to provide a good user experience.

8. References Table of Contents

9. Development Environment Table of Contents

The software presented in this article was developed in the following environment:

Microsoft Windows 7 Professional Service Pack 1
Microsoft Visual Studio 2008 Professional
Microsoft .Net Framework Version 3.5 SP1
Microsoft Visual C# 2008

10. History Table of Contents

ViewFile V3.108/11/2017Revised Article and Software
ViewFile V1.1 and Head V1.206/23/2014Original Article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

gggustafson
Software Developer (Senior)
United States United States
I started programming more than 42 years ago using AutoCoder and RPG (note no suffixing numbers). Programs and data were entered using punched cards. Turnaround between submitting a job for compilation and execution was about 3 hours. So much for the "good old days!" Today, I particularly enjoy programming real-time software. I consider myself capable in WinForms, Mobile Apps, and C# although there are occasions that I yearn to return to C and the Win32 API.

You may also be interested in...

Pro

Comments and Discussions

 
GeneralMy vote of 5 Pin
Emre Ataseven23-Jun-14 23:40
professionalEmre Ataseven23-Jun-14 23:40 
GeneralRe: My vote of 5 Pin
gggustafson24-Jun-14 5:49
professionalgggustafson24-Jun-14 5:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.171114.1 | Last Updated 13 Aug 2017
Article Copyright 2014 by gggustafson
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid