기술 문서

HotXLS Delphi Component: Delphi에서 data validation, AutoFilter, and worksheet tables

HotXLS의 세 가지 기능은 같은 워크시트를 공유하지만 전혀 다른 객체를 다루며, 이들이 비슷한 일을 한다고 가정하는 순간부터 문제가 시작됩니다. 데이터 유효성 검사는 사용자가 입력할 수 있는 값을 제한하는 규칙을 범위에 부착합니다. 자동 필터는 저장된 조건 정의를 영역에 부착하여 뷰어가 어떤 행을 보여줄지를 바꿉니다. 표는 이름이 붙고 타입이 지정된 구조에 밴드 스타일을 입혀 범위를 감쌉니다. 하나는 입력을 제한하고, 하나는 뷰를 기록하며, 하나는 스키마를 부과합니다. 이 셋 중 어느 것도 스스로 단 하나의 셀 값도 옮기지 않으며, 특히 자동 필터는 사람들을 헷갈리게 합니다. 그 이름이 마치 동작을 암시하지만 실제로는 정의만 저장하기 때문입니다. 각 호출이 어떤 객체를 건드리는지, 그리고 효과가 실제로 언제 구현되는지를 아는 것이야말로 테스트에서와 Excel에서 동일하게 동작하는 워크북과 조용히 어긋나는 워크북을 가르는 요소입니다

Delphi에서 HotXLS 워크시트 기능 세 가지 다이어그램: 데이터 유효성은 입력을 제한하고, AutoFilter는 뷰 정의를 저장하며, 테이블은 스키마를 부과
데이터 유효성 검사, AutoFilter, 테이블은 모두 HotXLS의 같은 워크시트 범위에 붙지만, 실체화되는 시점은 각각 다릅니다 — 입력, 파일 열기, 저장입니다

자동 필터는 정의를 저장할 뿐 행을 잘라내지 않습니다

저장된 파일 안의 자동 필터는 조건 레코드입니다. 행을 숨기는 일은 나중에, Excel이 워크북을 열고 데이터에 대해 조건을 평가할 때 일어납니다. HotXLS는 이 레코드를 충실하게 기록할 뿐 아무것도 잘라내지 않습니다. 필터링한 모든 행은 여전히 파일 안에 물리적으로 존재합니다. 거부된 주문을 제외하기 위해 필터를 적용한 다음 워크북을 다시 읽는 파이프라인은 거부된 주문을 포함한 모든 행을 그대로 보게 되며, 이 코드는 API 기준으로는 옳지만 작성자의 머릿속 모델 기준으로는 틀린 것입니다. XLSX 워크시트에서는 SetAutoFilter가 필터링된 영역을 선언하고, AddAutoFilterColumn이 그 영역의 한 열에 조건을 부착합니다. 서버 측 코드가 실제 결과를 필요로 할 때, 이를테면 요약에 들어갈 행 수라든지 일치하는 행만 전달해야 할 때는, 파일이 바뀐 척하는 대신 라이브러리가 조건을 대신 평가해 줍니다:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R, Visible: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('orders.xlsx');
    Sheet := Book.Sheets[0];

    Sheet.SetAutoFilter('A1:E500');
    // 열 id 3 = 필터 범위 안에서 네 번째 열(0부터 시작하는 오프셋)
    Sheet.AddAutoFilterColumn(3, xlsxAfOpGreaterOrEqual, '1000');

    Visible := 0;
    for R := 2 to 500 do
      if Sheet.AutoFilterRowVisible(R) then
        Inc(Visible);
    // 이제 Visible은 파일을 연 뒤 Excel이 보여줄 값과 일치합니다

    Book.SaveAs('orders-filtered.xlsx');
  finally
    Book.Free;
  end;
end;

