Site hosted by Angelfire.com: Build your free website today!

Home
DelphiZeus
15 A. Program Code for UseFiles
UseFiles a Program for
Writting and Reading files

Home



The UseFiles Program

This page contains the code used to make the UseFiles program, which is about the file access methods that are in Lesson 15. For this program there is the UseFiles.dpr program file and three units called - UseFilesU.pas , SplitFilesU.pas and DataFilesU.pas . . This program also uses the MakeApp.pas unit fron lesson 13. The "Main" unit for this is the UseFilesU.pas , this has the startup code that creates the windows and processes their messages. It has the code to creates a system "Tab Control" with 3 tabs on it, which are used to change "Pages" shown to the user. Page one is a text editor page, page two is the "Split File" page, page three is the "Muti-Segment" data file page. The UseFilesU unit only has the code used to read and write a "Text" file used in the text editor, multi-line Edit control. The code use for tab page two "File Splitting", is in the SplitFileU.pas unit. In this unit you are shown how to transfer bytes from one file to another. The code used for tab page three "Data Files", in in the DataFilesU.pas unit. The code in this unit shows you how to place your data bytes in a file as a "Data Segment", so you can have several program variables stored in one file and then read them to for information that your program requires.


UseFiles Program Code

The code methods for file access in this program are explained in Lesson 15 - Writing and Reading Files -. You shoud have read Lesson 15 and some of the Win32 Help subjects recomended there for information about the CreateFile( ) function and using file handles. Below is alot of code for this program, By this point in these lessons, I hope that you have enough experience with API methods to go through the code units below and work with just one or two functions at a time. I did not present this as many small - step by step - code units. But I have sorted the code into 3 units and the program file. Since there is so much code here, I have a link to a ZIP file that has all of the program code and Units used here -
FileProg.zip
I have included many comments in the code below, to give you some information about what I am doing in each part of the code. When you use disk files to store and retrive data, you may need to use several different methods for data identification, position and size, that will depend on what you need in your data files. There are 3 tab control pages in this program, and I tried to have a different file building method on each tab page. Each tab page has it's own code Unit. First lets look at the program file code -

UseFiles.DPR Program File

You have seen the methods for this program file in Lesson-11 and Lesson-13 - MakeApp Unit - You will need the MakeApp.pas unit from Lesson-13 for this program file and the UseFilesU.pas unit. I have included the ThemeXP.RES resource to have the themes GUI on this program, this code for this ThemeXP.RES is in Lesson-12 - Visual Themes in XP - . You can leave out the ThemeXP.RES resource with no effect on the code below.

program UseFiles;

uses
  MakeApp, UseFilesU;

{$R *.RES}
{$R ThemeXP.RES}
// ThemeXP.RES has the Theme manifest in it

begin
if MakeProgram then // MakeProgram in UseFilesU
  RunMsgLoop; // RunMsgLoop in MakeApp
end.

UseFilesU Unit

This is the "Main Unit" for this program and has the code for all window creation and message handling, along with code for the first tab control page for text file writting and reading. This uses window creation methods that have already been explained here at DelphiZeus in lessons before. This unit uses the MakeApp.pas unit from Lesson-13 - MakeApp Unit for the main window (Form) creation and other windows (buttons, panel) and font creation. The new kind of system control in this code is a system "Tab Control" which is explained in Lesson-15. The code for creating a tab control is in the MakeControls procedure, this procedure also creates the fonts and 5 buttons. After the tab control is created, I need to create three "Page Windows" as pages for the tab control. Each page window is shown or hidden as the tab control changes selected tab. The first page is just a system multi-line EDIT control, which is used for a text editor to load and save text files. I wanted to show you that for Tab Control pages, that you can use many different kinds of "Container Windows" as a parent for controls that will be displayed for each separate "Page" of the tab control. The second page is a system STATIC control, which will be used as a "Container" window, NOT as a STATIC control. Code to create this Page 2 window and it's child controls is in the MakePage2 procedure. Page 3 is a "Panel" window from the MakeApp Unit, used as a "Container" window, code for creating Page 3 and it's child controls is in the MakePage3 procedure.

The message handling function for hForm1 is MessageFunc, the new code methods in it are for the page changes for the tab control. In the WM_NOTIFY message processing, I test the WParam for the tab ID of ID_TabCtrl and then cast the LParam as a PNMHdr, to get the code member as a TCN_SELCHANGE or TCN_SELCHANGING value. This message does NOT have information about which tab it is for, so you have to ask the tab control which tab is selected with the TCM_GETCURSEL message. Then you hide and show the page windows for that tab selection.

The two procedures that write and read a text file are SaveTextFile and LoadTextFile.

unit UseFilesU;

interface


function MakeProgram: Boolean;
{the MakeProgram function will call some functions to create the windows and
 controls for this Application. If there is a creation error, it returns False}

procedure SysErrorMsg(const Text, Title: String);
// shows message box with system error text

function GetSplitFolder: String;
// will get the folder path from the hFolderEdit edit

function OpenDialog1(Use1: Cardinal): String;
// shows the Open and Save dialogs

const
One = 1;
Two = 2;
// below are Radio Button ID
ID_1MegRB = 1010;
ID_EqualRB = 1011;

// message box file access error text is below
Err = 'ERROR - ';
CreateErrText = 'System could NOT Create file';
CreateErrTitle = 'Did NOT Create File';
ReadErrText = 'System could NOT Read file';
ReadErrTitle = 'Did NOT Read File';
WriteErrText = 'System could NOT Write file';
WriteErrTitle = 'Did Not Write File';

var    
hForm1: Integer = 0; // handle of Main Window (Form)
hPage2, hIntEdit, hStrEdit, hListBox, hEdit2: Integer;


implementation
                 
uses
  Windows, Messages, SplitFilesU, DataFilesU, ShlObj, ActiveX,
  MakeApp, CommDlg, CommCtrl, SmallUtils;
                          // ActiveX is needed for the shell IMAlloc


const
butHeight = 24;
// control ID numbers below
ID_ExitBut = 1000;
ID_LoadTBut = 1001;
ID_SaveTBut = 1002;
ID_TabGetBut = 1003;
ID_TabSetBut = 1004;
ID_SplitBut = 1005;
ID_RestoreBut = 1006;
ID_CopyFileBut = 1007;
ID_OpenSplitBut = 1008;
ID_OpenResBut = 1009;

ID_DataNameBut = 1012;
ID_SaveFixedBut = 1013;
ID_LoadFixedBut = 1014;
ID_SaveData1But = 1015;
ID_LoadData1But = 1016;
ID_SaveVar1But = 1017;
ID_LoadVar1But = 1018;
ID_SaveStrBut = 1019;
ID_GetListBut = 1020;
ID_LoadStrBut =1021;
ID_TabCtrl = 2000;

TextRect: TRect = (left: 290; Top: 142; Right: 510; Bottom: 164);
// TextRect has the area needed for hPage2 refresh of the File Size text


var
Font1, Font2, Font3, hTab1, hPage1, hPage3, hFolderEdit, hSplitEdit,
hRestEdit, hDataEdit, hVarEdit, hButTab: Integer;
pPageProc: Pointer;
sFileSize, PersonalFolder: String;
// PersonalFolder will have the path for the user's My Documents folder
FirstOpen: Boolean = True;

