레거시 MaskedTextBox 는 System.Windows.Forms.MaskedTextBox 를 상속하고 Mask 를 재정의하지 않아 WinForms 기본 마스크 디자이너(프리셋 목록 + 시험 입력)를 그대로 물려받는다. SheetMe 인스펙터는 평범한 텍스트 상자뿐이라 마스크 문법을 외워야 했다. [프리셋] LegacyMaskCatalog — .NET 문화권 기본값(짧은 날짜/전화번호 등) 대신 운영 활성 디자인 전수 census 에서 실제로 쓰이는 마스크를 사용 건수와 함께 제공한다. 기존 서식과 같은 표기를 그대로 고를 수 있어 표기 흔들림이 줄어든다. 날짜/시간/날짜+시간/번호 그룹 + 표준 3종. [미리보기] 두 줄로 보여준다. 빈 값 표시 — 마스크만으로 화면에 어떻게 보이는지(ToPromptDisplay) 시험 입력 — 값을 쳐 보면 어떻게 채워지는지(Apply, 신규) 인스펙터 행에도 '빈 값 표시'를 상시 노출해 편집기를 열지 않아도 확인된다. [Apply 규칙] 구현 중 모호한 경우가 드러나 규칙을 단순하게 확정했다. 1) 리터럴은 항상 그대로 출력되고 입력을 소비하지 않는다. 2) 지시자는 입력에서 자기에게 맞는 첫 문자를 가져오며, 맞지 않는 문자와 공백은 건너뛴다. 값만 치든(20260812) 구분자를 같이 치든(2026-08-12, 2026년 08월 12일) 결과가 같아진다. 공백을 건너뛰는 이유는 '9' 가 공백도 받아들이기 때문이다 — 그대로 두면 구분자 공백이 데이터 자리를 먹어 한 칸씩 밀린다. '9' 의 공백 허용은 빈 자리를 위한 것이고 그건 프롬프트 문자가 이미 표현한다. 한계를 코드에 명시했다: 리터럴이 숫자인 마스크(2\000년 — 운영 460건 중 1건)는 사용자가 리터럴까지 치면 어디까지가 데이터인지 원리적으로 가릴 수 없다. 편집 가능한 자리만 치면 된다. 미리보기용이므로 휴리스틱을 쌓기보다 규칙을 단순하게 두는 편이 낫다고 판단했다. 안내문에서 백슬래시를 글리프로 보여주지 않는다 — 한글 글꼴에서 원화 기호로 렌더되어 오해를 부른다. 검증: 테스트 124/124(마스크 26건 추가 — Apply 자리 채움/구분자 무시/유효성 거부/대소문자 변환자/ 이스케이프/공백 처리, 프리셋 형식·중복·운영 다빈도 포함 검사). edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0. 실제 UI 로 대화상자 열어 프리셋 그룹·건수·두 줄 미리보기 확인. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
275 lines
14 KiB
C#
275 lines
14 KiB
C#
namespace SheetMe.Core.Catalog;
|
|
|
|
/// <summary>
|
|
/// 팔레트 컨트롤 카탈로그 — 팔레트 목록·기본 크기·필드 생성 규칙의 단일 출처.
|
|
/// 속성 편집 스키마(PropertyDef)는 P3(인스펙터)에서 확장한다.
|
|
/// </summary>
|
|
public static class ControlRegistry
|
|
{
|
|
#region Member Fields
|
|
private static readonly List<ControlDescriptor> all = new()
|
|
{
|
|
new()
|
|
{
|
|
Type = "Label", DisplayName = "라벨", DefaultW = 120, DefaultH = 22,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Text", Label = "텍스트", Editor = PropEditorKind.MultilineText },
|
|
// ContentAlignment 9값 전체 — Bottom* 3값이 빠져 있으면 그 라벨을 선택하는 것만으로 속성이 삭제된다(운영 249건)
|
|
new() { Key = "TextAlign", Label = "정렬", Editor = PropEditorKind.Choice, Default = "TopLeft",
|
|
Choices = new[] { "TopLeft", "TopCenter", "TopRight", "MiddleLeft", "MiddleCenter", "MiddleRight", "BottomLeft", "BottomCenter", "BottomRight" } },
|
|
new() { Key = "ForeColor", Label = "글자색", Editor = PropEditorKind.Color },
|
|
new() { Key = "BackColor", Label = "배경색", Editor = PropEditorKind.Color },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "TextBox", DisplayName = "텍스트박스", DefaultW = 160, DefaultH = 22, CreatesField = true,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Text", Label = "텍스트", Editor = PropEditorKind.Text },
|
|
new() { Key = "Multiline", Label = "여러 줄", Editor = PropEditorKind.Toggle },
|
|
new() { Key = "TextAlign", Label = "정렬", Editor = PropEditorKind.Choice, Default = "Left", Choices = new[] { "Left", "Center", "Right" } },
|
|
new() { Key = "IsRequiredValue", Label = "필수 입력", Editor = PropEditorKind.Choice, Choices = new[] { "No", "Yes" } },
|
|
new() { Key = "InitialValue", Label = "초기값", Editor = PropEditorKind.Text },
|
|
new() { Key = "DataInterfaceTag", Label = "데이터 태그", Editor = PropEditorKind.DataInterfaceTag },
|
|
new() { Key = "DataActionTag", Label = "액션 태그", Editor = PropEditorKind.DataActionTag },
|
|
new() { Key = "DataActionTagControl", Label = "액션 대상 컨트롤", Editor = PropEditorKind.Text },
|
|
// 레거시 TextBox.vb:63 mbRwdRsvWrdYon = True — 2022-09-28 추가된 속성이라 그 이전 서식엔 키가 없다.
|
|
// 기본값을 알려주지 않으면 항상 '꺼짐'으로 보이고, 사용자가 껐다 켜면 명시 False 가 기록돼 기능이 죽는다.
|
|
new() { Key = "RwdRsvWrdYon", Label = "상용구 사용", Editor = PropEditorKind.Toggle, Default = "True" },
|
|
new() { Key = "RwdOrderAutYon", Label = "처방연동 상용구", Editor = PropEditorKind.Toggle },
|
|
new() { Key = "BackColor", Label = "배경색", Editor = PropEditorKind.Color },
|
|
new() { Key = "BorderStyle", Label = "테두리", Editor = PropEditorKind.Choice, Default = "Fixed3D", Choices = new[] { "None", "FixedSingle", "Fixed3D" } },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "MaskedTextBox", DisplayName = "마스크입력", DefaultW = 160, DefaultH = 22, CreatesField = true,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Mask", Label = "마스크", Editor = PropEditorKind.Mask },
|
|
new() { Key = "IsRequiredValue", Label = "필수 입력", Editor = PropEditorKind.Choice, Choices = new[] { "No", "Yes" } },
|
|
new() { Key = "DataInterfaceTag", Label = "데이터 태그", Editor = PropEditorKind.DataInterfaceTag },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "CheckBox", DisplayName = "체크박스", DefaultW = 110, DefaultH = 20, CreatesField = true, DefaultDataType = "bool",
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Text", Label = "텍스트", Editor = PropEditorKind.Text },
|
|
new() { Key = "Checked", Label = "기본 체크", Editor = PropEditorKind.Toggle },
|
|
new() { Key = "Score", Label = "점수", Editor = PropEditorKind.Number },
|
|
new() { Key = "IsRequiredValue", Label = "필수 입력", Editor = PropEditorKind.Choice, Choices = new[] { "No", "Yes" } },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "RadioButton", DisplayName = "라디오버튼", DefaultW = 110, DefaultH = 20, CreatesField = true,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Text", Label = "텍스트", Editor = PropEditorKind.Text },
|
|
new() { Key = "Checked", Label = "기본 선택", Editor = PropEditorKind.Toggle },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "ComboBox", DisplayName = "콤보박스", DefaultW = 140, DefaultH = 22, CreatesField = true,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Items", Label = "선택지(줄바꿈 구분)", Editor = PropEditorKind.StringList },
|
|
new() { Key = "IsRequiredValue", Label = "필수 입력", Editor = PropEditorKind.Choice, Choices = new[] { "No", "Yes" } },
|
|
new() { Key = "DataTableField", Label = "데이터 필드", Editor = PropEditorKind.Text },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "ListBox", DisplayName = "리스트박스", DefaultW = 140, DefaultH = 90, CreatesField = true,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Items", Label = "항목(줄바꿈 구분)", Editor = PropEditorKind.StringList },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "CheckList", DisplayName = "체크리스트", DefaultW = 160, DefaultH = 90, CreatesField = true,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Items", Label = "항목(줄바꿈 구분)", Editor = PropEditorKind.StringList },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "DateTimePicker", DisplayName = "날짜선택", DefaultW = 140, DefaultH = 22, CreatesField = true, DefaultDataType = "datetime",
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Format", Label = "형식", Editor = PropEditorKind.Choice, Default = "Long", Choices = new[] { "Long", "Short", "Time", "Custom" } },
|
|
new() { Key = "CustomFormat", Label = "사용자 형식", Editor = PropEditorKind.Text },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "Panel", DisplayName = "패널", DefaultW = 240, DefaultH = 140, IsContainer = true,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "BackColor", Label = "배경색", Editor = PropEditorKind.Color },
|
|
new() { Key = "BorderStyle", Label = "테두리", Editor = PropEditorKind.Choice, Default = "None", Choices = new[] { "None", "FixedSingle", "Fixed3D" } },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "GroupBox", DisplayName = "그룹박스", DefaultW = 240, DefaultH = 140, IsContainer = true,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Text", Label = "제목", Editor = PropEditorKind.Text },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "Line", DisplayName = "선", DefaultW = 200, DefaultH = 1,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "LineColor", Label = "선 색", Editor = PropEditorKind.Color },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "PictureBox", DisplayName = "이미지", DefaultW = 120, DefaultH = 90,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "DataInterfaceTag", Label = "데이터 태그", Editor = PropEditorKind.DataInterfaceTag },
|
|
new() { Key = "SizeMode", Label = "크기 모드", Editor = PropEditorKind.Choice, Default = "Normal", Choices = new[] { "Normal", "StretchImage", "AutoSize", "CenterImage", "Zoom" } },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "CalcBox", DisplayName = "계산박스", DefaultW = 100, DefaultH = 22, CreatesField = true, DefaultDataType = "decimal",
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Formula", Label = "수식", Editor = PropEditorKind.MultilineText },
|
|
new() { Key = "Text", Label = "텍스트", Editor = PropEditorKind.Text },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "Button", DisplayName = "버튼", DefaultW = 90, DefaultH = 26,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Text", Label = "텍스트", Editor = PropEditorKind.Text },
|
|
new() { Key = "DataActionTag", Label = "액션 태그", Editor = PropEditorKind.DataActionTag },
|
|
new() { Key = "DataActionTagControl", Label = "액션 대상 컨트롤", Editor = PropEditorKind.Text },
|
|
new() { Key = "GetDataActionTagControl", Label = "입력 파라미터 컨트롤", Editor = PropEditorKind.Text },
|
|
new() { Key = "KeyboardShortcut", Label = "단축키", Editor = PropEditorKind.Text },
|
|
new() { Key = "BackColor", Label = "배경색", Editor = PropEditorKind.Color },
|
|
},
|
|
},
|
|
new()
|
|
{
|
|
Type = "DataTable", DisplayName = "데이터소스", DefaultW = 34, DefaultH = 34,
|
|
Properties = new PropertyDef[]
|
|
{
|
|
new() { Key = "Query", Label = "쿼리(SQL)", Editor = PropEditorKind.SqlQuery },
|
|
new() { Key = "DataInterfaceTag", Label = "데이터 태그", Editor = PropEditorKind.DataInterfaceTag },
|
|
// 레거시 MDataTable.vb:15 mbExcuteQuery = True — 미기록 서식에서 '꺼짐'으로 보이면 안 된다
|
|
new() { Key = "ExcuteQuery", Label = "쿼리 실행", Editor = PropEditorKind.Toggle, Default = "True" },
|
|
},
|
|
},
|
|
};
|
|
|
|
private static readonly Dictionary<string, ControlDescriptor> byType =
|
|
all.ToDictionary(d => d.Type, StringComparer.Ordinal);
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>팔레트에 노출할 전체 컨트롤 목록</summary>
|
|
public static IReadOnlyList<ControlDescriptor> All => all;
|
|
#endregion
|
|
|
|
#region Methods
|
|
/// <summary>타입명으로 기술자 조회 — 미지원 타입(Placeholder 포함)은 null</summary>
|
|
public static ControlDescriptor? Find(string type)
|
|
=> byType.TryGetValue(type, out var descriptor) ? descriptor : null;
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>팔레트 컨트롤 1종의 기술자</summary>
|
|
public sealed class ControlDescriptor
|
|
{
|
|
/// <summary>중립 타입명(LegacyTypeCatalog 와 공유하는 키)</summary>
|
|
public string Type { get; init; } = string.Empty;
|
|
|
|
/// <summary>팔레트 표시명(한글)</summary>
|
|
public string DisplayName { get; init; } = string.Empty;
|
|
|
|
/// <summary>드롭 시 기본 너비(px)</summary>
|
|
public double DefaultW { get; init; }
|
|
|
|
/// <summary>드롭 시 기본 높이(px)</summary>
|
|
public double DefaultH { get; init; }
|
|
|
|
/// <summary>컨테이너 여부(Panel/GroupBox — 자식 배치 가능)</summary>
|
|
public bool IsContainer { get; init; }
|
|
|
|
/// <summary>배치 시 데이터 필드를 만드는 입력 컨트롤인지</summary>
|
|
public bool CreatesField { get; init; }
|
|
|
|
/// <summary>필드 기본 데이터 타입</summary>
|
|
public string DefaultDataType { get; init; } = "string";
|
|
|
|
/// <summary>인스펙터 속성 편집 스키마(타입 전용 — 공통 X/Y/W/H/Id/폰트는 인스펙터가 별도 제공)</summary>
|
|
public IReadOnlyList<PropertyDef> Properties { get; init; } = Array.Empty<PropertyDef>();
|
|
}
|
|
|
|
/// <summary>속성 편집기 종류</summary>
|
|
public enum PropEditorKind
|
|
{
|
|
/// <summary>문자열</summary>
|
|
Text,
|
|
/// <summary>여러 줄 문자열</summary>
|
|
MultilineText,
|
|
/// <summary>숫자</summary>
|
|
Number,
|
|
/// <summary>참/거짓</summary>
|
|
Toggle,
|
|
/// <summary>선택지</summary>
|
|
Choice,
|
|
/// <summary>색(레거시 invariant 문자열)</summary>
|
|
Color,
|
|
/// <summary>문자열 목록(콤보 items 등 — 줄바꿈 구분 편집)</summary>
|
|
StringList,
|
|
/// <summary>데이터 태그 피커(bzDataInterface 메서드 — 자동 채움 원천)</summary>
|
|
DataInterfaceTag,
|
|
/// <summary>액션 태그 피커(EN_DataActionTyp — 더블클릭/버튼 액션)</summary>
|
|
DataActionTag,
|
|
/// <summary>SQL 쿼리 — 전용 편집기 창(치환 변수 삽입 지원)</summary>
|
|
SqlQuery,
|
|
|
|
/// <summary>입력 마스크 — 프리셋 목록 + 미리보기 대화상자</summary>
|
|
Mask,
|
|
}
|
|
|
|
/// <summary>인스펙터 속성 정의 1건 — PropBag 의 레거시 Property 를 편집</summary>
|
|
public sealed class PropertyDef
|
|
{
|
|
/// <summary>PropBag 키(레거시 Property 이름)</summary>
|
|
public string Key { get; init; } = string.Empty;
|
|
|
|
/// <summary>표시 라벨(한글)</summary>
|
|
public string Label { get; init; } = string.Empty;
|
|
|
|
/// <summary>편집기 종류</summary>
|
|
public PropEditorKind Editor { get; init; }
|
|
|
|
/// <summary>Choice 편집기의 선택지</summary>
|
|
public string[]? Choices { get; init; }
|
|
|
|
/// <summary>
|
|
/// PropBag 에 키가 없을 때(미설정) 인스펙터에 표시할 값 — <b>표시 전용</b>이다.
|
|
/// 사용자가 건드리지 않으면 XML 에 기록되지 않는다(왕복 무손실 불변식 유지).
|
|
/// 레거시 필드 초기값이 True 인 속성이나 WinForms DefaultValueAttribute 로 미기록되는 속성처럼,
|
|
/// "키 부재"와 "명시적 False/빈값"이 UI 상 구분되지 않아 오조작을 부르는 경우에만 지정한다.
|
|
/// </summary>
|
|
public string? Default { get; init; }
|
|
}
|