请看我的简单编辑器(200分)

6

631229

Unregistered / Unconfirmed
GUEST, unregistred user!
我的简单编辑器可以编辑文本文件和EQ文件。EQ文件是File Of TEQ,关于TEQ的定义请看
程序。TEQConversion重载了TConversion的ConvertReadStream和ConvertWriteStream
函数,经运行ConvertReadStream函数没有问题,能显示EQ文件的内容,但存盘时死机,说
明ConvertWriteStream函数有问题,调试时发现到了ConvertWriteStream函数的第一
条Val语句时就死机。请各位大侠看这个ConvertWriteStream函数该如何写?

unit EditUnit;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Menus, StdCtrls, ComCtrls;

type
TEditForm = class(TForm)
MainMenu1: TMainMenu;
FileMenu: TMenuItem;
FileOpenItem: TMenuItem;
FileCloseItem: TMenuItem;
RichEdit1: TRichEdit;
OpenDialog: TOpenDialog;
procedure FileOpenItemClick(Sender: TObject);
procedure FileCloseItemClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

type EarthQuake_Date_Time_Magnitude_Depth=Int64;
//地震的日期、时间、震级及震级类型、震源深度组成一个64位整数,其中:年为
//Smallint占16位,月占4位,日占5位,时占5位,分占6位,秒占10位(秒以整数
//存放不含小数点,如432代表43.2秒;深度占10位,震级占7位,震级类型占1位。
type EarthQuake_Longtitude=Smallint;
//震中经度(不含小数点,如11293代表112.93)占两个字节;
type EarthQuake_Latitude=Smallint;
// 震中纬度(不含小数点,如2818代表28.18度)占两个字节;
type EarthQuake_Serial_Accuracy=Smallint;
//序列号13位,精度3位组成一个Smallint;
//以上共14个字节组成一条地震事件记录。

type TEQ=Packed Record
EQDTMD:EarthQuake_Date_Time_Magnitude_Depth;
EQLon:EarthQuake_Longtitude;
EQLat:EarthQuake_Latitude;
EQSA:EarthQuake_Serial_Accuracy;
end;

type
TEQConversion = class(TConversion)
public
function ConvertReadStream(Stream:TStream; Buffer:pChar;
BufSize:integer): integer; override;
function ConvertWriteStream(Stream:TStream; Buffer:pChar;
BufSize:integer): integer; override;
end;

type
Teq2=Record
nian:smallint;
yue,ri,shi,fen:Byte;
weidu:smallint;
jingdu:smallint;
Ms,miao:Byte;
shendu:smallint;
serial:smallint;
end;

var
EditForm: TEditForm;

Const
TwoPower56:Int64=72057594037927936;
TwoPower48:Int64=281474976710656
TwoPower44:Int64=17592186044416
TwoPower40:Int64=1099511627776;
TwoPower39:Int64=549755813888
TwoPower34:Int64=17179869184
TwoPower32:Int64=4294967296;
TwoPower28:Int64=268435456
TwoPower24:Int64=16777216;
TwoPower18:Int64=262144
TwoPower16:Int64=65536;
TwoPower8:Int64=256
//以上常数是2的各次幂

implementation

{$R *.DFM}

procedure TEditForm.FormCreate(Sender: TObject);
begin
Richedit1.RegisterConversionFormat('EQ',TEQConversion);
end;

procedure TEditForm.FileOpenItemClick(Sender: TObject);
begin
OpenDialog.Execute;
Richedit1.Lines.LoadFromFile(OpenDialog.FileName);
Caption:=OpenDialog.FileName;
end;

procedure TEditForm.FileCloseItemClick(Sender: TObject);
begin
Richedit1.Lines.SaveToFile(Caption);
end;