procedure SysErrorMsg(const Text, Title: String);
begin
// shows a message box that adds the system error text
MessageBox(hForm1, PChar(Err+Text+#10+ SysErrorMessage(GetLastError)),
           PChar(Err+Title), MB_ICONERROR);
end;

function GetSplitFolder: String;
begin
// gets the text out of hFolderEdit and test for the folder
Result := GetWindowStr(hFolderEdit);
if not DirectoryExists(Result) then
  begin
  MessageBox(hForm1, 'ERROR - You must Have a valid Folder Path in the'+
             ' Folder Edit box', 'ERROR - Not a Valid Folder', MB_ICONERROR);
  Result := '';
  Exit;
  end;
end;

// OpenDialog1 shows the system Open and Save Dialog boxes
function OpenDialog1(Use1: Cardinal): String;
var
OfName : TOpenFilename;
FileName: Array[Zero..2047] of Char;
begin
ZeroMemory(@FileName, SizeOf(FileName));
ZeroMemory(@Ofname, SizeOf(OfName));

with OfName do
  begin
  lStructSize := sizeof(OfName);
  hWndOwner := hForm1;
  hInstance := SysInit.hInstance;
  nMaxFile := SizeOf(FileName);
  lpStrFile := @FileName;
  Flags := OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_HIDEREADONLY;
  Case Use1 of // Use1 is set to change filter and title
    Zero: begin
      lpstrFilter := 'Text files  .TXT'#0'*.txt'#0'All files'#0'*.*'#0#0;
      lpstrTitle := 'Open a Text File';
      end;
    One: begin
      lpstrFilter := 'All files'#0'*.*'#0#0;
      lpstrTitle := 'Open a File to Split';
      end;
    Two: begin
      lpstrFilter := 'Split files  3p1, 1meg1'#0'*.3p1;*.1meg1'#0'All files'#0'*.*'#0#0;
      lpstrTitle := 'Open a File to Restore';
      end;
    3: begin
      lpstrFilter := 'Text files  .TXT'#0'*.txt'#0'All files'#0'*.*'#0#0;
      lpstrTitle := 'Save a Text File';
      Flags := OFN_EXPLORER or OFN_PATHMUSTEXIST or
               OFN_OVERWRITEPROMPT or OFN_HIDEREADONLY;
      end;
    4: begin
      lpstrFilter := 'Multi-Data files  .MDTF'#0'*.mdtf'#0'All files'#0'*.*'#0#0;
      lpstrTitle := 'Save a Multi-Data File';
      lpstrDefExt := 'mdtf';
      Flags := OFN_EXPLORER or OFN_PATHMUSTEXIST or OFN_OVERWRITEPROMPT or
                OFN_HIDEREADONLY or OFN_EXTENSIONDIFFERENT;
      end;
    5: begin
      lpstrFilter := 'All files'#0'*.*'#0#0;
      lpstrTitle := 'Open a File to COPY';
      end;
    6: begin
      lpstrFilter := 'All files'#0'*.*'#0#0;
      lpstrTitle := 'Get File Name for New Copyed File';
      Flags := OFN_EXPLORER or OFN_PATHMUSTEXIST or
                OFN_OVERWRITEPROMPT or OFN_HIDEREADONLY;
      end;
    end;

  nFilterIndex := One;
  if FirstOpen then
    begin
    FirstOpen := False;
    lpstrInitialDir:= PChar(PersonalFolder)
    end;
  end;

Result := '';
// if Use1 is 3, 4, or 6 then save dialog
if Use1 in [3,4,6] then
  begin
  if GetSaveFileName(ofName) then
  Result := FileName;
  end else // open dialog
  if GetOpenFileName(ofName) then
  Result := FileName;
end;


procedure SaveTextFile;
var
hFile, TextLength, BytesWrite: Cardinal;
FileName: String;
pText: PChar;
begin
// this procedure will open a file and write a PChar string into it
FileName := OpenDialog1( 3);
if Filename = '' then Exit;
hFile := CreateFile(PChar(FileName),GENERIC_WRITE,Zero,
                    nil, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or
                    FILE_FLAG_SEQUENTIAL_SCAN,Zero);
{this file is opened with a Write Only flag, I have the share set to zero so
 no other program and read or write to this file during the write operation.
 By using the CREATE_ALWAYS flag a file is created or existing file is erased.
 I use the FILE_FLAG_SEQUENTIAL_SCAN flag to allow the system to use a more
 efficient file read and catching operations, however on small files this
 makes no difference}

if hFile = INVALID_HANDLE_VALUE then
  begin  // check for open file
  SysErrorMsg(CreateErrText+#10+FileName, CreateErrTitle);
  Exit;
  end;

// I will get the amount of text charaters in the multi-line edit
TextLength := GetWindowTextLength(hPage1);
if TextLength < One then
  begin
  // if the edit is empty then Close Handle and exit
  CloseHandle(hFile);
  Exit;
  end;

GetMem(pText, TextLength+One); // add one for NULL charater
{this time I use a PChar, and get a memory block for it with GetMem( )}
GetWindowText(hPage1, pText, TextLength+One);

// you will need to dereference a Pointer, pText, for a WriteFile( )
if (not WriteFile(hFile, pText^, TextLength, BytesWrite, nil)) or
       (TextLength <> BytesWrite) then
  begin // test the FileWrite and also the BytesWrite to see if successful
  CloseHandle(hFile);
  DeleteFile(PChar(FileName)); // delete the file if there is a write error
  SysErrorMsg(WriteErrText, WriteErrTitle);
  end;

FreeMem(pText);
CloseHandle(hFile);
end;


procedure LoadTextFile;
const
pError: PChar = 'File Load ERROR';

var
hFile, SizeF, BytesRead: Cardinal;
Text1: String;
begin
// this procedure will open and read a text file into a string
Text1 := OpenDialog1(Zero);
if Text1 = '' then Exit;

hFile := CreateFile(PChar(Text1), GENERIC_READ, FILE_SHARE_READ, nil,
                    OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, Zero);
{ file is opened for reading only with GENERIC_READ, ,
 For Share, I include a FILE_SHARE_READ to allow other programs to
 read from this file.
 The file must exist or a file error and no open file with OPEN_EXISTING.
 The file Flag is set to FILE_FLAG_SEQUENTIAL_SCAN, which may help in
 access speed for larger files}

if hFile = INVALID_HANDLE_VALUE then
  begin
// file handle should be checked to see if system has located and opened the file
  SysErrorMsg(CreateErrText+#10+Text1, CreateErrTitle);
  Exit;
  end;

SizeF := GetFileSize(hFile, nil);
// for most file reads you will need the size of the file on disk
if (SizeF = MAXDWORD) then
  begin
  { the GetFileSize will usually work, although if the file is larger than
  4 gigs and there is no lpFileSizeHigh it will fail}
  CloseHandle(hFile);
  // You must try to always use CloseHandle( ) when you are done with the file
  MessageBox(hForm1, 'GetFileSize  FAILED', pError, MB_ICONERROR);
  Exit;
  end;

if SizeF = Zero then
  begin
  // if the file size is zero, there is nothing in the file to read
  CloseHandle(hFile);
  SetWindowText(hPage1, nil);
  Exit;
  end;

if SizeF > 32768 then
  begin
{ although text files have no file header information, you will ususally need to
 test some property of the file bytes, to see if it is a file you want to use.
 For an example, I just test for the size of 32 Kb, even though it is not nessary}
  CloseHandle(hFile);
  MessageBox(hForm1, PChar('ERROR, File size is MORE than 32,768'#10+
             'CAN NOT LOAD, file is to large at '+Int2Thos(SizeF)),
              pError, MB_ICONERROR);
  Exit;
  end; 

// next are the file read operations
SetLength(Text1, SizeF);
{ a memory block is needed to read the file bytes into, since this is for text
  charaters I use a String, and to get the memory I use SetLength( ) }

{the next code is typical for a file read, ReadFile( ) will return False
 if it fails. You should also test the BytesRead for the amount that the
 file should read thats in SizeF}
if not ReadFile(hFile, Text1[One], SizeF, BytesRead, nil) or (SizeF <> BytesRead) then
  begin
  SysErrorMsg(ReadErrText, ReadErrTitle);
  CloseHandle(hFile);
  Exit;
  end;
CloseHandle(hFile);
// always be sure to CloseHandle( )

SetWindowText(hPage1, @Text1[One]);
//Place the Text1 string in to the Muti-line Edit hPage1
end;



function PageFunc(hWnd, Msg, WParam, LParam: Integer): Integer; stdcall;
begin
// inorder to get Tab keys, I subclass the muti-line Edit (hPage1)
case Msg of
WM_GETDLGCODE:
  begin
{WM_GETDLGCODE is sent to SubClassed Proc to get a result
that will tell the OS which Keys to use or not for the Dialog
keys, a multiline edit will be less easy to get Tab Stop
functioning, because it may need Tab key input}
  Result := DLGC_WANTALLKEYS;
{DLGC_WANTALLKEYS tells the OS to send all keys to this Edit Box
Multi-Line Edits should get all keys}
  Exit;
  end;
end;
Result := CallWindowProc(pPageProc,hWnd,Msg,wParam,lParam);
end;


procedure GetTheFile(Opp: Cardinal);
var
FileName: String;
ErrorMode, hFind: Cardinal;
FindData: TWin32FindData;
begin
// this procedure gets the file size of the file to split, to display on hPage2
if (Opp < One) or (Opp > Two) then Exit;
FileName := OpenDialog1( Opp);
if Filename = '' then Exit;
if Opp = One then
  begin
  ErrorMode := SetErrorMode(SEM_FailCriticalErrors);
  hFind := FindFirstFile(PChar(FileName), FindData);
  SetErrorMode(ErrorMode);
  if hFind <> INVALID_HANDLE_VALUE then
    begin
    FindClose(hFind);
    sFileSize := 'File Size '+Int2Thos(FindData.nFileSizeLow)+' bytes';
    SetWindowText(hSplitEdit, PChar(FileName));
    end else
    sFileSize := 'Invalid File';
  InvalidateRect(hPage2, @TextRect, True);
  end else
  SetWindowText(hRestEdit, PChar(FileName));
end;


function Page2MsgFunc(hWnd,Msg,wParam,lParam:Integer):Integer; stdcall;
var
PaintS: TPaintStruct;
cRect: TRect;
begin
// this is the messages processing for the STATIC window hPage2
case Msg of
  WM_PAINT:
    begin
  {On Page 2 I draw text and I draw two 3D bars}
    BeginPaint(hWnd, PaintS);
    SetBkMode(PaintS.hdc, TRANSPARENT);
    TextOut(PaintS.hdc,TextRect.Left,TextRect.Top, PChar(sFileSize),Length(sFileSize));
    SelectObject(PaintS.hdc, VarFont);
    TextOut(PaintS.hdc,84,198, 'Folder where split files go -',29);
    SetBkColor(PaintS.hdc, $99FF99);
    SelectObject(PaintS.hdc, Font1);
    SetBkMode(PaintS.hdc, OPAQUE);
    // the text draw below are for the titles on the page with a green background
    TextOut(PaintS.hdc,236,6, ' Copy a File ',13);
    TextOut(PaintS.hdc,240,86, ' Split a File ',14);
    TextOut(PaintS.hdc,226,270, ' Restore a File ',16);

  {the line drwing code below will draw two green 3D bars on the page}
    SelectObject(PaintS.hdc, CreatePen(PS_SOLID,Zero, $FCFFF4));
  // this first line draw is for the HighLight color
    MoveToEx(PaintS.hdc,Zero,256,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,256);
    MoveToEx(PaintS.hdc,Zero,74,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,74);
    DeleteObject(SelectObject(PaintS.hdc, CreatePen(PS_DOT,Zero, $70A260)));
  // a green Dot pen is used to draw the line body, with 3 lines
    MoveToEx(PaintS.hdc,Zero,255,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,255);
    MoveToEx(PaintS.hdc,Zero,73,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,73);
    MoveToEx(PaintS.hdc,Zero,257,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,257);
    MoveToEx(PaintS.hdc,Zero,75,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,75);
    MoveToEx(PaintS.hdc,Zero,258,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,258);
    MoveToEx(PaintS.hdc,Zero,76,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,76);
    DeleteObject(SelectObject(PaintS.hdc, GetStockObject(BLACK_PEN)));
  // a black pen is used to draw the shadow line
    MoveToEx(PaintS.hdc,Zero,259,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,259);
    MoveToEx(PaintS.hdc,Zero,77,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,77);

    EndPaint(hWnd,PaintS);
    Result := Zero;
    Exit; // Does NOT allow DefWindowProc to be called
    end;
  WM_ERASEBKGND:
    begin
    GetClientRect(hWnd, cRect);
    SelectObject(wParam, GetSysColorBrush(COLOR_BTNFACE));
    PatBlt(wParam,Zero,Zero,cRect.Right,cRect.Bottom, PATCOPY);
    Result := One;
    Exit;
    end;
   // below are the button click messages for page 2
  WM_COMMAND: case LOWORD(wParam) of
    ID_CopyFileBut: CopyAFile; // the split procedures are in  SplitFilesU.pas
    ID_SplitBut: StartSplit(GetWindowStr(hSplitEdit));
    ID_RestoreBut: StartRestore(GetWindowStr(hRestEdit));
    ID_OpenSplitBut: GetTheFile(One);
    ID_OpenResBut: GetTheFile(Two);
    end;
  end;

Result := DefWindowProc(hWnd,Msg,wParam,lParam);
 { PLEASE NOTICE. . . I do NOT use the system STATIC message processing
 I use the DefWindowProc( ) because I do NOT want STATIC window functioning}
end;


function PanelFunc1(iMsg,wParam,lParam:Integer):Integer;
var
PaintS: TPaintStruct;
FileName: String;
begin
 // this is the message function for Page 3, a panel
Result := -2; // -2 will call the Default system message processing
case iMsg of
  WM_PAINT:
    begin
    BeginPaint(hPage3, PaintS); // use the hPage3  handle
    SetBkColor(PaintS.hdc, $FFBAC2);
    SelectObject(PaintS.hdc, Font1);
  // text draw below is for the Titles on page 2
    TextOut(PaintS.hdc,170,4, ' Save Fixed Data to a File ',27);
    TextOut(PaintS.hdc,156,188, ' Save Variable Data to a File ',30);
  // the line draws below create a 3D bar on the page
    SelectObject(PaintS.hdc, CreatePen(PS_SOLID,Zero, $FFE4DC));
    MoveToEx(PaintS.hdc,Zero,172,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,172);
    DeleteObject(SelectObject(PaintS.hdc, CreatePen(PS_DOT,Zero, $666666)));
    MoveToEx(PaintS.hdc,Zero,171,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,171);
    MoveToEx(PaintS.hdc,Zero,173,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,173);
    MoveToEx(PaintS.hdc,Zero,174,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,174);
    // draws smaller line
    MoveToEx(PaintS.hdc,110,269,nil);
    LineTo(PaintS.hdc,494,269);
    MoveToEx(PaintS.hdc,110,270,nil);
    LineTo(PaintS.hdc,494,270);

    DeleteObject(SelectObject(PaintS.hdc, GetStockObject(BLACK_PEN)));
    MoveToEx(PaintS.hdc,Zero,175,nil);
    LineTo(PaintS.hdc,PaintS.rcPaint.Right,175);
    MoveToEx(PaintS.hdc,110,271,nil);
    LineTo(PaintS.hdc,494,271);
    SelectObject(PaintS.hdc, VarFont);
    SetBkMode(PaintS.hdc, TRANSPARENT);
    TextOut(PaintS.hdc,2,32, 'path --> C:\Data File 1.FixedData',33);
    EndPaint(hPage3,PaintS);
    Result := Zero; // Does NOT allow DefWindowProc to be called
    end;
  WM_COMMAND: if LOWORD(wParam) = ID_DataNameBut then
    begin
    FileName := OpenDialog1( 4);
    if Filename = '' then Exit;
    SetWindowText(hDataEdit, PChar(FileName));
    end else // data file procedures below are in  DataFilesU.pas
    case LOWORD(wParam) of
      ID_SaveFixedBut: SaveFixed;
      ID_LoadFixedBut: LoadFixed;
      ID_SaveData1But: SaveRecords(GetWindowStr(hDataEdit));
      ID_LoadData1But: LoadRecords(GetWindowStr(hDataEdit));
      ID_SaveVar1But: SaveVarData(GetWindowStr(hVarEdit));
      ID_LoadVar1But: LoadVarData(GetWindowStr(hVarEdit));
      ID_SaveStrBut: SaveStringRecs;
      ID_GetListBut: LoadListBox;
      ID_LoadStrBut: Load1StringRec;
      end;
  end;
end;


procedure GetTabInfo;
var
TabInfo: TTCItem;
aryChar: Array[Zero..31] of Char;
begin
// this gets the text and LParam for the second tab
TabInfo.mask := TCIF_TEXT or TCIF_PARAM;
TabInfo.pszText := @aryChar;
TabInfo.cchTextMax := 32;
SendMessage(hTab1, TCM_GETITEM, One, Integer(@TabInfo));
MessageBox(hForm1, PChar(TabInfo.pszText+', lParam '+Int2Str(TabInfo.lParam)),
           'Tab 2 Info', MB_ICONINFORMATION);
end;


procedure SetTabInfo;
var
TabInfo: TTCItem;
Sel: Integer;
begin
TabInfo.mask := TCIF_TEXT;
TabInfo.pszText:= ' New Tab Text ';
SendMessage(hTab1, TCM_SETITEM, One, Integer(@TabInfo));
{ the TCM_SETITEM message will change the tab text and redraw the tab control.
  but it will NOT redraw any child controls (the pages). It will overdraw anything
  visible on the tab control.  So you will have to use code (below)
  to tell the system to redraw the page that is shown}
Sel := SendMessage(hTab1, TCM_GETCURSEL,Zero,Zero);
case Sel of
// I use the RedrawWindow( ) because it does child windows also
  Zero: RedrawWindow(hPage1, nil,Zero, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE);
  One: RedrawWindow(hPage2, nil,Zero, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE
                    or RDW_ALLCHILDREN);
  Two: RedrawWindow(hPage3, nil,Zero, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE
                    or RDW_ALLCHILDREN);
  end;
end;



function MessageFunc(hWnd,Msg,wParam,lParam:Integer):Integer; stdcall;
begin
Result := Zero;
case Msg of
  WM_DESTROY: PostQuitMessage(Zero);

  WM_NOTIFY:
    begin
    if (wParam = ID_TabCtrl) then // check for Tab control ID
      if (PNMHdr(lParam).code = TCN_SELCHANGE) then
        begin
        { the TCN_SELCHANGE signals a new tab is selected,
          but you will need to get which tab this message is for
          with the TCM_GETCURSEL message}
        case SendMessage(hTab1, TCM_GETCURSEL,Zero,Zero) of
        // show the page that is now selected
          Zero: begin
            ShowWindow(hPage1,SW_SHOW);
            EnableWindow(GetDlgItem(hForm1, ID_LoadTBut), True);
            EnableWindow(GetDlgItem(hForm1, ID_SaveTBut), True);
            SetFocus(hPage1);
            end;
          One: begin ShowWindow(hPage2,SW_SHOW); SetFocus(hSplitEdit); end;
          Two: begin ShowWindow(hPage3,SW_SHOW); SetFocus(hDataEdit); end;
          end;
        Exit;
        end else
        if (PNMHdr(lParam).code = TCN_SELCHANGING) then
        begin
        // the TCN_SELCHANGING signals a tab is now UN-selected
        case SendMessage(hTab1, TCM_GETCURSEL,Zero,Zero) of
        // hide the page that is now Un-selected
          Zero: begin
            ShowWindow(hPage1,SW_HIDE);
            EnableWindow(GetDlgItem(hForm1, ID_LoadTBut), False);
            EnableWindow(GetDlgItem(hForm1, ID_SaveTBut), False);
            end;
          One: ShowWindow(hPage2,SW_HIDE);
          Two: ShowWindow(hPage3,SW_HIDE);
          end;
        Exit;
        end;
    end;

  WM_COMMAND: case LOWORD(wParam) of
    ID_ExitBut: PostMessage(hForm1, WM_CLOSE, Zero, Zero);
    ID_LoadTBut: LoadTextFile; // loads a text file into the edit
    ID_SaveTBut: SaveTextFile; // saves a Text file from the edit
    ID_TabGetBut: GetTabInfo; // gets some tab information
    ID_TabSetBut: SetTabInfo;
    end;
  end;
Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;


procedure MakePage2(tabRect1: TRect);
var
pSpecialDir : PItemIdList;
hNew: Integer;
TabInfo: TTCItem;
begin
{ for a "Page" in the Tab Control, I will create and use a container window.
  Page 2 is a system STATIC control, but I use it as a container window, NOT
  as a static window, so I sub-class it to get the button clicks.
  I placed the WS_EX_CONTROLPARENT  ex-style in this so Tab key press will move focus}
hPage2 := CreateWindowEx(WS_EX_CONTROLPARENT, 'STATIC', 'Page2', WS_CHILD or WS_BORDER,
                TabRect1.Left,TabRect1.Top, TabRect1.Right, TabRect1.Bottom,
                hTab1, 44, hInstance, nil);
{This hPage2 is NOT visible because the Text Edit Page is the first shown.
 And I have placed a border on it with WS_BORDER so you can see the
 size of it on the Tab Control, and it's position there}

SetWindowLong(hPage2, GWL_WNDPROC, Cardinal(@Page2MsgFunc));
{ I do NOT get the system STATIC window Proc function address here
 I will use the DefWndProc for this Static, since I do not want static display}

// I create buttons and edits below with hPage2 as their parent
MakeButton(246,34, 100, butHeight, 'Get File and Copy', hPage2, ID_CopyFileBut);

hSplitEdit := CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT',
           '? Path and File Name for Split goes here', WS_VISIBLE or WS_CHILD or
           ES_AUTOHSCROLL or WS_TABSTOP, 6, 114, 506, 22,hPage2, Zero,hInstance,nil);
SendMessage(hSplitEdit,WM_SETFONT,VarFont, Zero);

MakeButton(518,114, 80, butHeight, 'Get File to Split', hPage2, ID_OpenSplitBut);
// below I create 2 Radio buttons for split file creation options
hNew := CreateWindow('BUTTON','3 Equal Pieces Split',
              WS_VISIBLE or WS_CHILD or BS_AUTORADIOBUTTON or BS_TEXT,
              6,140,116,21,hPage2,ID_EqualRB,hInstance,nil);
SendMessage(hNew,WM_SETFONT,VarFont,Zero);
SendMessage(hNew, BM_SETCHECK, One, Zero);
SendMessage(CreateWindow('BUTTON','1 Megabyte Split',
             WS_VISIBLE or WS_CHILD or BS_AUTORADIOBUTTON or BS_TEXT or WS_GROUP,
             6,163,98,21,hPage2,ID_1MegRB,hInstance,nil),WM_SETFONT,VarFont,Zero);
MakeButton(144,148, 82, butHeight, 'Start Split File', hPage2, ID_SplitBut);

{ Below is code to get the Personal Folder file path (known as My Documents)
  for the current user and put the path in the PersonalFolder string.
  It requires the Shell function  SHGetSpecialFolderLocation( ) }
SHGetSpecialFolderLocation(hForm1, CSIDL_PERSONAL, pSpecialDir);
{there are several folders recorded in the system for each User, that the
 SHGetSpecialFolderLocation function can get the path for,
 like CSIDL_PROGRAMS or CSIDL_SENDTO }
SetLength(PersonalFolder, 1023);
PersonalFolder[1] := #0;
{you will need to get a text path from the PItemIdList
 with SHGetPathFromIDList. Remember, it is posible that there
 is NO personal folder registered for this user}
SHGetPathFromIDList(pSpecialDir, PChar(PersonalFolder));
// now PersonalFolder has the file path to folder, if registered
CoTaskMemFree(pSpecialDir);
// ALWAYS Free any PItemIdList you get from the system
SetLength(PersonalFolder, PCharLength(PChar(PersonalFolder)));
// Always find out if the special folder Exists on disk
if (Length(PersonalFolder) < 4) or (not DirectoryExists(PersonalFolder)) then
    PersonalFolder := 'C:\';

hFolderEdit := CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT',
           PChar(PersonalFolder), WS_VISIBLE or WS_CHILD or ES_AUTOHSCROLL or WS_TABSTOP,
           6, 214, 490, 22,hPage2, Zero,hInstance,nil);
SendMessage(hFolderEdit,WM_SETFONT,VarFont, Zero);
hRestEdit := CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT',
           '? Path and File Name for Restore goes here', WS_VISIBLE or WS_CHILD or
           ES_AUTOHSCROLL or WS_TABSTOP, 6, 298, 490, 22,hPage2, Zero,hInstance,nil);
SendMessage(hRestEdit,WM_SETFONT,VarFont, Zero);

MakeButton(502,298, 96, butHeight, 'Get File to Restore', hPage2, ID_OpenResBut);
MakeButton(6,326, 98, butHeight, 'Start Restore File', hPage2, ID_RestoreBut);

{the next code is just for an example of making a Tab Control with Buttons.
 Include the  TCS_BUTTONS  flag to get buttons on a Tab Control.
 This tab button control does not do anything}
hButTab := CreateWindowEx(WS_EX_LEFT, WC_TABCONTROL, nil,
    TCS_BUTTONS or WS_CHILD or WS_BORDER or WS_TABSTOP or
    WS_CLIPSIBLINGS or WS_VISIBLE or TCS_HOTTRACK,
    {181,332}427,12, 170, butHeight, hPage2, Zero, hInstance,nil);
SendMessage(hButTab,WM_SETFONT,VarFont,Zero);

TabInfo.mask := TCIF_TEXT or TCIF_PARAM;
// the mask tells the system which members of the TTCItem to read and use
TabInfo.pszText:= ' First ';
// the pszText is a PChar that will set the Text on the tab item
TabInfo.lParam:= 200;
SendMessage(hButTab, TCM_INSERTITEM, Zero, Integer(@TabInfo));

TabInfo.pszText:= ' Second ';
TabInfo.lParam:= 201;
SendMessage(hButTab, TCM_INSERTITEM, One, Integer(@TabInfo));

TabInfo.pszText:= ' Button 3 ';
TabInfo.lParam:= 202;
SendMessage(hButTab, TCM_INSERTITEM, Two, Integer(@TabInfo));
end;


procedure MakePage3(tabRect1: TRect);
var
sDC: Cardinal;
pStr: PChar;
Size1: TSize;
begin
{ Page 3 is a "Panel" window that is a container window to show and hide as
 a page in the Tab Control}
hPage3 := MakePanel(tabRect1.Left,tabRect1.Top, tabRect1.Right,
    tabRect1.Bottom, hTab1, PanelFunc1, 55, psTab);
// MakePanel function is in the MakeApp.pas unit
ShowWindow(hPage3, SW_HIDE);
// hide the panel because the Text Edit page is the first to show

// I create buttons and Edits as children of hPage3
MakeButton(170,28, 118, butHeight, 'Save Fixed Data File', hPage3, ID_SaveFixedBut);
MakeButton(314,28, 118, butHeight, 'Load Fixed Data File', hPage3, ID_LoadFixedBut);
MakeButton(491,64, 110, butHeight, 'Get Data File Name', hPage3, ID_DataNameBut);

hDataEdit := CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT',
           '? Path and Data File Name for Data Array File goes here',
           WS_VISIBLE or WS_CHILD or ES_AUTOHSCROLL or WS_TABSTOP,
           6, 66, 480, 21,hPage3, Zero, hInstance,nil);
