속성 패널 고도화 — 쌍 배치·스타일 세그먼트·행 축소로 세로 25% 절감
사용자 지적: "속성패널 디자인 보기 불편한데? 고도화해줘".
[실측한 낭비] 단일 선택 시 '공통+글꼴'만으로 354px 를 쓴다. 표준 행 34px 중 실제 필요 높이는
18px(테두리 2 + 글자 16)뿐이라 행마다 12px 가 공기다. 가장 긴 TextBox 는 25행 801px 로 기본 창
뷰포트(약 790px)를 이미 넘겨 스크롤이 강제됐다.
[쌍 배치] X|Y, 너비|높이를 한 줄 2칸으로. 라벨은 칩 바깥 왼쪽 자연폭 — [200] 의 XYPair 규약
(Grid star/10/star)과 같다. 네 줄이 두 줄이 된다.
구조가 핵심이다: **자식 행은 평범한 행 그대로**이고 컨테이너만 새로 만들었다. 값·커밋·"여러 값"
판정이 전부 자식 안에서 끝나므로 커밋 규약(Initialize 는 Commit 을 내지 않고, 값이 바뀔 때만
Undo 스냅샷 1회)이 한 글자도 안 바뀌고, boundsRows 가 자식을 들고 있어 드래그 중 실시간 갱신
(RefreshBoundsRows)도 그대로다. X 만 갈리고 Y 는 같은 다중선택도 칸별로 옳게 표시된다
(그래서 셀마다 전용 Grid 를 둔다 — 오버레이의 포커스 트리거가 AncestorType=Grid 다).
[스타일 세그먼트] 굵게·기울임·밑줄·취소선을 한 줄 아이콘 토글로. 종전에는 굵게·밑줄만 있었고
각각 한 줄이었다. **기울임·취소선은 기술적 제약이 아니라 단순 누락이었다** — LegacyFont 의
파싱·직렬화·렌더·폰트 일괄 변경 대화상자까지 전부 구현돼 있고 인스펙터만 호출을 빠뜨렸다.
CurrentFontOf 가 Italic/Strikeout 을 복사·보존하므로 네 개를 동시에 노출해도 왕복이 안 깨진다.
[행 치수] 인스펙터 전용 지역 스타일로 입력 높이 30→26. 전역 TextBox 를 낮추면 검색칩·대화상자
입력까지 따라 줄어 버튼과 정렬이 깨진다. **ComboBox 는 MinHeight 가 아니라 Height 고정**이라
Height 를 함께 내려야 한다 — 안 그러면 선택 행만 4px 어긋난다(설계 검토에서 잡은 실제 결함).
[라벨] 폭 86→92 + 줄바꿈. 실측상 가장 긴 라벨이 11자("입력 파라미터 컨트롤")라 86px 에서
"액션 대상 컨…" 처럼 잘렸다. 자르면 툴팁을 띄워야 아는 정보가 되므로 접는 쪽을 택했다.
[그 밖에] 태그·마스크 편집기를 여는 "…" 버튼은 26px 칩 옆에서 점 하나로 보여 아이콘 버튼으로.
섹션 헤더 건수를 줄 수가 아니라 **속성 수**로(LeafCount) — 쌍/세그먼트는 한 줄이지만 속성은
여럿이라 그냥 세면 "공통 3"으로 실제보다 적게 나온다.
인스펙터 패널 최소폭 220→250 — 스타일 행이 라벨 92 + 토글 4칸 128 = 220px 를 요구한다.
효과: 공통 5행→3행, 글꼴 4행→3행(속성은 4→6개로 늘었다). TextBox 기준 25행→18행.
'전체 속성 표시' 토글이 기본 창에서 스크롤 없이 보인다(종전에는 한참 아래).
검증: 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0,
종이 렌더 P062 픽셀 차이 0. 인스펙터를 300px 호스트 창에 담아 양 테마로 렌더해 배치 확인
(dialog-shots 진단에 09/10 추가 — 속성 패널 레이아웃 회귀를 눈으로 잡는 자동 경로).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
14944bd316
commit
abd7eb7d0e
@@ -64,6 +64,7 @@ public static class LucideIcons
|
|||||||
["bold"] = """<path d="M14 12a4 4 0 0 0 0-8H6v8" /> <path d="M15 20a4 4 0 0 0 0-8H6v8Z" />""",
|
["bold"] = """<path d="M14 12a4 4 0 0 0 0-8H6v8" /> <path d="M15 20a4 4 0 0 0 0-8H6v8Z" />""",
|
||||||
["italic"] = """<line x1="19" x2="10" y1="4" y2="4" /> <line x1="14" x2="5" y1="20" y2="20" /> <line x1="15" x2="9" y1="4" y2="20" />""",
|
["italic"] = """<line x1="19" x2="10" y1="4" y2="4" /> <line x1="14" x2="5" y1="20" y2="20" /> <line x1="15" x2="9" y1="4" y2="20" />""",
|
||||||
["underline"] = """<path d="M6 4v6a6 6 0 0 0 12 0V4" /> <line x1="4" x2="20" y1="20" y2="20" />""",
|
["underline"] = """<path d="M6 4v6a6 6 0 0 0 12 0V4" /> <line x1="4" x2="20" y1="20" y2="20" />""",
|
||||||
|
["strikethrough"] = """<path d="M16 4H9a3 3 0 0 0-2.83 4" /> <path d="M14 12a4 4 0 0 1 0 8H6" /> <line x1="4" x2="20" y1="12" y2="12" />""",
|
||||||
["zoom-in"] = """<circle cx="11" cy="11" r="8" /> <line x1="21" x2="16.65" y1="21" y2="16.65" /> <line x1="11" x2="11" y1="8" y2="14" /> <line x1="8" x2="14" y1="11" y2="11" />""",
|
["zoom-in"] = """<circle cx="11" cy="11" r="8" /> <line x1="21" x2="16.65" y1="21" y2="16.65" /> <line x1="11" x2="11" y1="8" y2="14" /> <line x1="8" x2="14" y1="11" y2="11" />""",
|
||||||
["zoom-out"] = """<circle cx="11" cy="11" r="8" /> <line x1="21" x2="16.65" y1="21" y2="16.65" /> <line x1="8" x2="14" y1="11" y2="11" />""",
|
["zoom-out"] = """<circle cx="11" cy="11" r="8" /> <line x1="21" x2="16.65" y1="21" y2="16.65" /> <line x1="8" x2="14" y1="11" y2="11" />""",
|
||||||
["maximize"] = """<path d="M8 3H5a2 2 0 0 0-2 2v3" /> <path d="M21 8V5a2 2 0 0 0-2-2h-3" /> <path d="M3 16v3a2 2 0 0 0 2 2h3" /> <path d="M16 21h3a2 2 0 0 0 2-2v-3" />""",
|
["maximize"] = """<path d="M8 3H5a2 2 0 0 0-2 2v3" /> <path d="M21 8V5a2 2 0 0 0-2-2h-3" /> <path d="M3 16v3a2 2 0 0 0 2 2h3" /> <path d="M16 21h3a2 2 0 0 0 2-2v-3" />""",
|
||||||
|
|||||||
@@ -119,6 +119,28 @@ public static class DialogShots
|
|||||||
("06-font-manager", () => new Views.FontManagerDialogView(designer)),
|
("06-font-manager", () => new Views.FontManagerDialogView(designer)),
|
||||||
("07-register-sheet", () => new Views.RegisterSheetDialogView("S999", "표본 서식")),
|
("07-register-sheet", () => new Views.RegisterSheetDialogView("S999", "표본 서식")),
|
||||||
("08-preview", () => new Views.PreviewWindow(designer)),
|
("08-preview", () => new Views.PreviewWindow(designer)),
|
||||||
|
// 인스펙터는 UserControl 이라 창이 없다 — 실폭 300px 호스트 창에 담아 실제 배치를 찍는다.
|
||||||
|
// 속성 패널 레이아웃 회귀를 눈으로 확인할 수 있는 유일한 자동 경로다.
|
||||||
|
("09-inspector-label", () => HostInspector(designer, "Label1")),
|
||||||
|
("10-inspector-textbox", () => HostInspector(designer, "TextBox1")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>인스펙터를 실제 패널 폭(300px)의 창에 담는다 — 컨트롤 하나를 선택한 상태로</summary>
|
||||||
|
private static Window HostInspector(DesignerViewModel designer, string controlId)
|
||||||
|
{
|
||||||
|
var page = designer.Pages[0];
|
||||||
|
var target = page.Controls.FirstOrDefault(c => c.Id == controlId) ?? page.Controls[0];
|
||||||
|
designer.Selection.SetSingle(target);
|
||||||
|
designer.Inspector.Rebuild();
|
||||||
|
|
||||||
|
return new Window
|
||||||
|
{
|
||||||
|
Title = $"속성 — {controlId}",
|
||||||
|
Width = 300,
|
||||||
|
Height = 720,
|
||||||
|
Content = new Views.InspectorView { DataContext = designer.Inspector },
|
||||||
|
Background = Application.Current.TryFindResource("B.Panel") as Brush,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,18 +98,18 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
AffectsVisual = false,
|
AffectsVisual = false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AddBoundsRow("X", vm => vm.X, (vm, v) => vm.X = v);
|
// 위치·크기는 짝지어 한 줄씩 — 네 줄(약 136px)을 두 줄로 줄인다
|
||||||
AddBoundsRow("Y", vm => vm.Y, (vm, v) => vm.Y = v);
|
AddBoundsPair("X", vm => vm.X, (vm, v) => vm.X = v,
|
||||||
AddBoundsRow("너비", vm => vm.Width, (vm, v) => vm.Width = Math.Max(1, v));
|
"Y", vm => vm.Y, (vm, v) => vm.Y = v);
|
||||||
AddBoundsRow("높이", vm => vm.Height, (vm, v) => vm.Height = Math.Max(1, v));
|
AddBoundsPair("너비", vm => vm.Width, (vm, v) => vm.Width = Math.Max(1, v),
|
||||||
|
"높이", vm => vm.Height, (vm, v) => vm.Height = Math.Max(1, v));
|
||||||
|
|
||||||
// 폰트(공통) — Font Property(상속 시 빈 값)
|
// 폰트(공통) — Font Property(상속 시 빈 값)
|
||||||
AddSection("글꼴");
|
AddSection("글꼴");
|
||||||
AddFontRow("글꼴", f => f.Family, (f, v) => f.Family = v.Length == 0 ? f.Family : v);
|
AddFontRow("글꼴", f => f.Family, (f, v) => f.Family = v.Length == 0 ? f.Family : v);
|
||||||
AddFontRow("크기(pt)", f => f.SizePt.ToString("0.##", CultureInfo.InvariantCulture),
|
AddFontRow("크기(pt)", f => f.SizePt.ToString("0.##", CultureInfo.InvariantCulture),
|
||||||
(f, v) => f.SizePt = double.TryParse(v, NumberStyles.Number, CultureInfo.InvariantCulture, out var size) && size > 0 ? size : f.SizePt);
|
(f, v) => f.SizePt = double.TryParse(v, NumberStyles.Number, CultureInfo.InvariantCulture, out var size) && size > 0 ? size : f.SizePt);
|
||||||
AddFontToggleRow("굵게", f => f.Bold, (f, v) => f.Bold = v);
|
AddFontStyleSegment();
|
||||||
AddFontToggleRow("밑줄", f => f.Underline, (f, v) => f.Underline = v);
|
|
||||||
|
|
||||||
// 타입 전용 — 전체 선택이 같은 타입일 때만
|
// 타입 전용 — 전체 선택이 같은 타입일 때만
|
||||||
var type = items[0].Type;
|
var type = items[0].Type;
|
||||||
@@ -270,7 +270,23 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
AddRow(row, binding);
|
AddRow(row, binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddBoundsRow(string label, Func<ControlViewModel, double> get, Action<ControlViewModel, double> set)
|
/// <summary>
|
||||||
|
/// 경계 값 두 개를 한 줄에 — X|Y, 너비|높이.
|
||||||
|
/// 자식 행은 boundsRows 에 그대로 등록되므로 드래그·리사이즈 중 실시간 갱신
|
||||||
|
/// (RefreshBoundsRows)이 종전과 똑같이 동작한다.
|
||||||
|
/// </summary>
|
||||||
|
private void AddBoundsPair(
|
||||||
|
string leftLabel, Func<ControlViewModel, double> getLeft, Action<ControlViewModel, double> setLeft,
|
||||||
|
string rightLabel, Func<ControlViewModel, double> getRight, Action<ControlViewModel, double> setRight)
|
||||||
|
{
|
||||||
|
var left = CreateBoundsRow(leftLabel, getLeft, setLeft);
|
||||||
|
var right = CreateBoundsRow(rightLabel, getRight, setRight);
|
||||||
|
var pair = new PairRowViewModel(leftLabel, left, rightLabel, right);
|
||||||
|
currentSection?.Adopt(pair);
|
||||||
|
Rows.Add(pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NumberRowViewModel CreateBoundsRow(string label, Func<ControlViewModel, double> get, Action<ControlViewModel, double> set)
|
||||||
{
|
{
|
||||||
var row = new NumberRowViewModel(label);
|
var row = new NumberRowViewModel(label);
|
||||||
var binding = new RowBinding
|
var binding = new RowBinding
|
||||||
@@ -285,8 +301,9 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
},
|
},
|
||||||
AffectsVisual = false,
|
AffectsVisual = false,
|
||||||
};
|
};
|
||||||
AddRow(row, binding, afterCommit: () => designer.Selection.NotifyBoundsChanged());
|
BindRow(row, binding, afterCommit: () => designer.Selection.NotifyBoundsChanged());
|
||||||
boundsRows.Add((row, binding));
|
boundsRows.Add((row, binding));
|
||||||
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddFontRow(string label, Func<LegacyFont, string> get, Action<LegacyFont, string> set)
|
private void AddFontRow(string label, Func<LegacyFont, string> get, Action<LegacyFont, string> set)
|
||||||
@@ -303,9 +320,25 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddFontToggleRow(string label, Func<LegacyFont, bool> get, Action<LegacyFont, bool> set)
|
/// <summary>
|
||||||
|
/// 글꼴 스타일 세그먼트 — 굵게·기울임·밑줄을 한 줄에.
|
||||||
|
/// 체크박스 하나에 한 줄씩이면 세 줄(약 100px)이고, 기울임은 아예 빠져 있었다.
|
||||||
|
/// </summary>
|
||||||
|
private void AddFontStyleSegment()
|
||||||
{
|
{
|
||||||
AddRow(new ToggleRowViewModel(label), new RowBinding
|
var segment = new SegmentRowViewModel("스타일");
|
||||||
|
segment.Items.Add(NewFontToggle("bold", "굵게", f => f.Bold, (f, v) => f.Bold = v));
|
||||||
|
segment.Items.Add(NewFontToggle("italic", "기울임", f => f.Italic, (f, v) => f.Italic = v));
|
||||||
|
segment.Items.Add(NewFontToggle("underline", "밑줄", f => f.Underline, (f, v) => f.Underline = v));
|
||||||
|
segment.Items.Add(NewFontToggle("strikethrough", "취소선", f => f.Strikeout, (f, v) => f.Strikeout = v));
|
||||||
|
currentSection?.Adopt(segment);
|
||||||
|
Rows.Add(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SegmentItem NewFontToggle(string icon, string label, Func<LegacyFont, bool> get, Action<LegacyFont, bool> set)
|
||||||
|
{
|
||||||
|
var toggle = new ToggleRowViewModel(label);
|
||||||
|
BindRow(toggle, new RowBinding
|
||||||
{
|
{
|
||||||
Get = vm => get(vm.EffectiveFont) ? "True" : "False",
|
Get = vm => get(vm.EffectiveFont) ? "True" : "False",
|
||||||
Set = (vm, value) =>
|
Set = (vm, value) =>
|
||||||
@@ -315,6 +348,7 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
vm.Model.Props.SetText("Font", LegacyFormat.FormatFont(font));
|
vm.Model.Props.SetText("Font", LegacyFormat.FormatFont(font));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
return new SegmentItem(icon, label, toggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>편집 기준 폰트 — 명시 Font 있으면 그 값, 없으면 유효(상속) 폰트 복사본을 물화</summary>
|
/// <summary>편집 기준 폰트 — 명시 Font 있으면 그 값, 없으면 유효(상속) 폰트 복사본을 물화</summary>
|
||||||
@@ -352,6 +386,18 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void AddRow(PropertyRowViewModel row, RowBinding binding, Action? afterCommit = null)
|
private void AddRow(PropertyRowViewModel row, RowBinding binding, Action? afterCommit = null)
|
||||||
|
{
|
||||||
|
BindRow(row, binding, afterCommit);
|
||||||
|
currentSection?.Adopt(row);
|
||||||
|
Rows.Add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 행에 값과 커밋을 배선한다 — <b>Rows 에 넣지는 않는다</b>.
|
||||||
|
/// 쌍 행·세그먼트 행의 자식처럼 화면에는 나오지만 목록의 최상위 항목이 아닌 행에 쓴다.
|
||||||
|
/// 커밋 규약은 여기 한 곳에만 있으므로 자식도 동일하게 동작한다.
|
||||||
|
/// </summary>
|
||||||
|
private void BindRow(PropertyRowViewModel row, RowBinding binding, Action? afterCommit = null)
|
||||||
{
|
{
|
||||||
var items = designer.Selection.Items;
|
var items = designer.Selection.Items;
|
||||||
var values = items.Select(binding.Get).Distinct(StringComparer.Ordinal).ToList();
|
var values = items.Select(binding.Get).Distinct(StringComparer.Ordinal).ToList();
|
||||||
@@ -384,8 +430,6 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
afterCommit?.Invoke();
|
afterCommit?.Invoke();
|
||||||
};
|
};
|
||||||
currentSection?.Adopt(row);
|
|
||||||
Rows.Add(row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenameControl(ControlViewModel vm, string newId)
|
private void RenameControl(ControlViewModel vm, string newId)
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ public abstract class PropertyRowViewModel : ViewModelBase
|
|||||||
/// <summary>선택 대상들의 값이 서로 다른지 — "여러 값" 표시</summary>
|
/// <summary>선택 대상들의 값이 서로 다른지 — "여러 값" 표시</summary>
|
||||||
public bool IsMixed { get; private set; }
|
public bool IsMixed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 이 행이 품고 있는 '속성' 개수 — 섹션 헤더의 건수 표시용.
|
||||||
|
/// 쌍 행·세그먼트 행은 한 줄이지만 속성은 여럿이라, 줄 수를 세면 헤더가 "공통 3"처럼
|
||||||
|
/// 실제보다 적게 나온다. 사용자가 세는 것은 줄이 아니라 속성이다.
|
||||||
|
/// </summary>
|
||||||
|
public virtual int LeafCount => 1;
|
||||||
|
|
||||||
/// <summary>소속 섹션 — null 이면 섹션 밖(항상 표시)</summary>
|
/// <summary>소속 섹션 — null 이면 섹션 밖(항상 표시)</summary>
|
||||||
public SectionRowViewModel? Section { get; set; }
|
public SectionRowViewModel? Section { get; set; }
|
||||||
|
|
||||||
@@ -114,7 +121,14 @@ public sealed class SectionRowViewModel : PropertyRowViewModel
|
|||||||
public string ChevronIcon => isExpanded ? "chevron-down" : "chevron-right";
|
public string ChevronIcon => isExpanded ? "chevron-down" : "chevron-right";
|
||||||
|
|
||||||
/// <summary>접힌 상태에서 몇 개가 숨어 있는지</summary>
|
/// <summary>접힌 상태에서 몇 개가 숨어 있는지</summary>
|
||||||
public string CountText => Children.Count == 0 ? string.Empty : Children.Count.ToString();
|
public string CountText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var total = Children.Sum(c => c.LeafCount);
|
||||||
|
return total == 0 ? string.Empty : total.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>헤더 클릭 — 접기/펴기</summary>
|
/// <summary>헤더 클릭 — 접기/펴기</summary>
|
||||||
public M.Framework.WPF.ICustomCommand? ToggleCommand { get; }
|
public M.Framework.WPF.ICustomCommand? ToggleCommand { get; }
|
||||||
@@ -139,6 +153,78 @@ public sealed class TextRowViewModel : PropertyRowViewModel
|
|||||||
public TextRowViewModel(string label) : base(label) { }
|
public TextRowViewModel(string label) : base(label) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 두 값을 한 줄에 나란히 두는 행 — X|Y, 너비|높이.
|
||||||
|
///
|
||||||
|
/// 자식 행은 <b>평범한 행 그대로</b>다. 값·커밋·"여러 값" 판정이 전부 자식 안에서 끝나므로
|
||||||
|
/// 커밋 규약(Initialize 는 Commit 을 내지 않고, 값이 바뀔 때만 Undo 스냅샷 1회)이 그대로 유지된다.
|
||||||
|
/// 한쪽만 값이 갈리는 다중선택도 자연히 옳게 나온다.
|
||||||
|
/// 이 행 자신은 컨테이너일 뿐이라 Commit 을 갖지 않는다.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PairRowViewModel : PropertyRowViewModel
|
||||||
|
{
|
||||||
|
/// <summary>왼쪽 칸 라벨(짧게 — X, 너비)</summary>
|
||||||
|
public string LeftLabel { get; }
|
||||||
|
|
||||||
|
/// <summary>왼쪽 칸 행</summary>
|
||||||
|
public PropertyRowViewModel Left { get; }
|
||||||
|
|
||||||
|
/// <summary>오른쪽 칸 라벨</summary>
|
||||||
|
public string RightLabel { get; }
|
||||||
|
|
||||||
|
/// <summary>오른쪽 칸 행</summary>
|
||||||
|
public PropertyRowViewModel Right { get; }
|
||||||
|
|
||||||
|
/// <summary>한 줄이지만 속성은 둘</summary>
|
||||||
|
public override int LeafCount => 2;
|
||||||
|
|
||||||
|
public PairRowViewModel(string leftLabel, PropertyRowViewModel left, string rightLabel, PropertyRowViewModel right)
|
||||||
|
: base(leftLabel + "/" + rightLabel)
|
||||||
|
{
|
||||||
|
LeftLabel = leftLabel;
|
||||||
|
Left = left;
|
||||||
|
RightLabel = rightLabel;
|
||||||
|
Right = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>세그먼트 한 칸 — 아이콘 토글 하나</summary>
|
||||||
|
public sealed class SegmentItem
|
||||||
|
{
|
||||||
|
/// <summary>Lucide 아이콘명</summary>
|
||||||
|
public string Icon { get; }
|
||||||
|
|
||||||
|
/// <summary>툴팁(라벨 대용 — 아이콘만으로는 뜻이 안 서는 사용자를 위해)</summary>
|
||||||
|
public string ToolTip { get; }
|
||||||
|
|
||||||
|
/// <summary>실제 값을 들고 있는 토글 행</summary>
|
||||||
|
public ToggleRowViewModel Toggle { get; }
|
||||||
|
|
||||||
|
public SegmentItem(string icon, string toolTip, ToggleRowViewModel toggle)
|
||||||
|
{
|
||||||
|
Icon = icon;
|
||||||
|
ToolTip = toolTip;
|
||||||
|
Toggle = toggle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 토글 묶음을 한 줄 아이콘 세그먼트로 — 굵게·기울임·밑줄.
|
||||||
|
/// 체크박스 하나에 한 줄씩 쓰면 세 줄(약 100px)을 먹는데, 서로 배타적이지 않은 같은 축의
|
||||||
|
/// 속성이라 한 줄에 모으는 편이 읽기도 쉽다. <see cref="PairRowViewModel"/> 과 같은 이유로
|
||||||
|
/// 값은 전부 자식 토글이 들고 있다.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SegmentRowViewModel : PropertyRowViewModel
|
||||||
|
{
|
||||||
|
/// <summary>세그먼트 칸들</summary>
|
||||||
|
public List<SegmentItem> Items { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>한 줄이지만 속성은 칸 수만큼</summary>
|
||||||
|
public override int LeafCount => Items.Count;
|
||||||
|
|
||||||
|
public SegmentRowViewModel(string label) : base(label) { }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>여러 줄 문자열 행(Text/수식/항목 목록)</summary>
|
/// <summary>여러 줄 문자열 행(Text/수식/항목 목록)</summary>
|
||||||
public sealed class MultilineTextRowViewModel : PropertyRowViewModel
|
public sealed class MultilineTextRowViewModel : PropertyRowViewModel
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,17 +11,73 @@
|
|||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<ctl:IconNameToVisualConverter x:Key="IconName"/>
|
<ctl:IconNameToVisualConverter x:Key="IconName"/>
|
||||||
|
|
||||||
<!-- 행 VM 타입별 암시적 템플릿 -->
|
<!--
|
||||||
|
라벨 폭. 실측상 가장 긴 라벨이 11자("입력 파라미터 컨트롤", "선택지(줄바꿈 구분)")라
|
||||||
|
86px 에서는 "액션 대상 컨…" 처럼 잘렸다. 92px + 줄바꿈이면 두 줄로 접혀 전부 읽힌다
|
||||||
|
(자르기보다 낫다 — 잘린 라벨은 툴팁을 띄워봐야 아는 정보가 된다).
|
||||||
|
-->
|
||||||
<Style x:Key="RowLabel" TargetType="TextBlock">
|
<Style x:Key="RowLabel" TargetType="TextBlock">
|
||||||
<Setter Property="Width" Value="86"/>
|
<Setter Property="Width" Value="92"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
|
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
|
||||||
<Setter Property="FontSize" Value="12"/>
|
<Setter Property="FontSize" Value="12"/>
|
||||||
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
|
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,6,0"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="RowEditor" TargetType="Control">
|
|
||||||
|
<!--
|
||||||
|
인스펙터 전용 입력 치수 — 전역 TextBox 는 MinHeight 30 이라 한 화면에 몇 행 못 들어간다
|
||||||
|
(TextBox 하나만 21행이면 700px 를 넘겨 스크롤이 필수가 된다). 여기서만 26 으로 낮춘다.
|
||||||
|
전역 스타일을 낮추면 검색칩·대화상자 입력까지 같이 줄어드니 반드시 지역 스타일이어야 한다.
|
||||||
|
-->
|
||||||
|
<Style x:Key="RowInput" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
|
||||||
|
<Setter Property="MinHeight" Value="26"/>
|
||||||
<Setter Property="FontSize" Value="12"/>
|
<Setter Property="FontSize" Value="12"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
<!-- ComboBox 는 테마가 MinHeight 가 아니라 Height 를 '고정'한다 — MinHeight 만 낮추면
|
||||||
|
선택 행만 30px 로 남아 다른 행과 4px 어긋난다. 반드시 Height 를 내려야 한다. -->
|
||||||
|
<Style x:Key="RowCombo" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||||
|
<Setter Property="Height" Value="26"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- 쌍 행의 짧은 라벨(X/Y/너비/높이) — 값 칸을 최대한 넓게 남긴다 -->
|
||||||
|
<Style x:Key="PairLabel" TargetType="TextBlock">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
|
||||||
|
<Setter Property="FontSize" Value="11.5"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,5,0"/>
|
||||||
|
<Setter Property="MinWidth" Value="22"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- 세그먼트 한 칸 — 붙어 있는 아이콘 토글(굵게/기울임/밑줄/취소선) -->
|
||||||
|
<!-- 폭 30 + 간격 2 = 4칸 128px. 라벨 92 와 합쳐 220px 라 패널 최소폭(250 → 행 가용 221)에 들어간다 -->
|
||||||
|
<Style x:Key="SegmentToggle" TargetType="ToggleButton">
|
||||||
|
<Setter Property="Width" Value="30"/>
|
||||||
|
<Setter Property="Height" Value="26"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ToggleButton">
|
||||||
|
<Border x:Name="bd" Background="{DynamicResource B.Input}"
|
||||||
|
BorderBrush="{DynamicResource B.InputBorder}" BorderThickness="1"
|
||||||
|
Margin="0,0,2,0" CornerRadius="6" SnapsToDevicePixels="True">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Line2}"/>
|
||||||
|
</Trigger>
|
||||||
|
<!-- 켜짐은 채움 + 강조 테두리 둘 다 — 채움만으로는 라이트에서 1.1:1 이라 안 읽힌다 -->
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Accent2}"/>
|
||||||
|
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
"여러 값" 표시 — 다중선택에서 값이 갈리면 편집기가 빈 칸으로 보인다.
|
"여러 값" 표시 — 다중선택에서 값이 갈리면 편집기가 빈 칸으로 보인다.
|
||||||
@@ -93,18 +149,64 @@
|
|||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"/>
|
<TextBox Style="{StaticResource RowInput}" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"/>
|
||||||
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<!-- 두 값 한 줄 — X|Y, 너비|높이. 자식 행이 값·커밋을 그대로 들고 있다 -->
|
||||||
|
<DataTemplate DataType="{x:Type ins:PairRowViewModel}">
|
||||||
|
<Grid Margin="0,2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<DockPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="{Binding LeftLabel}" Style="{StaticResource PairLabel}"/>
|
||||||
|
<Grid DataContext="{Binding Left}">
|
||||||
|
<TextBox Style="{StaticResource RowInput}" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}" Text="여러"/>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
<DockPanel Grid.Column="2">
|
||||||
|
<TextBlock Text="{Binding RightLabel}" Style="{StaticResource PairLabel}"/>
|
||||||
|
<Grid DataContext="{Binding Right}">
|
||||||
|
<TextBox Style="{StaticResource RowInput}" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}" Text="여러"/>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<!-- 아이콘 토글 묶음 — 굵게/기울임/밑줄/취소선 -->
|
||||||
|
<DataTemplate DataType="{x:Type ins:SegmentRowViewModel}">
|
||||||
|
<DockPanel Margin="0,2">
|
||||||
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
||||||
|
<ItemsControl ItemsSource="{Binding Items}" HorizontalAlignment="Left">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate><StackPanel Orientation="Horizontal"/></ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ToggleButton Style="{StaticResource SegmentToggle}"
|
||||||
|
IsChecked="{Binding Toggle.IsOn}"
|
||||||
|
IsThreeState="{Binding Toggle.IsMixed, Mode=OneWay}"
|
||||||
|
ToolTip="{Binding ToolTip}"
|
||||||
|
Content="{Binding Icon, Converter={StaticResource IconName}, ConverterParameter=14|B.Ink}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</DockPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type ins:MultilineTextRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:MultilineTextRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" VerticalAlignment="Top"
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" VerticalAlignment="Top"
|
||||||
Margin="0,4,0,0" ToolTip="{Binding Label}"/>
|
Margin="0,4,0,0" ToolTip="{Binding Label}"/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
<TextBox Style="{StaticResource RowInput}" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"
|
||||||
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="48" MaxHeight="120"
|
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="48" MaxHeight="120"
|
||||||
VerticalScrollBarVisibility="Auto"/>
|
VerticalScrollBarVisibility="Auto"/>
|
||||||
<TextBlock Style="{StaticResource MixedHintOverlay}" VerticalAlignment="Top" Margin="7,7,0,0"/>
|
<TextBlock Style="{StaticResource MixedHintOverlay}" VerticalAlignment="Top" Margin="7,7,0,0"/>
|
||||||
@@ -116,7 +218,7 @@
|
|||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"/>
|
<TextBox Style="{StaticResource RowInput}" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"/>
|
||||||
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
@@ -136,7 +238,7 @@
|
|||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ComboBox ItemsSource="{Binding Choices}" SelectedItem="{Binding ValueText}" FontSize="12"/>
|
<ComboBox Style="{StaticResource RowCombo}" ItemsSource="{Binding Choices}" SelectedItem="{Binding ValueText}"/>
|
||||||
<TextBlock Style="{StaticResource MixedHintOverlay}" Margin="9,0,0,0"/>
|
<TextBlock Style="{StaticResource MixedHintOverlay}" Margin="9,0,0,0"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
@@ -145,11 +247,13 @@
|
|||||||
<DataTemplate DataType="{x:Type ins:TagPickerRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:TagPickerRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
||||||
<Button DockPanel.Dock="Right" Content="…" Width="24" Margin="4,0,0,0"
|
<!-- 26px 칩 옆에서 "…" 는 점 하나로 보인다 — 아이콘 버튼으로 -->
|
||||||
Command="{Binding BrowseCommand}" ToolTip="목록에서 선택"/>
|
<Button DockPanel.Dock="Right" Width="28" Height="26" Padding="0" Margin="4,0,0,0"
|
||||||
|
Command="{Binding BrowseCommand}" ToolTip="목록에서 선택"
|
||||||
|
Content="{Binding Source=list, Converter={StaticResource IconName}, ConverterParameter=13}"/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
<TextBox Style="{StaticResource RowInput}" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"
|
||||||
ToolTip="직접 입력 또는 … 버튼으로 선택"/>
|
ToolTip="직접 입력하거나 오른쪽 버튼으로 목록에서 선택"/>
|
||||||
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
@@ -182,7 +286,7 @@
|
|||||||
<DataTemplate DataType="{x:Type ins:QueryRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:QueryRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
||||||
<Button DockPanel.Dock="Right" Content="편집…" Padding="6,1" Margin="4,0,0,0"
|
<Button DockPanel.Dock="Right" Content="편집…" Padding="8,2" Height="26" Margin="4,0,0,0"
|
||||||
Command="{Binding EditCommand}" ToolTip="쿼리 편집기 열기 (데이터소스 더블클릭과 동일)"/>
|
Command="{Binding EditCommand}" ToolTip="쿼리 편집기 열기 (데이터소스 더블클릭과 동일)"/>
|
||||||
<TextBlock Text="{Binding Summary}" FontSize="11" Foreground="{DynamicResource B.Muted}"
|
<TextBlock Text="{Binding Summary}" FontSize="11" Foreground="{DynamicResource B.Muted}"
|
||||||
VerticalAlignment="Center" TextTrimming="CharacterEllipsis"
|
VerticalAlignment="Center" TextTrimming="CharacterEllipsis"
|
||||||
@@ -194,13 +298,14 @@
|
|||||||
<StackPanel Margin="0,2">
|
<StackPanel Margin="0,2">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
||||||
<Button DockPanel.Dock="Right" Content="…" Padding="8,1" Margin="4,0,0,0"
|
<Button DockPanel.Dock="Right" Width="28" Height="26" Padding="0" Margin="4,0,0,0"
|
||||||
Command="{Binding BrowseCommand}"
|
Command="{Binding BrowseCommand}"
|
||||||
ToolTip="마스크 편집기 — 자주 쓰는 마스크 목록과 미리보기"/>
|
ToolTip="마스크 편집기 — 자주 쓰는 마스크 목록과 미리보기"
|
||||||
|
Content="{Binding Source=list, Converter={StaticResource IconName}, ConverterParameter=13}"/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
<TextBox Style="{StaticResource RowInput}" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"
|
||||||
FontFamily="Consolas, D2Coding, 굴림체"
|
FontFamily="Consolas, D2Coding, 굴림체"
|
||||||
ToolTip="0=필수 숫자, 9=선택 숫자, #=숫자·부호 — 오른쪽 … 버튼에서 목록·미리보기"/>
|
ToolTip="0=필수 숫자, 9=선택 숫자, #=숫자·부호 — 오른쪽 버튼에서 목록·미리보기"/>
|
||||||
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
@@ -215,7 +320,7 @@
|
|||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
||||||
<!-- 스와치 = 피커 버튼(클릭 → 색상 선택 대화상자), 직접 입력도 병행 -->
|
<!-- 스와치 = 피커 버튼(클릭 → 색상 선택 대화상자), 직접 입력도 병행 -->
|
||||||
<Button DockPanel.Dock="Right" Width="26" Height="22" Margin="4,0,0,0" Padding="0"
|
<Button DockPanel.Dock="Right" Width="30" Height="26" Margin="4,0,0,0" Padding="0"
|
||||||
Command="{Binding PickCommand}" Cursor="Hand"
|
Command="{Binding PickCommand}" Cursor="Hand"
|
||||||
ToolTip="클릭하여 색상 선택">
|
ToolTip="클릭하여 색상 선택">
|
||||||
<Button.Template>
|
<Button.Template>
|
||||||
@@ -231,7 +336,7 @@
|
|||||||
</Button.Template>
|
</Button.Template>
|
||||||
</Button>
|
</Button>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
<TextBox Style="{StaticResource RowInput}" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"
|
||||||
ToolTip="R, G, B 또는 색 이름 (예: 224, 224, 224 / White) — 오른쪽 스와치 클릭 시 피커"/>
|
ToolTip="R, G, B 또는 색 이름 (예: 224, 224, 224 / White) — 오른쪽 스와치 클릭 시 피커"/>
|
||||||
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -307,7 +307,9 @@
|
|||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="300" MinWidth="220" MaxWidth="560"/>
|
<!-- 최소 250 — 글꼴 스타일 행이 라벨 92 + 토글 4칸 128 = 220px 를 요구한다
|
||||||
|
(행 가용폭 = 폭 − 스크롤바 9 − 여백 20). 220 이면 마지막 토글이 잘린다. -->
|
||||||
|
<ColumnDefinition Width="300" MinWidth="250" MaxWidth="560"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- 좌: 알약 탭 — 서식 목록 / 레이어 / 도구 상자 -->
|
<!-- 좌: 알약 탭 — 서식 목록 / 레이어 / 도구 상자 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user