MyBaseを試してみる。(インデックスを使わずに、行の追加、挿入、削除編)
前にも書きましたが、私がBDE(TTable)+Paradoxを使っていて一番便利だと思っていたところはインデックスを設定しなくてもAppendとInsertを使い分けることによって、レコードの順番が保持されるところでした。「データベースなんだからインデックスは、ちゃんと作ろうよ」と言われそうですが、本当なら、リスト構造+TStringGrid(or TListView)のところをDBGridを使うためだけのParadoxだったので、そんな点をメリットに感じていました。まあDBGridも複数行のDrag&Drop、Cut&Pasteを実装したりして、データベースというより表計算のようなソフトになっていましたけど。
だから私はMyBaseにもそんな仕様?を求めていました。同じように動作するならTTable(BDE)との置き換えが簡単かも知れないと期待しながら、そしてメモリ内なんだからインデックスはあったら便利だけど、無くても動くよね~なんて気軽に考えていました。しかしこれがなかなか難しく、結論としてインデックスがない場合のフィルターには対応できませんでした。
(フィルターについては、前回紹介したCloneCursor+2段DBGridで妥協しました)
インデックスを使わないMyBaseで困ったところ等を挙げます。
実行時画面
フィルター処理の前に試したこと(^-^)
MyBaseを試してみる。(インデックスを使わずに、行の追加、挿入、削除編) REVENGE(^o^)もご覧下さい。
だから私はMyBaseにもそんな仕様?を求めていました。同じように動作するならTTable(BDE)との置き換えが簡単かも知れないと期待しながら、そしてメモリ内なんだからインデックスはあったら便利だけど、無くても動くよね~なんて気軽に考えていました。しかしこれがなかなか難しく、結論としてインデックスがない場合のフィルターには対応できませんでした。
(フィルターについては、前回紹介したCloneCursor+2段DBGridで妥協しました)
インデックスを使わないMyBaseで困ったところ等を挙げます。
その1 ClientDataSetは、Append、Insertメソッドを持っており、単純に追加、挿入の処理が できます。しかし、DBGrid上で、追加、挿入の処理が正しくできているように見えて も、そのままファイルに保存して、再度読み込むと挿入も追加扱いになっています。 MergeChangeLogメソッドを呼び出した後、ファイルに保存すると正しいレコードの 順番で書き込まれます。 その2 ClientDataSetは、Deltaというプロパティを持っており、そこに変更されたデータを 保存しています。そのため、元に戻す処理も簡単にできるようになっています。 しかしながら、Insertで挿入したレコードやDeleteで削除したレコードは、最終行に 戻されるだけです。 その3 フィルター処理し解除した場合、挿入したレコードは、追加扱いになっています。 MergeChangeLogメソッドを呼び出して、ファイルに保存すれば正しく動作すること から、フィルター前にいろんなことを試してみましたが、一度目は成功してもなぜか 二度目以降は正しく動作しませんでした。 (この記事の最後にどのような処理をしたか、書き残しておきますね) その4 複数行の処理を元に戻すためにSavePointを利用しています。一度に処理した内 容は、一度に元に戻したいからです。 そのため、元に戻す処理は1度しか使えない仕様でプログラムしています。デザイン画面
var RecNoList: TList<Integer>; // Undoに使用 SP: Integer = -1; // Undoの処理に使用 SN: Integer = 0; // サンプルデータ procedure MakeSample(DataSet: TDataSet); var F: Boolean; begin F := DataSet.Active; if not F then DataSet.Open; try // サンプルデータの追加 with DataSet do begin Appendrecord(['iPod Touch']); Appendrecord(['Zaurus C-860']); Appendrecord(['Delphi 2009 Handbook']); Appendrecord(['GEORGIA BLACK']); CheckBrowseMode; end; finally if not F then DataSet.Close; end; end; // データベースの作成 procedure CreateDB(CDS: TClientDataSet; UsingIndex: Boolean); var I: Integer; begin CDS.Close; CDS.FieldDefs.Add('ITEM',ftWideString,20); CDS.CreateDataSet; CDS.Close; for I := 0 to CDS.FieldDefs.Count - 1 do CDS.FieldDefs[I].CreateField(CDS); CDS.Open; MakeSample(CDS); end; // DataSetの有効/無効 procedure DataSetEnabled(DataSet: TDataSet; Value: Boolean); begin if Value then DataSet.EnableControls else DataSet.DisableControls; end; // SaveToFile procedure cdsSaveToFile(CDS: TClientDataSet; const FileName: String); begin if CDS.ChangeCount > 0 then CDS.MergeChangeLog; CDS.SaveToFile(FileName); end; // LoadFromFile procedure cdsLoadFromFile(CDS: TClientDataSet; const FileName: String); begin CDS.LoadFromFile(FileName); end; // 通し番号を返します。別に要らない処理です。 function GetSN: String; begin Inc(SN); Result := IntToStr(SN); end; // 現在の状態を保存 procedure SaveUndoPoint(CDS: TClientDataSet); begin // 変更があれば更新させます。 if CDS.ChangeCount > 0 then CDS.MergeChangeLog; SP := (CDS as TClientDataSet).SavePoint; end; // SavePointから元に戻す処理 procedure Undo(CDS: TClientDataSet); begin if SP > -1 then CDS.SavePoint := SP; SP := -1; end; // 削除されたレコードを元に戻す処理 ※コテコテ(^-^) procedure UndoDelete(CDS: TClientDataSet); var I, RC, RecNo: Integer; Delta: TClientDataSet; begin if (RecNoList.Count <> 0) and (CDS.ChangeCount > 0) then begin Delta := TClientDataSet.Create(nil); try Delta.Data := CDS.Delta; Delta.Open; Delta.Last; I := -1; RC := CDS.RecordCount; // Deltaから順番に戻していきます。 while not Delta.Bof do begin Inc(I); if I < RecNoList.Count then begin RecNo := RecNoList[I]; if RecNo > RC then begin CDS.Append; Inc(RC); end else begin CDS.RecNo := RecNo; CDS.Insert; Inc(RC); end; CDS.FieldByName('Item').AsString := Delta.FieldByName('Item').AsString; end; Delta.Prior; end; CDS.CheckBrowseMode; CDS.MergeChangeLog; RecNoList.Clear; finally Delta.Free; end; end; end; // Undo procedure cdsUndoNoIndex(CDS: TClientDataSet); begin if RecNoList.Count > 0 then UndoDelete(CDS) // 削除 else if (SP > -1) then Undo(CDS) // 追加/挿入-SavePoint else if CDS.ChangeCount > 0 then CDS.UndoLastChange(True); // 上記以外 end; // 追加 procedure cdsAppendNoIndex(CDS: TClientDataSet; Count: Integer = 1); var I: Integer; begin SaveUndoPoint(CDS); DataSetEnabled(CDS, False); try // Count分空白行を追加します。 for I := 0 to Count - 1 do begin // 追加します。 CDS.Append; CDS.FieldByName('ITEM').AsString := '追加'+ GetSN; end; CDS.CheckBrowseMode; // 今回追加した空白行の先頭レコードに移動させます。 CDS.MoveBy(1-Count); finally DataSetEnabled(CDS, True); end; end; // 挿入 procedure cdsInsertNoIndex(CDS: TClientDataSet; Count: Integer = 1); var I: Integer; begin SaveUndoPoint(CDS); DataSetEnabled(CDS, False); try for I := 0 to Count -1 do begin // 挿入します。 CDS.Insert; CDS.FieldByName('ITEM').AsString := '挿入'+ GetSN; end; CDS.CheckBrowseMode; finally DataSetEnabled(CDS, True); end; end; // 削除 procedure cdsDeleteNoIndex(CDS: TClientDataSet; BMList: TBookmarkList = nil); type TIntegerComparer = TComparer<Integer>>; var I: Integer; begin // セル編集時は、BMList.Countは0です。 // その場合の処理は含まれていないです。 if not Assigned(BMList) then Exit; RecNoList.Clear; SaveUndoPoint(CDS); DataSetEnabled(CDS, False); try if Assigned(BMList) and (BMList.Count > 0) then begin for I := BMList.Count -1 downto 0 do begin CDS.GotoBookmark(BMList[I]); RecNoList.Add(CDS.RecNo); CDS.Delete; end; BMList.Clear; end; // RecNoListのソート RecNoList.Sort(TComparer<Integer>.Default); finally DataSetEnabled(CDS, True); end; end; procedure TForm1.FormCreate(Sender: TObject); begin // Undo用 RecNoList:= TList<Integer>.Create; // データベースの作成 CreateDB(ClientDataSet1, False); end; procedure TForm1.FormDestroy(Sender: TObject); begin FreeAndNil(RecNoList); end; procedure TForm1.ClientDataSet2FilterRecord(DataSet: TDataSet; var Accept: Boolean); begin Accept := AnsiPos(Edit2.Text, DataSet.FieldByName('ITEM').AsString) > 0 end; // SaveToFile procedure TForm1.SpeedButton1Click(Sender: TObject); var FileName: String; begin FileName := ExtractFilePath(Application.ExeName) + 'MyBase.cds'; cdsSaveToFile(ClientDataSet1,FileName); end; // LoadFromFile procedure TForm1.SpeedButton2Click(Sender: TObject); var FileName: String; begin FileName := ExtractFilePath(Application.ExeName) + 'MyBase.cds'; if FileExists(FileName) then cdsLoadFromFile(ClientDataSet1, FileName); end; // 追加 procedure TForm1.SpeedButton3Click(Sender: TObject); begin cdsAppendNoIndex(ClientDataSet1, StrToIntDef(Edit1.Text,1)); end; // 挿入 procedure TForm1.SpeedButton4Click(Sender: TObject); begin cdsInsertNoIndex(ClientDataSet1, StrToIntDef(Edit1.Text,1)); end; // 削除 procedure TForm1.SpeedButton5Click(Sender: TObject); begin cdsDeleteNoIndex(ClientDataSet1, DBGrid1.SelectedRows); end; // Undo procedure TForm1.SpeedButton6Click(Sender: TObject); begin cdsUndoNoIndex(ClientDataSet1); end; // Filter procedure TForm1.SpeedButton7Click(Sender: TObject); begin ClientDataSet2.Filtered := not ClientDataSet2.Filtered; if ClientDataSet2.Filtered then begin ClientDataSet2.CloneCursor(ClientDataSet1, TRUE, False); ClientDataSet2.OnFilterRecord := ClientDataSet2FilterRecord; ClientDataSet2.Filtered := True; end else ClientDataSet2.Close; end;
実行時画面
フィルター処理の前に試したこと(^-^)
// 次のcdsUpdate0~cdsUpdate3までいずれも役に // 立ちませんでした。 // いずれもClientDataSet1.Filtered := Trueの前に // 入れてテストしたものです。 // ファイルの読み書きが正しくできるなら // 採用するかどうかは別として // この処理で動作すると考えた。 // 2度目以降使えない(T_T) procedure cdsUpdate0(CDS: TClientDataSet); begin Form1.SpeedButton1Click(nil); Form1.SpeedButton2Click(nil); end; procedure cdsUpdate1(CDS: TClientDataSet); var Data: OleVariant; begin // 2度目以降使えない(T_T) if CDS.ChangeCount > 0 then CDS.MergeChangeLog; Data := CDS.Data; CDS.Data := Data; end; procedure cdsUpdate2(CDS: TClientDataSet); var MS: TMemoryStream; begin // 2度目以降使えない(T_T) if CDS.ChangeCount > 0 then CDS.MergeChangeLog; MS := TMemoryStream.Create; try CDS.SaveToStream(MS, dfBinary); MS.Position := 0; CDS.LoadFromStream(MS); finally MS.Free; end; end; procedure cdsUpdate3(CDS: TClientDataSet); var SL1, SL2: TStringList; I, J: Integer; FieldName, Value: String; begin // こんなコテコテの処理をしたところで、 // フィルターで編集されたデータは、 // 一番最後に移動してしまいます。 if CDS.ChangeCount > 0 then CDS.MergeChangeLog; SL1 := TStringList.Create; SL2 := TStringList.Create; try SL1.StrictDelimiter := True; SL1.Delimiter := ','; CDS.DisableControls; try CDS.First; while not CDS.EOF do begin SL1.Clear; for I := 0 to CDS.Fields.Count -1 do if (CDS.Fields[I].FieldKind = fkData) then SL1.Add(CDS.Fields[I].FieldName +'='+ CDS.Fields[I].AsString); SL2.Add(SL1.CommaText); CDS.Next; end; // 次の処理はEmptyDataSetを使う方が簡単 CDS.Last; while not CDS.BOF do CDS.Delete; for I := 0 to SL2.Count-1 do begin SL1.CommaText := SL2[I]; CDS.Append; for J := 0 to SL1.Count-1 do begin FieldName := SL1.Names[J]; Value := SL1.Values[FieldName]; CDS.FieldByName(FieldName).AsString := Value; end; end; CDS.CheckBrowseMode; finally CDS.EnableControls; end; finally SL2.Free; SL1.Free; end; end;
MyBaseを試してみる。(インデックスを使わずに、行の追加、挿入、削除編) REVENGE(^o^)もご覧下さい。
| 固定リンク