function TEQConversion.ConvertReadStream(Stream:TStream; Buffer:pChar;
BufSize:integer): integer;
var s:string;
buf:array[1..14] of char;
n:integer;
TempLonLat:Smallint;
ComplexRec:Int64;
begin
Result := 0;
s:='';
n := Stream.Read(buf,14);
if n = 0 then Exit;
ComplexRec:=(Int64(Ord(buf[8])) shl 56)+(Int64(Ord(buf[7])) shl 48)+
(Int64(Ord(buf[6])) shl 40)+(Int64(Ord(buf[5])) shl 32)+
(Int64(Ord(buf[4])) shl 24)+(Int64(Ord(buf[3])) shl 16)+
(Int64(Ord(buf[2])) shl 8)+Int64(Ord(buf[1]));
AppendStr(s,StringOfChar(' ',4-length(IntToStr(ComplexRec shr 48)))+IntToStr(ComplexRec shr 48)+',');
//ComplexRec shr 48为年的值
if length(IntToStr((ComplexRec And (TwoPower48-1)) shr 44))=2 then
AppendStr(s,IntToStr((ComplexRec And (TwoPower48-1)) shr 44)+',')
else AppendStr(s,'0'+IntToStr((ComplexRec And (TwoPower48-1)) shr 44)+',');
//(ComplexRec And (TwoPower48-1)) shr 44为月的值
if length(IntToStr((ComplexRec And (TwoPower44-1)) shr 39))=2 then
AppendStr(s,IntToStr((ComplexRec And (TwoPower44-1)) shr 39)+',')
else AppendStr(s,'0'+IntToStr((ComplexRec And (TwoPower44-1)) shr 39)+',');
//(ComplexRec And (TwoPower44-1)) shr 39为日的值
if length(IntToStr((ComplexRec And (TwoPower39-1)) shr 34))=2 then
AppendStr(s,IntToStr((ComplexRec And (TwoPower39-1)) shr 34)+',')
else AppendStr(s,'0'+IntToStr((ComplexRec And (TwoPower39-1)) shr 34)+',');
//(ComplexRec And (TwoPower39-1)) shr 34为时的值
if length(IntToStr((ComplexRec And (TwoPower34-1)) shr 28))=2 then
AppendStr(s,IntToStr((ComplexRec And (TwoPower34-1)) shr 28)+',')
else AppendStr(s,'0'+IntToStr((ComplexRec And (TwoPower34-1)) shr 28)+',');
//(ComplexRec And (TwoPower34-1)) shr 28为分的值
AppendStr(s,StringOfChar(' ',4-length(FloatToStr(((ComplexRec And (TwoPower28-1)) shr 18)/10)))+FloatToStr(((ComplexRec And (TwoPower28-1)) shr 18)/10)+',');
//((ComplexRec And (TwoPower28-1)) shr 18)/10为秒的值
TempLonLat:=256*Ord(buf[10])+Ord(buf[9]);
AppendStr(s,StringOfChar(' ',7-length(FloatToStr(TempLonLat/100)))+FloatToStr(TempLonLat/100)+',');
//256*Ord(buf[10])+Ord(buf[9])为经度的值*100
TempLonLat:=256*Ord(buf[12])+Ord(buf[11]);
AppendStr(s,StringOfChar(' ',6-length(FloatToStr(TempLonLat/100)))+FloatToStr(TempLonLat/100)+',');
//256*Ord(buf[12])+Ord(buf[11])为纬度的值*100
AppendStr(s,StringOfChar(' ',3-length(FloatToStr(((ComplexRec And (TwoPower8-1)) shr 1)/10)))+FloatToStr(((ComplexRec And (TwoPower8-1)) shr 1)/10)+',');
//((ComplexRec And (TwoPower8-1)) shr 1)/10为震级的值
AppendStr(s,IntToStr(ComplexRec And 1)+',');
//ComplexRec And 1为震级类型的值
AppendStr(s,StringOfChar(' ',3-length(IntToStr((ComplexRec And (TwoPower18-1)) shr 8)))+IntToStr((ComplexRec And (TwoPower18-1)) shr 8)+',');
//(ComplexRec And (TwoPower18-1)) shr 8为深度的值
AppendStr(s,StringOfChar(' ',3-Length(IntToStr((256*(Ord(buf[14]))+Ord(buf[13])) shr 3)))+IntToStr((256*(Ord(buf[14]))+Ord(buf[13])) shr 3)+',');
//(256*(Ord(buf[14]))+Ord(buf[13]))) shr 3为序列号的值
AppendStr(s,IntToStr((Ord(buf[13])) And 7));
//(Ord(buf[13])) And 7为精度的值
AppendStr(s,#13#10);
StrPCopy(Buffer,s);
Result := length(s);
end;

function TEQConversion.ConvertWriteStream(Stream:TStream; Buffer:pChar;
BufSize:integer): integer;
var s:string;
buf:String[54];
n,code:integer;
EQ2Rec:TEQ2;
ComplexRec:Int64;
begin
Result := 0;
s:='';
n := Stream.Write(buf,54);
if n = 0 then Exit;
Val(Copy(buf,1,4),EQ2Rec.nian,code);
Val(Copy(buf,6,2),EQ2Rec.yue,code);
Val(Copy(buf,9,2),EQ2Rec.ri,code);
Val(Copy(buf,12,2),EQ2Rec.shi,code);
Val(Copy(buf,15,2),EQ2Rec.fen,code);
if pos('.',Copy(buf,18,4))=0 then Val(Copy(buf,18,4),EQ2Rec.miao,code)
else Val(Copy(buf,18,2)+Copy(buf,21,1),EQ2Rec.miao,code);
Val(Copy(buf,23,pos('.',Copy(buf,23,7))-1)+Copy(buf,23+pos('.',Copy(buf,23,7)),7-pos('.',Copy(buf,23,7))),EQ2Rec.jingdu,code);
Val(Copy(buf,31,pos('.',Copy(buf,31,6))-1)+Copy(buf,31+pos('.',Copy(buf,31,6)),6-pos('.',Copy(buf,31,6))),EQ2Rec.weidu,code);
if pos('.',Copy(buf,38,3))=0 then Val(Copy(buf,38,3),EQ2Rec.Ms,code)
else Val(Copy(buf,38,1)+Copy(buf,40,1),EQ2Rec.Ms,code);
Val(Copy(buf,44,3),EQ2Rec.shendu,code);
Val(Copy(buf,48,3),EQ2Rec.serial,code);
With EQ2Rec do
begin
ComplexRec:=nian*TwoPower48+Yue*TwoPower44+Ri*TwoPower39+ Shi*TwoPower34+Fen*TwoPower28+10*Miao*TwoPower18+Shendu*TwoPower8+2*Ms;
end;
AppendStr(s,chr(ComplexRec And (TwoPower8-1)));
AppendStr(s,chr((ComplexRec And (TwoPower16-1)) shr 8));
AppendStr(s,chr((ComplexRec And (TwoPower24-1)) shr 16));
AppendStr(s,chr((ComplexRec And (TwoPower32-1)) shr 24));
AppendStr(s,chr((ComplexRec And (TwoPower40-1)) shr 32));
AppendStr(s,chr((ComplexRec And (TwoPower48-1)) shr 40));
AppendStr(s,chr((ComplexRec And (TwoPower56-1)) shr 48));
AppendStr(s,chr(ComplexRec shr 56));
AppendStr(s,chr((EQ2Rec.jingdu) And (TwoPower8-1)));
AppendStr(s,chr((EQ2Rec.jingdu) shr 8));
AppendStr(s,chr((EQ2Rec.weidu) And (TwoPower8-1)));
AppendStr(s,chr((EQ2Rec.weidu) shr 8));
AppendStr(s,chr(((EQ2Rec.serial)*8) And (TwoPower8-1)));
AppendStr(s,chr(((EQ2Rec.serial)*8) shr 8));
StrPCopy(Buffer,s);
Result := length(s);
end;
end.
 
在你ConvertWriteStream中, 所有的Val的Copy應是Buffer,
而Stream.write(Buf, 54), 應該是放在函數的最後面
如此才能將buffer中的資料轉換到Buf, 最後再寫入流中
如下:
if BufSize = 0 then Exit;
Val(Copy(buffer^,1,4),EQ2Rec.nian,code);
Val(Copy(buffer^,6,2),EQ2Rec.yue,code);
Val(Copy(buffer^,9,2),EQ2Rec.ri,code);
Val(Copy(buffer^,12,2),EQ2Rec.shi,code);
Val(Copy(buffer^,15,2),EQ2Rec.fen,code);
.
.
n := Stream.Write(buf,54);
 
记住posion:=0
 
我看看,不错。
 
lorderic your are well done!!!
 
我试过lorderic的方法,好象存出来的文件也不对。
 
我仔細看過了, 如果你當中的取位及演算正確,
應是可以將Buf的宣告去掉, 那是沒有用的
照我之前的方法, 不要這一行
n := Stream.Write(buf,54);
最後將你原來的
StrPCopy(Buffer,s)
改為
Stream.Write(PChar(s)^, 14);
即可
如果還是不正確, 表示你原來由Buffer內容組成complexrecord的那一段有錯誤,
如果可以的話, 你可以提供顯示到RichEdit後的格式作為參考, 則會更容易捉出問題
 
各位(特别是Lorderic)的讨论虽然没有全对,但确实给了我很大启发,大大减少了自己摸
索的时间(我被这个问题折磨了半个月了!)终于搞定了!这个问题虽然解决了,但我还是
知其然而不知其所以然。我看了ComCtrls.pas和Classes.pas两个文件,没看出来TrichEdit
何时调用的TConversion类,也就不明白TConversion类在TrichEdit类的Line.SaveToFile()
过程中是如何起作用的。因此我暂不结贴,希望有高人论述一下其中道理。非常感谢各位!
在此将正确代码贴出两者对比就不难发现错在哪了。我认为除了Lorderic指出的问题外,还
有一个关键的地方:即必须正确设置ConvertWriteStream的返回值。(这是我自己摸索出来
的)。
function TEQConversion.ConvertWriteStream(Stream:TStream; Buffer:pChar;
BufSize:integer): integer;
var s:string;
n,code:integer;
EQ2Rec:TEQ2;
MySecond:smallint;
ComplexRec:Int64;
begin
Result := 0;
if BufSize = 0 then Exit;
s:='';
Val(Copy(buffer,1,4),EQ2Rec.nian,code);
Val(Copy(buffer,6,2),EQ2Rec.yue,code);
Val(Copy(buffer,9,2),EQ2Rec.ri,code);
Val(Copy(buffer,12,2),EQ2Rec.shi,code);
Val(Copy(buffer,15,2),EQ2Rec.fen,code);
if pos('.',Copy(buffer,18,4))=0 then Val(Copy(buffer+'0',18,4),MySecond,code)
else Val(Copy(buffer,18,2)+Copy(buffer,21,1),MySecond,code);
Val(Copy(buffer,23,pos('.',Copy(buffer,23,7))-1)+Copy(buffer,23+pos('.',Copy(buffer,23,7)),7-pos('.',Copy(buffer,23,7)))+StringOfChar('0',pos('.',Copy(buffer,23,7))-5),EQ2Rec.jingdu,code);
Val(Copy(buffer,31,pos('.',Copy(buffer,31,6))-1)+Copy(buffer,31+pos('.',Copy(buffer,31,6)),6-pos('.',Copy(buffer,31,6)))+StringOfChar('0',pos('.',Copy(buffer,31,6))-4),EQ2Rec.weidu,code);
if pos('.',Copy(buffer,38,3))=0 then Val(Copy(buffer,38,3)+'0',EQ2Rec.Ms,code)
else Val(Copy(buffer,38,1)+Copy(buffer,40,1),EQ2Rec.Ms,code);
Val(Copy(buffer,44,3),EQ2Rec.shendu,code);
Val(Copy(buffer,48,3),EQ2Rec.serial,code);
With EQ2Rec do
begin
ComplexRec:=nian*TwoPower48+Yue*TwoPower44+Ri*TwoPower39+
Shi*TwoPower34+Fen*TwoPower28+10*MySecond*TwoPower18+Shendu*TwoPower8+2*Ms;
end;
AppendStr(s,chr(Byte(ComplexRec And (TwoPower8-1))));
AppendStr(s,chr(Byte((ComplexRec And (TwoPower16-1)) shr 8)));
AppendStr(s,chr(Byte((ComplexRec And (TwoPower24-1)) shr 16)));
AppendStr(s,chr(Byte((ComplexRec And (TwoPower32-1)) shr 24)));
AppendStr(s,chr(Byte((ComplexRec And (TwoPower40-1)) shr 32)));
AppendStr(s,chr(Byte((ComplexRec And (TwoPower48-1)) shr 40)));
AppendStr(s,chr(Byte((ComplexRec And (TwoPower56-1)) shr 48)));
AppendStr(s,chr(Byte(ComplexRec shr 56)));
AppendStr(s,chr(Byte((EQ2Rec.jingdu) And (TwoPower8-1))));
AppendStr(s,chr(Byte((EQ2Rec.jingdu) shr 8)));
AppendStr(s,chr(Byte((EQ2Rec.weidu) And (TwoPower8-1))));
AppendStr(s,chr(Byte((EQ2Rec.weidu) shr 8)));
AppendStr(s,chr(Byte(((EQ2Rec.serial)*8) And (TwoPower8-1))));
AppendStr(s,chr(Byte(((EQ2Rec.serial)*8) shr 8)));
n:=Stream.Write(PChar(s)^,length(s));
if n=0 then exit;
Result := 54;
end;
关于
Result := 54;的意义,研究TEQConversion.ConvertWriteStream函数后你就会明白了。
一周之内没有高人回答我就结贴给分。
 
我有看過了Delphi Source Code, 傳回值只要傳回寫入Stream的Bytes數, 就可以了;
至於它是在何處調用TConversion類的方法, 我把我目前看到的說明一下:

TRichEdit的Lines的類, 是宣告在同一Unit中的TRichEditStrings
在呼叫TRichEdit.RegisterConversionFormat時, 它會呼叫私有AppendConversionFormat,
在AppendConversionFormat中, 它會用一單向的連接串列結構記錄所有register的
TConversion類; 串列的起始變量為ConversionFormatList; 我覺得這部份有一點小問
題, 在Unit在finalization時, 並沒有釋放ConversionFormatList;

再來是, Register後, 在那裏使用呢? 此時就要看TRichEditStrings的Code
以下是LoadFromFile
procedure TRichEditStrings.LoadFromFile(const FileName: string);
var
Ext: string;
Convert: PConversionFormat;
begin
Ext := AnsiLowerCaseFileName(ExtractFileExt(Filename)); //取得檔案副檔名
System.Delete(Ext, 1, 1);
Convert := ConversionFormatList; //Register的TConversion類List
//以下程式為依照檔案副檔名來找尋對應的TConversion類
while Convert <> nil do
with Convert^ do
if Extension <> Ext then Convert := Next
else Break;
if Convert = nil then
Convert := @TextConversionFormat; //找不到對應的TConversion類, 就用預設的讀取Text文檔的類
if FConverter = nil then FConverter := Convert^.ConversionClass.Create; //依到的類建立實例
try
inherited LoadFromFile(FileName); //呼叫父類(TStrings)的LoadFromFile, 而父類的LoadFromFile會呼叫LoadFromStream
except
FConverter.Free;
FConverter := nil;
raise;
end;
RichEdit.DoSetMaxLength($7FFFFFF0);
end;

再接下來看LoadFromStream的Code:
procedure TRichEditStrings.LoadFromStream(Stream: TStream);
var
EditStream: TEditStream; //Windows定義, 在使用EM_STREAMIN或EM_STREAMOUT時, 必需在lParam傳入該結構
Position: Longint;
TextType: Longint;
StreamInfo: TRichEditStreamInfo;
//Delphi定義, 在EM_STREAMIN或EM_STREAMOUT時, callback function 的dwCookie會傳入此結構的指針
Converter: TConversion;
begin
StreamInfo.Stream := Stream;
if FConverter <> nil then Converter := FConverter
else Converter := RichEdit.DefaultConverter.Create; //如果未指定FConverter, 則建立預設的Converter
StreamInfo.Converter := Converter; //將Conveter設定到StreamInfo, 稍後會經由dwCookie參數傳入到callback function中
try
with EditStream do
begin
dwCookie := LongInt(Pointer(@StreamInfo)); //將dwCookie設定為StreamInfo的內存位置
pfnCallBack := @StreamLoad; //設定Callback function為StreamLoad;
dwError := 0;
end;
Position := Stream.Position;
if PlainText then TextType := SF_TEXT
else TextType := SF_RTF;
//以下為送入EM_STREAMIN消息, 此消息會呼叫lParam傳入的TEditStream結構指定的Callback function
SendMessage(RichEdit.Handle, EM_STREAMIN, TextType, Longint(@EditStream));
if (TextType = SF_RTF) and (EditStream.dwError <> 0) then
begin
Stream.Position := Position;
if PlainText then TextType := SF_RTF
else TextType := SF_TEXT;
SendMessage(RichEdit.Handle, EM_STREAMIN, TextType, Longint(@EditStream));
if EditStream.dwError <> 0 then
raise EOutOfResources.Create(sRichEditLoadFail);
end;
finally
if FConverter = nil then Converter.Free;
end;
end;

最後再看StreamLoad函數(Unit的私有函數)
function StreamLoad(dwCookie: Longint; pbBuff: PByte;
cb: Longint; var pcb: Longint): Longint; stdcall;
//dwCookie -> 傳入EditStream中dwCookie的值
//pbBuff -> 放置要顯示於TRichEdit內容的buffer區
//cb -> Buffer區的大小
//pcb -> 傳回實際讀入buffer區Byte數, 傳回0表示讀取結束, 即完成載入
//傳回值 0表示正常; 非0表示讀取失敗
var
Buffer, pBuff: PChar;
StreamInfo: PRichEditStreamInfo;
begin
Result := NoError;
StreamInfo := PRichEditStreamInfo(Pointer(dwCookie)); //由dwCookie中取得在LoadFromStream指定的StreamInfo結構
Buffer := StrAlloc(cb + 1); //宣告temp buffer
try
cb := cb div 2; //只用一半的buffer給ConvertReadStream, 因為最後還要做AdjustLineBreaks的動作
pcb := 0;
pBuff := Buffer + cb; //呼叫ConvertReadStream時所使用的Buffer
try
if StreamInfo^.Converter <> nil then //呼叫Coverter的CovertReadStream函數
pcb := StreamInfo^.Converter.ConvertReadStream(StreamInfo^.Stream, pBuff, cb);
if pcb > 0 then //如果有讀入資料
begin
pBuff[pcb] := #0; //設定讀入的結尾
if pBuff[pcb - 1] = #13 then pBuff[pcb - 1] := #0;
//AdjustLineBreaks 是匯編碼, 主要調整換行字元, 如果沒有猜錯, 應是將單一的#10轉換為#10#13
pcb := AdjustLineBreaks(Buffer, pBuff);
Move(Buffer^, pbBuff^, pcb); //在temp buffer寫入到pbBuffer
end;
except
Result := ReadError;
end;
finally
StrDispose(Buffer);
end;
end;

至於SaveToFile、SaveToStream...等與上面就類似, 我想就不用說明了!!
 
多人接受答案了。
 
顶部