MyBaseを試してみる。(インデックスを使って、行の追加、挿入、削除編)
レコードの順番にインデックスを使って、行の編集をするバージョンです。言うまでもなく、インデックスは検索や目的に合ったレコードの並び替えをするために、取り扱うデータを分析し、その内容によっては、新規にカテゴリを作ってそのIDを割り当てたり、個別にコードを割り当てたりして作成されるべきものです。
しかしここでは、各レコードの順番を登録するフィールド(ここではORD)を用意し、そのインデックス(ここではORD_IDX)を利用してレコードの順番を保持します。そのため、場合によっては、一度に全レコードのORDフィールドを書き換えるという禁断の処理です(^-^)プロの方々から「データベースの使い方を知ってるのか?」と怒られそうですが、「レコードの並びは、ユーザーが決める」「レコード数はせいぜい数千から1万件程度」を前提条件としています。もちろん、このような場合、リスト+仮想ListView(or StringGrid)が最適なのは、知っていますが、UIとしてDBGridを使うための処理です(^-^)
しかしここでは、各レコードの順番を登録するフィールド(ここではORD)を用意し、そのインデックス(ここではORD_IDX)を利用してレコードの順番を保持します。そのため、場合によっては、一度に全レコードのORDフィールドを書き換えるという禁断の処理です(^-^)プロの方々から「データベースの使い方を知ってるのか?」と怒られそうですが、「レコードの並びは、ユーザーが決める」「レコード数はせいぜい数千から1万件程度」を前提条件としています。もちろん、このような場合、リスト+仮想ListView(or StringGrid)が最適なのは、知っていますが、UIとしてDBGridを使うための処理です(^-^)
// COMMON // サンプルデータを追加します。 procedure MakeSample(CDS: TClientDataSet); var No: Integer; F: Boolean; begin F := CDS.Active; if not F then CDS.Open; try // サンプルデータの追加 No := 0; with CDS do begin Inc(No); Appendrecord([No, 'Delphi XE2 Starter ESD',10,'本',18000]); Inc(No); Appendrecord([No, 'Delphi XE2 Professional ESD',5,'本',94000]); Inc(No); Appendrecord([No, 'Delphi XE2 Enterprise ESD',10,'本',236000]); Inc(No); Appendrecord([No, 'Delphi XE2 Ultimate ESD',3,'本',356000]); Inc(No); Appendrecord([No, 'Delphi XE2 Architect ESD',1,'本',416000]); CheckBrowseMode; end; finally if not F then CDS.Close; end; end; // データベースの作成 procedure CreateDB(CDS: TClientDataSet); var I: Integer; begin // データベースの作成 CDS.Close; CDS.FieldDefs.Add('ORD',ftInteger); CDS.FieldDefs.Add('ITEM',ftWideString,30); CDS.FieldDefs.Add('QTY',ftFloat); CDS.FieldDefs.Add('UNIT',ftWideString,4); CDS.FieldDefs.Add('PRICE',ftCurrency); CDS.IndexDefs.Add('ORD_IDX','ORD', [ixPrimary]); CDS.CreateDataSet; CDS.Close; for I := 0 to CDS.FieldDefs.Count - 1 do CDS.FieldDefs[I].CreateField(CDS); // 計算フィールド with TCurrencyField.Create(CDS) do begin FieldName := 'AMOUNT'; Visible:=True; FieldKind := fkInternalCalc; DataSet := CDS; end; // 集合フィールド with TAggregateField.Create(CDS) do begin DisplayLabel := '合計'; DisplayWidth := 10; DisplayFormat := '#,###,###,###'; AlignMent := taRightJustify; FieldKind := fkAggregate; FieldName := 'TOTAL_F'; ReadOnly := True; Expression := 'SUM(AMOUNT)'; Active := True; DataSet := CDS; end; CDS.AggregatesActive := True; // インデックスの設定 CDS.IndexName := 'ORD_IDX'; // 表示用にフィールド幅を設定 CDS.FieldByName('ORD').DisplayWidth := 3; CDS.FieldByName('ITEM').DisplayWidth := 25; CDS.FieldByName('QTY').DisplayWidth := 3; CDS.FieldByName('UNIT').DisplayWidth := 4; CDS.FieldByName('PRICE').DisplayWidth := 8; CDS.FieldByName('AMOUNT').DisplayWidth := 10; // 計算フィールドの設定 CDS.OnCalcFields := Form1.CalcFields; // DBEditの設定 Form1.DBEdit1.DataField := 'TOTAL_F'; CDS.Open; // サンプルデータの作成 MakeSample(CDS); end; // DataSetの有効/無効 procedure DataSetEnabled(DataSet: TDataSet; Value: Boolean); begin if Value then DataSet.EnableControls else DataSet.DisableControls; end; // Filteredの設定 procedure cdsSetFiltered(CDS: TClientDataSet; F: Boolean); begin if F and (not CDS.Filtered) then CDS.Filtered := F else if (not F) and CDS.Filtered then CDS.Filtered := F; end; // Indexの設定 procedure cdsSetIndexFieldNames(CDS: TClientDataSet; S: String); begin if (S <> '') then CDS.IndexFieldNames := S else CDS.IndexName := 'ORD_IDX'; end; // 追加/挿入項目用の通し番号を返します。 var SN: Integer = 0; function GetSN: String; begin Inc(SN); Result := IntToStr(SN); end; // UNDO var SP: Integer = -1; // 現在の状態を保存 procedure SaveUndoPoint(CDS: TClientDataSet); begin // 変更があれば更新させます。 if CDS.ChangeCount > 0 then CDS.MergeChangeLog; SP := CDS.SavePoint; end; // Undo procedure Undo(CDS: TClientDataSet); begin if (SP > -1) then CDS.SavePoint := SP; SP := -1; end; // 追加/挿入/削除 // 追加 procedure cdsAppendUsingIndex(CDS: TClientDataSet; Count: Integer = 1); var I, No: Integer; begin //フィルター中と他のインデックスの場合は処理させません。 if CDS.Filtered or (CDS.IndexName <> 'ORD_IDX') then Exit; SaveUndoPoint(CDS); DataSetEnabled(CDS, False); try No := CDS.RecordCount; for I := 0 to Count - 1 do begin Inc(No); // 追加します。 CDS.Insert; // 10000回以下だとAppendより速かったから(^-^) // 空白のままだとレコードが追加されません。 // CDS.FieldByName('ITEM').AsString := '' ←これはOK CDS.FieldByName('ITEM').AsString := '追加'+GetSN; CDS.FieldByName('ORD').AsInteger := No; end; CDS.CheckBrowseMode; // 今回追加した空白行の先頭レコードに移動させます。 CDS.MoveBy(1-Count); finally DataSetEnabled(CDS, True); end; end; // 挿入 procedure cdsInsertUsingIndex(CDS: TClientDataSet; Count: Integer = 1); var I: Integer; NowRec: Integer; begin //フィルター中と他のインデックスの場合は処理させません。 if CDS.Filtered or (CDS.IndexName <> 'ORD_IDX') then Exit; SaveUndoPoint(CDS); DataSetEnabled(CDS, False); try // 挿入位置以後のレコードのORDをCount分増やして更新させます。 NowRec := CDS.RecNo; CDS.RecNo := NowRec; CDS.Last; while (not CDS.BOF) and (CDS.RecNo >= NowRec) do begin CDS.Edit; CDS.FieldByName('ORD').AsInteger := CDS.FieldByName('ORD').AsInteger + Count; CDS.Prior; end; CDS.CheckBrowseMode; // 挿入 CDS.RecNo := NowRec; for I := 0 to Count -1 do begin CDS.Insert; CDS.FieldByName('ITEM').AsString := '挿入'+GetSN; CDS.FieldByName('ORD').AsInteger := NowRec; Inc(NowRec); end; CDS.CheckBrowseMode; // カーソルを挿入位置に戻します。 CDS.MoveBy(1-Count); //Form1.DBGrid1.SelectedRows.Clear; finally DataSetEnabled(CDS, True); end; end; // 削除 procedure cdsDeleteUsingIndex(CDS: TClientDataSet; BMList: TBookmarkList = nil); var I: Integer; F: Boolean; S: String; J: Integer; begin if CDS.RecordCount = 0 then Exit; SaveUndoPoint(CDS); DataSetEnabled(CDS, False); // 取り扱うデータ量によっては、高速化のため計算部分の処理をさせない。 CDS.AggregatesActive := False; CDS.AutoCalcFields := False; try // 削除対象を削除します。 // フィルターやインデックスに関係なく選択されたものを削除します。 if (BMList = nil) or (BMList.Count = 0) then begin CDS.Delete; // レコードの位置 if CDS.Eof then J := CDS.RecordCount else J := CDS.RecNo; end else if (BMList.Count = 1) and (not CDS.BookmarkValid(BMList[0])) then begin // 挿入した後、そのまま削除ボタンを押した時、 //「レコードが見つかりません」というエラーが表示される時が // あるため、その対応です。 // cdsInsertUsingIndex中でForm1.DBGrid1.SelectedRows.Clear; // をすれば、このエラーは発生しない。 Exit; end else begin J := 0; for I := BMList.Count -1 downto 0 do begin CDS.GotoBookmark(BMList[I]); if I = BMList.Count -1 then J := CDS.FieldByName('ORD').AsInteger; CDS.Delete; end; // レコードの位置 J := J - BMList.Count+1; end; // 選択されたままになるのでクリアしておきます。 BMList.Clear; // ORDを通し番号に修正します。 // フィルターは解除しておきます。 F := CDS.Filtered; cdsSetFiltered(CDS, False); // ORD以外のインデックスの場合は、一度ORDに設定します。 // 基本的に順番用インデックスの入れ替えはしないつもりだけど・・・ S := CDS.IndexFieldNames; cdsSetIndexFieldNames(CDS, ''); // 番号を揃えます。 // 本当はBMList[0]のレコードまでを処理するのがいいかな。 try CDS.First; while (not CDS.Eof) do begin if CDS.FieldByName('ORD').AsInteger <> CDS.RecNo then begin CDS.Edit; CDS.FieldByName('ORD').AsInteger := CDS.RecNo; end; CDS.Next; end; CDS.CheckBrowseMode; // レコード位置を移動させます。 if J < CDS.RecordCount then CDS.RecNo := J else CDS.Last; finally cdsSetFiltered(CDS, F); cdsSetIndexFieldNames(CDS, S); end; finally CDS.AutoCalcFields := True; CDS.AggregatesActive := True; DataSetEnabled(CDS, True); end; end; // TForm1 procedure TForm1.FormCreate(Sender: TObject); begin CreateDB(ClientDataSet1); SaveUndoPoint(ClientDataSet1); end; // 計算フィールド procedure TForm1.CalcFields(DataSet: TDataSet); begin DataSet.FieldByName('AMOUNT').AsCurrency := DataSet.FieldByName('QTY').AsFloat * DataSet.FieldByName('Price').AsCurrency; end; // 追加 procedure TForm1.SpeedButton1Click(Sender: TObject); begin cdsAppendUsingIndex(ClientDataSet1, StrToIntDef(Edit1.Text,1)); end; // 挿入 procedure TForm1.SpeedButton2Click(Sender: TObject); begin cdsInsertUsingIndex(ClientDataSet1, StrToIntDef(Edit1.Text,1)); end; // 削除 procedure TForm1.SpeedButton3Click(Sender: TObject); begin cdsDeleteUsingIndex(ClientDataSet1, DBGrid1.SelectedRows); ClientDataSet1.AggregatesActive := True;; end; // Undo procedure TForm1.SpeedButton4Click(Sender: TObject); begin if (SP > -1) then Undo(ClientDataSet1) else if ClientDataSet1.ChangeCount > 0 then ClientDataSet1.UndoLastChange(True); end;レコードの順番を登録するフィールド(ここではORD)の番号を、10000毎にするとか、浮動小数点フィールドとかにして、挿入時には、その間の番号を設定すると、番号の打ち直しが減って、処理がより早くなるかも知れないですね。
| 固定リンク