AutoFilterRowVisible은 행 단위로 답을 주며, 일치하는 집합을 한 번에 훑어야 할 때는 PreviewAutoFilterRows가 콜백을 통해 영역 전체를 순회합니다. 어느 쪽도 정답이 아닌 경우가 하나 있습니다. 요구사항이 뷰가 아니라 개인정보 보호 차원의 절단이어서 제외된 행이 파일에 아예 존재해서는 안 된다면, 그 행들을 통째로 삭제해야 합니다. 그런 경우 필터는 잘못된 도구입니다. 수신자 누구든 클릭 한 번으로 필터를 지울 수 있고, 그러면 숨기려던 데이터가 다시 화면에 나타나기 때문입니다

열 id는 열 번호가 아니라 오프셋입니다

위 코드 조각의 주석은 이 API에서 가장 많은 디버깅 시간을 잡아먹는 함정을 짚어줍니다. AddAutoFilterColumn은 워크시트 열이 아니라 필터 범위 안에서 0부터 시작하는 위치로 대상을 식별합니다. A1:E500에 대한 필터에서는 두 번호 체계가 우연히 딱 1만큼 차이가 나는데, 이는 빠른 테스트는 통과하지만 동료가 다른 열을 필터링하는 순간 깨지는 딱 그런 종류의 아슬아슬한 어긋남입니다. C열에서 시작하는 필터라면 id 0이 C열을 의미하므로 불일치가 빠르게 드러납니다. 필터 범위가 실행 시점에 계산될 때는, 워크시트 열 상수가 아니라 범위 문자열을 만든 것과 동일한 변수에서 열 id를 유도하십시오. 각 열은 두 연산자, 두 조건, and/or 연결자를 받는 오버로드를 통해 두 번째 조건도 받아들이며, 이는 Excel의 사용자 정의 필터 대화상자를 그대로 반영합니다. XLS 파사드는 SetAutoFilterApplyAutoFilter로 같은 영역을 다루며, 그 조건 및 연산자 매개변수는 더 오래된 COM 스타일 관례를 따라 필드를 1부터 번호를 매깁니다. 파사드를 바꾸는 것은 인덱스 기준을 바꾸는 것을 의미하므로, 호출 지점에는 어느 쪽을 쓰고 있는지 주석으로 남길 가치가 있습니다

HotXLS AutoFilter가 저장된 Excel 파일에 모든 행을 저장하는 동안 Delphi 미리보기 API가 Excel이 보여줄 행을 평가함을 보여주는 다이어그램, 0 기반 열 id 오프셋 포함
저장된 파일은 모든 행을 유지하고 기준만 기록하는 반면, Excel은 평가 후 행을 숨깁니다 — 그리고 AddAutoFilterColumn은 범위 안에서 0 기반 오프셋으로 컬럼을 지정합니다

유효성 검사 규칙은 사용자가 그 아래에서 편집하는 계약입니다

세 기능 중 유효성 검사만이 향후 입력을 능동적으로 제한하며, 작성 완료를 위해 내보냈다가 처리를 위해 돌아오는 워크북에서 가장 많은 설계 관심을 받을 가치가 있습니다. 목록 변형이 그 작업의 대부분을 담당합니다:

var
  Idx: Integer;
begin
  Idx := Sheet.AddListValidation('C2:C500', 'New,Approved,Blocked');
  Sheet.DataValidations[Idx].SetPrompt('Status',
    'Pick one of the listed states');
  Sheet.DataValidations[Idx].SetError('Invalid status',
    'Type or paste only listed values', xlsxDvErrStop);
  Sheet.DataValidations[Idx].AllowBlank := False;

  // 수량: 정수, 0 이상
  Sheet.AddWholeNumberValidation('D2:D500', xlsxDvOpGreaterOrEqual, '0');
end;

