diff --git a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
index e221c5b..6c20d59 100644
--- a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
+++ b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
@@ -922,7 +922,9 @@ public sealed class InspectorViewModel : ViewModelBase
PropEditorKind.DataActionTag => new TagPickerRowViewModel(def.Label,
"액션 태그 선택 — 더블클릭/버튼 액션(EN_DataActionTyp)", LegacyTagCatalog.DataActionTags,
LegacyTagUsageCatalog.SuggestFor(def.Key, controlType)) { IsActionTag = true },
- PropEditorKind.SqlQuery => new QueryRowViewModel(def.Label),
+ // 컨트롤 이름을 넘긴다 — 안 넘기면 인스펙터로 연 편집기에서만 《컨트롤명》 칩이 사라져
+ // 같은 창이 진입 경로에 따라 능력이 달라진다
+ PropEditorKind.SqlQuery => new QueryRowViewModel(def.Label) { ControlNamesOf = ControlNames },
PropEditorKind.Mask => new MaskRowViewModel(def.Label),
// 배선 대상 목록은 '지금 이 서식'의 데이터소스다 — 런타임도 같은 서식 안에서만 이름을 찾는다
PropEditorKind.DataTableField => new DataTableFieldRowViewModel(def.Label, DataTableNames),
@@ -953,6 +955,37 @@ public sealed class InspectorViewModel : ViewModelBase
/// 레거시 편집기는 열려 있는 모든 디자인 서피스를 훑어 다른 서식의 데이터소스까지 섞어 보여 주고,
/// 그걸 고르면 런타임에 조용히 실패한다(bzMDataTableField.vb:18-63). 우리는 현재 문서로 한정한다.
///
+ ///
+ /// 이 서식의 모든 컨트롤 이름 — 쿼리 편집기의 《컨트롤명》 치환 후보다.
+ ///
+ /// 데이터소스 아이콘에서 편집기를 열면 이 목록이 넘어가는데 인스펙터의 쿼리 행에서는
+ /// 안 넘어가서, 같은 창이 진입 경로에 따라 능력이 달랐다.
+ ///
+ private IReadOnlyList ControlNames()
+ {
+ var names = new List();
+ void Visit(SheetMe.Core.Models.ControlElement element)
+ {
+ if (element.Id.Length > 0)
+ {
+ names.Add(element.Id);
+ }
+ foreach (var child in element.Children)
+ {
+ Visit(child);
+ }
+ }
+ foreach (var page in designer.Document.Pages)
+ {
+ foreach (var control in page.Controls)
+ {
+ Visit(control);
+ }
+ }
+ names.Sort(StringComparer.Ordinal);
+ return names;
+ }
+
private IReadOnlyList DataTableNames()
{
var names = new List();
diff --git a/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs b/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
index 23c91e9..f8df160 100644
--- a/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
+++ b/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
@@ -782,9 +782,20 @@ public sealed class QueryRowViewModel : PropertyRowViewModel
protected override void OnValueApplied() => OnPropertyChanged(nameof(Summary));
+ ///
+ /// 이 서식의 컨트롤 이름 — 편집기의 《컨트롤명》 치환 후보가 된다.
+ ///
+ /// 안 넘기면 같은 창이 진입 경로에 따라 능력이 달라진다 —
+ /// 데이터소스 아이콘에서 열면 컨트롤 변수 칩이 나오는데
+ /// 인스펙터의 쿼리 행에서 열면 그 칩이 통째로 사라졌다.
+ /// 목록은 문서마다 다르므로 카탈로그가 아니라 호출부가 준다.
+ ///
+ public Func>? ControlNamesOf { get; init; }
+
private void OnEdit()
{
- var dialog = new Views.QueryEditorWindow(Label, ValueText)
+ var dialog = new Views.QueryEditorWindow(Label, ValueText,
+ showVariables: true, controlNames: ControlNamesOf?.Invoke())
{
Owner = System.Windows.Application.Current.MainWindow,
};
diff --git a/src/SheetMe.Designer/Views/QueryEditorWindow.xaml b/src/SheetMe.Designer/Views/QueryEditorWindow.xaml
index 9ef8c4e..ec3657f 100644
--- a/src/SheetMe.Designer/Views/QueryEditorWindow.xaml
+++ b/src/SheetMe.Designer/Views/QueryEditorWindow.xaml
@@ -323,9 +323,33 @@
+
+
+
+
+
+
+
+
+ 하나도 없는 쿼리에 '변수 NULL' 을 적으면 거짓이다 —
+ // 치환할 것이 없으니 NULL 로 바뀐 것도 없다. 그런데 그 꼬리표가 늘 붙어 있어서
+ // 정작 진짜 경고가 붙은 날에도 눈에 안 들어왔다. 늘 뜨는 경고는 경고가 아니다.
+ var hasSubstitution = converted.Hits.Count > 0;
+ var basis = variables is null && hasSubstitution ? "변수 NULL — 구문만 검사" : string.Empty;
var notes = new List();
// 환자를 안 골랐으면 모든 변수가 Unknown 이다 — 그것을 "아직 옮기지 않았다"고 적으면
// 거짓이 된다(ChtNum 은 옮겨져 있다). 그 사유는 위 basis 줄이 이미 말한다.
@@ -611,8 +615,11 @@ public partial class QueryEditorWindow : Window
// 빈 조각은 버린다 — 안 버리면 "성공 · · 경고" 처럼 가운뎃점이 떠 있는다
TrialSummary.Text = string.Join(" · ",
new[] { head, basis }.Concat(notes).Where(s => s.Length > 0));
+ // 0행이어도 표를 띄운다 — 컬럼 이름은 이미 알고 있고, 그것이 배선 필드 칸에
+ // 옮겨 적을 값이다. 표를 접으면 방금 조회한 컬럼명을 다시 알아내려고
+ // 쿼리를 또 돌려야 한다(0행이 배선 작업에서 드문 상태도 아니다).
TrialGrid.ItemsSource = ToGridRows(result);
- TrialGrid.Visibility = result.Rows.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
+ TrialGrid.Visibility = result.Columns.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
}
else
{
@@ -621,6 +628,9 @@ public partial class QueryEditorWindow : Window
new[] { "실패 — " + result.Error, basis }.Concat(notes).Where(s => s.Length > 0));
TrialGrid.ItemsSource = null;
TrialGrid.Visibility = Visibility.Collapsed;
+ // 실패했으면 표가 어차피 없다 — 그 자리에 돌린 문장을 바로 보여 준다.
+ // 오라클 오류 문구만으로는 어디가 틀렸는지 알 수 없고, 답은 치환된 문장에 있다.
+ SetTrialSqlVisible(true);
}
}
@@ -773,6 +783,37 @@ public partial class QueryEditorWindow : Window
/// 검증 이후 SQL 이 바뀌었을 때 요약 앞에 붙는 표시
private const string StaleMark = "(수정됨 — 다시 실행하세요) ";
+ ///
+ /// '돌린 SQL' 접기/펴기 — 치환을 마친 문장을 창 안에서 확인한다.
+ ///
+ /// 0행이 나왔을 때 치환이 NULL 이라 조건이 사라진 것인지
+ /// 이 환자에게 자료가 없는 것인지 가르는 유일한 수단이다. 그 답은 돌린 문장에 있다.
+ /// 전에는 그 문장을 필드에 담아 두기만 하고(진단용) 화면에는 내보내지 않았다.
+ ///
+ private void OnToggleTrialSql(object sender, RoutedEventArgs e)
+ {
+ var show = TrialSqlPane.Visibility != Visibility.Visible;
+ SetTrialSqlVisible(show);
+ }
+
+ private void SetTrialSqlVisible(bool show)
+ {
+ TrialSqlPane.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
+ ShowTrialSqlButton.Content = show ? "돌린 SQL 숨기기" : "돌린 SQL 보기";
+ if (show)
+ {
+ TrialSqlText.Text = lastTrialSql;
+ }
+ }
+
+ private void OnCopyTrialSql(object sender, RoutedEventArgs e)
+ {
+ if (lastTrialSql.Length > 0)
+ {
+ Clipboard.SetText(lastTrialSql);
+ }
+ }
+
/// 줄·열 표시 — 오류 메시지의 위치를 찾아갈 때 쓴다
private void UpdateCaretText()
{