diff --git a/src/SheetMe.Core/Catalog/LegacyQueryVariableCatalog.cs b/src/SheetMe.Core/Catalog/LegacyQueryVariableCatalog.cs index 9895b1c..80d1504 100644 --- a/src/SheetMe.Core/Catalog/LegacyQueryVariableCatalog.cs +++ b/src/SheetMe.Core/Catalog/LegacyQueryVariableCatalog.cs @@ -6,7 +6,8 @@ namespace SheetMe.Core.Catalog; /// 이 true 면 컬럼명을 채워야 완성된다(DataRow 접근형). /// 삽입 후 사용자가 컬럼명 자리를 고쳐야 하므로 편집기가 그 부분을 선택해 준다. /// -public sealed record QueryVariable(string Group, string Token, string Description, bool NeedsColumn = false) +public sealed record QueryVariable( + string Group, string Token, string Description, bool NeedsColumn = false, string TypeName = "String") { /// /// 목록에 띄우는 짧은 형태 — 클래스 전체 이름을 뺀 나머지. @@ -15,6 +16,18 @@ public sealed record QueryVariable(string Group, string Token, string Descriptio /// 정작 다른 부분(속성명)이 오른쪽 끝에서 잘려 무엇이 무엇인지 구분되지 않는다. /// 전체 문자열은 툴팁과 삽입 결과로 확인할 수 있다. /// + /// + /// 상단 칩으로 거르는 큰 갈래 — 그룹은 일곱이라 칩으로 늘어놓으면 그것대로 목록이 된다. + /// 갈래는 '값이 어디서 오는가'로 나눈다. + /// + public string Bucket => Group switch + { + "환자" or "외부연계" or "환자 상세(컬럼 지정)" => "환자", + "서식" or "서식 상세(컬럼 지정)" => "서식", + "작업자" => "작업자", + _ => "컨트롤", + }; + public string ShortForm { get @@ -83,14 +96,14 @@ public static class LegacyQueryVariableCatalog // ── 환자(bzPatientInfo) — 스칼라 ── new("환자", $"<<{Patient}.ChtNum>>", "차트번호(등록번호)"), new("환자", $"<<{Patient}.ComNum>>", "내원번호"), - new("환자", $"<<{Patient}.ComNum_Refer>>", "참조 내원번호(없으면 0)"), + new("환자", $"<<{Patient}.ComNum_Refer>>", "참조 내원번호(없으면 0)", TypeName: "Int32"), new("환자", $"<<{Patient}.ComCvtCom>>", "전환 내원번호(외래↔입원 연결)"), new("환자", $"<<{Patient}.PatTyp>>", "환자구분 O 외래 / I 입원"), new("환자", $"<<{Patient}.PatTyp_Refer>>", "참조 내원의 환자구분"), new("환자", $"<<{Patient}.AdpDtm>>", "적용일시 yyyyMMddHHmm"), new("환자", $"<<{Patient}.OrderAdpDtm>>", "처방 적용일시"), new("환자", $"<<{Patient}.Age>>", "나이(산출 불가 시 ?)"), - new("환자", $"<<{Patient}.AgeInMonth>>", "개월수"), + new("환자", $"<<{Patient}.AgeInMonth>>", "개월수", TypeName: "Int32"), new("환자", $"<<{Patient}.Sex>>", "성별"), new("환자", $"<<{Patient}.PatMblPhn>>", "휴대전화번호"), new("환자", $"<<{Patient}.ComDayCar>>", "낮병동 여부 Y/N"), @@ -101,11 +114,11 @@ public static class LegacyQueryVariableCatalog // 외부연계(OEM) 슬롯 — E_OemInf 의 같은 이름 컬럼으로 넘어간다. // 슬롯별 의미는 연계 프로그램마다 달라 설명을 단정하지 않는다(OemNum1 만 처방키 관행). new("외부연계", $"<<{Patient}.OemTableNam>>", "원본 테이블명"), - new("외부연계", $"<<{Patient}.OemNum1>>", "숫자키1(주로 처방키)"), - new("외부연계", $"<<{Patient}.OemNum2>>", "숫자키2"), - new("외부연계", $"<<{Patient}.OemNum3>>", "숫자키3"), - new("외부연계", $"<<{Patient}.OemNum4>>", "숫자키4"), - new("외부연계", $"<<{Patient}.OemNum5>>", "숫자키5"), + new("외부연계", $"<<{Patient}.OemNum1>>", "숫자키1(주로 처방키)", TypeName: "Int32"), + new("외부연계", $"<<{Patient}.OemNum2>>", "숫자키2", TypeName: "Int32"), + new("외부연계", $"<<{Patient}.OemNum3>>", "숫자키3", TypeName: "Int32"), + new("외부연계", $"<<{Patient}.OemNum4>>", "숫자키4", TypeName: "Int32"), + new("외부연계", $"<<{Patient}.OemNum5>>", "숫자키5", TypeName: "Int32"), new("외부연계", $"<<{Patient}.OemVar1>>", "문자값1"), new("외부연계", $"<<{Patient}.OemVar2>>", "문자값2"), new("외부연계", $"<<{Patient}.OemVar3>>", "문자값3"), @@ -114,30 +127,30 @@ public static class LegacyQueryVariableCatalog // ── 환자 상세(DataRow) — 컬럼명을 채워야 완성된다 ── // item 은 반드시 소문자다(정규식이 대소문자를 구분한다). - new("환자 상세(컬럼 지정)", $"<<{Patient}.PatInfDR.item(\"컬럼명\")>>", "환자 기본정보 P_PatInf 의 컬럼", NeedsColumn: true), - new("환자 상세(컬럼 지정)", $"<<{Patient}.ComInfDR.item(\"컬럼명\")>>", "내원정보 P_ComInf 의 컬럼", NeedsColumn: true), - new("환자 상세(컬럼 지정)", $"<<{Patient}.ComInfDR_Refer.item(\"컬럼명\")>>", "참조 내원의 P_ComInf 컬럼", NeedsColumn: true), - new("환자 상세(컬럼 지정)", $"<<{Patient}.CodInfDR.item(\"컬럼명\")>>", "진료과·담당의 P_CodInf 의 컬럼", NeedsColumn: true), - new("환자 상세(컬럼 지정)", $"<<{Patient}.CoiInfDR.item(\"컬럼명\")>>", "보험 자격정보 P_CoiInf 의 컬럼", NeedsColumn: true), - new("환자 상세(컬럼 지정)", $"<<{Patient}.CowInfDR.item(\"컬럼명\")>>", "입원 병동·병실 P_CowInf 의 컬럼", NeedsColumn: true), + new("환자 상세(컬럼 지정)", $"<<{Patient}.PatInfDR.item(\"컬럼명\")>>", "환자 기본정보 P_PatInf 의 컬럼", NeedsColumn: true, TypeName: "DataRow"), + new("환자 상세(컬럼 지정)", $"<<{Patient}.ComInfDR.item(\"컬럼명\")>>", "내원정보 P_ComInf 의 컬럼", NeedsColumn: true, TypeName: "DataRow"), + new("환자 상세(컬럼 지정)", $"<<{Patient}.ComInfDR_Refer.item(\"컬럼명\")>>", "참조 내원의 P_ComInf 컬럼", NeedsColumn: true, TypeName: "DataRow"), + new("환자 상세(컬럼 지정)", $"<<{Patient}.CodInfDR.item(\"컬럼명\")>>", "진료과·담당의 P_CodInf 의 컬럼", NeedsColumn: true, TypeName: "DataRow"), + new("환자 상세(컬럼 지정)", $"<<{Patient}.CoiInfDR.item(\"컬럼명\")>>", "보험 자격정보 P_CoiInf 의 컬럼", NeedsColumn: true, TypeName: "DataRow"), + new("환자 상세(컬럼 지정)", $"<<{Patient}.CowInfDR.item(\"컬럼명\")>>", "입원 병동·병실 P_CowInf 의 컬럼", NeedsColumn: true, TypeName: "DataRow"), // ── 서식(bzSheetInfo) — 스칼라 ── new("서식", $"<<{Sheet}.ShtCod>>", "서식 코드"), new("서식", $"<<{Sheet}.ShtNam>>", "서식명"), - new("서식", $"<<{Sheet}.EmrKey>>", "EMR 키(신규 작성 시 0)"), + new("서식", $"<<{Sheet}.EmrKey>>", "EMR 키(신규 작성 시 0)", TypeName: "Int32"), new("서식", $"<<{Sheet}.AdpDtm>>", "작성일시"), - new("서식", $"<<{Sheet}.TemKey>>", "템플릿 키"), + new("서식", $"<<{Sheet}.TemKey>>", "템플릿 키", TypeName: "Int32"), new("서식", $"<<{Sheet}.PatTyp>>", "서식 기준 환자구분"), new("서식", $"<<{Sheet}.EmrGbn>>", "EMR 구분"), new("서식", $"<<{Sheet}.OdrNum>>", "처방 번호"), new("서식", $"<<{Sheet}.OdrSeq>>", "처방 일련번호"), new("서식", $"<<{Sheet}.LocalPath>>", "로컬 경로"), - new("서식", $"<<{Sheet}.PrintCstKey>>", "출력 대상 키"), + new("서식", $"<<{Sheet}.PrintCstKey>>", "출력 대상 키", TypeName: "Int32"), new("서식", $"<<{Sheet}.PrintCsdStrDtm>>", "출력 기간 시작일시"), new("서식", $"<<{Sheet}.PrintCsdEndDtm>>", "출력 기간 종료일시"), - new("서식 상세(컬럼 지정)", $"<<{Sheet}.ShtMst.item(\"컬럼명\")>>", "서식 마스터 E_ShtMst 의 컬럼", NeedsColumn: true), - new("서식 상세(컬럼 지정)", $"<<{Sheet}.SdgMst.item(\"컬럼명\")>>", "디자인 마스터 E_SdgMst 의 컬럼", NeedsColumn: true), + new("서식 상세(컬럼 지정)", $"<<{Sheet}.ShtMst.item(\"컬럼명\")>>", "서식 마스터 E_ShtMst 의 컬럼", NeedsColumn: true, TypeName: "DataRow"), + new("서식 상세(컬럼 지정)", $"<<{Sheet}.SdgMst.item(\"컬럼명\")>>", "디자인 마스터 E_SdgMst 의 컬럼", NeedsColumn: true, TypeName: "DataRow"), // ── 작업자(bzWorkInfo) — 스칼라 ── new("작업자", $"<<{Work}.WrkUid>>", "작성자 ID"), @@ -148,6 +161,28 @@ public static class LegacyQueryVariableCatalog /// 런타임이 StartsWith 로 비교하는 접두어 3종 — 검증·진단용 public static readonly IReadOnlyList Prefixes = new[] { Patient, Sheet, Work }; + /// 컨트롤 값 치환 그룹 이름 + public const string ControlGroup = "이 서식의 컨트롤"; + + /// + /// 같은 서식의 컨트롤 값을 넣는 치환 — 《컨트롤명》. + /// + /// <<…>> 와는 다른 문법이고 다른 단계다. clsMDataTable.ConvertQuery 는 + /// <<…>> 를 모두 치환한 뒤 로 다시 쪼개 + /// poAllControlOnSheet(이름).GETVALUE 를 박는다(clsMDataTable.vb:78-94). + /// + /// 디자인 시점에는 무조건 "0" 으로 치환된다(:86, 컨트롤 목록이 없을 때). + /// 그래서 레거시 편집기에서 실행해 보면 통과하는데 운영에서는 다른 값이 들어간다 — + /// 검증 실행 결과를 그대로 믿으면 안 되는 지점이라 화면에 적어 둔다. + /// + public static IReadOnlyList ControlVariables(IEnumerable controlNames) + => controlNames + .Where(n => n.Length > 0) + .Distinct(StringComparer.Ordinal) + .OrderBy(n => n, StringComparer.OrdinalIgnoreCase) + .Select(n => new QueryVariable(ControlGroup, $"《{n}》", n, TypeName: "컨트롤 값")) + .ToList(); + /// /// clsMDataTable.vb:113 의 정규식을 그대로 옮긴 것 — 클래스명 뒤 경로가 이 형태여야 값이 나온다. /// item 이 소문자여야 하는 것까지 포함해 원문 그대로 쓴다(대소문자 무시 옵션을 주면 안 된다). @@ -166,6 +201,12 @@ public static class LegacyQueryVariableCatalog /// public static bool IsResolvable(string token) { + // 《컨트롤명》 은 다른 문법이다 — 이름이 비지만 않으면 형태로는 유효하다. + // 그 이름의 컨트롤이 실제로 있는지는 편집기가 문서를 보고 따로 검사한다. + if (token.StartsWith('《') && token.EndsWith('》')) + { + return token.Length > 2; + } if (!token.StartsWith("<<", StringComparison.Ordinal) || !token.EndsWith(">>", StringComparison.Ordinal)) { return false; diff --git a/src/SheetMe.Core/Catalog/SqlFormatter.cs b/src/SheetMe.Core/Catalog/SqlFormatter.cs new file mode 100644 index 0000000..e4922c9 --- /dev/null +++ b/src/SheetMe.Core/Catalog/SqlFormatter.cs @@ -0,0 +1,147 @@ +using System.Text; + +namespace SheetMe.Core.Catalog; + +/// +/// SQL 보기 좋게 정리 — 예약어 대문자, 주요 절 앞에서 줄바꿈, 괄호 깊이만큼 들여쓰기. +/// +/// 완전한 포매터가 아니다. 목적은 남이 쓴 한 줄짜리 쿼리를 읽을 수 있게 펼치는 것이지 +/// 표준 서식을 강제하는 것이 아니다. 그래서 문자열·주석·치환 변수는 손대지 않는다 — +/// 그 안의 내용을 건드리면 뜻이 바뀐다(특히 치환 변수는 대소문자가 맞아야 런타임이 찾는다). +/// +public static class SqlFormatter +{ + #region Member Fields + /// 앞에서 줄을 바꾸는 절 + private static readonly HashSet BreakBefore = new(StringComparer.OrdinalIgnoreCase) + { + "SELECT", "FROM", "WHERE", "GROUP", "HAVING", "ORDER", "UNION", "MINUS", "INTERSECT", + "JOIN", "INNER", "LEFT", "RIGHT", "FULL", "CROSS", "AND", "OR", "SET", "VALUES", "INTO", + }; + #endregion + + #region Methods + /// + /// 정리한 결과를 돌려준다. 원문이 비면 그대로 돌려준다. + /// + public static string Format(string sql) + { + if (string.IsNullOrWhiteSpace(sql)) + { + return sql; + } + + var tokens = SqlTokenizer.Tokenize(sql); + var builder = new StringBuilder(sql.Length + 64); + var depth = 0; + var atLineStart = true; + + void NewLine() + { + // 이미 줄 시작이면 빈 줄을 만들지 않는다 + if (atLineStart) + { + return; + } + builder.Append('\n'); + atLineStart = true; + } + + void Indent() + { + if (atLineStart) + { + builder.Append(' ', Math.Max(0, depth) * 4); + atLineStart = false; + } + } + + void Write(string text) + { + Indent(); + builder.Append(text); + } + + foreach (var token in tokens) + { + var piece = sql.Substring(token.Start, token.Length); + + switch (token.Kind) + { + case SqlTokenKind.Keyword: + if (BreakBefore.Contains(piece)) + { + NewLine(); + } + // 예약어만 대문자로 — 식별자를 건드리면 대소문자를 가리는 DB 에서 깨진다 + Write(piece.ToUpperInvariant()); + break; + + case SqlTokenKind.Comment: + NewLine(); + Write(piece); + NewLine(); + break; + + // 문자열·치환 변수는 원문 그대로. 안을 건드리면 뜻이 바뀐다. + case SqlTokenKind.Text: + case SqlTokenKind.Variable: + case SqlTokenKind.Broken: + case SqlTokenKind.Function: + case SqlTokenKind.Number: + Write(token.Kind == SqlTokenKind.Function ? piece.ToUpperInvariant() : piece); + break; + + default: + WritePlain(piece); + break; + } + } + + void WritePlain(string piece) + { + foreach (var ch in piece) + { + switch (ch) + { + case '(': + Write("("); + depth++; + break; + case ')': + depth--; + Write(")"); + break; + case ',': + Write(","); + NewLine(); + break; + case '\n': + NewLine(); + break; + case ' ': + case '\t': + case '\r': + // 줄 시작의 공백은 들여쓰기가 대신한다 + if (!atLineStart) + { + builder.Append(' '); + } + break; + default: + Write(ch.ToString()); + break; + } + } + } + + // 줄 끝 공백을 걷어낸다 — 정리했는데 보이지 않는 찌꺼기가 남으면 diff 가 지저분해진다 + var lines = builder.ToString().Split('\n'); + for (var i = 0; i < lines.Length; i++) + { + lines[i] = lines[i].TrimEnd(); + } + return string.Join('\n', lines).Trim('\n'); + } + #endregion +} diff --git a/src/SheetMe.Core/Catalog/SqlTokenizer.cs b/src/SheetMe.Core/Catalog/SqlTokenizer.cs index 5133f2a..2647b9e 100644 --- a/src/SheetMe.Core/Catalog/SqlTokenizer.cs +++ b/src/SheetMe.Core/Catalog/SqlTokenizer.cs @@ -115,6 +115,22 @@ public static class SqlTokenizer continue; } + // 컨트롤 값 치환 《컨트롤명》 — <<>> 와 다른 문법이고 런타임의 다른 단계에서 처리된다 + if (ch == '《') + { + var close = text.IndexOf('》', i + 1); + FlushPlain(i); + if (close < 0) + { + tokens.Add(new SqlToken(i, text.Length - i, SqlTokenKind.Broken)); + return tokens; + } + tokens.Add(new SqlToken(i, close + 1 - i, SqlTokenKind.Variable)); + i = close + 1; + plainStart = i; + continue; + } + // 한 줄 주석 if (ch == '-' && i + 1 < text.Length && text[i + 1] == '-') { @@ -227,13 +243,20 @@ public static class SqlTokenizer var cursor = start; while (cursor < end) { - var open = text.IndexOf("<<", cursor, StringComparison.Ordinal); + // 두 문법 중 먼저 나오는 것을 떼어 낸다 + var openAngle = text.IndexOf("<<", cursor, StringComparison.Ordinal); + var openBracket = text.IndexOf('《', cursor); + var useBracket = openBracket >= 0 && (openAngle < 0 || openBracket < openAngle); + var open = useBracket ? openBracket : openAngle; if (open < 0 || open >= end) { break; } - var close = text.IndexOf(">>", open + 2, StringComparison.Ordinal); - if (close < 0 || close + 2 > end) + var close = useBracket + ? text.IndexOf('》', open + 1) + : text.IndexOf(">>", open + 2, StringComparison.Ordinal); + var closeLength = useBracket ? 1 : 2; + if (close < 0 || close + closeLength > end) { break; } @@ -241,8 +264,8 @@ public static class SqlTokenizer { tokens.Add(new SqlToken(cursor, open - cursor, textKind)); } - tokens.Add(new SqlToken(open, close + 2 - open, SqlTokenKind.Variable)); - cursor = close + 2; + tokens.Add(new SqlToken(open, close + closeLength - open, SqlTokenKind.Variable)); + cursor = close + closeLength; } if (cursor < end) { diff --git a/src/SheetMe.Designer/Controls/QueryGroupExpandedConverter.cs b/src/SheetMe.Designer/Controls/QueryGroupExpandedConverter.cs new file mode 100644 index 0000000..3f089af --- /dev/null +++ b/src/SheetMe.Designer/Controls/QueryGroupExpandedConverter.cs @@ -0,0 +1,24 @@ +using System.Globalization; +using System.Windows.Data; +using SheetMe.Core.Catalog; + +namespace SheetMe.Designer.Controls; + +/// +/// 치환 변수 그룹을 처음에 펼쳐 둘지 — 그룹 이름으로 판단한다. +/// +/// 일곱 그룹을 다 펴 두면 목록이 길어져 원하는 그룹이 화면 밖으로 밀리고, +/// 다 접어 두면 무엇이 있는지 한 번은 눌러 봐야 안다. +/// 가장 많이 쓰는 '환자'와, 이 서식에서만 유효해서 존재 자체가 정보인 '컨트롤'만 펴 둔다. +/// +public sealed class QueryGroupExpandedConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var name = value as string ?? string.Empty; + return name is "환자" || name == LegacyQueryVariableCatalog.ControlGroup; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => throw new NotSupportedException(); +} diff --git a/src/SheetMe.Designer/Diagnostics/DialogShots.cs b/src/SheetMe.Designer/Diagnostics/DialogShots.cs index 22c0b89..a679aed 100644 --- a/src/SheetMe.Designer/Diagnostics/DialogShots.cs +++ b/src/SheetMe.Designer/Diagnostics/DialogShots.cs @@ -113,7 +113,7 @@ public static class DialogShots { ("01-sheet-open", () => new Views.SheetOpenDialogView(_ => SampleSheets())), ("02-sheet-history", () => new Views.SheetHistoryDialogView("S999", "표본 서식", SampleVersions())), - ("03-query-editor", () => new Views.QueryEditorWindow("데이터소스", + ("03-query-editor", () => NewQueryEditor(designer, "SELECT ChtNum, PatNam FROM P_PatMst\nWHERE ChtNum = <>")), // 실제 카탈로그(384종)와 실제 제안 순위로 찍는다 — 표본 몇 개로는 분류·건수가 안 보인다 ("04-tag-picker", () => new Views.TagPickerDialogView( @@ -311,6 +311,10 @@ public static class DialogShots }; /// 폰트 관리자·미리보기용 표본 문서 — 컨트롤이 있어야 목록이 비지 않는다 + /// 쿼리 편집기 — 이 서식의 컨트롤을 《컨트롤명》 후보로 넘겨 그룹이 실제로 차는지 본다 + private static Window NewQueryEditor(DesignerViewModel designer, string sql) + => new Views.QueryEditorWindow("데이터소스", sql, controlNames: designer.ControlNames()); + private static DesignerViewModel SampleDesigner() { var document = new FormDocument { FormId = "S999", Title = "표본 서식" }; diff --git a/src/SheetMe.Designer/Themes/Tokens.Dark.xaml b/src/SheetMe.Designer/Themes/Tokens.Dark.xaml index e80731d..09ecad2 100644 --- a/src/SheetMe.Designer/Themes/Tokens.Dark.xaml +++ b/src/SheetMe.Designer/Themes/Tokens.Dark.xaml @@ -47,6 +47,7 @@ 오류는 B.CloseHover 와 같은 값이라 다크 팔레트에 새 색이 늘지 않는다. --> + diff --git a/src/SheetMe.Designer/Themes/Tokens.Light.xaml b/src/SheetMe.Designer/Themes/Tokens.Light.xaml index 25ecd1b..c851138 100644 --- a/src/SheetMe.Designer/Themes/Tokens.Light.xaml +++ b/src/SheetMe.Designer/Themes/Tokens.Light.xaml @@ -81,6 +81,7 @@ --> + diff --git a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs index 1f4f8b1..be54821 100644 --- a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs @@ -946,6 +946,13 @@ public sealed class DesignerViewModel : ViewModelBase } } + /// + /// 문서의 모든 컨트롤 이름 — 쿼리의 《컨트롤명》 치환 후보. + /// 런타임도 같은 서식 안에서만 이름을 찾으므로(clsMDataTable 의 poAllControlOnSheet) 범위가 같다. + /// + public IReadOnlyList ControlNames() + => AllControls().Select(c => c.Id).Where(id => id.Length > 0).Distinct(StringComparer.Ordinal).ToList(); + /// 문서 전체 컨트롤 순회(컨테이너 자식 포함, 모델 순서) private IEnumerable AllControls() { diff --git a/src/SheetMe.Designer/Views/DesignerCanvasView.xaml.cs b/src/SheetMe.Designer/Views/DesignerCanvasView.xaml.cs index d2ee058..a10705c 100644 --- a/src/SheetMe.Designer/Views/DesignerCanvasView.xaml.cs +++ b/src/SheetMe.Designer/Views/DesignerCanvasView.xaml.cs @@ -130,7 +130,10 @@ public partial class DesignerCanvasView : UserControl // 데이터소스(MDataTable)는 인라인 대신 전용 쿼리 편집기 창 if (target is DataTableViewModel dataTable) { - var queryDialog = new QueryEditorWindow(dataTable.Id, dataTable.Model.Props.GetText("Query") ?? string.Empty) + // 《컨트롤명》 치환 후보 — 같은 서식의 컨트롤만 유효하다 + var queryDialog = new QueryEditorWindow(dataTable.Id, + dataTable.Model.Props.GetText("Query") ?? string.Empty, + controlNames: subscribedDesigner.ControlNames()) { Owner = Window.GetWindow(this), }; diff --git a/src/SheetMe.Designer/Views/QueryEditorWindow.xaml b/src/SheetMe.Designer/Views/QueryEditorWindow.xaml index 107a3a4..a3ea31f 100644 --- a/src/SheetMe.Designer/Views/QueryEditorWindow.xaml +++ b/src/SheetMe.Designer/Views/QueryEditorWindow.xaml @@ -2,157 +2,322 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ctl="clr-namespace:SheetMe.Designer.Controls" - Title="쿼리 편집" Width="1060" Height="640" MinWidth="780" MinHeight="440" + Title="쿼리 편집" Width="1180" Height="760" MinWidth="880" MinHeight="520" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" Style="{StaticResource ThemedWindow}"> - - + + + - - - - +