목록과 정수를 넘어, 같은 계열은 AddCustomValidation을 통해 소수, 날짜, 시간, 텍스트 길이, 자유 형식 수식까지 다루며, 범용 AddDataValidation은 설정 기반 규칙 빌더를 위한 전체 타입-연산자 매트릭스를 노출합니다. 오류 스타일은 그 이름이 시사하는 것보다 더 중요합니다. xlsxDvErrStop은 잘못된 입력을 완전히 거부합니다. 경고와 정보 스타일은 클릭 한 번이면 값을 통과시킵니다. 워크북을 다시 읽는 코드가 규칙을 벗어난 값을 견딜 수 있는지 여부에 따라 열마다 선택하십시오. 프롬프트 텍스트나 파일과 함께 배포하는 README에 넣어야 할 경계가 둘 있습니다. Excel의 유효성 검사는 타이핑은 지키지만, 유효성 검사가 적용된 범위 위에 블록을 붙여넣으면 규칙을 그냥 지나쳐 버리므로, 워크북을 다시 읽는 코드는 셀을 신뢰하지 말고 다시 검증해야 합니다. 그리고 규칙은 여러분이 건네준 리터럴 범위만 다루므로, 최종 행 수를 알기 전에 유효성 검사를 부착하면 나중에 추가된 꼬리 부분은 규칙의 보호를 받지 못합니다. 데이터를 먼저 쓰고, 그런 다음 규칙의 크기를 실제 범위에 맞추십시오

레거시 파사드는 같은 규칙 계열을 하나의 사용 편의성 차이와 함께 제공합니다. XLS 쪽 생성자들, 즉 AddWholeNumberValidation, AddDecimalValidation, AddDateValidation, AddTimeValidation, AddTextLengthValidation, AddCustomValidation은 인덱스가 아니라 TDataValidation 객체를 직접 반환하므로, 프롬프트와 오류 설정은 조회가 아니라 반환된 참조에서 바로 이어집니다. 연산자 열거형(xlsDvBetween, xlsDvGreaterThan 등)은 XLSX 집합을 그대로 반영하므로, 반환 스타일의 차이를 제외하면 규칙 구축 코드는 두 파사드 사이에서 그대로 이식됩니다. 프롬프트 텍스트 자체도 규칙만큼이나 많은 고민을 들일 가치가 있습니다. 빈 오류 상자로 입력을 거부하는 드롭다운은 사용자에게 IT 부서로 메일을 보내라고 가르치는 셈이지만, 허용된 상태를 이름으로 알려주는 드롭다운은 사용자에게 셀을 고치고 계속 진행하도록 가르칩니다

라이브러리가 대신 흡수해 주는 극성 반전 하나

OOXML 유효성 검사 XML을 직접 읽어본 사람이라면 뒤집힌 showDropDown 속성을 만난 적이 있을 것입니다. ISO/IEC 29500에서는 true 값이 "드롭다운 화살표를 숨긴다"는 뜻이며, 이는 이름이 읽히는 것과는 정반대입니다. HotXLS는 이를 내부적으로 뒤집어 주므로, 유효성 검사 규칙의 ShowDropDown 속성은 이름이 말하는 그대로 동작하며, true는 드롭다운을 보여줍니다. 화상을 입을 수 있는 유일한 경우는 진실의 수준을 섞을 때입니다. 코드에서 속성을 설정하는 한편, 동료가 저장된 XML을 감사하면서 자신에게는 거꾸로 보이는 그 속성을 "고쳐" 버리는 경우가 그렇습니다. 리뷰 도구에서는 속성과 원시 XML 중 어느 쪽이 권위 있는지 결정하고, 그 결정이 사는 곳에 이 반전 사실을 적어두십시오

표는 범위에 스키마와 이름을 부여합니다

Excel 용어로 ListObject라 불리는 워크시트 표는 이름, 타입이 지정된 열, 밴드 스타일, 구조적 참조 지원으로 범위를 감쌉니다. 이는 사용자가 정렬하고 확장하기 시작할 때 생성된 워크북을 완성된 느낌으로 만들어주는 기능입니다. 생성은 파사드 전반에 걸쳐 대칭적이며, AddTable은 이름, 범위, 열 목록을 받습니다:

