4-7. Master ＆ Detail 예제 상속용 부모폼에는 DataSet이 하나밖에 없기때문에 Master/Detail 구조에 사용하려면 몇 가지 수정작업과 추가 코드가 필요하다. Master DataSet이 스크롤 될 때 서버로부터 Detail 정보를 가져오게 처리할 수도 있지만 데이터 건수가 한번에 가져오지 못할 정도가 아니라면 처리 속도나 서버 부하면에서 좋은 선택은 아닌것 같다. 본 예제에서는 전체 자료를 클라이언트에 모두 가져와서 Filter로 처리하게 만들었다. 만약 조회조건이 있는 경우라면 쓸데 없는 자료를 가져오지 않게 Master의 조건에 맞는 Detail 자료를 가져오게 SQL문을 구성해야한다. Master/Detail로 만들어야할 경우가 많다면 본 예제를 참고해서 Master/Detail용 부모폼을 만들어 상속하면 코딩 양을 상당히 줄일 수 있을 것이다.
    unit code_frm;
    :
    :
    const
      DETAIL_TABLENAME = 'slminorm';
    :
    :
    type
      TfrmCode = class(TfrmSIMLazQ)
        dbgMaster: TDBGrid;
        dbgDetail: TDBGrid;
        dsMaster: TDataSource;
        dsDetail: TDataSource;
        qrDetail: TBufDataset;
        qrDetailMAJOR_CODE: TStringField;
        qrDetailMINOR_CODE: TStringField;
        qrDetailMINOR_NAME: TStringField;
        qrDetailREMARK: TStringField;
        qrDetailUSE_YN: TStringField;
        qrMasterMAJOR_CODE: TStringField;
        qrMasterMAJOR_NAME: TStringField;
        qrMasterREMARK: TStringField;
        qrMasterUSE_YN: TStringField;
        procedure dbgDetailEnter(Sender: TObject);
        procedure dbgMasterEnter(Sender: TObject);
        procedure dbgMasterExit(Sender: TObject);
        procedure dbgMasterKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
        procedure dsMasterStateChange(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormShow(Sender: TObject);
        procedure qrDetailAfterDelete(DataSet: TDataSet);
        procedure qrDetailAfterInsert(DataSet: TDataSet);
        procedure qrDetailBeforeDelete(DataSet: TDataSet);
        procedure qrDetailBeforeEdit(DataSet: TDataSet);
        procedure qrDetailBeforeInsert(DataSet: TDataSet);
        procedure qrDetailBeforePost(DataSet: TDataSet);
        procedure qrMasterAfterInsert(DataSet: TDataSet);
        procedure qrMasterAfterOpen(DataSet: TDataSet);
        procedure qrMasterMAJOR_CODESetText(Sender: TField; const aText: string);
      private
        u_DSIndex: Int32;
      public
        function UF_Modified: Boolean; override;
        procedure UP_SetSQL; override;
        procedure UP_OpenSQL; override;
        procedure UP_Append; override;
        procedure UP_Delete; override;
        procedure UP_Save; override;
    :
    :
    procedure TfrmCode.dbgDetailEnter(Sender: TObject);
    begin
      u_DSIndex := 2;
      dbgMaster.TitleFont.Color := clSilver;
      dbgDetail.TitleFont.Color := clBlack;
      Application.MainForm.Tag := 1;
    end;

    procedure TfrmCode.dbgMasterEnter(Sender: TObject);
    begin
      u_DSIndex := 1;
      dbgMaster.TitleFont.Color := clBlack;
      dbgDetail.TitleFont.Color := clSilver;
      Application.MainForm.Tag := 1;
    end;

    procedure TfrmCode.dbgMasterExit(Sender: TObject);
    begin
      Application.MainForm.Tag := 0;
    end;

    procedure TfrmCode.dbgMasterKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    begin
      if Key = VK_RETURN then // uses LCLType
      begin
        with TDBGrid(Sender) do
        begin
          if (not (SelectedColumn.ButtonStyle in [cbsCheckboxColumn, cbsButtonColumn])) and
             (not SelectedColumn.ReadOnly) and (not EditorMode) then Exit; // uses Grids
          if Shift = [] then
          begin
            if SelectedIndex < (Columns.Count - 1) then SelectedIndex := SelectedIndex + 1
            else
            begin
              SelectedIndex := 0;
              DataSource.DataSet.Next;
              if DataSource.DataSet.Eof then DataSource.DataSet.Append;
            end;
          end
          else if ssShift in Shift then
          begin
            if SelectedIndex > 0 then SelectedIndex := SelectedIndex - 1
            else
            begin
              SelectedIndex := Columns.Count - 1;
              DataSource.DataSet.Prior;
            end;
          end;
        end;
      end;
    end;

    procedure TfrmCode.dsMasterStateChange(Sender: TObject);
    begin
      qrMasterMAJOR_CODE.ReadOnly := not (qrMaster.State in [dsInsert]);
    end;

    procedure TfrmCode.FormCreate(Sender: TObject);
    begin
      inherited;
      u_DSIndex := 1;
    end;

    procedure TfrmCode.FormShow(Sender: TObject);
    begin
      u_TableName := 'slmajorm';
      UP_OpenSQL;
    end;

    procedure TfrmCode.qrDetailAfterDelete(DataSet: TDataSet);
    begin
      if (u_SQL <> '') and DataSet.Active then
        qrDetail.Tag := dmSIMLaz.UF_SIMLazExec(u_SQL);
    end;

    procedure TfrmCode.qrDetailAfterInsert(DataSet: TDataSet);
    begin
      with DataSet do
      begin
        FieldByName('major_code').AsString := qrMasterMAJOR_CODE.AsString;
        FieldByName('use_yn').AsString := '1';
      end;
      dbgDetail.SelectedIndex := 0;
    end;

    procedure TfrmCode.qrDetailBeforeDelete(DataSet: TDataSet);
    begin
      if (not u_auth_d) and DataSet.Active then
      begin
        MessageDlg(Self.Caption, '삭제 권한이 없습니다!', mtError, [mbOK], 0);
        Abort;
      end
      else
      begin
        if DataSet.State in [dsInsert] then u_SQL := ''
        else u_SQL := dmSIMLaz.UF_SIMLazDML(qrDetail, DETAIL_TABLENAME, ukDelete);
      end;
    end;

    procedure TfrmCode.qrDetailBeforeEdit(DataSet: TDataSet);
    begin
      if (not u_auth_m) and DataSet.Active then
      begin
        MessageDlg(Self.Caption, '수정 권한이 없습니다!', mtError, [mbOK], 0);
        Abort;
      end;
      if DataSet.RecordCount < 1 then DataSet.Append;
    end;

    procedure TfrmCode.qrDetailBeforeInsert(DataSet: TDataSet);
    begin
      if (not u_auth_n) and DataSet.Active then
      begin
        MessageDlg(Self.Caption, '등록 권한이 없습니다!', mtError, [mbOK], 0);
        Abort;
      end
      else if u_downkey_flag then Abort;
    end;

    procedure TfrmCode.qrDetailBeforePost(DataSet: TDataSet);
    begin
      u_SQL := '';
      if DataSet.State in [dsInsert] then
        u_SQL := dmSIMLaz.UF_SIMLazDML(qrDetail, DETAIL_TABLENAME, ukInsert)
      else
        u_SQL := dmSIMLaz.UF_SIMLazDML(qrDetail, DETAIL_TABLENAME, ukModify);
    end;

    procedure TfrmCode.qrMasterAfterInsert(DataSet: TDataSet);
    begin
      DataSet.FieldByName('use_yn').AsString := '1';
      dbgMaster.SelectedIndex := 0;
    end;

    procedure TfrmCode.qrMasterAfterOpen(DataSet: TDataSet);
    begin
      if not DataSet.ControlsDisabled then
        qrDetail.Filter :=
          'major_code = ' + QuotedStr(DataSet.FieldByName('major_code').AsString);
    end;

    procedure TfrmCode.qrMasterMAJOR_CODESetText(Sender: TField; const aText: string);
    begin
      TField(Sender).AsString := UpperCase(aText);
    end;

    function TfrmCode.UF_Modified: Boolean;
    begin
      Result := (qrMaster.Active and (qrMaster.State in [dsEdit, dsInsert])) or
                (qrDetail.Active and (qrDetail.State in [dsEdit, dsInsert]));
    end;

    procedure TfrmCode.UP_SetSQL;
    begin
      u_SQL := 'SELECT major_code, major_name, use_yn, remark FROM slmajorm ORDER BY major_code';
    end;

    procedure TfrmCode.UP_OpenSQL;
    begin
      if qrDetail.Active then qrDetail.Close;
      inherited UP_OpenSQL;
      dbgMaster.SetFocus;

      u_SQL := 'SELECT major_code, minor_code, minor_name, use_yn, remark' +
        ' FROM slminorm ORDER BY major_code, minor_code';
      dmSIMLaz.UP_SIMLazOpen(qrDetail, u_SQL, False);
    end;

    procedure TfrmCode.UP_Append;
    begin
      if not u_auth_n then Exit;

      case u_DSIndex of
        1: if qrMaster.Active and (not qrMaster.ReadOnly) then qrMaster.Append;
        2: if qrDetail.Active and (not qrDetail.ReadOnly) then qrDetail.Append;
      end;
    end;

    procedure TfrmCode.UP_Delete;
    begin
      if not u_auth_d then Exit;
      if ((u_DSIndex = 1) and
          ((not qrMaster.Active) or (qrMaster.RecordCount < 1) or qrMaster.ReadOnly)) or
         ((u_DSIndex = 2) and
          ((not qrDetail.Active) or (qrDetail.RecordCount < 1) or qrDetail.ReadOnly)) then Exit;

      if MessageDlg(Self.Caption,
        '선택된 자료를 삭제 하시겠습니까?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
      begin
        case u_DSIndex of
          1: qrMaster.Delete;
          2: qrDetail.Delete;
        end;
      end;
    end;

    procedure TfrmCode.UP_Save;
    var
      l_Flag: Boolean;
    begin
      if not (u_auth_n or u_auth_m or u_auth_d) then Exit;

      l_Flag := False;
      if qrDetail.Active and (not qrDetail.ReadOnly) and
         (qrDetail.State in [dsEdit, dsInsert]) then
      begin
        qrDetail.Post;
        l_Flag := True;
      end;
      if qrMaster.Active and (not qrMaster.ReadOnly) an
         (qrMaster.State in [dsEdit, dsInsert]) then
      begin
        qrMaster.Post;
        l_Flag := True;
      end;
      if l_Flag then ShowMessage('변경된 내용이 저장 되었습니다!');
    end;