20年以上前、Delphiでプログラムを始めた時、教えてもらった方法は危険ですという表示が出ていました。
今は出てないようですが??
子供の勉強で家計簿プログラムをつくるのを機会に新しい方法を教えてもらおうと思って質問します。
下のデータを使って、
①1~8日までのデータを一括で保存する
②作ったファイルに9日のデータを追加する
③6~9日のデータを読み込む
の3つのプログラムを紹介してください。
正直、本を読んで調べるとかnetで調べるとかしてみましたが、
分からんとしか言えません。
よろしくお願いします。
a:array[1..5] of DType=
((Day:'2023/02/01';InC:100000;Eat:0;Oth:0;Sum:0;Bal:100000),
(Day:'2023/02/02';InC:0;Eat:1429;Oth:0;Sum:1429;Bal:98571),
(Day:'2023/02/06';InC:0;Eat:3237;Oth:7240;Sum:10477;Bal:88094),
(Day:'2023/02/08';InC:0;Eat:685;Oth:0;Sum:685;Bal:87409),
(Day:'2023/02/09';InC:0;Eat:479;Oth:0;Sum:479;Bal:86930));
TR = packed record
D1: TDateTime;
B1: Byte;
I1: Integer;
S1: array[0..11] of Char; //String は使わない
end;
①
var
I: Integer;
R: array[1..8] of TR;
FileStream: TFileStream;
Y,M,D: Word;
begin
DecodeDate(Now,Y,M,D);
for I:=1 to 8 do
begin
R[I].D1 := EncodeDate(Y,M,I);
R[I].B1 := 10 + I;
R[I].I1 := 12340 + I;
StrPCopy(R[I].S1,'AAAAA' + IntToStr(I));
end;
FileStream := TFileStream.Create('A.TXT',fmCreate);
FileStream.Write(R,SizeOf(R));
FileStream.Free;
②
var
I: Integer;
R: array[9..9] of TR; //6 - 8
FileStream: TFileStream;
Y,M,D: Word;
begin
DecodeDate(Now,Y,M,D);
for I:=9 to 9 do
begin
R[I].D1 := EncodeDate(Y,M,I);
R[I].B1 := 20 + I;
R[I].I1 := 22340 + I;
StrPCopy(R[I].S1,'CCCCCCC' + IntToStr(I));
end;
FileStream := TFileStream.Create('A.TXT',fmOpenReadWrite);
FIleStream.Position := FileStream.Size;
FileStream.Write(R,SizeOf(R));
FileStream.Free;
③
var
I: Integer;
FileStream: TFileStream;
R: array[6..9] of TR; //6 - 9
begin
FileStream := TFileStream.Create('A.TXT',fmOpenRead);
FIleStream.Position := SizeOf(TR) * 5;
FileStream.Read(R,SizeOf(R));
FileStream.Free;
for I:= Low(R) to High(R) do
begin
Memo1.Lines.Add(DateTimeToStr(R[I].D1));
Memo1.Lines.Add(IntToStr(R[I].B1));
Memo1.Lines.Add(IntToStr(R[I].I1));
Memo1.Lines.Add(R[I].S1);
end;
AAAAAさん、ありがとうございました。
後で、研究して作ってみます。
また、質問させていただくかも知れませんので、
よろしくお願いします。
下のように直してもよろしいでしょうか??
③
var
I: Integer;
FileStream: TFileStream;
R: array[1..4] of TR; //6 - 9 //直しました
begin
FileStream := TFileStream.Create('A.TXT',fmOpenRead);
FIleStream.Position := SizeOf(TR) *(6-1); //5;直しました
FileStream.Read(R,SizeOf(TR*4)); //直しました
FileStream.Free;
for I:= Low(R) to High(R) do
begin
Memo1.Lines.Add(DateTimeToStr(R[I].D1));
Memo1.Lines.Add(IntToStr(R[I].B1));
Memo1.Lines.Add(IntToStr(R[I].I1));
Memo1.Lines.Add(R[I].S1);
end;
訂正します。
FileStream.Read(R,SizeOf(TR)*4); //直しました
参考にして自分のプログラムを作り始めました。
Varの宣言を各procudureで行っていたら、読み込みがうまくいかなったです。
上で一括宣言したらうまく動くようになりました。
すごく長い間悩んでいました。今日できるようになりました。
問題は、追加ができません。
また、読み込みも全部読み込むのならできるんですが、最近3日分を読み込むとかができません。
FIleStream.Positionをどう変えても最初の3日間が読み込まれます。
どこが悪いのか、よろしくお願いします。
procedure TForm1.Button2Click(Sender: TObject);
begin
for I:=5 to 5 do
begin
DD.Day := a[I].day;
DD.InC := a[I].InC;;
DD.Eat := a[I].Eat;
DD.Oth := a[I].Oth;;
DD.Sum := a[I].Sum;
DD.Bal := a[I].Bal;;
end;
FileStream := TFileStream.Create('c:\Study\家計簿\KakeiBo2023.TXT',fmOpenReadWrite);
FIleStream.Position := FileStream.Size;
FileStream.Write(DD,SizeOf(Dtype));
FileStream.Free;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
setlength(R,3);
FileStream := TFileStream.Create('c:\Study\家計簿\KakeiBo2023.TXT',fmOpenRead);
FIleStream.Position := Sizeof(Dtype)*4;
FileStream.Read(R,SizeOf(R));
FileStream.Free;
for I:= 0 to 2 do
Memo1.Lines.Add(IntToStr(R[I].Day)+','+IntToStr(R[I].InC)+','+
IntToStr(R[I].Eat)+','+IntToStr(R[I].Oth)+','+IntToStr(R[I].Sum)+','+
IntToStr(R[I].Bal));
end;
昨日作ったファイルを、今日読み込んだらすべて0が入っています。
このfilestreamってほんとに使えるのですか??
FileStreamを使って、実際にファイル、データを操作されてる方、いますか??
おられましたら、投稿してください。
よろしくお願いします。
//読み込み
procedure TForm2.Button1Click(Sender: TObject);
var
I: Integer;
D: array of TData;
FileStream: TFileStream;
begin
SetLength(D,10);
FileStream := TFileStream.Create('FN.TXT',fmOpenRead);
for I:=0 to 9 do
begin
FileStream.Read(D[I],SizeOf(TDATA));
end;
FileStream.Free;
for I:=0 to 9 do
begin
Memo1.Lines.Add(IntToStr(I) + '='+IntToStr(D[I].X) + ' ' + IntToStr(D[I].Y));
end;
end;
//書き込み
procedure TForm2.Button2Click(Sender: TObject);
var
I: Integer;
D: array of TData;
FileStream: TFileStream;
begin
SetLength(D,10);
for I:=0 to 9 do
begin
D[I].X := 32 + I;
D[I].Y := 65 + I;
end;
FileStream := TFileStream.Create('FN.TXT',fmCreate);
for I:=0 to 9 do
begin
FileStream.Write(D[I],SizeOf(TDATA));
end;
FileStream.Free;
end;
AAAAAさん、ありがとうございます。
データが残るようになりましたね。
問題は、追加ができるかなんです。
また、部分読み込みができる必要があります。
何十年ものデータを管理しなければなりませんので、
FileStream以外でもいいので、安心して使える方法を紹介してください。
よろしくお願いします。
自作DBを作って そのDBを使ったDBアプリ(家計簿)を作ろうとしてるよね
市販のDB(BDEでも)使ったDBアプリうケーションにしたらどうだい?
ファイルの読み書きは1年単位にするとか一か月単位にするとか(ファイルを分ける)にすればファイルの操作なんて
関係なくなるけど
家計簿なら EXCEL のほうがいいよ
AAAAAさんへ
家計簿は子供のプログラムの勉強用です。
memo1を使って、かなりのころはできるようになりました。
私が何十年も使っているブロックリードは危険だといわれていたので、これを機会に新しい方法に変えようと思っているのです。
FileStreamでは無理なんですかね??
お子様のお勉強用なら、TStringGrid(jclとjvclをインストールしてTJvStringGridを使うほうがお勧めですが。並び替えもサポートしているので。)で表示してTStringListでcsvファイルに保存するのもお勧めかもです。
また、TClientDatasetだと、ファイルベースでフィルターも使えるのでお勉強にはいいかもですね。
いきなりStreamのお勉強はハードルが高いかもです。
外していたらすいません。
何十年というデータを扱うのであれば、データ量も大量になることが、当然想像つくが、
それを自前のデータファイル管理しようとか・・・
使い方の理解できないFileStreamで、どうにかしようとするより
AAAAAさんの仰るとおり、データベース化することを、おすすめする。
FireDACで扱うことができるSQLiteや、データベースコンポーネントつきAbsolute Database
https://www.componentace.com/bde_replacement_database_delphi_absolute_database.htm
なんかを使えばよろしかろう。
AAA様や、AT.W様のように(midas)データベースを使用するような方法です。
(別途インストールしないでDelphiが標準で搭載しているmidasを使用する)
但し、これではお勉強にならないかもです。
ほとんどコンポーネントとプロパティ設定だけで完成してしまうので。
Delphiってよくできていますよね。
TClientDataSetをフォームにドラッグ&ドロップ
TDataSourceとTDGBridをドラッグ&ドロップする
オブジェクトインスペクタで
DataSource1.DataSet:=ClientDataSet1
に設定する
オブジェクトインスペクタで
DGBrid1.DataSource:=DataSource1
に設定する
ClientDataSet1を右クリック⇒フィールドエディタ
フィールドの新規作成をクリックしてフィールドを追加する
ID(id Integer Autoinc)、日付(date Date)、
用途(usage String100)、金額(price currency)
オブジェクトインスペクタで
ClientDataSet1
のプロパティIndexesDefs[...]をクリックして「新規追加」ボタンを押し、
Fieldsにidを追加(name=ClientDataSetIndex1)する
ClientDataSetIndex1.Options.ixPrimary:=Trueに設定する
ClientDataSetIndex1.Options.ixUnique:=Trueに設定する
ClientDataSet1.IndexNameでidを選択する
ClientDataSet1.FileNameに'a.xml'などを入れる
(ClientDataSet1が閉じられるときに自動保存し、開かれる時に自動読み取りする)
//ClientDataSet1.OnNewRecordに以下を入れるとIDフィールドが自動的にインクリメントされる
procedure TForm1.ClientDataSet1NewRecord(DataSet: TDataSet);
var g:TAggregate;
NewId:Integer;
begin
ClientDataSet1.AggregatesActive:=False;
ClientDataSet1.Aggregates.Clear;
g:=ClientDataSet1.Aggregates.Add;
g.Expression:='Max(id)';
g.AggregateName:='MaxId';
g.Active:=True;
ClientDataSet1.AggregatesActive:=True;
NewId:=ClientDataSet1.Aggregates[0].Value;
Inc(NewId);
ClientDataSet1.FieldByName('id').AsInteger:=NewId;
end;
ClientDataSet1を右クリックして
「データセットの作成」をクリックする
実行すれば、midasベースですが、データベースを別途インストールしなくても
簡易なmidasデータベースアプリの出来上がりです。
(Unit1.pasのuses に midasを追加することをお勧めします)
ClientDataSet1.Filter:='price>=100 and price<=200';
に好きにWhere句をいれて
ClientDataSet1.Filtered:=True;
にすれば、フィルター(SQLのwhere句で絞る)も出来ます。
//priceフィールドの合計はButton1.OnClickに以下で計算できます
procedure TForm1.Button1Click(Sender: TObject);
var g:TAggregate;
begin
ClientDataSet1.AggregatesActive:=False;
ClientDataSet1.Aggregates.Clear;
g:=ClientDataSet1.Aggregates.Add;
g.Expression:='Sum(Price)';
g.AggregateName:='SumPrice';
g.Active:=True;
ShowMessage(Inttostr(ClientDataSet1.Aggregates[0].Value));
end;
mamさん、AT.Wさん、ありがとうございます。
正直、ブロックリードだけでプログラムは完成しているので、新しいfile操作の方法を教えてもらいたいのですが、Delphiにはないのでしょうか??
しばらく時間はかかると思いますが、教えていただいた方法も試してみます。
家計簿って1日1行?
はい、1行でやっています。
本格的に作ると、1行ではダメな感じですが、プログラムの練習ですから。
> 新しいfile操作の方法を教えてもらいたい
教えてもらいましたよね?もういちどこの質問の最初の回答からひとつずつやり直してみてはいかがでしょう?
うまくいかないのは基本的にASさんの問題であって、Delphiの問題ではないです。
(おすすめはこの質問をいったん解決としたうえで、うまくいかなかったことを他の人にもわかるようにきちんと整理して別に質問をすることです)
一応確認なんだけど、すでに出来上がっている家計簿のプログラムがあって
そこで使用している AssinFile とか Reset とか BlockRead を変えたいって話でいいんだよね?
ためしてないけど 大体こんな感じなはず
procedure AssignFile(var F: TFILE; FILENAME: String);
begin
F.FILENAME := FILENAME;
end;
procedure FileClose(var F: TFILE);
begin
F.FileStream.Free;
F.FileStream := nil;
end;
procedure Append(var F: TFILE);
begin
if FileExists(F.Filename) = True then
begin
if Assigned(F.FileStream) = True then FileClose(F);
F.FileStream := TFileStream.Create(F.FILENAME,fmOpenWrite);
F.FileStream.Position := F.FileStream.Size;
end;
end;
procedure Rewrite(var F: TFILE; RecSize: Integer = 128);
begin
if Assigned(F.FileStream) = True then FileClose(F);
if FileExists(F.Filename) = True then DeleteFile(F.FILENAME);
F.FileStream := TFileStream.Create(F.FILENAME,fmOpenWrite);
end;
function Eof(var F: TFILE): Boolean;
begin
if F.FileStream.Position = F.FileStream.Size then RESULT := True else RESULT := False;
end;
procedure Reset(var F: TFILE; RECSIZE: Integer = 128);
begin
F.FileStream := TFileStream.Create(F.FILENAME,FILEMODE);
F.RECSIZE := RECSIZE;
end;
function BlockRead(var F: TFILE; var Buffer; Count: Integer): LongInt;
begin
RESULT := F.FileStream.Read(Buffer,F.RECSIZE * Count);
end;
procedure Seek(var F: TFILE; N: Integer);
begin
F.FileStream.Position := N * F.RECSIZE;
end;
function FilePos(var F: TFILE): Int64;
begin
RESULT := F.FileStream.Position;
end;
function FileSize(var F: TFILE): Int64;
begin
RESULT := (F.FileStream.Size div F.RECSIZE);
end;
function BlockWrite(var F: TFILE; var Buffer; Count: Integer): LongInt;
begin
RESULT := F.FileStream.Write(Buffer,F.RECSIZE * Count);
end;
AAAAAさん、ありがとうございます。
読ませていただきましたが、データはどの変数に入れるのでしょうか??
HFUKUSHIさんへ
①1~8日までのデータを一括で保存する
②作ったファイルに9日のデータを追加する
③6~9日のデータを読み込む
の3つのプログラムを紹介してください。
とお願いしているんですが?
よろしくお願いします。
分からないのならいいですので。
TFILE = record
FileStream: TFileStream
Filename: String
RecSize: Int64;
end
わすれてた
> の3つのプログラムを紹介してください。
> とお願いしているんですが?
ご自分で考えておつくりください。
> よろしくお願いします。
私はお断りします。他の優しい方は何かしら答えてくれるかもしれませんが。
> 分からないのならいいですので。
www
> ①1~8日までのデータを一括で保存する
> ②作ったファイルに9日のデータを追加する
> ③6~9日のデータを読み込む
> の3つのプログラムを紹介してください。
> とお願いしているんですが?
は、これまでの投稿(AAAAA さんの投稿と、AS さんが試されたコード)で、
いくつか実現されているかと思うのですが、
現時点で、どこまで出来ていて、どこが出来ていない(判らない)のか、
改めて、提示するのは、いかがですか?
すでに、いくつか投稿があるのに、
> 新しいfile操作の方法を教えてもらいたいのですが、
と書かれると、HFUKUSHI さんの投稿にあるように、
> 教えてもらいましたよね?
と、他の人は、思うかもしれませんね。
> FileStream以外でもいいので、安心して使える方法を紹介してください。
安心して使える方法は、何一つありません。
理解できずに使えば、全て危険です。
TFileStreamは、使えます。
そもそも使い物にならないクラスが、標準で利用できるものでしょうか。
いったいどのような自信をもって発言されているのかわかりませんが、自分が扱えないからと言って、不良品の如く発言するのはやめていただきたい。
学習が目的と言いつつ、模範解答を得ようとしていませんか?
学習と言うなら、FileStreamの正しい使い方を学ぶことも当てはまるのでは?
学習とはなんたるか、まず、そこから考えていただきたいものである。
procedure CloseFile(var F: TFILE);
begin
F.FileStream.Free;
F.FileStream := nil;
end;
から
function BlockWrite(var F: TFILE; var Buffer; Count: Integer): LongInt;
begin
RESULT := F.FileStream.Write(Buffer,F.RECSIZE * Count);
end;
までをコメントにして
F: TFILE を F: FILE にしても動く
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TFILE = record
FileStream: TFileStream;
Filename: String;
RecSize: Int64;
end;
TDATA = record
Day: array[0..9] of AnsiChar; //日付 //10byte
Inc: Int64; //入金 //8byte
Eat: Int64; //食費 //8byte
Oth: Int64; //その他 //8byte
Sum: Int64; //食費 + その他 //8byte
Bal: Int64; //残金 //8byte 計 50byte
end;
TForm2 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form2: TForm2;
//基本データ
DATA:array[1..4] of TDATA=
((Day:'2023/02/01';InC:100000;Eat:0;Oth:0;Sum:0;Bal:100000),
(Day:'2023/02/02';InC:0;Eat:1429;Oth:0;Sum:1429;Bal:98571),
(Day:'2023/02/06';InC:0;Eat:3237;Oth:7240;Sum:10477;Bal:88094),
(Day:'2023/02/08';InC:0;Eat:685;Oth:0;Sum:685;Bal:87409));
//追加データ
DATA2:array[1..1] of TDATA=
((Day:'2023/02/09';InC:0;Eat:479;Oth:0;Sum:479;Bal:86930));
implementation
{$R *.dfm}
procedure CloseFile(var F: TFILE);
begin
F.FileStream.Free;
F.FileStream := nil;
end;
procedure AssignFile(var F: TFILE; FILENAME: String);
begin
F.FILENAME := FILENAME;
F.FileStream := nil;
end;
procedure Rewrite(var F: TFILE; RecSize: Integer = 128);
begin
if Assigned(F.FileStream) = True then CloseFile(F);
if FileExists(F.Filename) = True then DeleteFile(F.FILENAME);
F.FileStream := TFileStream.Create(F.FILENAME,fmCreate);
F.RECSIZE := RECSIZE;
end;
procedure Reset(var F: TFILE; RecSize: Integer = 128);
begin
if Assigned(F.FileStream) = True then CloseFile(F);
F.FileStream := TFileStream.Create(F.FILENAME,FILEMODE);
F.RECSIZE := RECSIZE;
end;
function Eof(var F: TFILE): Boolean;
begin
if F.FileStream.Position >= F.FileStream.Size then RESULT := True else RESULT := False;
end;
function BlockRead(var F: TFILE; var Buffer; Count: Integer): LongInt;
begin
RESULT := F.FileStream.Read(Buffer,F.RECSIZE * Count);
end;
procedure Seek(var F: TFILE; N: Integer);
begin
F.FileStream.Position := N * F.RECSIZE;
end;
function FilePos(var F: TFILE): Int64;
begin
RESULT := F.FileStream.Position;
end;
function FileSize(var F: TFILE): Int64;
begin
RESULT := (F.FileStream.Size div F.RECSIZE);
end;
function BlockWrite(var F: TFILE; var Buffer; Count: Integer): LongInt;
begin
RESULT := F.FileStream.Write(Buffer,F.RECSIZE * Count);
end;
//データ作成
procedure TForm2.Button1Click(Sender: TObject);
var
F: TFILE;
I: Integer;
begin
AssignFile(F,'A.TXT');
Rewrite(F,SizeOf(TDATA));
for I := Low(DATA) to High(DATA) do
begin
BlockWrite (F,DATA[I],1);
end;
CloseFile(F);
end;
//全部表示
procedure TForm2.Button2Click(Sender: TObject);
var
F: TFILE;
DATA: TDATA;
function XXX(Value: Int64): String;
begin
RESULT := ' ' + IntToStr(Value);
RESULT := Copy(RESULT,Length(RESULT)-6,6);
end;
begin
AssignFile(F,'A.TXT');
FILEMODE := fmOpenRead;
Reset(F,SizeOf(TDATA));
while Eof(F) = False do
begin
BlockRead(F,DATA,1);
Memo1.Lines.Add(DATA.Day + ' ' + XXX(DATA.Inc) + ' ' + XXX(DATA.Eat) + ' ' + XXX(DATA.Oth) + ' ' + XXX(DATA.Sum) + ' ' + XXX(DATA.Bal));
end;
CloseFile(F);
end;
//追加
procedure TForm2.Button3Click(Sender: TObject);
var
F: TFILE;
I: Integer;
begin
AssignFile(F,'A.TXT');
FILEMODE := fmOpenWrite;
Reset(F,SizeOf(TDATA));
Seek(F,FileSize(F));
for I := Low(DATA2) to High(DATA2) do
begin
BlockWrite (F,DATA2[I],1);
end;
CloseFile(F);
end;
//3-4
procedure TForm2.Button4Click(Sender: TObject);
var
I: Integer;
F: TFILE;
DATA: TDATA;
function XXX(Value: Int64): String;
begin
RESULT := ' ' + IntToStr(Value);
RESULT := Copy(RESULT,Length(RESULT)-6,6);
end;
begin
AssignFile(F,'A.TXT');
FILEMODE := fmOpenRead;
Reset(F,SizeOf(TDATA));
Seek(F,2);
for I:= 1 to 2 do
begin
BlockRead(F,DATA,1);
Memo1.Lines.Add(DATA.Day + ' ' + XXX(DATA.Inc) + ' ' + XXX(DATA.Eat) + ' ' + XXX(DATA.Oth) + ' ' + XXX(DATA.Sum) + ' ' + XXX(DATA.Bal));
end;
CloseFile(F);
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
Memo1.Font.Name := 'MS ゴシック';
end;
end.
AAAAAさん、ありがとうございました。
やはり、この方法以外にはありませんか。
20年以上前に教えてもらった方法です。
この方法以外に何かないかと質問していたのです。
あきらめがつきました。
解決の印はつけませんので、
また何かありましたらお願いします。
AssignFile(F1, fileName); // 入力ファイルを開く
Reset(F1, SizeOf(Dtype)); // byte単位に変更する
FileMode := 0; // ファイルモードを読み出し専用に設定する
try
Reset(F1, SizeOf(DType));
seek(F1,N-haba); // ファイルは0から始まるから
BlockRead(F1, S1, Haba, readDdNum);
finally
CloseFile(F1);
end;
いや・・・・・
FileStream 使用してるやん
FileStreamの使い方が理解できてなさそうだから AssignFile とかの形式にまねただけで
1日1行って事は365行しかないわけで StringList に読み込んじゃったほうが良い気がするけど
AAAAAさん、すみません。
研究してみます。
AAAAAさん、ありがとうございました。
部分呼び出しはできるようになっていますね。
ただ、追加ができないような。
こちらも研究してみますが、確認をお願いします。
AAAAAさん、完成しています。
ありがとうございました。
今後もよろしくお願いします。
ツイート | ![]() |