Delphi에서 HotXLS 워크시트 테이블 다이어그램: 타입화된 열, 구조적 참조, 통합문서 고유 이름, 합계 행 어펜드 함정
HotXLS 테이블은 자신의 범위를 이름, 타입 있는 컬럼, 줄무늬 스타일로 감싸며, 합계 행은 데이터 바로 아래 — 성급한 마지막 행 추가가 떨어지는 자리 — 에 놓입니다
var
  Cols: TStringList;
begin
  Cols := TStringList.Create;
  try
    Cols.CommaText := 'OrderId,Customer,Status,Amount,Owner';
    Sheet.AddTable('Orders', 'A1:E500', Cols);
  finally
    Cols.Free;
  end;
end;

XLSX 쪽에서 결과로 만들어진 표 객체는 StyleName(내장된 TableStyleMedium2 계열과 그 형제들), 줄무늬 토글, 합계 행 플래그를 노출하므로, 하우스 스타일을 적용하는 것은 수작업 서식 처리가 아니라 속성 대입이 됩니다. 레거시 .xls 파일에서는 같은 호출이 BIFF8 표 레코드를 기록하며, 이 파사드는 행, 열, 데이터 필드로 구축된 요약 뷰를 위한 AddPivotTable도 제공합니다. 이는 이전 형식에서의 "표"가 OOXML의 ListObject보다 더 넓은 범위를 다룬다는 사실을 일깨워줍니다. 표 이름은 데이터베이스 뷰에 이름을 붙이듯 지으십시오. 구조적 참조로 Orders[Amount]를 읽는 다운스트림 코드는 위치 기반 코드를 깨뜨리는 열 재정렬을 견뎌냅니다

나중의 정리 작업을 줄여주는 관례가 둘 있습니다. Excel은 표 이름이 워크북 전체에서 고유해야 한다고 요구하므로, 영역마다 시트 하나씩 만들어내는 생성기라면 Orders를 재사용하는 대신 Orders_EMEA 같은 체계가 필요합니다. 중복은 쓰기 시점에는 실패하지 않습니다. 사용자가 파일을 열 때 복구 대화상자로 나타나며, 이는 이를 발견하기에 최악의 장소입니다. 다른 관례는 합계 행에 관한 것입니다. 활성화되면 이는 데이터 범위 바로 아래에 자리하므로, 이후 "마지막으로 사용된 행 더하기 1"로 추가하는 코드는 데이터 뒤가 아니라 합계 밴드 안에 값을 쓰게 됩니다. 표의 범위와 별개로 데이터의 범위를 추적하면 추가된 값이 원하는 위치에 도달합니다

이 세 기능은 데이터 입력용 산출물에서 자연스럽게 결합됩니다. 표는 편집 가능한 영역을 정의하고, 유효성 검사는 사용자가 입력하는 열을 제한하며, 미리 설정된 필터는 수신자가 처음 몇 번 클릭할 수고를 덜어줍니다. 제외된 행이 여전히 파일 안에 있고 호기심 많은 수신자가 이를 드러낼 수 있다는 점을 기억하는 한, 워크북이 열리자마자 중요한 행에 초점을 맞추도록 필터를 이미 적용한 채로 배포하는 것에도 타당한 근거가 있습니다. 이 파이프라인의 상류 절반에 해당하는, 쿼리 결과를 효율적으로 시트에 담는 방법은 Delphi에서 데이터베이스 결과를 Excel로 내보내기에서 다루며, 수식이 유효성 검사를 거친 데이터를 요약하는 워크북은 안정적인 시트 간 참조를 위한 정의된 이름의 도움을 받습니다

유효성 검사, 필터, 표는 단순한 값의 그리드를 배포하는 것과 작은 애플리케이션을 배포하는 것 사이의 차이를 만듭니다. 완전한 규칙, 필터, 표 레퍼런스는 HotXLS Delphi Component 제품 페이지에 있습니다