SendMessage(hDataEdit,WM_SETFONT,VarFont, Zero);

// the next code will get the text width for a 61 character string in a fixed width font

GetMem(pStr, 62);
FillChar(pStr^, 61, 'k');
pStr[61] := #0;
sDC := GetDC(Zero);
SelectObject(sDC, Font2); // Font2 is a fixed width font
GetTextExtentPoint32(sDC, pStr, 61, Size1);

{ I make this EDIT the width needed to display 61 text characters
 this edit is used to set text in a 63 length fixed string, so it can not have
 more than 63 charaters in it, it can not have the ES_AUTOHSCROLL }
hStrEdit := CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT',
           '63 Character Fixed String Data for Array Goes Here',
           WS_VISIBLE or WS_CHILD or WS_TABSTOP,
           6, 106, Size1.cx+6, Size1.cy+6,hPage3, Zero, hInstance,nil);
SendMessage(hStrEdit,WM_SETFONT,Font2, Zero);

GetTextExtentPoint32(sDC, pStr, 9, Size1);
ReleaseDC(Zero, sDC);
FreeMem(pStr);
{ this Edit will get a number as text for a Cardinal variable,
 so I make it wide enough for 9 text charaters}
hIntEdit := CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT',
           '12345', WS_VISIBLE or WS_CHILD or ES_NUMBER or WS_TABSTOP, 6, 136,
           Size1.cx+7, Size1.cy+6,hPage3, Zero, hInstance,nil);
SendMessage(hIntEdit,WM_SETFONT,Font2, Zero);

MakeButton(170,138, 118, butHeight, 'Save Data Array File', hPage3, ID_SaveData1But);
MakeButton(314,138, 118, butHeight, 'Load Data Array File', hPage3, ID_LoadData1But);

hVarEdit := CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT',
       PChar(PersonalFolder+'\varData1.vsdf'), WS_VISIBLE or WS_CHILD or
       ES_AUTOHSCROLL or WS_TABSTOP, 6, 216, 480, 21,hPage3, Zero, hInstance,nil);
SendMessage(hVarEdit,WM_SETFONT,VarFont, Zero);

MakeButton(12,237, 124, butHeight, 'Save Variable Data File', hPage3, ID_SaveVar1But);
MakeButton(150,237, 124, butHeight, 'Load Variable Data File', hPage3, ID_LoadVar1But);
MakeButton(214,278, 120, butHeight, 'Make a Text Rec File', hPage3, ID_SaveStrBut);
MakeButton(210,310, 128, butHeight, 'Get List from File Header', hPage3, ID_GetListBut);
MakeButton(214,342, 120, butHeight, '<-- Load Text from List', hPage3, ID_LoadStrBut);
// list box below displays a list of text strings in a file
hListBox := MakeListBox(4,282,200,90,hPage3,#255'No File Loaded'#0#0,
        WS_VISIBLE or WS_CHILD or LBS_NOTIFY
              or WS_VSCROLL or WS_CLIPSIBLINGS or WS_TABSTOP or WS_DISABLED);
hEdit2 := CreateWindowEx(WS_EX_CLIENTEDGE,'EDIT',nil,
    WS_VISIBLE or WS_CHILD or ES_LEFT or ES_AUTOVSCROLL {or WS_VSCROLL or WS_HSCROLL} or
    ES_MULTILINE, 364,278, 230, 88,hPage3,Zero,hInstance,nil);
SendMessage(hEdit2,WM_SETFONT,VarFont,Zero);
end;


procedure MakeControls;
var
TabInfo: TTCItem;
tabRect: TRect;
begin
// sFileSize is the string to draw that has the File Size for file splits
sFileSize := 'no split file';
// MakeFont is in the MakeApp unit
Font1 := MakeFont(-15, 10, 'Arial', True);
Font2 := MakeFont(-13, Zero, 'Courier New');
Font3 := MakeFont(-13, Zero, 'Times New Roman', True);

