쿼리 편집기 세 가지 — 한글이 사라지던 것, 줄 번호가 밀리던 것, 클릭이 먹지 않던 것
1) 한글로 입력하면 글자가 안 보였다. 색칠은 "투명한 TextBox 뒤에 우리가 그린다"로 되어 있다. 그런데 IME 조합 중인 글자는 아직 TextBox.Text 에 없다 — TextChanged 가 오지 않으니 우리는 그릴 수 없고, TextBox 는 제 Foreground(투명)로 그리니 아무것도 안 보인다. 자음·모음을 맞추는 내내 빈 화면이었고, 확정하고 나서야 나타났다. 조합이 시작되면 TextBox 에게 잉크색을 돌려주고 색칠 층을 감춘다(ShowRawText). 확정되면(TextInput) 되돌리고, ESC 로 취소돼 CompositionText 가 비어도 되돌린다. 조합 도중 포커스를 잃으면 끝 신호가 오지 않으므로 blur 에서도 되돌린다 — 색 없는 상태로 굳지 않게. 조합 중 잠깐 단색이 되는 편이 글자가 안 보이는 것보다 낫다. 2) 줄 번호가 실제 줄과 어긋났다. 번호를 "1\n2\n3…" TextBlock 한 덩어리로 두면 번호의 줄 간격은 TextBlock 조판이, 본문의 줄 간격은 TextBox 가 정한다. 한글 글꼴 대체가 끼면 조금씩 벌어져 아래로 갈수록 눈에 띄게 밀렸다. LineNumberGutter 를 만들어 각 줄 첫 글자의 y 를 GetRectFromCharacterIndex 로 물어 그 자리에 번호를 그린다. 스크롤·여백·폰트 대체가 전부 TextBox 기준으로 반영돼 오므로 구조적으로 어긋날 수 없다. 폭은 가장 큰 번호에 맞춰 잡고 번호는 오른쪽 정렬한다. 3) 자동완성 목록의 항목을 클릭하면 아무것도 안 들어가고 목록만 닫혔다. Popup 이 StaysOpen=False 라 마우스를 캡처하고 있었다. 항목을 누른 그 클릭이 '바깥 클릭'으로 먼저 소비돼 팝업이 닫히고, 항목은 클릭을 받지 못했다. StaysOpen=True 로 바꾸고 닫는 일은 코드가 맡는다(커서 이동·포커스 상실·창 비활성 — 이미 있던 핸들러들). 집는 처리는 팝업 안쪽 Border 에서 터널 단계로 받는다. 진단(--query-popup)에 "목록 항목을 집으면 입력되고 목록이 닫힌다"를 더해 17건. 마우스 라우팅 자체는 화면 없이 흉내 낼 수 없어(Ctrl+Enter 때와 같은 이유) 핸들러가 부르는 경로를 그대로 태워 삽입까지 확인한다. IME 조합은 합성할 수 없어 1) 은 실기 확인이 필요하다. 게이트: 테스트 230/230, --edit-smoke 0건, --db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6ce055b00c
commit
749de6b566
@@ -0,0 +1,174 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace SheetMe.Designer.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 줄 번호 홈통 — 번호를 <b>TextBox 가 알려 준 줄 위치</b>에 그린다.
|
||||||
|
///
|
||||||
|
/// <b>왜 TextBlock 한 줄로 두면 안 되는가.</b> 예전에는 "1\n2\n3…" 을 TextBlock 에 넣어 두었다.
|
||||||
|
/// 그러면 번호의 줄 간격은 TextBlock 의 조판이 정하고 본문의 줄 간격은 TextBox 가 정한다 —
|
||||||
|
/// 글꼴 대체(한글)나 여백이 조금만 달라도 아래로 갈수록 벌어져 번호와 줄이 어긋난다.
|
||||||
|
///
|
||||||
|
/// 여기서는 각 줄의 첫 글자 위치를 <see cref="TextBox.GetRectFromCharacterIndex(int)"/> 로 물어
|
||||||
|
/// 그 y 에 번호를 그린다. 스크롤·여백·글꼴 대체가 전부 TextBox 기준으로 반영돼 오므로 어긋날 수 없다.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LineNumberGutter : FrameworkElement
|
||||||
|
{
|
||||||
|
#region Member Fields
|
||||||
|
private TextBox? source;
|
||||||
|
private ScrollViewer? scroller;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
/// <summary>줄 위치를 물어볼 대상</summary>
|
||||||
|
public TextBox? Source
|
||||||
|
{
|
||||||
|
get => source;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(source, value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Detach();
|
||||||
|
source = value;
|
||||||
|
Attach();
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>오른쪽 여백 — 구분선과 번호 사이</summary>
|
||||||
|
public double RightPadding { get; set; } = 10;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
private void Attach()
|
||||||
|
{
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
source.TextChanged += OnChanged;
|
||||||
|
source.SizeChanged += OnChanged;
|
||||||
|
source.Loaded += OnLoaded;
|
||||||
|
HookScroller();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Detach()
|
||||||
|
{
|
||||||
|
if (source is not null)
|
||||||
|
{
|
||||||
|
source.TextChanged -= OnChanged;
|
||||||
|
source.SizeChanged -= OnChanged;
|
||||||
|
source.Loaded -= OnLoaded;
|
||||||
|
}
|
||||||
|
if (scroller is not null)
|
||||||
|
{
|
||||||
|
scroller.ScrollChanged -= OnScrolled;
|
||||||
|
scroller = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
HookScroller();
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HookScroller()
|
||||||
|
{
|
||||||
|
if (source is null || scroller is not null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scroller = FindScrollViewer(source);
|
||||||
|
if (scroller is not null)
|
||||||
|
{
|
||||||
|
scroller.ScrollChanged += OnScrolled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ScrollViewer? FindScrollViewer(DependencyObject root)
|
||||||
|
{
|
||||||
|
if (root is ScrollViewer found)
|
||||||
|
{
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
var count = VisualTreeHelper.GetChildrenCount(root);
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (FindScrollViewer(VisualTreeHelper.GetChild(root, i)) is { } child)
|
||||||
|
{
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScrolled(object sender, ScrollChangedEventArgs e) => InvalidateVisual();
|
||||||
|
|
||||||
|
private void OnChanged(object sender, EventArgs e) => InvalidateVisual();
|
||||||
|
|
||||||
|
/// <summary>가장 큰 번호가 들어갈 만큼만 폭을 잡는다 — 줄이 늘면 홈통도 넓어진다</summary>
|
||||||
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
|
{
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
return new Size(28, 0);
|
||||||
|
}
|
||||||
|
var lines = 1;
|
||||||
|
foreach (var ch in source.Text)
|
||||||
|
{
|
||||||
|
if (ch == '\n')
|
||||||
|
{
|
||||||
|
lines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var probe = Measure(lines.ToString(CultureInfo.InvariantCulture));
|
||||||
|
return new Size(Math.Max(20, probe.Width) + RightPadding, availableSize.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FormattedText Measure(string text)
|
||||||
|
=> new(text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight,
|
||||||
|
new Typeface(source!.FontFamily, source.FontStyle, source.FontWeight, source.FontStretch),
|
||||||
|
source.FontSize, Brushes.Black, VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||||
|
|
||||||
|
protected override void OnRender(DrawingContext dc)
|
||||||
|
{
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = source.Text;
|
||||||
|
var brush = TryFindResource("B.Muted") as Brush ?? Brushes.Gray;
|
||||||
|
var height = ActualHeight;
|
||||||
|
|
||||||
|
var lineIndex = 0;
|
||||||
|
var lineStart = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var rect = source.GetRectFromCharacterIndex(Math.Min(lineStart, text.Length));
|
||||||
|
if (!rect.IsEmpty && !double.IsInfinity(rect.Top) && !double.IsNaN(rect.Top)
|
||||||
|
&& rect.Bottom >= 0 && rect.Top <= height)
|
||||||
|
{
|
||||||
|
var label = Measure((lineIndex + 1).ToString(CultureInfo.InvariantCulture));
|
||||||
|
label.SetForegroundBrush(brush);
|
||||||
|
// 오른쪽 정렬 — 자릿수가 늘어도 번호 끝이 구분선에서 같은 거리를 유지한다
|
||||||
|
dc.DrawText(label, new Point(ActualWidth - RightPadding - label.Width, rect.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
var newline = text.IndexOf('\n', lineStart);
|
||||||
|
if (newline < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lineStart = newline + 1;
|
||||||
|
lineIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -700,7 +700,28 @@ public static class DbSmoke
|
|||||||
$"blur 전 열림={openBeforeBlur}, blur 후 열림={popup.IsOpen}");
|
$"blur 전 열림={openBeforeBlur}, blur 후 열림={popup.IsOpen}");
|
||||||
|
|
||||||
// ⑦ 바깥 클릭으로 닫히는 설정인지
|
// ⑦ 바깥 클릭으로 닫히는 설정인지
|
||||||
Check("바깥 클릭으로 닫히는 설정(StaysOpen=False)", !popup.StaysOpen);
|
Check("항목 클릭이 캡처에 먹히지 않는 설정(StaysOpen=True)", popup.StaysOpen);
|
||||||
|
|
||||||
|
// ⑦-2 목록 항목을 집으면 실제로 입력돼야 한다.
|
||||||
|
// 예전에는 클릭이 포커스를 가져가 LostFocus 가 팝업을 닫아 버려 아무것도 안 들어갔다.
|
||||||
|
// 마우스 라우팅 자체는 화면 없이 흉내 낼 수 없어(Ctrl+Enter 때와 같은 이유)
|
||||||
|
// 핸들러가 부르는 그 경로(PickCompletionForSmoke)를 태워 삽입까지 확인한다.
|
||||||
|
var list = (System.Windows.Controls.ListBox)window.FindName("CompletionList")!;
|
||||||
|
Type("SELECT * FROM E_Sht");
|
||||||
|
if (popup.IsOpen && list.Items.Count > 0)
|
||||||
|
{
|
||||||
|
var before = box.Text;
|
||||||
|
list.UpdateLayout();
|
||||||
|
var picked = window.PickCompletionForSmoke(0);
|
||||||
|
DrainDispatcher();
|
||||||
|
Check("목록 항목을 집으면 입력되고 목록이 닫힌다",
|
||||||
|
picked && box.Text.Length > before.Length && !popup.IsOpen,
|
||||||
|
$"'{before}' → '{box.Text}'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Check("목록 항목을 집으면 입력되고 목록이 닫힌다", false, "검사 전에 목록이 열리지 않았다");
|
||||||
|
}
|
||||||
|
|
||||||
// ⑧ 색칠 레이어와 실제 글자 위치가 맞는지 — 한글이 섞이면 폰트 대체 때문에 어긋났었다.
|
// ⑧ 색칠 레이어와 실제 글자 위치가 맞는지 — 한글이 섞이면 폰트 대체 때문에 어긋났었다.
|
||||||
// 레이어는 TextBox 에게 좌표를 물으므로, 같은 인덱스의 사각형이 곧 그리는 자리다.
|
// 레이어는 TextBox 에게 좌표를 물으므로, 같은 인덱스의 사각형이 곧 그리는 자리다.
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ public static class DialogShots
|
|||||||
("03-query-editor", () => NewQueryEditor(designer,
|
("03-query-editor", () => NewQueryEditor(designer,
|
||||||
"SELECT ChtNum, PatNam FROM P_PatMst\nWHERE ChtNum = <<M.CMM.HISOperatingInfo.bzPatientInfo.ChtNum>>")),
|
"SELECT ChtNum, PatNam FROM P_PatMst\nWHERE ChtNum = <<M.CMM.HISOperatingInfo.bzPatientInfo.ChtNum>>")),
|
||||||
("03b-query-trial", () => NewQueryEditorWithTrial(designer)),
|
("03b-query-trial", () => NewQueryEditorWithTrial(designer)),
|
||||||
|
// 한글이 섞인 쿼리 — 색칠 레이어와 줄번호가 한글에서 어긋나는지 눈으로 확인할 경로
|
||||||
|
("03c-query-hangul", () => NewQueryEditor(designer,
|
||||||
|
"select 이름, 주소 from 환자정보\nwhere 상태 = '입원'\n-- 세 번째 줄 주석\nand 나이 >= 20")),
|
||||||
// 실제 카탈로그(384종)와 실제 제안 순위로 찍는다 — 표본 몇 개로는 분류·건수가 안 보인다
|
// 실제 카탈로그(384종)와 실제 제안 순위로 찍는다 — 표본 몇 개로는 분류·건수가 안 보인다
|
||||||
("04-tag-picker", () => new Views.TagPickerDialogView(
|
("04-tag-picker", () => new Views.TagPickerDialogView(
|
||||||
"데이터 태그 선택 — 자동 채움 원천(bzDataInterface)",
|
"데이터 태그 선택 — 자동 채움 원천(bzDataInterface)",
|
||||||
|
|||||||
@@ -333,23 +333,27 @@
|
|||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<!-- 줄 번호 홈통 — 회색 면 + 곧은 세로 구분선 -->
|
<!-- 줄 번호 홈통 — 회색 면 + 곧은 세로 구분선.
|
||||||
<Border Grid.Column="0" Background="{DynamicResource B.Chip}" Padding="12,10,10,10"
|
번호는 TextBox 가 알려 준 줄 위치에 그린다(TextBlock 한 덩어리로 두면
|
||||||
|
본문과 줄 간격이 달라 아래로 갈수록 어긋난다). -->
|
||||||
|
<Border Grid.Column="0" Background="{DynamicResource B.Chip}" Padding="12,0,0,0"
|
||||||
BorderThickness="0,0,1,0" BorderBrush="{DynamicResource B.Line2}">
|
BorderThickness="0,0,1,0" BorderBrush="{DynamicResource B.Line2}">
|
||||||
<TextBlock x:Name="LineNumbers" FontFamily="D2Coding, 굴림체, Consolas" FontSize="13.5"
|
<ctl:LineNumberGutter x:Name="LineNumbers" IsHitTestVisible="False"/>
|
||||||
Foreground="{DynamicResource B.Muted}" TextAlignment="Right" MinWidth="20"/>
|
|
||||||
</Border>
|
</Border>
|
||||||
<Grid Grid.Column="1">
|
<Grid Grid.Column="1">
|
||||||
<ctl:SqlHighlightLayer x:Name="Highlight" IsHitTestVisible="False"/>
|
<ctl:SqlHighlightLayer x:Name="Highlight" IsHitTestVisible="False"/>
|
||||||
<!-- StaysOpen=False 여야 바깥을 클릭했을 때 닫힌다 -->
|
<!-- StaysOpen=True 다. False 로 두면 Popup 이 마우스를 캡처해
|
||||||
|
항목을 누른 그 클릭이 '바깥 클릭'으로 먼저 소비돼 목록만 닫히고
|
||||||
|
아무것도 입력되지 않는다. 대신 커서 이동·포커스 상실·스크롤·창 비활성에서
|
||||||
|
코드가 직접 닫는다(위 핸들러들). -->
|
||||||
<Popup x:Name="CompletionPopup" Placement="Relative" AllowsTransparency="True"
|
<Popup x:Name="CompletionPopup" Placement="Relative" AllowsTransparency="True"
|
||||||
StaysOpen="False" PopupAnimation="None" Focusable="False">
|
StaysOpen="True" PopupAnimation="None" Focusable="False">
|
||||||
<Border Background="{DynamicResource B.Surface}" BorderThickness="1"
|
<Border Background="{DynamicResource B.Surface}" BorderThickness="1"
|
||||||
BorderBrush="{DynamicResource B.InputBorder}" CornerRadius="8" Padding="3">
|
BorderBrush="{DynamicResource B.Line2}" CornerRadius="8" Padding="3"
|
||||||
|
PreviewMouseLeftButtonDown="OnCompletionPicked">
|
||||||
<ListBox x:Name="CompletionList" MaxHeight="260" MinWidth="280" FontSize="12"
|
<ListBox x:Name="CompletionList" MaxHeight="260" MinWidth="280" FontSize="12"
|
||||||
BorderThickness="0" Background="Transparent"
|
BorderThickness="0" Background="Transparent"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||||
MouseDoubleClick="OnCompletionPicked">
|
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<DockPanel MinWidth="260">
|
<DockPanel MinWidth="260">
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public partial class QueryEditorWindow : Window
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool typing;
|
private bool typing;
|
||||||
|
|
||||||
|
/// <summary>한글 조합 중인가 — 조합 중에는 색칠을 접고 TextBox 가 제 글자를 그린다</summary>
|
||||||
|
private bool composing;
|
||||||
|
|
||||||
/// <summary>목록이 걸려 있는 낱말의 끝 — 커서가 이 범위를 벗어나면 목록은 더 이상 유효하지 않다</summary>
|
/// <summary>목록이 걸려 있는 낱말의 끝 — 커서가 이 범위를 벗어나면 목록은 더 이상 유효하지 않다</summary>
|
||||||
private int completionEnd;
|
private int completionEnd;
|
||||||
|
|
||||||
@@ -109,6 +112,11 @@ public partial class QueryEditorWindow : Window
|
|||||||
{
|
{
|
||||||
Title = $"쿼리 편집 — {ownerLabel}";
|
Title = $"쿼리 편집 — {ownerLabel}";
|
||||||
Highlight.Source = SqlBox;
|
Highlight.Source = SqlBox;
|
||||||
|
LineNumbers.Source = SqlBox;
|
||||||
|
// 한글 조합(IME) 동안에는 색칠을 잠시 접고 TextBox 가 제 글자를 그리게 한다 — 아래 설명
|
||||||
|
TextCompositionManager.AddTextInputStartHandler(SqlBox, OnComposing);
|
||||||
|
TextCompositionManager.AddTextInputUpdateHandler(SqlBox, OnComposing);
|
||||||
|
TextCompositionManager.AddTextInputHandler(SqlBox, OnComposed);
|
||||||
RebuildBucketChips();
|
RebuildBucketChips();
|
||||||
ApplyVariableFilter(string.Empty);
|
ApplyVariableFilter(string.Empty);
|
||||||
var connection = Services.ConfigService.Current.ConnectionString;
|
var connection = Services.ConfigService.Current.ConnectionString;
|
||||||
@@ -228,7 +236,14 @@ public partial class QueryEditorWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSqlLostFocus(object sender, RoutedEventArgs e) => CompletionPopup.IsOpen = false;
|
private void OnSqlLostFocus(object sender, RoutedEventArgs e) => CloseOnBlur();
|
||||||
|
|
||||||
|
private void CloseOnBlur()
|
||||||
|
{
|
||||||
|
CompletionPopup.IsOpen = false;
|
||||||
|
// 조합 도중 포커스를 잃으면 조합 끝 신호가 오지 않는다 — 색칠 없는 상태로 굳지 않게 되돌린다
|
||||||
|
ShowRawText(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 스크롤로 자리가 움직였을 때 목록을 새 자리에 다시 놓는다.
|
/// 스크롤로 자리가 움직였을 때 목록을 새 자리에 다시 놓는다.
|
||||||
@@ -374,7 +389,49 @@ public partial class QueryEditorWindow : Window
|
|||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCompletionPicked(object sender, MouseButtonEventArgs e) => AcceptCompletion();
|
/// <summary>
|
||||||
|
/// 목록 항목 클릭 — 누르는 즉시 넣는다.
|
||||||
|
///
|
||||||
|
/// 예전에는 더블클릭이었는데, 클릭이 편집기에서 포커스를 가져가면서 LostFocus 가 팝업을 닫아
|
||||||
|
/// 아무것도 입력되지 않고 사라졌다. 여기서 눌린 항목을 직접 찾아 처리하고 이벤트를 소비해
|
||||||
|
/// 포커스가 아예 옮겨 가지 않게 한다.
|
||||||
|
/// </summary>
|
||||||
|
private void OnCompletionPicked(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.OriginalSource is DependencyObject origin && PickCompletionAt(origin))
|
||||||
|
{
|
||||||
|
// 소비하지 않으면 ListBoxItem 이 포커스를 가져가고 편집기가 LostFocus 를 받는다
|
||||||
|
e.Handled = true;
|
||||||
|
SqlBox.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 눌린 지점에서 항목을 찾아 넣는다 — 처리했으면 true.
|
||||||
|
/// 클릭 지점은 항목 안의 글자일 수도, 항목 컨테이너 자체일 수도 있다.
|
||||||
|
/// </summary>
|
||||||
|
private bool PickCompletionAt(DependencyObject origin)
|
||||||
|
{
|
||||||
|
var container = ItemsControl.ContainerFromElement(CompletionList, origin) as ListBoxItem;
|
||||||
|
if (container?.DataContext is not SqlCompletionItem item)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CompletionList.SelectedItem = item;
|
||||||
|
AcceptCompletion();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 진단(--query-popup)에서 '항목 클릭'을 부르는 통로.
|
||||||
|
/// WPF 의 마우스 라우팅은 화면 없이 흉내 낼 수 없어(합성 이벤트가 캡처·히트테스트를 못 거친다)
|
||||||
|
/// 핸들러 본문을 그대로 태워 삽입 로직만 확인한다.
|
||||||
|
/// </summary>
|
||||||
|
internal bool PickCompletionForSmoke(int index)
|
||||||
|
{
|
||||||
|
var container = CompletionList.ItemContainerGenerator.ContainerFromIndex(index);
|
||||||
|
return container is DependencyObject origin && PickCompletionAt(origin);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>목록이 떠 있는 동안의 키 처리 — 처리했으면 true</summary>
|
/// <summary>목록이 떠 있는 동안의 키 처리 — 처리했으면 true</summary>
|
||||||
private bool HandleCompletionKey(KeyEventArgs e)
|
private bool HandleCompletionKey(KeyEventArgs e)
|
||||||
@@ -494,6 +551,36 @@ public partial class QueryEditorWindow : Window
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods - Editing
|
#region Methods - Editing
|
||||||
|
/// <summary>
|
||||||
|
/// 한글을 치는 동안(IME 조합 중) 글자가 사라지던 문제.
|
||||||
|
///
|
||||||
|
/// 색칠은 "투명한 TextBox 뒤에 우리가 그린다"로 되어 있다. 그런데 조합 중인 글자는
|
||||||
|
/// 아직 <see cref="TextBox.Text"/> 에 들어오지 않는다 — TextChanged 가 안 오니 우리는 그릴 수 없고,
|
||||||
|
/// TextBox 는 제 Foreground(투명)로 그리니 아무것도 안 보인다. 자음·모음을 맞추는 내내 빈 화면이었다.
|
||||||
|
///
|
||||||
|
/// 그래서 조합이 시작되면 TextBox 에게 잉크색을 돌려주고 색칠 층을 감춘다. 조합이 끝나
|
||||||
|
/// 글자가 확정되면(TextInput → 이어서 TextChanged) 원래대로 되돌린다. 조합 중 잠깐 단색이 되는
|
||||||
|
/// 것이 글자가 안 보이는 것보다 낫다.
|
||||||
|
/// </summary>
|
||||||
|
private void OnComposing(object? sender, TextCompositionEventArgs e)
|
||||||
|
// 조합이 취소되면(ESC) CompositionText 가 비면서 여기로 온다 — 그때는 바로 색칠로 돌아간다
|
||||||
|
=> ShowRawText(e.TextComposition.CompositionText.Length > 0);
|
||||||
|
|
||||||
|
private void OnComposed(object? sender, TextCompositionEventArgs e) => ShowRawText(false);
|
||||||
|
|
||||||
|
private void ShowRawText(bool raw)
|
||||||
|
{
|
||||||
|
if (composing == raw)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
composing = raw;
|
||||||
|
SqlBox.Foreground = raw
|
||||||
|
? (System.Windows.Media.Brush)FindResource("B.Ink")
|
||||||
|
: System.Windows.Media.Brushes.Transparent;
|
||||||
|
Highlight.Visibility = raw ? Visibility.Hidden : Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnSqlChanged(object sender, TextChangedEventArgs e)
|
private void OnSqlChanged(object sender, TextChangedEventArgs e)
|
||||||
{
|
{
|
||||||
Refresh();
|
Refresh();
|
||||||
@@ -516,7 +603,6 @@ public partial class QueryEditorWindow : Window
|
|||||||
var text = SqlBox.Text;
|
var text = SqlBox.Text;
|
||||||
LengthText.Text = $"{text.Length:N0}자 · {CountLines(text)}줄";
|
LengthText.Text = $"{text.Length:N0}자 · {CountLines(text)}줄";
|
||||||
UpdateCaretText();
|
UpdateCaretText();
|
||||||
UpdateLineNumbers(text);
|
|
||||||
UpdateProblems(text);
|
UpdateProblems(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,17 +667,6 @@ public partial class QueryEditorWindow : Window
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateLineNumbers(string text)
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
var count = CountLines(text);
|
|
||||||
for (var i = 1; i <= count; i++)
|
|
||||||
{
|
|
||||||
builder.Append(i).Append('\n');
|
|
||||||
}
|
|
||||||
LineNumbers.Text = builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 저장 전에 눈에 띄어야 하는 것만 알린다 — 닫히지 않은 조각과 축약된 치환 변수.
|
/// 저장 전에 눈에 띄어야 하는 것만 알린다 — 닫히지 않은 조각과 축약된 치환 변수.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user