![]() Home |
15 A. Program Code for UseFiles UseFiles a Program for Writting and Reading files |
![]() Home |
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 -
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. |
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

H O M E 