FreePascal Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
FreePacal 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
델마당
볼랜드포럼 광고 모집

FreePascal 강좌/문서
[19] 라자루스를 이용한 업무 프로그램 개발 - 18
어느좋은날 [freepascal] 1581 읽음    2020-06-13 22:59
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) and (qrMaster.State in [dsEdit, dsInsert]) then
  begin
    qrMaster.Post;
    l_Flag := True;
  end;
  if l_Flag then ShowMessage('변경된 내용이 저장 되었습니다!');
end;
어느좋은날 [freepascal]   2020-06-13 23:24 X
올려놓고보니 거의 스팸 수준으로 "전체 최신글" 목록을 덮어버렸네요.^^
필요할지는 모르겠지만... 필요하신 분이 있다면 강좌에 사용된 소스를 자료실에 올려놓도록 하겠습니다.
오랑캐꽃 [oranke]   2020-06-18 00:27 X
라자루스의 종합선물세트같은 글이네요.
좋은 글 감사드립니다. 그리고 고생하셨습니다. ^^
어느좋은날 [freepascal]   2020-06-25 16:11 X
코로나19 때문에 프로젝트가 날아가서 할 일이 없다보니...
델파이를 대체할 수 있을지 이것저것 테스트하면서 만들어본겁니다. ^^
라자루스의 인지도가 낮다보니 소스가 필요한 분은 없는 것 같네요. ㅎㅎㅎ;;;
레포트도 테스트 해봤는데 불편한 점이 있긴해도 못 쓸 정도는 아니네요.
브리오 [egsuh]   2020-09-15 11:45 X
안녕하세요.  Lazarus 를 애용하고 있습니다 -- 웹서버를 만들고 있어요. 쓰신 내용 보니까 제가 모르는 게 많습니다. 아마 제가 도움을 부탁드리게 될 것 같습니다.
브리오 [egsuh]   2020-09-15 11:50 X
그런데 전체 내용이, DB 를 제공하는 서버 프로그램이 계속 있고 여러 클라이언트에서 접속해서 query 를 요구하면 그 결과를 서버에서 실행해서 전달하는 프로그램인가요? 만약 그렇다면 서버 부분은 모든 클라이언트 request 를 순차적으로 실행하게 되나요?  서버가 동작 중일때 다음 요청이 오면 어떻게 하나요?   웹서버 (이 경우 웹서버가 client가 되겠죠)와 DB 서버를 이런 식으로 연결하는 것이 가능할지 궁금합니다 (안 될 이유는 없다고 생각되지만).
어느좋은날 [freepascal]   2020-09-15 18:47 X
네. 맞습니다. 클라이언트가 DB 서버에 바로 접속하지않고 미들웨어를 통해 데이터를 주고 받는 형태입니다.
Indy 서버는 쓰레드 형태로 동작하기때문에 요청에 대해 순차적이 아니라 동시에 처리가 됩니다.
웹 형태로도 개발이 가능합니다.
예제에 사용된 Indy TCP Server를 HTTP Server로 바꾸고 프로토콜도 HTTP에서 주고받는 파라메터 형태로 바꾸면 됩니다.
참고로 제가 개발한 몇몇 업무에서 Indy HTTP Server를 이용해서 위와 같은 형태로 서비스 하고 있습니다.
브리오 [egsuh]   2020-09-16 11:59 X
그러니까  (DB server) <--> (Web server) <--> (Client. Browser)  이런 형태일까요?

FPC web module 에 FastCGI 방식이 있는데, 이게 메모리 관리에 약간 문제가 있습니다 (개발자도 인정한 부분입니다). 그래서 CGI 방식을 쓰되, DB connection 만큼은 말하자면 RAM 상주 형태로 하고 싶네요 (웹브라우저에서 request가 올 때마다 database connection을 하는 건 좀 심각할 것 같습니다).  지금 웹서버 내부 작동 부분은 프로그램이 거의 다 되어 있어서 DB만 따로 떼어놓고 싶은 겁니다.
브리오 [egsuh]   2020-09-16 13:51 X
그나저나 소스 파일을 다 올려 놓으실 수는 없는지요? 아니면 egsuh@nate.com 으로 보내 주시면 제가 정말로 사례하겠습니다. 맥주 한 잔이라도^^
어느좋은날 [freepascal]   2020-09-16 15:24 X
이 글 맨 첫 댓글에 달려있듯이 원래 소스를 올려놓을 생각이었습니다.
그런데 아무도 관심이 없는 것 같아서 그냥 넘어갔던거죠. ㅎㅎㅎ;;;
소스를 필요로 하는 분이 계시니 자료실에 올려놓도록 하겠습니다. ^^
브리오 [egsuh]   2020-09-16 17:50 X
저도 그거 봤습니다. ㅎㅎ 감사합니다.

+ -

관련 글 리스트
19 라자루스를 이용한 업무 프로그램 개발 - 18 어느좋은날 1581 2020/06/13
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.