GetClientRect(hForm1, TabRect);
{ TabRect will be used for control placement and Tab page sizes
 I stsrt with the TabRect as the Form's Client Rect }

TabRect.Bottom := TabRect.Bottom - 30;
// MakeButton is in the MakeApp unit
MakeButton(TabRect.Right-94,TabRect.Bottom,
                    84, butHeight, 'E X I T', hForm1, ID_ExitBut, Font1);

MakeButton(6,TabRect.Bottom, 84, butHeight, 'Load Text File', hForm1, ID_LoadTBut);
MakeButton(110,TabRect.Bottom, 84, butHeight, 'Save Text File', hForm1, ID_SaveTBut);
MakeButton(300,TabRect.Bottom, 76, butHeight, 'Get Tab Info', hForm1, ID_TabGetBut);
MakeButton(386,TabRect.Bottom, 76, butHeight, 'Set Tab Info', hForm1, ID_TabSetBut);

    //  Next is code for the Tab Control  / / /
SetRect(TabRect, 4, One, TabRect.Right - 8, TabRect.Bottom -12);
{I adjust the TabRect (the Client Rect of hForm1) to have a 4 pixel space on the
 left and right of the Tab control, and a 42 pixel space below it for the buttons}


{the Tab control creation has the WS_EX_CONTROLPARENT to pass the Tab messages.
 I also have the TCS_HOTTRACK style bit, so mouse tracking is enabled}
hTab1 := CreateWindowEx(WS_EX_CONTROLPARENT, WC_TABCONTROL, nil,
    WS_VISIBLE or WS_CHILD or BS_NOTIFY or WS_TABSTOP or
    WS_CLIPSIBLINGS or TCS_HOTTRACK or WS_CLIPCHILDREN,
    TabRect.Left,TabRect.Top, TabRect.Right, TabRect.Bottom,
    hForm1, ID_TabCtrl, hInstance,nil);
{the WC_TABCONTROL constant is for the text 'SysTabControl32' }
SendMessage(hTab1,WM_SETFONT,Font3,Zero);
// be sure to set the tab font before you call TabCtrl_AdjustRect

{ to Add Tabs, you must set the TTCItem record, TabInfo, for the new item (tab) }
with TabInfo do
  begin
  // this will add three tabs to tab control
  mask := TCIF_TEXT or TCIF_PARAM;
// the mask tells the system which members of the TTCItem to read and use
  pszText:= ' Text Editor ';
// the pszText is a PChar that will set the Text on the tab item
  lParam:= 100;
{ the lParam is a user defined integer that you can use to
  store information for each separate tab}

//Place a new tab (item) in hTab1 with a TCM_INSERTITEM in SendMessage( )
  SendMessage(hTab1, TCM_INSERTITEM, Zero, Integer(@TabInfo));

  pszText:= ' Split a File '; // second tab item
  lParam:= 101;
  SendMessage(hTab1, TCM_INSERTITEM, One, Integer(@TabInfo));

  pszText:= ' Save Data to File '; // third tab item
  lParam:= 102;
  SendMessage(hTab1, TCM_INSERTITEM, Two, Integer(@TabInfo));
  end;

GetClientRect(hTab1, TabRect);

TabCtrl_AdjustRect(hTab1, False, @TabRect);
{ the TabCtrl_AdjustRect is suppose to set the rectangle to the correct
  size of the "Page" area for the Tab Control. It is close, but I found it
  nessary increase the width and decrease the height to get the correct size
  I needed for pages. The next SetRect( ) does this}
SetRect(TabRect, TabRect.Left-Two, TabRect.Top+One,
        (TabRect.Right+Two) - TabRect.Left, TabRect.Bottom -(TabRect.Top+One));
{you might like the tab page size that TabCtrl_AdjustRect gives you, or
 my adjustment of that page size, or you may want to do your own size}


{I will create three controls as Pages for the Tab control, these controls
 will be a child of the hTab1 and 2 are "Container" windows. A Tab control does
 NOT have any pages that are changed with the selection of a Tab, so you will
 need to show and hide the 3 page controls when the Tab selection changes.
 This will hide and show all of the controls these pages contain.
 Since the Tab control does not change pages, you will need to get the
 WM_NOTIFY message and change them in the TCN_SELCHANGE notify}

{Page one is just a multi-line Edit}
hPage1 := CreateWindowEx(WS_EX_CLIENTEDGE,'Edit','Type Text Here ->',
    WS_VISIBLE or WS_CHILD or ES_LEFT or ES_AUTOVSCROLL or WS_VSCROLL or WS_HSCROLL or
    ES_MULTILINE, TabRect.Left,TabRect.Top, TabRect.Right,
    TabRect.Bottom,hTab1,Zero,hInstance,nil);
SendMessage(hPage1,WM_SETFONT,Font2,Zero);

pPageProc := Pointer(SetWindowLong(hPage1, GWL_WNDPROC, Integer(@PageFunc)));
// sub-class hPage1 to get tab keys

// MakePage2 procedure creates the second page for splitting a file
MakePage2(tabRect);

// MakePage3 procedure creates the third page for multi-Data files
MakePage3(tabRect);

SetFocus(hPage1);
end;


function MakeProgram: Boolean;
begin
Result := False;
// SetWinClass and MakeForm are in the MakeApp unit
if SetWinClass('FilesU Class', @MessageFunc) = Zero then Exit;
hForm1 := MakeForm(DEF, DEF, 620, 443, 'Read and Write Files');
if hForm1 = Zero then Exit;
Result := True;
MakeControls;
end;


initialization

finalization
DeleteObject(Font1);
DeleteObject(Font2);
DeleteObject(Font3);

end.

SplitFilesU Unit

This is the code that is used for the file split operations of page two. It has three procedures that use files, the CopyFile , the StartSplit( ) , and the StartRestore procedures. The file operations in this unit create files, one for reading and another for writting. After the files are created with CreateFile( ), a while loop will be executed which will read a "Chunk" size (512 bytes) section of the read file and then write this to the write file. The CopyFile procedure will copy the entire read file to another file.

I have code in this unit for the operation of dividing (Split) a File into smaller pieces as new files, and then code for a file "Restore" (rebuild), to read these new smaller files and and write a new "Whole" file as the restored original file. The StartSplit( ) procedure will get a file path and name to open as a read file, that will be divided (split) into smaller files. There are two options for this split file a "3 Piece" split or a "One Megabyte" split, which is controlled by the radio buttons on page two. The code for the 3 piece split is in the Split3Pieces( ) procedure, this always divides the file into 3 equal size pieces. This is the basic code for a simple file split that just copies bytes to another file, it does Not add any file header to the split file pieces. The code for a "One Megabyte" split is in the Split1Meg( ) procedure. This uses the same method for coping bytes from one file to another that the Split3Pieces( ) procedure used. But in this procedure I place a "File Header" on the first file split piece, to introduce you to working with a file header. This file header has five members, the FileID, the numPieces, the NameLength, the FileName, and the CreateTime. The FileID is the file Identifier, used to mark this file as a certain kind of file (one meg split). The numPieces is used to record the amount of files created as "File Pieces" for this One Megabyte split. The NameLength is required to record the number of bytes in the FileName string, so when you read the FileName out of the file you can read the correct amount of bytes. I record the original "File Name" for the file in the string FileName. The CreateTime is used to record the original file's time of creation, which is read for the "Restore File" operation and given to the new file.

The StartRestore( ) procedure will get a file name from an edit, and rebuild a new "Whole File" from the files (smaller piece files), which is a copy of the original file that was split. The code that restrores a "3 piece split" is in the Restore3Pieces( ) procedure and the code for a "One Megabyte Restore" is in the Restore1Meg( ) procedure.

unit SplitFilesU;

interface

procedure CopyAFile;
{the CopyAFile procedure is your basic code to Copy a file}

procedure StartSplit(const InFile: String);
{the  StartSplit procedure will take a file path and name, and then divide
(split) that file into smaller files

The Split3Pieces( ) and Split1Meg( ) procedures do the file splits}

procedure StartRestore(const InFile: String);
{the  StartRestore procedure will take a file path and name for the first
piece of a file split and combine (restore) the file pieces back to the
orriginal file}


implementation

uses
  MakeApp, Windows, Messages, UseFilesU, SmallUtils;

const
// FileID is a safety Identifier for 1 Meg split files
FileID: Cardinal = $C2FAD1;
// ChunkSize is the memory block size used for file data transfers
// ChunkSize is 512 (hex 200) because it will give faster performance
ChunkSize: Cardinal = $200;
// Meg is a One megabyte constant
Meg: Cardinal = $100000;


{ All of the file work below is based on opening two files, one to read from
 and one to write to. Then all of these will do a "While" loop, that will
 read "ChunkSize" bytes from the read file and then writes these bytes to
 the write file.

  The first procedure is the CopyAFile procedure below, which shows an
  Open and Save dialog box to get file names, and then copies an entire
  file to another location.}

procedure CopyAFile;
var
ReadName, WriteName: String;
hFileRead, hFileWrite, Size1, BytesRead, BytesWrite: Cardinal;
BadFile: Boolean;
pBufMem: Pointer;
FileTime1: TFileTime;
begin
ReadName := OpenDialog1(5);
// ReadName is the file name to copy from
if ReadName = '' then Exit;
WriteName := OpenDialog1(6);
// WriteName is the file name for the copy of the file
if WriteName = '' then Exit;

hFileRead := CreateFile(PChar(ReadName),GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or
               FILE_FLAG_SEQUENTIAL_SCAN, Zero);
// the hFileRead will be used to read the bytes from
if hFileRead = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+ReadName, CreateErrTitle);
  Exit;
  end;

