레거시로는 되는데 SheetMe 로는 못 만들던 것들을 메웠다. 어떤 속성을 올릴지는 취향으로 정하지 않고
--db-props 로 운영 디자인 1,271건 / 컨트롤 164,091개를 세고, 각 컨트롤 .vb 의 필드 선언을 읽어 정했다.
■ 선(Line) — 새로 그은 세로선이 EMR 에서 사라지던 문제
MLine 은 방향을 크기가 아니라 Orientation 으로 정한다(MLine.vb:181-190).
키가 없으면 Horizontal 이라 (0,0)→(Width,0) 을 그리므로, 폭 1·높이 210 으로 세로처럼 만든 선은
디자이너에만 보이고 기록지에서는 1px 점이 된다. 운영 선 32,599개 중 Orientation 보유 12,811개이고
그 중 Vertical 이 12,721개 — 세로선은 예외가 아니라 주류다.
Orientation·BorderWidth·DashStyle 을 인스펙터에 올렸다. 그리고 값만 올리지 않고
레거시 컨트롤이 스스로 지키는 불변식을 함께 옮겼다(LineGeometry):
· 두께 축은 항상 BorderWidth 다 — MLine.OnResize 가 매번 되돌린다. 그래서 선을 끌어
두껍게 만들 수 없다. 이걸 허용하면 '세로처럼 보이지만 EMR 에선 점'인 선을 다시 만들 수 있다.
· 방향 전환은 길이를 보존한 채 축을 바꾼다(레거시 Orientation 세터: Width = Height 후 OnResize).
· 가로로 되돌리면 Orientation 키를 지운다 — <DefaultValue(Horizontal)> 라 레거시 직렬화기도 생략한다.
--db-lines 를 새로 만들어 확인한 결과 운영 선 32,599개에서 경계 모양과 Orientation 불일치는 0건이다.
레거시가 구조적으로 막아 왔다는 뜻이고, 그래서 캔버스 렌더는 건드리지 않았다(P062 바이트 동일).
■ 공통 런타임 속성 — 서명란·인쇄 제외·필수입력·자동높이·재조회
신규 컨트롤은 Props 가 사실상 비어 있어 '전체 속성(0개)'이었고, 고급 편집도 이미 있는 키만 나열한다.
결과적으로 서명이 필요한 동의서나 점수를 합산하는 평가지를 SheetMe 만으로 새로 만들 수 없었다.
타입별 기술자에 다음을 추가했다(기본값은 각 컨트롤 .vb 필드 선언에서 확인한 값이다):
PrintOutPut(기본 True) · PreventEditing · IsRequiredValue(No/Yes) · Visible · AutoHeight ·
ReLoadData 3종 · MPictureBox 의 IsSignature/SignatureIndex.
PrintOutPut 을 True 로 표시하는 것이 중요하다 — 키가 없는 컨트롤이 '인쇄 안 함'으로 보이면
사용자가 껐다 켜는 순간 명시 False 가 기록돼 실제로 인쇄에서 빠진다
(런타임 필터: If Me.Visible = False OrElse mbPrintOutPut = False Then Return False).
■ 감사 권고와 다르게 한 것 3가지 — 근거가 반대였다
1. Score 를 5개 타입에 추가하라는 권고는 따르지 않았다. 실제로 저장값을 읽는 것은
CheckBox·RadioButton 뿐이다(게터가 mdScore 반환). TextBox·MaskedTextBox 의 Score 게터는
저장값을 무시하고 Me.Text 를 숫자로 읽으며, CalcBox 는 세터가 오히려 Text 를 덮어쓴다.
편집기를 붙였으면 사용자는 배점을 걸었다고 믿고 런타임은 무시하는 상태가 된다.
ComboBox 는 Score 가 아니라 ItemScore("/" 구분, 항목 순서와 1:1)를 노출했다.
실측이 먼저 신호를 줬다: ComboBox 의 Score 값 분포가 Text 와 정확히 같았다(-×625, ++×22).
2. Button 에는 PrintOutPut 을 붙이지 않았다 — MButton.vb 에 그 속성 자체가 없다.
3. 라벨의 표시 여부는 소문자 visible 이다. Label.vb:209-217 이 <Browsable(False)> Shadows 로
Control.Visible 을 가리고 직렬화기가 그 그림자를 쓴다. 운영에서도 소문자 58,864건(99.7%)
대 대문자 196건. 대문자로 쓰면 라벨은 그대로 보인다.
■ '속성 추가' 자동완성
큐레이션 밖의 값을 걸려면 키를 손으로 쳐야 했는데, 철자가 틀려도 경고가 없고
그 서식은 배포된 뒤에야 이상하게 동작한다. 타입별 실사용 키 목록(LegacyPropertyCatalog,
--db-props 집계 기반)을 제안 칩으로 깔았다. 이미 있는 키와 인스펙터가 이미 다루는 키는 빼고,
부분 일치로 좁히며 접두 일치를 앞에 둔다. 자유 입력은 그대로 열어 뒀다 — 제안이지 검증이 아니고
사이트가 추가한 키도 있다.
■ 1단계 잔여분: 선택 밖 컨트롤을 Ctrl+드래그하면 엉뚱한 것이 끌리던 문제
Ctrl/Shift 클릭은 선택을 바꾸지 않고 업에서 토글하므로, 그 상태로 드래그가 시작되면
잡은 컨트롤이 아니라 기존 선택분이 끌려갔다(선택이 비어 있으면 아무것도 안 끌렸다).
드래그가 실제로 시작되는 지점에서 잡은 것을 선택에 넣는다 — 기존 다중선택은 유지한다(업 토글과 같은 규칙).
편집 스모크 14건 추가(선 기하 6 · Ctrl+드래그 2 · 자동완성 5 · 라벨 소문자 함정 1).
회귀: 테스트 124/124, 편집 스모크 183건 실패 0,
DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
5.6 KiB
C#
110 lines
5.6 KiB
C#
using SheetMe.Core.Catalog;
|
|
|
|
namespace SheetMe.Designer.ViewModels.Inspector;
|
|
|
|
/// <summary>인스펙터 상단 탭</summary>
|
|
public enum InspectorTab
|
|
{
|
|
/// <summary>화면에 어떻게 보이는가 — 글자·색·정렬·테두리·크기·표시 형식</summary>
|
|
Design,
|
|
|
|
/// <summary>무슨 값이 채워지는가 — 자동 채움 원천, 조회, 초기값, 입력 형식·검증, 저장 대상</summary>
|
|
Data,
|
|
|
|
/// <summary>조작하면 무슨 일이 일어나는가 — 액션 태그, 대상 컨트롤, 단축키</summary>
|
|
Behavior,
|
|
}
|
|
|
|
/// <summary>
|
|
/// 속성 → 인스펙터 탭 분류.
|
|
///
|
|
/// <b>Core 에 두지 않는다.</b> ControlRegistry 는 UI 무관하게 유지하고 표시 계층인 여기서만
|
|
/// 매핑한다(PaletteIconCatalog 와 같은 선례). 탭 구성은 화면 사정이지 서식 데이터의 성질이 아니다.
|
|
///
|
|
/// 분류가 틀리면 탭이 없느니만 못하다 — 사용자가 속성 하나를 찾아 세 탭을 다 뒤지게 된다.
|
|
/// 그래서 키를 하나씩 명시하고, 모르는 키는 <see cref="InspectorTab.Design"/> 이 아니라
|
|
/// 편집기 종류로 추론한다(사이트가 추가한 커스텀 속성 대비).
|
|
/// </summary>
|
|
public static class InspectorTabCatalog
|
|
{
|
|
#region Member Fields
|
|
private static readonly Dictionary<string, InspectorTab> Map = new(StringComparer.Ordinal)
|
|
{
|
|
// ── 디자인: 화면에 어떻게 보이는가 ──
|
|
["Text"] = InspectorTab.Design, // 라벨·버튼·그룹상자 표시 문구
|
|
["TextAlign"] = InspectorTab.Design,
|
|
["ForeColor"] = InspectorTab.Design,
|
|
["BackColor"] = InspectorTab.Design,
|
|
["LineColor"] = InspectorTab.Design,
|
|
["BorderStyle"] = InspectorTab.Design,
|
|
["SizeMode"] = InspectorTab.Design, // 이미지 맞춤 방식
|
|
["Format"] = InspectorTab.Design, // 날짜 표시 형식
|
|
["CustomFormat"] = InspectorTab.Design,
|
|
["Multiline"] = InspectorTab.Design, // 여러 줄로 '보이는가'
|
|
["AutoHeight"] = InspectorTab.Design, // 내용에 맞춰 높이가 늘어나는가
|
|
// 표시 여부는 '어떻게 보이는가'의 일부다. 라벨만 소문자 키를 쓴다(Label.vb 의 Shadows 속성).
|
|
["Visible"] = InspectorTab.Design,
|
|
["visible"] = InspectorTab.Design,
|
|
// 화면이 아니라 종이에 나오는가 — 성격은 표시 여부와 같은 축이다
|
|
["PrintOutPut"] = InspectorTab.Design,
|
|
// 선 3종
|
|
["Orientation"] = InspectorTab.Design,
|
|
["BorderWidth"] = InspectorTab.Design,
|
|
["DashStyle"] = InspectorTab.Design,
|
|
|
|
// ── 데이터: 무슨 값이 채워지는가 ──
|
|
["DataInterfaceTag"] = InspectorTab.Data, // 자동 채움 원천
|
|
["DataTableField"] = InspectorTab.Data, // 저장 대상 필드
|
|
["Query"] = InspectorTab.Data,
|
|
["ExcuteQuery"] = InspectorTab.Data,
|
|
["InitialValue"] = InspectorTab.Data,
|
|
["Mask"] = InspectorTab.Data, // 입력 형식
|
|
["IsRequiredValue"] = InspectorTab.Data, // 검증
|
|
["Items"] = InspectorTab.Data, // 고를 수 있는 값 목록
|
|
["Checked"] = InspectorTab.Data, // 기본 체크 = 초기값
|
|
["Score"] = InspectorTab.Data, // 설문 배점
|
|
["ItemScore"] = InspectorTab.Data, // 콤보 항목별 배점("/" 구분, 항목 순서와 1:1)
|
|
["Formula"] = InspectorTab.Data, // 다른 컨트롤의 Score 를 합산해 이 칸의 값을 만든다
|
|
["IsSignature"] = InspectorTab.Data, // 이 칸에 채워지는 것이 서명 이미지인가
|
|
["SignatureIndex"] = InspectorTab.Data,
|
|
// 저장된 기록을 다시 열 때 값을 어디서 가져오는가 — 저장된 값인가, 지금 조회한 값인가
|
|
["ReLoadDataOnSavedSheet"] = InspectorTab.Data,
|
|
["ReLoadDataOnViewMode"] = InspectorTab.Data,
|
|
["ReLoadDataMsgNoCheck"] = InspectorTab.Data,
|
|
|
|
// ── 동작: 조작하면 무슨 일이 일어나는가 ──
|
|
["DataActionTag"] = InspectorTab.Behavior,
|
|
["DataActionTagControl"] = InspectorTab.Behavior,
|
|
["GetDataActionTagControl"] = InspectorTab.Behavior,
|
|
["KeyboardShortcut"] = InspectorTab.Behavior,
|
|
// 상용구 2종은 이름이 '입력 보조'처럼 보이지만 레거시에서는 조작 훅이다.
|
|
// RwdRsvWrdYon 은 포커스(Enter) 핸들러 부착 게이트라 상용구 창이 이 칸에 연동되고,
|
|
// RwdOrderAutYon 은 상용구를 고른 결과로 OCS 처방을 전송하는 게이트다.
|
|
["RwdRsvWrdYon"] = InspectorTab.Behavior,
|
|
["RwdOrderAutYon"] = InspectorTab.Behavior,
|
|
// 읽기 전용은 '입력을 받는가'라서 동작이다(TextBox 에서는 곧 ReadOnly 다)
|
|
["PreventEditing"] = InspectorTab.Behavior,
|
|
};
|
|
#endregion
|
|
|
|
#region Methods
|
|
/// <summary>속성이 속한 탭</summary>
|
|
public static InspectorTab TabOf(PropertyDef def)
|
|
{
|
|
if (Map.TryGetValue(def.Key, out var tab))
|
|
{
|
|
return tab;
|
|
}
|
|
// 목록에 없는 키(사이트 커스텀 등)는 편집기 종류로 추론한다 — 무조건 디자인으로 보내면
|
|
// 성격이 다른 속성이 디자인 탭에 쌓인다.
|
|
return def.Editor switch
|
|
{
|
|
PropEditorKind.DataActionTag => InspectorTab.Behavior,
|
|
PropEditorKind.DataInterfaceTag or PropEditorKind.SqlQuery or PropEditorKind.Mask
|
|
=> InspectorTab.Data,
|
|
_ => InspectorTab.Design,
|
|
};
|
|
}
|
|
#endregion
|
|
}
|