Size1 := GetFileSize(hFileRead, @BytesWrite);
if (Size1 = MaxDWord) or (BytesWrite <> Zero) then
  begin // test the file size for larger than 4 gigs
  CloseHandle(hFileRead);
  MessageBox(hForm1, PChar('ERROR - FileSize is to large for copy'#10+ReadName),
            'ERROR - Did not Create File', MB_ICONERROR);
  Exit;
  end;

hFileWrite := CreateFile(PChar(WriteName),GENERIC_WRITE,FILE_SHARE_READ,nil,
         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
// hFileWrite is the Copied file where the bytes from hFileRead are written
if hFileWrite = INVALID_HANDLE_VALUE then
  begin
  CloseHandle(hFileRead);
  SysErrorMsg(CreateErrText+#10+WriteName, CreateErrTitle);
  Exit;
  end;

if Size1 = 0 then
  begin
  CloseHandle(hFileRead);
  CloseHandle(hFileWrite);
  Exit; // if there are no bytes to read then Exit
  end;

{ pBufMem is the byte Storage area (buffer) of memory
used to hold the data transfered from read file to write file}
GetMem(pBufMem, ChunkSize);

BadFile := False; // BadFile is used to delete a bad copy file

while Size1 > Zero do
  begin { Keep reading and writing bytes until all bytes are copied.
     ChunkSize is used for the amount to read, if there are less than ChunkSize
     bytes to read, then ReadFile will only read the amount of bytes left,
     it will NOT read past the end of the file. BytesRead will have the correct
     amount of bytes acually read from the file. }
  if (not ReadFile(hFileRead,pBufMem^,ChunkSize,BytesRead,nil)) then
    begin
    SysErrorMsg(ReadErrText, ReadErrTitle);
    BadFile := True;
    Break;
    end;

  if BytesRead = Zero then Break;

  if (not WriteFile(hFileWrite,pBufMem^,BytesRead,BytesWrite, nil)) or
     (BytesRead <> BytesWrite) then
    begin
    SysErrorMsg(WriteErrText, WriteErrTitle);
    BadFile := True;
    Break;
    end;
  Dec(Size1, BytesWrite);
  end;  // while loop

// to make a copy file you should get the Modified Date and set that in new file
if GetFileTime(hFileRead,nil,nil,@FileTime1) then
  SetFileTime(hFileWrite,nil,nil,@FileTime1);
// Close All file handles you open
CloseHandle(hFileRead);
CloseHandle(hFileWrite);
FreeMem(pBufMem); // free your memory buffer

if BadFile then // if a file is bad then delete the file
  DeleteFile(PChar(WriteName))
  else
  MessageBox(hForm1, PChar('Copy File is Finished, you can see the file at'#10
           +WriteName),'FINISHED, Copy File', MB_ICONINFORMATION);
end;



// / / / / / / / / / / / / / / / / / / / / / / / / / / / / /

// CODE BELOW FOR FILE SPLITS

{ the StartSplit procedure below, is used to begin both the 3 piece and
  One Meg file split. The large file to be split is Opened in the StartSplit
  procedure, and it's handle is passed to the 2 split procedures,
  Split3Pieces and Split1Meg. the Split3Pieces( ) procedure will read from
  a source file and create and write to 3 destination files in a FOR LOOP,
  placing one third of file Data bytes in each of the destination files}

procedure Split3Pieces(hInFile: Cardinal; var OutFile: String);
var
hChopFile, Size1, ChopSize, BytesRead,
BytesWrite, ReadSize, Total, i: Cardinal;
pBufMem: Pointer;
BadFile: BOOL;
aryFiles: Array[One..3] of String;

begin
// hInFile is the handle of the file created in the StartSplit procedure
Size1 := GetFileSize(hInFile, @ReadSize);
if (Size1 = MaxDWord) or (ReadSize <> Zero) then
  begin // test the file size for larger than 4 gigs
  CloseHandle(hInFile);
  MessageBox(hForm1, 'ERROR - FileSize is to large'#10+'Over 4 Gigs',
            'ERROR - Did not Create File', MB_ICONERROR);
  Exit;
  end;

if Size1 < 3 then
  begin
  // you will need at least 3 bytes in the file
  CloseHandle(hInFile);
  MessageBox(hForm1, 'ERROR - FileSize is to Small, must be Larger than 2 bytes',
             'ERROR - Can NOT Split File', MB_ICONERROR);
  Exit;
  end;

{ I will use a memory buffer in pBufMem for the file byte transfers
  I get a memory block of ChunkSize in pBufMem with GetMem( ) }
GetMem(pBufMem, ChunkSize);

Total := Zero; // Total will record the bytes written to file in each WriteFile
BadFile := False;
// BadFile is used if there is a file write error, to delete the files
hChopFile := Zero;
SetLength(OutFile, Length(OutFile)- One);

for i := One to 3 do // this will loop three times, and create a new file each loop
  begin
  if BadFile then Break;
  aryFiles[i] := OutFile+Int2Str(i); // aryFiles will record file names for delete

  // create file and test for valid file handle
  hChopFile := CreateFile(PChar(aryFiles[i]),GENERIC_WRITE,FILE_SHARE_READ,nil,
               CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
  if hChopFile = INVALID_HANDLE_VALUE then
    begin
    SysErrorMsg(CreateErrText+#10+aryFiles[i], CreateErrTitle);
    BadFile := True;
    Break;
    end;
  { ChopSize is the new file size, one thid of total bytes
   be sure to adjust ChopSize for the last piece, using Total}
  if i = 3 then
    ChopSize := Size1-Total
    else
    ChopSize := Size1 div 3;

  while ChopSize > Zero do
    begin // keep Reading and writing bytes until Chopsize bytes are transfered
    // you will need to adjust the ReadSize for the last file read
    if ChopSize > ChunkSize then ReadSize := ChunkSize else ReadSize := ChopSize;
     if (not ReadFile(hInFile,pBufMem^,ReadSize,BytesRead,nil)) or
     (ReadSize <> BytesRead) then
        begin
        SysErrorMsg(ReadErrText, ReadErrTitle);
        BadFile := True;
        Break;
        end;

    if BytesRead = Zero then Break;

    if (not WriteFile(hChopFile,pBufMem^,BytesRead,BytesWrite,nil)) or
     (BytesRead <> BytesWrite) then
      begin
      SysErrorMsg(WriteErrText, WriteErrTitle);
      BadFile := True;
      Break;
      end;
    Dec(ChopSize, BytesWrite);
    Inc(Total, BytesWrite);
    end;
  // Close All file handles you open
  CloseHandle(hChopFile);
  hChopFile := Zero;
  end; // for loop

FreeMem(pBufMem);

CloseHandle(hInFile);
if hChopFile <> Zero then
  CloseHandle(hChopFile);

if BadFile then
  begin // if a file is bad then delete all three piece files
  for Size1 := One to 3 do
    DeleteFile(PChar(aryFiles[Size1]));
  end else
  MessageBox(hForm1, PChar('Three Piece Split is Finished, you can see the files'+
           ' at'#10+aryFiles[One]),'FINISHED, 3 Piece Split', MB_ICONINFORMATION);
end;


{ the Split1Meg( ) procedure will read from a source file and write
  to a destination file in a FOR LOOP, placing one Meg of Data bytes
  in the destination file . .

  This code is almost the same as in the Split3Pieces( ) above.
  I included it here because, in this one I place a "File Header"
  on the first file. I think it is important to always have a "File Header"
  on your files.}

procedure Split1Meg(hInFile: Cardinal; var OutFile, FileName: String);
var
hChopFile, Size1, ChopSize, BytesRead,
BytesWrite, ReadSize, Total, numPieces, HeaderSize, i: Cardinal;
NameLength: Word;
BadFile: Bool;
pMemBuf: Pointer;
CreateTime: TFileTime;

  function tryWrite(var Source; Size: Cardinal; opPos: Integer): Bool;
  begin
  {because WriteFile needs to be called many times, I placed it in this function.
   This function will Write the File and test for a file write Error}
  if WriteFile(hChopFile,Source,Size,BytesWrite,nil) and (BytesWrite = Size) then
    Result := False // Result is False for a Successful Write
    else
    begin
    Result := True;
    // a Result of True will end the file split
    BadFile := True;
    // BadFile as True will delete destination file
    SysErrorMsg(WriteErrText, WriteErrTitle+' '+Int2Str(opPos));
    { show a file write Error Message
     with a debug reference of where it happened in opPos}
    end;
  end;

begin
NameLength := Length(FileName);
if NameLength < 3 then // checks for empty string
  begin
  CloseHandle(hInFile);
  MessageBox(hForm1, 'ERROR - File name is less than 3 characters long',
             'ERROR - Can NOT Create a File', MB_ICONERROR);
  Exit;
  end;

Size1 := GetFileSize(hInFile, @ReadSize);
// get file size to test if it is larger than a Megabyte
if (Size1 = MaxDWord) or (ReadSize <> Zero) then
  begin
  CloseHandle(hInFile);
  MessageBox(hForm1, 'ERROR - FileSize is incorrect, to large'#10'Over 4 Gigs',
             'ERROR - Can NOT Split File', MB_ICONERROR);
  Exit;
  end;

if Size1 <= Meg then
  begin
  CloseHandle(hInFile);
  MessageBox(hForm1, 'ERROR - FileSize is to Small, must be Larger than a Megabyte',
             'ERROR - Can NOT Split File', MB_ICONERROR);
  Exit;
  end;

// these created files will have a File Header with the file name in it
HeaderSize := SizeOf(FileID) + SizeOf(numPieces) + SizeOf(NameLength) +
              NameLength + SizeOf(CreateTime);
{ HeaderSize is the amount of bytes added as the file header in the
  first file piece. This header has 2 Cardinals (FileID, numPieces),
  a Word (NameLength), and a TFileTime (CreateTime), which is 18 bytes,
  and the number of text characters in FileName, NameLength}
Size1 := Size1 + HeaderSize;
// Add the HeaderSize to the Size1 to get the total size of bytes for split.

numPieces := Size1 div Meg;
// you need to find out how may new files to create
if Size1 mod Meg <> Zero then
  Inc(numPieces);

GetMem(pMemBuf, ChunkSize);
{ I will use a ChunkSize memory Buffer to get and transfer the file bytes in the
  read and write operations. A ChunkSize of $200 seems to give the best performance
  You should experiment with different chunk sizes to see what difference it makes}

  try // try is here so finally will call FreeMem( )
  Total := HeaderSize;  // Total will record the amount read from the source file
  BadFile := False;
  for i := One to numPieces do // FOR LOOP for each file piece
    begin
    if BadFile then Break;
    // OutFile if the file Path and Name for each piece
    SetLength(OutFile, Length(OutFile)- Length(Int2Str(i-One)));
    OutFile := OutFile+Int2Str(i); // add the Piece Number to the file name

    hChopFile := CreateFile(PChar(OutFile),GENERIC_WRITE,Zero,nil,
               CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
    if hChopFile = INVALID_HANDLE_VALUE then
      begin
      CloseHandle(hInFile);
      SysErrorMsg(CreateErrText+#10+OutFile, CreateErrTitle);
      Exit;
      // pBufMem freed in the finally
      end;

    if i = numPieces then
      ChopSize := Size1-Total // last piece will be smaller than a Meg
      else
      ChopSize := Meg;

  {File Headers are an important and widely used way to store data for identifing
   and using the bytes in a file. You must be able to place the information you
   need for using a file, in it's file Header}

    if i = One then // only for the first piece, add a File header
      begin
      if tryWrite(FileID, SizeOf(FileID), -1) then Exit;
      { I add a File Identification number for safty, since there may be other
       files with the .1m1 file extentions}
      if tryWrite(numPieces, SizeOf(numPieces), -2) then Exit;
  // I must add the Number of file Pieces, to use when restoring this file
      if tryWrite(NameLength, SizeOf(NameLength), -3) then Exit;
  // For all text in a file, you will need to write its character length to file
      if tryWrite(FileName[One], NameLength, -4) then Exit;
      { you can NOT use the SizeOf( ) function for text strings.
        I use a reference to the first character in the FileName string,
        this will write the NameLength number of characters to file}
      GetFileTime(hInFile, nil, nil, @CreateTime);
      if tryWrite(CreateTime, SizeOf(CreateTime), -5) then Exit;
      ChopSize := Meg - HeaderSize;//(NameLength + HeaderSize);
      // you will need to change the ChopSize in order to get a one Meg file
      end;


  while ChopSize > Zero do
    begin
    if ChopSize > ChunkSize then ReadSize := ChunkSize else ReadSize := ChopSize;
     if not ReadFile(hInFile,pMemBuf^,ReadSize,BytesRead,nil) or
     (ReadSize <> BytesRead) then
        begin
        SysErrorMsg(ReadErrText, ReadErrTitle+' '+Int2Str(i));
        BadFile := True;
        Exit;
        end;

    if BytesRead = Zero then Break;
  {this while loop is like the one above in the Split3Pieces procedure.
  But now I have added the tryWrite function, which will test the
  file write operation for success and return True if there i a failure}
    if tryWrite(pMemBuf^, BytesRead, i) then Exit;

    Dec(ChopSize, BytesWrite);
    // I record the Total bytes written to the file
    Inc(Total, BytesWrite);
    end;

  CloseHandle(hChopFile);
  hChopFile := Zero
  end; // while loop
  
  finally
  FreeMem(pMemBuf);

  CloseHandle(hInFile);
  if hChopFile <> Zero then
    CloseHandle(hChopFile);
  end;

if not BadFile then // I do not delete the bad files created
MessageBox(hForm1, PChar('One Meg Split is Finished, you can see the files at'#10+
           OutFile),'FINISHED, 3 Piece Split', MB_ICONINFORMATION);
end;


procedure StartSplit(const InFile: String);
var
hReadFile: Cardinal;
FileName, OutFile: String;
begin
// get the file path and name from the hSplitEdit edit box
if (InFile = '') or (InFile[One] = '?') or (not FileExists(InFile)) then
  begin
  MessageBox(hForm1, 'ERROR - You must Have a valid File Path and Name in the'+
             'File to Split Edit box', 'ERROR - Not a Valid File', MB_ICONERROR);
  Exit;
  end;
FileName := InFile;

OutFile := GetSplitFolder;
// get the uotput folder from the hFolderEdit  edit box
if OutFile = '' then Exit;

if OutFile[Length(OutFile)] <> '\' then
  OutFile := OutFile+'\'; // check for back slash
// get the OutFile path and name by adding the file name to the OutFile path
OutFile := OutFile+GetFileName(FileName);

hReadFile := CreateFile(PChar(FileName),GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
// the hReadFile created above will be used in both the Split3Pieces and Split1Meg
if hReadFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+FileName, CreateErrTitle);
  Exit;
  end;


if SendDlgItemMessage(hPage2, ID_1MegRB, BM_GETCHECK,Zero,Zero) = BST_CHECKED then
  begin
  SetLength(OutFile, Length(OutFile)- Length(GetFileExt(OutFile)));
  // chop off the file extention for the OutFile and add the split piece extention
  OutFile := OutFile+'.1meg0';
  FileName := GetFileName(FileName);
  Split1Meg(hReadFile, OutFile, FileName);
  end else
  begin
  OutFile := OutFile+'.3p0';
  Split3Pieces(hReadFile, OutFile);
  end;
end;


// / / /  File Restore Code / / / / / / / / / / / / /


{procedure Restore3Pieces( ) will open and read 3 split files, and create and
 write a "Whole" file from the pieces}

procedure Restore3Pieces(var SplitName, OutFile: String);
var
hSplitFile, hWholeFile, Size1, ReadSize, BytesRead, BytesWrite, i: Cardinal;
pBufMem: Pointer;
BadFile: BOOL;
begin
// Create a file, hWholeFile, to write all of the pieces to
hWholeFile := CreateFile(PChar(OutFile),GENERIC_WRITE,FILE_SHARE_READ,nil,
           CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hWholeFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+OutFile, CreateErrTitle);
  Exit;
  end;

GetMem(pBufMem, ChunkSize);
// pBufMem is the memory block the file byte transfer will be stored into
hSplitFile := Zero;
BadFile := False;
for i := One to 3 do
  begin
  if BadFile then Break;
  // set the last file extention character to the piece number
  SplitName[Length(SplitName)] := Int2Str(i)[1];
  hSplitFile := CreateFile(PChar(SplitName),GENERIC_READ,FILE_SHARE_READ,nil,
             OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
  // open 3 split piece files in this loop to read bytes from
  if hSplitFile = INVALID_HANDLE_VALUE then
    begin
    SysErrorMsg(CreateErrText+#10+SplitName, CreateErrTitle);
    CloseHandle(hWholeFile);
    Exit;
    end;
  Size1 := GetFileSize(hSplitFile, @ReadSize);

  while Size1 > Zero do
    begin
    // Size1 is tested to see if the file piece is at the end of it's bytes
    if Size1 > ChunkSize then ReadSize := ChunkSize else ReadSize := Size1;
    // ReadSize will be ChunkSize untill there are less bytes left in the file
    if (not ReadFile(hSplitFile,pBufMem^,ReadSize,BytesRead,nil)) or
       (ReadSize <> BytesRead) then
      begin
      SysErrorMsg(ReadErrText, ReadErrTitle);
      BadFile := True;
      Break;
      end;
    // stop loop if there are no bytes read from file
    if BytesRead = Zero then Break;
    // test file write and the amount written to file
    if (not WriteFile(hWholeFile, pBufMem^, BytesRead, BytesWrite, nil)) or
       (BytesRead <> BytesWrite) then
      begin
      SysErrorMsg(WriteErrText, WriteErrTitle);
      BadFile := True;
      Break;
      end;
    Dec(Size1, BytesWrite);
    end; // while loop
  CloseHandle(hSplitFile);
  hSplitFile := Zero;
  end; // for loop

FreeMem(pBufMem);

CloseHandle(hWholeFile);
if hSplitFile <> Zero then
  CloseHandle(hSplitFile);

// if there is a read or write error, with BadFile = true, then delete OutFile
if BadFile then
  DeleteFile(PChar(OutFile))
  else
  MessageBox(hForm1, PChar('Three Piece Restore is Finished, you can see the'+
        ' files at'#10+OutFile),'FINISHED, 3 Piece Split', MB_ICONINFORMATION);
end;



{Restore1Meg procedure will read the file header of the first 1 Meg file piece
and then combine the files together}

procedure Restore1Meg(var SplitName, OutFolder: String);
var
hSplitFile, hWholeFile, Size1, ReadSize, BytesRead,
BytesWrite, numPieces, HeaderSize, i: Cardinal;
NameLength: Word;
pBufMem: Pointer;
FileName: String;
CreateTime: TFileTime;
BadFile: BOOL;

  function tryRead(var Dest; Size: Cardinal; opPos: Integer): Bool;
  begin
  if ReadFile(hSplitFile,Dest, Size, BytesRead,nil) and (Size = BytesRead) then
    Result := False
    else
    begin
    // the  opPos  will tell you at which operation the error happened
    SysErrorMsg(ReadErrText, ReadErrTitle+' '+Int2Str(opPos));
    CloseHandle(hSplitFile);
    BadFile := True;
    Result := True;
    end;
  end;

begin
// this is like the 3 piece restore, except I read the file header for One Meg split
hSplitFile := CreateFile(PChar(SplitName),GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hSplitFile = INVALID_HANDLE_VALUE then
    begin
    SysErrorMsg(CreateErrText+#10+SplitName, CreateErrTitle);
    Exit;
    end;

Size1 := GetFileSize(hSplitFile, @ReadSize);
if Size1 <> Meg then
  begin
  CloseHandle(hSplitFile);
  MessageBox(hForm1, 'ERROR Meg - This is NOT a valid One Meg Split File',
             'ERROR - Not a Valid Split File', MB_ICONERROR);
  Exit;
  end;

if tryRead(i, SizeOf(i), -1) then Exit;
// this is where I read the file header from the first file piece
if i <> FileID then
  begin
  // make sure the File ID is correct
  CloseHandle(hSplitFile);
  MessageBox(hForm1, 'ERROR FileID - This is NOT a valid One Meg Split File  ',
             'ERROR - Not a Valid Split File', MB_ICONERROR);
  Exit;
  end;

if tryRead(numPieces, SizeOf(numPieces), -2) then Exit;
{ all of the file header data is read out of the file in the same order it was
 written to the file in the Split1Meg( ) procedure above}

if tryRead(NameLength, SizeOf(NameLength), -3) then Exit;

SetLength(FileName, NameLength);
{for any String text data you must read the number of bytes in the string first
and then set the string length so you can read the bytes from file}
if tryRead(FileName[One], NameLength, -4) then Exit;
FileName := OutFolder+FileName;
if tryRead(CreateTime, SizeOf(CreateTime), -5) then Exit;

hWholeFile := CreateFile(PChar(FileName),GENERIC_WRITE,FILE_SHARE_READ,nil,
           CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
{the hWholeFile is the file that is written to, with the bytes of the file pieces}
if hWholeFile = INVALID_HANDLE_VALUE then
  begin
  CloseHandle(hSplitFile);
  SysErrorMsg(CreateErrText+#10+FileName, CreateErrTitle);
  Exit;
  end;
// you must get the number of bytes used for the header and subtract it from Size1
HeaderSize := SizeOf(FileID) + SizeOf(numPieces) + SizeOf(NameLength) +
              NameLength + SizeOf(CreateTime);
Size1 := Size1 - HeaderSize;
GetMem(pBufMem, ChunkSize);
BadFile := False;
for i := One to NumPieces do // start from one
  begin
  if BadFile then Break;
  if i > One then
    begin
    // the first split piece was created before this FOR loop
    SetLength(SplitName, Length(SplitName)- Length(Int2Str(i-One)));
    // the next split file name is built from the SplitName and the  i  loop counter
    SplitName := SplitName+Int2Str(i);
    hSplitFile := CreateFile(PChar(SplitName),GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
    if hSplitFile = INVALID_HANDLE_VALUE then
      begin
      SysErrorMsg(CreateErrText+#10+SplitName, CreateErrTitle);
      BadFile := True;
      Break;
      end;

    Size1 := GetFileSize(hSplitFile, @ReadSize);
    end;


  while Size1 > Zero do
    begin
    // loop untill Size1 bytes are copied
    if Size1 > ChunkSize then // set the ReadSize
      ReadSize := ChunkSize
      else
      ReadSize := Size1;
    if tryRead(pBufMem^,ReadSize, i) then Break;
    if BytesRead = Zero then Break;

    if (not WriteFile(hWholeFile,pBufMem^,BytesRead,BytesWrite,nil)) or
     (BytesRead <> BytesWrite) then
      begin
      SysErrorMsg(WriteErrText, WriteErrTitle);
      BadFile := True;
      Break;
      end;

    Dec(Size1, BytesWrite); // reduce Size1 to track the number of bytes written
    end;

  CloseHandle(hSplitFile);
  hSplitFile := Zero;
  end;

FreeMem(pBufMem);
SetFileTime(hWholeFile, nil, nil, @CreateTime);
CloseHandle(hWholeFile);
if hSplitFile <> Zero then
  CloseHandle(hSplitFile);
if BadFile then
  DeleteFile(PChar(FileName)) // delete the bad file
  else
  MessageBox(hForm1, PChar('One Meg Restore is Finished, you can see the files at'#10+
           FileName),'FINISHED, 1 meg Restore', MB_ICONINFORMATION);

end;


{this StartRestore takes the file path and name of the first file piece, and
will test the file name extention and call Restore3Pieces or Restore1Meg }

procedure StartRestore(const InFile: String);
var
FileName, OutFile, Ext1: String;
begin
// test for valid file name
if (InFile = '') or (InFile[One] = '?') or (not FileExists(InFile)) then
  begin
  MessageBox(hForm1, 'ERROR - You must Have a valid File Path and file Name in the'+
             'File to Restore Edit box', 'ERROR - Not a Valid File', MB_ICONERROR);
  Exit;
  end;


Ext1 := UpperCase(GetFileExt(InFile));
// the file extention will determine which File restore procedure to call

OutFile := GetSplitFolder;
if OutFile = '' then Exit;

if OutFile[Length(OutFile)] <> '\' then
  OutFile := OutFile+'\'; // insure there is a  \

if Ext1 = '.3P1' then //  3P1  extention means it is a 3 piece restore
  begin
  OutFile := OutFile+GetFileName(InFile);
  // build the output file name from the OutFile and the Infile strings
  SetLength(OutFile, Length(OutFile)- Length(Ext1));
  FileName := InFile;
  Restore3Pieces(FileName, OutFile);
  end else
  if Ext1 = '.1MEG1' then // this is a one Meg restore
  begin
  FileName := InFile;
  // the Restore1Meg will read the outFile file name from it's file header
  Restore1Meg(FileName, OutFile);
  end else
  begin
  MessageBox(hForm1, 'ERROR - You must Have a .3p1 or .1meg1 File Extention'+
             ' for the Restroe File', 'ERROR - Not a Split File', MB_ICONERROR);
  Exit;
  end;

end;

end.



DataFilesU Unit

In this unit I try and show some methods to build files that will store several different things (variables, data bytes) in a file as sparate "Blocks" or "Segments" of data, I will call these file pieces a file "Data Segment". There will be times when you need to make a file for information "Storage", where you need to write and read many different kinds and types of data bytes in one file. Placing variables that do not change their memory allocation (Fixed-Size variable) into a file is the easiest so I start by using some "Fixed Size" variables which are written, then read to a file. You can see the code for this in the SaveFixed and LoadFixed procedures.

Next there is code in the SaveRecords( ) and LoadRecords( ) procedures which show you how to save a changing number of delphi records to a file. These records are of type TDataRec which only contains "Fixed-Size" variables, which makes file writting more easy than if they contained a String variable.

Next are the SaveVarData and LoadVarData procedures, which will use "Changing-Size" memory allocation variables, like a String and PChar. The important thing to see, is that since these types of variables will have a different "Byte Size" for their data blocks, depending on what text is in them, , you MUST place a number (integer) in the file to store the "Data Segment" size for every "Changing-Size" variable you write to file. Also this shows you how to write a "File Header" in the file, with a "File Type Identifier" or File ID. For any file that is read and calls for allocating memory from a number in that file, you should always have a "File Type Identifier" in the file.

In the SaveStringRecs procedure, I show some methods to write 5 Records (with strings in them) to file. Unlike Fixed-Size records, you will not be able to write and read the entire record in the file with one file operation. When you write this TStrRec to file you will need to also write a number in the file for the length of the string. So you write the two fixed size variables (width , height) to file (8 bytes) and then write the string length and then the string characters. . . . In this SaveStringRecs procedure are methods to have a file header with the position and size of all the Data Segments (TStrRec) in it, so you can read the file header and get the information you need to read only one TStrRec from the file. This is a method used for random access of files that are read for only one part of the information that they have, so you do not need to read the entire file to get a single data segment. I use a THeader record as the file header information "Block" with the position, size and description for each TStrRec data segment. The LoadListBox procedure will read all of the THeader records in the file and fill a List Box control with the 5 description texts in these 5 THeader. The the user can select a description in the List Box and run the Load1StringRec procedure, which will read one THeader record (indexed from the list box), and get the position and size from this THeader , then read only one TStrRec from the file, and show the record's TextLines in the memo Edit control.

unit DataFilesU;

interface

procedure SaveFixed;
{the SaveFixed and LoadFixed procedures will write and read a
 muti-segment data file, with several fixed size variables in it}
procedure LoadFixed;

procedure SaveRecords(const OutFile: String);
{the SaveRecords and LoadRecords procedures will write and read a file
 that has 8 TDataRec records in it}
procedure LoadRecords(const InFile: String);

procedure SaveVarData(const OutFile: String);
{the SaveVarData and LoadVarData procedures will write and read a file
 that has some changing size variables in it}
procedure LoadVarData(const InFile: String);

procedure SaveStringRecs;
{the SaveStringRecs procedure will write a file with 5 TStrRec records
 and a file header with 5 THeader records}

procedure LoadListBox;
{the LoadListBox procedure will read the file header and place the 5
 THeader ListText strings in the List Box}

procedure Load1StringRec;
{the Load1StringRec will get the Index from the List Box and get just
 ONE TStrRec record from the file}


implementation

uses
  MakeApp, Windows, Messages, UseFilesU, SmallUtils;


type
// the TDataRec record is a Fixed Variable record placed in a file
  TDataRec = packed Record
    TrackNum, SomeNum: Cardinal;
    aRect: TRect;
    Text: String[63];
    end;

// the TStrRec has a String, so you will need to write it's length to file
  TStrRec = packed Record
    Width, Height: Integer;
    TextLines: String;
    end;

{ THeader record is used in the file header, to keep file position and size of
  a TStrRec record. It has list text, a description which will be shown in
  a list box. Header records should only contain fixed size variables}
  THeader = packed Record
    Position, Size: Cardinal;
    ListText: String[18];
    end;

  // TOrd1 is an Ordinal type written to file in the SaveFixed procedure
  TOrd1 = (orFirst, orSecond, orMiddle, orNext, orLast);

const
{ FileDataID is a safety Identifier for the Var muti-data files
  I use 6 text characters for this one, and a Cardinal for the next one }
FileDataID: Array[0..5] of Char = 'VSDF01'; // the '01' is the version
// FileStrID is a safety Identifier for string muti-data files
FileStrID: Cardinal = 719394801;
DataFile = 'C:\Data File 1.FixedData';
// DataFile is the file path for the Fixed Variable file
StringFile = 'C:\String File 1.StrRec';
// DataFile is the file path for the Fixed Variable file


procedure SaveFixed;
var
hFile, BytesWrite: Cardinal;
// all of the fixed size variables below will be written to one file
Int1: Integer;
Word1: Word;
Real1: Real;
aRect: TRect;
aryPnt: Array[Zero..Two] of TPoint;
aryChar: Array[Zero..15] of Char;
Str1: String[32];
Ord1: TOrd1;
setOrd: Set of TOrd1;
begin
{this procedure will write several different types of
 fixed size variables to one file}
hFile := CreateFile(DataFile,GENERIC_WRITE,FILE_SHARE_READ,nil,
               CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or
               FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+DataFile, CreateErrTitle);
  Exit;
  end;

Int1 := -12345;
Word1 := 8888;
Real1 := 987.123;
SetRect(aRect, One,Two,3,4);
aryPnt[Zero].x := 22;
aryPnt[Zero].y := 44;
aryChar := 'Array Char 16'#0;
Str1 := ' String One';
Ord1 := orNext;
setOrd := [orFirst, orMiddle, orNext];
{ the varaiables are all given values above
 and all are written to file in the same way using the SizeOf( ) function below}

WriteFile(hFile,Int1,SizeOf(Int1),BytesWrite,nil);
WriteFile(hFile,Word1,SizeOf(Word1),BytesWrite,nil);
WriteFile(hFile,Real1,SizeOf(Real1),BytesWrite,nil);
WriteFile(hFile,aRect,SizeOf(aRect),BytesWrite,nil);
WriteFile(hFile,aryPnt,SizeOf(aryPnt),BytesWrite,nil);
WriteFile(hFile,aryChar,SizeOf(aryChar),BytesWrite,nil);
WriteFile(hFile,Str1,SizeOf(Str1),BytesWrite,nil);
WriteFile(hFile,Ord1,SizeOf(Ord1),BytesWrite,nil);
WriteFile(hFile,setOrd,SizeOf(setOrd),BytesWrite,nil);

BytesWrite := GetFileSize(hFile, nil);
CloseHandle(hFile);
{PLEASE NOTICE, that I do NOT test the WriteFile functions above to
 see if they are successful, instead I test the file size below.
  If the Size is not  105  then a write error has happened }
if BytesWrite = 105 then
  MessageBox(hForm1, PChar('New FixedData File is at - '+DataFile),
             'New Fixed Data File', MB_ICONERROR)
  else
  MessageBox(hForm1, 'ERROR - Data File is NOT the Correct Size',
             'Write File Error', MB_ICONERROR);
end;


procedure LoadFixed;
var
hFile, BytesWrite, Size: Cardinal;
// variables below are read from a file
Int1: Integer;
Word1: Word;
Real1: Real;
aRect: TRect;
aryPnt: Array[Zero..Two] of TPoint;
aryChar: Array[Zero..15] of Char;
Str1: String[32];
Ord1: TOrd1;
setOrd: Set of TOrd1;
begin
{this procedure will read all of the different types of
 fixed size variables above, from one file}
hFile := CreateFile(DataFile,GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+DataFile, CreateErrTitle);
  Exit;
  end;

Size := GetFileSize(hFile, nil);
if Size <> 105 then
  begin
  // check to see if the file has the correct amount of data to read
  CloseHandle(hFile);
  MessageBox(hForm1, 'ERROR - File is NOT the Correct Size',
             'Bad File Size', MB_ICONERROR);
  Exit;
  end;

{ IMPORTANT ! !  There is NO information in this file about what
  the bytes in this file are used for, the only reason you can read
  the data in this file is because you made the code that created this file.
  So you can read the bytes of this file as data into variables because you
  know the order and the size of the Data Blocks that YOU wrote to this file.
  All variables are read from the file with the same method.}
ReadFile(hFile,Int1,SizeOf(Int1),BytesWrite,nil);
{ you need to read the EXACT same type of variables out of the file
  in the EXACT same order that you wrote them to file or you will have errors}
ReadFile(hFile,Word1,SizeOf(Word1),BytesWrite,nil);
ReadFile(hFile,Real1,SizeOf(Real1),BytesWrite,nil);
ReadFile(hFile,aRect,SizeOf(aRect),BytesWrite,nil);
ReadFile(hFile,aryPnt,SizeOf(aryPnt),BytesWrite,nil);
ReadFile(hFile,aryChar,SizeOf(aryChar),BytesWrite,nil);
ReadFile(hFile,Str1,SizeOf(Str1),BytesWrite,nil);
ReadFile(hFile,Ord1,SizeOf(Ord1),BytesWrite,nil);
ReadFile(hFile,setOrd,SizeOf(setOrd),BytesWrite,nil);

CloseHandle(hFile);

if Ord1 in setOrd then
  MessageBox(hForm1, PChar('Fixed Size Data values are -'#10+Int2Str(int1)+' '+
             Int2Str(aRect.Left)+' '+ Int2Str(aryPnt[Zero].x)+' '+aryChar+Str1),
             'Fixed Data Read', MB_ICONINFORMATION)
  else
  MessageBox(hForm1, 'ERROR - Fixed Data File is NOT Correct',
             'Bad File Data', MB_ICONERROR);
end;


procedure SaveRecords(const OutFile: String);
var
hFile, BytesWrite: Cardinal;
i: Integer;
aryRec1: Array of TDataRec;
BadFile: Boolean;

  function tryWrite(var Source; Size: Cardinal): Bool;
  begin
  // this tryWrite function is called for every file write of data
  if WriteFile(hFile,Source,Size,BytesWrite,nil) and (BytesWrite = Size) then
    Result := False // WriteFile must succeed and the BytesWrite must equal Size
    else
    begin
    SysErrorMsg(WriteErrText, WriteErrTitle); // show error msg
    BadFile := True; // set BadFile to True to delete the new file
    Result := True; // Result is True if there is a write Error
    end;
  end;


begin
// this procedure will create a file and write 8 records of TDataRec to the file
if Length(OutFile) < 6  then
  begin
  MessageBox(hForm1, 'ERROR - Data File Name must be at least 6 Charaters',
             'Invalid Data File Name', MB_ICONERROR);
  Exit;
  end;

SetLength(aryRec1, 8);
{ get the memory for the records with SetLength( ) and
  then fill the records with data in the for loop below}
for i := Zero to High(aryRec1) do
  begin
  aryRec1[i].TrackNum := i;
  aryRec1[i].SomeNum := Str2IntDef(GetWindowStr(hIntEdit), Zero);
  aryRec1[i].aRect.Left := i+One;
  aryRec1[i].Text := Int2Str(i)+' '+GetWindowStr(hStrEdit);
  end;

hFile := CreateFile(PChar(OutFile),GENERIC_WRITE,FILE_SHARE_READ,nil,
                   CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or
                   FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+OutFile, CreateErrTitle);
  Exit;
  end;

BadFile := False; { BadFile will be set to True if there is a write Error.
 for almost all file writting, you should have a way to delete the file
 or fix it if there is a write error}
i := Length(aryRec1);

{there is the tryWrite( ) function above which is called when each time you
 will write a data segment to file, it tests to see if the WriteFile function
  was complete, and all the bytes were written,
  it returns True if there is a write file Error}
if not tryWrite(i, SizeOf(i)) then
  for i := Zero to High(aryRec1) do // loop through all records in array
    if tryWrite(aryRec1[i], SizeOf(TDataRec)) then Break;

CloseHandle(hFile);
if BadFile then // test for BadFile and Delete the file if it is bad
  begin
  DeleteFile(PChar(OutFile));
  MessageBox(hForm1, 'ERROR - Data File Could not be fully Written',
             'ERROR, NO data File', MB_ICONERROR);
  end else
  MessageBox(hForm1, PChar('Data File has been Created at'#10+OutFile),
             'SUCCESS, Data File Creation', MB_ICONINFORMATION);
end;


procedure LoadRecords(const InFile: String);
var
hFile: Cardinal;
i: Integer;
aryRec1: Array of TDataRec;
BytesRead: Cardinal;
BadFile: Boolean;

  function tryRead(var Dest; Size: Cardinal): Bool;
  begin
  // this tryRead function is called for every file read of data
  if ReadFile(hFile, Dest, Size, BytesRead,nil) and (Size = BytesRead) then
    Result := False // returns false if succesful
    else
    begin
    // if there is a Read Error this returns True and shows an Error Message
    SysErrorMsg(ReadErrText, ReadErrTitle);
    CloseHandle(hFile);
    // Close Handle and set BadFile to True
    BadFile := True;
    Result := True;
    end;
  end;


begin
// this procedure will read the file made in the SaveRecords procedure above
if Length(InFile) < 6  then
  begin
  MessageBox(hForm1, 'ERROR - Data File Name must be at least 6 Charaters',
             'Invalid Data File Name', MB_ICONERROR);
  Exit;
  end;

hFile := CreateFile(PChar(InFile),GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+InFile, CreateErrTitle);
  Exit;
  end;

BadFile := False; // BadFile will be made True if there is a Read Error

if not tryRead(i, SizeOf(i)) then // get the number of records in the file
  begin
  SetLength(aryRec1, i); // set array length to the number of records in the file
  for i := Zero to High(aryRec1) do // loop through and read all of the records
    if tryRead(aryRec1[i], SizeOf(TDataRec)) then Break;
  end else
  BadFile := True;

CloseHandle(hFile);
if BadFile then
  begin
  MessageBox(hForm1, PChar('ERROR - Data File Could not be fully Read '#10+InFile),
             'ERROR, NO READ data File', MB_ICONERROR);
  end else
  begin
  SetWindowText(hStrEdit, @aryRec1[High(aryRec1)].Text[One]);
  SetWindowText(hIntEdit, PChar(Int2Str(aryRec1[High(aryRec1)].SomeNum)));
  MessageBox(hForm1, PChar('Data File has been Read at'#10+InFile),
             'SUCCESS, Read Data File', MB_ICONINFORMATION);
  end;
end;


{the SaveVarData procedure will write changing size variables to file}
procedure SaveVarData(const OutFile: String);
var
hFile: Cardinal;
DataSize: Integer;
BytesWrite: Cardinal;

Str1: String;
pText: PChar;
aryInteger: Array of Integer;

  function noWrite(var Source; Size: Cardinal): Bool;
  begin
  // noWrite will return True if the write is NOT successful
  if WriteFile(hFile,Source,Size,BytesWrite,nil) and (BytesWrite = Size) then
    Result := False
    else
    begin
    // when there is a File Write error, I close the file and delete it
    CloseHandle(hFile);
    DeleteFile(PChar(OutFile));
    SysErrorMsg(WriteErrText, WriteErrTitle); // show Error message
    Result := True;
    end;
  end;


begin
// OutFile is the text in the hVarEdit Edit box
if Length(OutFile) < 6  then
  begin
  MessageBox(hForm1, 'ERROR - Data File Name must be at least 6 Charaters',
             'Invalid Data File Name', MB_ICONERROR);
  Exit;
  end;

  // initialize all variables
Str1 := 'String Text to write and read in a file';
pText := 'More Text to use in this file';
  // when the compilier creates the pText above it adds a #0 to the end
SetLength(aryInteger, 4);
aryInteger[0] := 4444444;
aryInteger[1] := 8888888;
aryInteger[2] := 1;
aryInteger[3] := -2;

hFile := CreateFile(PChar(OutFile),GENERIC_WRITE,FILE_SHARE_READ,nil,
                   CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or
                   FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+OutFile, CreateErrTitle);
  Exit;
  end;

{ first I write a File type ID text to the file.
  For all your data files you should have some kind of file Identifier
  as the first data block in the file, so you can try and detect if this file
  has the data bytes that you know how to read}
if noWrite(FileDataID, SizeOf(FileDataID)) then Exit; // 6 text chars
// if the file write of noWrite fails, then Exit
DataSize := Length(Str1); // get the size of string bytes and write it to file
if noWrite(DataSize, SizeOf(DataSize)) then Exit;
if noWrite(Str1[One], DataSize) then Exit; // write string text to file
DataSize := StrLen(pText)+1; // add one byte for the NULL char #0
if noWrite(DataSize, SizeOf(DataSize)) then Exit; // also writes the NUL char #0
if noWrite(pText^, DataSize) then Exit;
DataSize := Length(aryInteger);
if noWrite(DataSize, SizeOf(DataSize)) then Exit;// write number of integers in array
{ IMPORTANT - you MUST do the math to get the byte Size of all the Data in the array,
 multiply the length of the array by the size it's members -
   Length(aryInteger) * SizeOf(Integer) }
if noWrite(aryInteger[Zero], DataSize*SizeOf(Integer)) then Exit;
   // use the First menber of the array aryInteger[Zero] as the Source buffer
CloseHandle(hFile);
MessageBox(hForm1, PChar('Data File has been Created at'#10+OutFile),
             'SUCCESS, Data File Creation', MB_ICONINFORMATION);

end;


// LoadVarData will read the non-fixed size variables from the file
procedure LoadVarData(const InFile: String);
var
hFile,BytesRead: Cardinal;
ID: Array[Zero..5] of Char;
DataSize: Integer;

Str1: String;
pText: PChar;
aryInteger: Array of Integer;

  function noRead(var Source; Size: Cardinal): Bool;
  begin
  // noRead will return True if the file read is NOT successful
  if ReadFile(hFile,Source,Size,BytesRead,nil) and (BytesRead = Size) then
    Result := False
    else
    begin
    CloseHandle(hFile); // close handle if read error
    SysErrorMsg(ReadErrText, ReadErrTitle);
    Result := True;
    end;
  end;


begin
// InFile is the text in the hVarEdit Edit box
if Length(InFile) < 6  then
  begin
  MessageBox(hForm1, 'ERROR - Data File Name must be at least 6 Charaters',
             'Invalid Data File Name', MB_ICONERROR);
  Exit;
  end;
// open the file for reading
hFile := CreateFile(PChar(InFile),GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+InFile, CreateErrTitle);
  Exit;
  end;

// if noRead fails (with True) then Exit
if noRead(ID, SizeOf(ID)) then Exit;
// for safty get the File Identification number, and test it
if ID <> FileDataID then
 begin
 CloseHandle(hFile);
 MessageBox(hForm1, PChar('ERROR - This File is NOT a Valid Var Data File'#10+InFile),
             'ERROR, NOT a Valid Data File', MB_ICONERROR);
 Exit;
 end;
// read the length of the String text data
if noRead(DataSize, SizeOf(DataSize)) then Exit;
SetLength(Str1, DataSize); // get memory for the string
if noRead(Str1[One], DataSize) then Exit; // read data into string
if noRead(DataSize, SizeOf(DataSize)) then Exit;
// read text length and get memory, the NULL char #0 is included
GetMem(pText, DataSize);
if noRead(pText^, DataSize) then // this also reads the final NULL terminator
  begin
  FreeMem(pText); // free the memory before Exit
  Exit;
  end;
if noRead(DataSize, SizeOf(DataSize)) then // read number of integers in array
  begin
  FreeMem(pText);
  Exit;
  end;
SetLength(aryInteger, DataSize); // get memory for the array with SetLength( )
if noRead(aryInteger[Zero], DataSize*SizeOf(Integer)) then
  begin
  FreeMem(pText);
  Exit;
  end;

CloseHandle(hFile);
MessageBox(hForm1, PChar(Str1+#10+pText+#10+Int2Str(aryInteger[Zero])),
             'SUCCESS, Data File Read', MB_ICONINFORMATION);
FreeMem(pText);
end;


{this SaveStringRecs procedure will show how to save records with strings to file
this will create a file header with the position and size of each string record}
procedure SaveStringRecs;
var
hFile: Cardinal;
i, Len, headerPos: Integer;
aryStrR: Array of TStrRec; { TStrRec records are the Data stored in this file.
 I use a file header with one THeader record for every TStrRec record.
 The THeader record has the position and size of the TStrRec record in file,
 this allows you to access the data for just ONE TStrRec record at a time.}
aryHeader: Array of THeader;
BytesWrite: Cardinal;
BadFile: Boolean;

  function tryWrite(var Source; Size: Cardinal): Bool;
  begin
  // tryWrite will return True if the write is NOT successful
  if WriteFile(hFile,Source,Size,BytesWrite,nil) and (BytesWrite = Size) then
    Result := False
    else
    begin
    SysErrorMsg(WriteErrText, WriteErrTitle);
    BadFile := True;
    Result := True;
    end;
  end;

begin
SetLength(aryStrR, 5);
// there are 5 TStrRec in this aryStrR array
for i := 0 to High(aryStrR) do
  with aryStrR[i] do
  begin
  // fills the array elements with data
  Width := (i+1)*20;
  Height := (i+1)*10;
  { the TextLines below are the main "Data" stored in this file. These text
    strings would be long strings from a Memo Edit}
  case i of
    0: TextLines :=  'The text of FUNNY'#13#10'that is here'#13#10'for Text Lines.';
    1: TextLines :=  'More text to show, as SAD subject you'#13#10+
                     'can see in the edit box';
    2: TextLines :=  'This is the FOOD text'#13#10'with 4 lines'#13#10'of info'+
                     #13#10'here to view.';
    3: TextLines :=  'Words for the WATER'#13#10'are here in this'#13#10+
                     'Text Block of words.';
    4: TextLines :=  'The subject of MONEY has'#13#10'this text information'+
                     #13#10'as lines of words.';
    end;
  end;

hFile := CreateFile(StringFile,GENERIC_WRITE,FILE_SHARE_READ,nil,
               CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+StringFile, CreateErrTitle);
  Exit;
  end;

{In this file I will have a file header that is used to store data segment
 position and size in a THeader. There will be 5 THeader records in the the
 file header, one for each TStrRec in this file}
SetLength(aryHeader, Length(aryStrR)); // make aryHeader the same length as aryStrR
headerPos := (Length(aryHeader)* SizeOf(THeader))+8;
{all file data position is math, the headerPos is used to store the
 location of for each TStrRec, to get the position of the first TStrRec,
 you multiply the number of THeader in aryHeader, by the byte size of a
 THeader. . . You must add eight to it because you have 2 file header integers
 - FileStrID - and - Len - written to file.}
for i := 0 to High(aryHeader) do
  with aryHeader[i] do
  begin
  // in order to access one TStrRec record, you need it's file position
  Position := headerPos;
  // You must do the math to calculate the file bytes occupied by this TStrRec
  Size := 12+Length(aryStrR[i].TextLines);
  { the Size will be the length of the string + 12, , 8 bytes for the
    Width and Height in the record, and 4 bytes for the - Len - integer
    added to file later, to record the length of the string.}
  case i of
    0: ListText := 'Funny stuff';
    1: ListText := 'Sad things';
    2: ListText := 'The Food is here';
    3: ListText := 'You need Water';
    4: ListText := 'Show me the Money';
    // ListText will be shown in a List Box when the file header is read
    end;
  Inc(headerPos, Size); // add the record size to file position
  end;

BadFile := False;
Len := Length(aryStrR);
{ first I write a File type ID number to the file.
  For your data files you should have some kind of Identifier
  as the first data block in the file, so you can try and detect if this file
  has the data bytes that you know how to read}
if not tryWrite(FileStrID, SizeOf(FileStrID)) and
   not tryWrite(Len, SizeOf(Len)) and { write number of TStrRec in array
     put the aryHeader into the file as a file header}
     not tryWrite(aryHeader[0], Length(aryHeader)*SizeOf(THeader)) then
     for i := Zero to High(aryStrR) do // for loop through all strings
       begin
       if tryWrite(aryStrR[i], 8) then Break;
   {IMPORTANT - I do NOT write the entire TStrRec to file, since it has a string
    in it. I only write the Fixed-Size variables, Width and Height, by setting
    the file write Size to 8}
       Len := Length(aryStrR[i].TextLines);
   // IMPORTANT you must always write the length of the string data to file
       if tryWrite(Len, SizeOf(Len)) then Break;
       if tryWrite(aryStrR[i].TextLines[One], Len) then Break;
   { without the file segment size (number of bytes),
    you will not be able to read out this string}
       end;

CloseHandle(hFile);
if BadFile then
  begin
  DeleteFile(StringFile);
  MessageBox(hForm1, 'ERROR - String Record File Could not be fully Written',
             'ERROR, NO data File', MB_ICONERROR);
  end else
  MessageBox(hForm1, PChar('String Record File has been Created at'#10+StringFile),
             'SUCCESS, Data File Creation', MB_ICONINFORMATION);

end;


{the LoadListBox procedure will not read the entire file, it will only read the
 file header, which is 5 THeader records, it will read the 5 records and place
 the ListText string in a List Box}
procedure LoadListBox;
var
hFile, Len: Cardinal;
i: Integer;
aryHeader: Array of THeader;
BytesRead, ID: Cardinal;

  function tryRead(var Dest; Size: Cardinal): Bool;
  begin
  // returns True if Successful
  if ReadFile(hFile,Dest, Size, BytesRead,nil) and (Size = BytesRead) then
    Result := True
    else
    begin
    CloseHandle(hFile);
    SysErrorMsg(ReadErrText, ReadErrTitle);
    Result := False;
    end;
  end;


begin
hFile := CreateFile(StringFile,GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+StringFile, CreateErrTitle);
  Exit;
  end;


if tryRead(ID, SizeOf(ID)) then
  begin
  // read and test fot the File ID number
  if ID <> FileStrID then
    begin
    CloseHandle(hFile);
    MessageBox(hForm1, PChar('ERROR - This File is NOT a Valid String Data File'#10+
             StringFile), 'ERROR, NOT a Valid Data File', MB_ICONERROR);
    Exit;
    end else
    if tryRead(Len, SizeOf(Len)) then // read the number of records in the file
      begin
      if Len > 24 then
        begin
    // the value of 24 is not anything, you should use your own safety value here
        CloseHandle(hFile);
        MessageBox(hForm1, PChar('ERROR - Array length is more than limit '#10+
             StringFile), 'ERROR, Bad File Amount', MB_ICONERROR);
        Exit;
        end;
      SetLength(aryHeader, Len);// get memory for your array with SetLength( )
    // now read all of the file header records into the array
      if tryRead(aryHeader[0], Len*SizeOf(THeader)) then
        begin
        SendMessage(hListBox, LB_RESETCONTENT, Zero, Zero);
        //Clear and enable list box
        EnableWindow(hListBox, True);
        for i := 0 to High(aryHeader) do
          SendMessage(hListBox,LB_ADDSTRING, Zero, 
                      Integer(@aryHeader[i].ListText[One]));
          // FOR loop to send all ListText strings to hListBox
        end;
      end;
    end;
CloseHandle(hFile);
end;



{the Load1StringRec procedure will use the file created above, and read just
 ONE THeader record and ONE TStrRec out of the file.}
procedure Load1StringRec;
var
hFile: Cardinal;
Len, CurSel: Integer;
Header1: THeader;
StrRec1: TStrRec;
BytesRead, ID: Cardinal;
BadFile: Boolean;
MemoStr: String;

  function tryRead(var Dest; Size: Cardinal): Bool;
  begin
  // returns true is Un-successful
  if ReadFile(hFile,Dest, Size, BytesRead,nil) and (Size = BytesRead) then
    Result := False
    else
    begin
    CloseHandle(hFile);
    BadFile := True;
    Result := True;
    SysErrorMsg(ReadErrText, ReadErrTitle);
    end;
  end;


begin
if not IsWindowEnabled(hListBox) then
  begin
  MessageBox(hForm1, 'Error - You must Load a String Record File First',
            'ERROR, No File Loaded', MB_ICONERROR);
  Exit;
  end;

CurSel := SendMessage(hListBox, LB_GETCURSEL, Zero, Zero);
{ IMPORTANT - use the lixt box select index as the index of the THeader record
 and the index of the TStrRec to read from the file}
if CurSel = LB_ERR then
  begin
  MessageBox(hForm1, 'Error - There is no List Box Selection',
            'ERROR, No List box Select', MB_ICONERROR);
  SendMessage(hListBox, LB_SETCURSEL, Zero, Zero);
  Exit;
  end;

// open the file for reading
hFile := CreateFile(StringFile,GENERIC_READ,FILE_SHARE_READ,nil,
               OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,Zero);
if hFile = INVALID_HANDLE_VALUE then
  begin
  SysErrorMsg(CreateErrText+#10+StringFile, CreateErrTitle);
  Exit;
  end;

BadFile := False;

if not tryRead(ID, SizeOf(ID)) then
  begin
  // read and test for the File ID number
  if ID <> FileStrID then
    begin
    BadFile := True;
    MessageBox(hForm1, PChar('ERROR - File is NOT a Valid String Record Data File'+
             #10+StringFile), 'ERROR, NOT a Valid Data File', MB_ICONERROR);
    end else
    if not tryRead(Len, SizeOf(Len)) then // read the number of strings in the file
      begin
      {I have included a test for the size of the Len variable, which should contain
      the numbar of string records in this file. If it is larger than 24 then I error
      out of this code. You should have some kind of "Safety" check for array lengths
      when you are building code and testing it. If in your code you incorrctly
      place the file position you may read an incorrect amount for the array length.
      It may read a value of 4 gigs and try and set the array length to that.
      Although this string file is very simple, I still place a test. After you
      get this working and test it, you can remove your safty test.}
      if Len > 24 then
        begin
    // the value of 24 is not anything, you should use your own safety value here
        CloseHandle(hFile);
        MessageBox(hForm1, PChar('ERROR - Array length is more than limit '+
               #10+StringFile),'ERROR, Bad File Amount', MB_ICONERROR);
        Exit;
        end;
      if CurSel > Len then // another safety test
        begin
        CloseHandle(hFile);
        MessageBox(hForm1, 'Error, The List Box Selection is Out of Range of the file',
             'ERROR, Selection Incorrect', MB_ICONERROR);
        Exit;
        end;

    {use the Index in CurSel to get the file position of the THeader record to
     read. You mutiply the index by the size of the THeader. Use SetFilePointer
     to change the file position.}
      if CurSel > 0 then
        SetFilePointer(hFile,CurSel*SizeOf(THeader),nil, FILE_CURRENT);

     // Read only One THeader record from this file into Header1
      if not tryRead(Header1, SizeOf(Header1)) then
        begin
        {The file position of the TStrRec record for this Index is in the
         Header1.Position, so move the file to the new position}
        SetFilePointer(hFile,Header1.Position,nil, FILE_BEGIN); // from beginning
        tryRead(StrRec1, 8); // only read the 2 fixed-Size variables with 8 read size
        tryRead(Len, SizeOf(Len)); // you must use a length number to read a string
        if Len > 256 then // safety test
          begin
      // the value of 256 is not anything, you should use your own safety value here
          CloseHandle(hFile);
          MessageBox(hForm1, PChar('ERROR - Array length is more than limit '+
             #10+StringFile),'ERROR, Bad File Amount', MB_ICONERROR);
          Exit;
          end;
        SetLength(StrRec1.TextLines, Len);
      // get string memory with SetLength , and read the string
        tryRead(StrRec1.TextLines[1], Len);
      // next add the width and height to MemoStr string, and set text in hEdit2
        MemoStr := 'Width = '+Int2Str(StrRec1.Width)+'  Height = '+
                   Int2Str(StrRec1.Height)+#13#10#13#10+StrRec1.TextLines;
        SetWindowText(hEdit2, PChar(MemoStr));
        end;
      end else
      BadFile := True;
  end;

CloseHandle(hFile);
if BadFile then
  begin
  MessageBox(hForm1, PChar('ERROR - String File Could not be fully Read '+
             #10+StringFile), 'ERROR, NO READ data File', MB_ICONERROR);
  end;
end;

end.
You will need to keep working with the file read and write methods, using different variables and data blocks, and different data arrangements, and different file access (sequential, ramdom), and just experiment, until you get to understand what you need to do to make and read files that can store the information you need.

                           

Next Page
The next Lesson shows you more ways to create top-level Pop-Up windows and ways to use them.
  16. More Form Windows


       

Lesson -     One  Two  Three  Four  Five  Six  Seven  Eight  Nine  Ten  Eleven  Twelve  Thirteen




H O M E