Compare commits
2
Commits
f4e239075d
...
73095c1415
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73095c1415 | ||
|
|
f8531cb7e1 |
@@ -14,6 +14,17 @@ public static class LegacyTypeCatalog
|
||||
/// <summary>페이지 루트 클래스 단축명</summary>
|
||||
public const string HostClassName = "MDesignerHost";
|
||||
|
||||
/// <summary>
|
||||
/// 데이터소스의 <b>중립</b> 타입명 — 레거시 클래스명은 <c>MDataTable</c> 이고 여기서 이 이름으로 바뀐다.
|
||||
///
|
||||
/// <b>상수로 두는 이유.</b> 두 이름이 공존해서 실제로 사고가 났다.
|
||||
/// 배선 실행기가 레거시 이름(<c>MDataTable</c>)으로 컨트롤을 찾고 있었는데,
|
||||
/// 서식을 XML 에서 읽으면 그 시점에 이미 중립 이름으로 바뀌어 있어 <b>한 개도 못 찾았다</b> —
|
||||
/// 실사용 서식의 데이터소스 배선이 전부 "이 서식에 없습니다"로 죽었다.
|
||||
/// 문자열을 각자 적는 한 같은 일이 또 난다.
|
||||
/// </summary>
|
||||
public const string DataTableType = "DataTable";
|
||||
|
||||
/// <summary>페이지 루트 AQN (실샘플 sheetdesign.xml 원문 확인)</summary>
|
||||
public const string HostAqn =
|
||||
"M.EMR.UserControl.MDesignerHost, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
|
||||
@@ -42,7 +53,7 @@ public static class LegacyTypeCatalog
|
||||
["MPictureBox"] = "PictureBox",
|
||||
["MCalcBox"] = "CalcBox",
|
||||
["MButton"] = "Button",
|
||||
["MDataTable"] = "DataTable",
|
||||
["MDataTable"] = DataTableType,
|
||||
// Spread 는 읽기 매핑만(렌더) — E_SpdMst 디자인 없이는 신규 생성 불가하므로 쓰기 매핑/팔레트 미등록
|
||||
["Spread"] = "Spread",
|
||||
};
|
||||
|
||||
@@ -2266,7 +2266,7 @@ public static class DbSmoke
|
||||
probeDoc.Pages.Add(probePage);
|
||||
var probeSource = new SheetMe.Core.Models.ControlElement
|
||||
{
|
||||
Type = "MDataTable", Id = "MDataTable1",
|
||||
Type = SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType, Id = "MDataTable1",
|
||||
Bounds = new SheetMe.Core.Models.LayoutRect { X = 0, Y = 0, W = 20, H = 20 },
|
||||
};
|
||||
// 스칼라 치환과 DataRow 치환을 한 문장에 넣는다 — 둘은 다른 코드 경로다
|
||||
|
||||
@@ -462,7 +462,7 @@ public static class DialogShots
|
||||
{
|
||||
var source = new SheetMe.Core.Models.ControlElement
|
||||
{
|
||||
Type = "MDataTable", Id = id,
|
||||
Type = SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType, Id = id,
|
||||
Bounds = new SheetMe.Core.Models.LayoutRect { X = 0, Y = 0, W = 20, H = 20 },
|
||||
};
|
||||
source.Props.SetText("Query", query);
|
||||
|
||||
@@ -392,7 +392,7 @@ public static class EditSmoke
|
||||
}
|
||||
host.Children.Add(new ControlElement
|
||||
{
|
||||
Type = "MDataTable", Id = "안쪽데이터소스",
|
||||
Type = SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType, Id = "안쪽데이터소스",
|
||||
Bounds = new LayoutRect { X = 10, Y = 90, W = 120, H = 40 },
|
||||
});
|
||||
boxedPage.Controls.Add(host);
|
||||
@@ -736,7 +736,7 @@ public static class EditSmoke
|
||||
fieldDoc.Pages.Add(fieldPage);
|
||||
var source = new ControlElement
|
||||
{
|
||||
Type = "MDataTable", Id = "MDataTable1",
|
||||
Type = SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType, Id = "MDataTable1",
|
||||
Bounds = new LayoutRect { X = 0, Y = 0, W = 20, H = 20 },
|
||||
};
|
||||
source.Props.SetText("Query", "SELECT 'ㄱ' NAM FROM DUAL");
|
||||
@@ -1112,6 +1112,95 @@ public static class EditSmoke
|
||||
$"ValueText='{choiceRow.ValueText}' IsMixed={choiceRow.IsMixed}");
|
||||
}
|
||||
|
||||
// 15-a0000) 레이어 목록이 계층과 범위를 말하는가.
|
||||
//
|
||||
// 사용자가 콕 집어 "레이어 구분이 잘 안 된다"고 한 곳이다. 원인은 색이 아니라 <b>산수</b>였다 —
|
||||
// 셰브론이 자식 없는 행에서 Collapsed 로 사라지며 폭 16 이 통째로 없어지는데
|
||||
// 컨테이너 자식 들여쓰기도 정확히 16 이라, 부모와 자식의 아이콘이 같은 x 에 놓였다.
|
||||
{
|
||||
var tree = new DesignerViewModel(new FormDesignDataBusiness().CreateNew());
|
||||
tree.AddControlAt("RadioButton", new Point(100, 100));
|
||||
tree.AddControlAt("RadioButton", new Point(100, 140));
|
||||
tree.Selection.Set(tree.Pages[0].Controls.ToList(), tree.Pages[0].Controls[0]);
|
||||
tree.GroupSelection();
|
||||
var wrapped = tree.Pages[0].Controls.OfType<ViewModels.Controls.ContainerViewModel>().First();
|
||||
|
||||
Check("레이어: 컨테이너가 자식 수를 말한다", wrapped.LayerChildCountText == "(2)",
|
||||
wrapped.LayerChildCountText);
|
||||
|
||||
// 중첩까지 센다 — "이걸 지우면 몇 개가 같이 지워지나"가 그 수다.
|
||||
// 중첩 구조는 모델로 직접 짠다(Ctrl+G 를 두 번 겹치는 경로는 이 검사의 대상이 아니다).
|
||||
var nestDoc = new FormDocument();
|
||||
var nestPage = new FormPage { Width = 720, Height = 856 };
|
||||
nestDoc.Pages.Add(nestPage);
|
||||
var outerModel = new ControlElement
|
||||
{
|
||||
Type = "Panel", Id = "바깥",
|
||||
Bounds = new LayoutRect { X = 20, Y = 20, W = 300, H = 200 },
|
||||
};
|
||||
var innerModel = new ControlElement
|
||||
{
|
||||
Type = "Panel", Id = "안쪽",
|
||||
Bounds = new LayoutRect { X = 10, Y = 10, W = 200, H = 120 },
|
||||
};
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
innerModel.Children.Add(new ControlElement
|
||||
{
|
||||
Type = "RadioButton", Id = $"보기{i}",
|
||||
Bounds = new LayoutRect { X = 10, Y = 10 + (i * 30), W = 100, H = 20 },
|
||||
});
|
||||
}
|
||||
outerModel.Children.Add(innerModel);
|
||||
nestPage.Controls.Add(outerModel);
|
||||
var nested = new DesignerViewModel(nestDoc);
|
||||
|
||||
var outer = nested.Pages[0].Controls.OfType<ViewModels.Controls.ContainerViewModel>().First();
|
||||
var inner = outer.Children.OfType<ViewModels.Controls.ContainerViewModel>().First();
|
||||
Check("레이어: 자식 수는 중첩까지 센다", outer.LayerChildCountText == "(3)",
|
||||
$"{outer.LayerChildCountText} (바깥 = 안쪽 패널 1 + 라디오 2)");
|
||||
|
||||
// 컨테이너를 고르면 그 안이 밴드로 묶여야 한다
|
||||
nested.Selection.SetSingle(outer);
|
||||
Check("레이어: 고른 컨테이너의 안쪽이 범위로 표시된다",
|
||||
inner.Highlight == LayerHighlight.Navy && outer.Highlight == LayerHighlight.Blue,
|
||||
$"바깥={outer.Highlight} 안쪽={inner.Highlight}");
|
||||
Check("레이어: 손자까지 범위에 들어간다",
|
||||
inner.Children.All(c => c.Highlight == LayerHighlight.Navy),
|
||||
string.Join(",", inner.Children.Select(c => c.Highlight.ToString())));
|
||||
}
|
||||
|
||||
// 15-a000) 데이터소스를 <b>실사용과 같은 경로</b>로 만들었을 때도 러너가 찾는가.
|
||||
//
|
||||
// 이 검사가 없어서 배선 전체가 실사용에서 죽어 있는 것을 오래 못 봤다.
|
||||
// 러너는 레거시 클래스명("MDataTable")으로 컨트롤을 찾고 있었는데, 서식을 XML 에서 읽으면
|
||||
// 그 순간 중립 이름("DataTable")으로 바뀌어 있어 <b>한 개도 못 모았다</b> —
|
||||
// 모든 DataTableField 가 "이 서식에 없습니다"로 실패해 종이에서 빈칸이 됐다.
|
||||
// 그런데 진단들이 ControlElement 를 <b>손으로</b> 만들며 같은 레거시 이름을 써서
|
||||
// 러너의 오타와 짝이 맞아 전부 초록이었다. 그래서 여기서는 손으로 만들지 않는다.
|
||||
{
|
||||
Check("타입명: 레거시 MDataTable 은 중립 DataTable 로 읽힌다",
|
||||
SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType == "DataTable");
|
||||
|
||||
var dsDoc = new DesignerViewModel(new FormDesignDataBusiness().CreateNew());
|
||||
dsDoc.AddControlAt("DataTable", new Point(60, 60)); // 팔레트가 쓰는 그 경로
|
||||
var made = dsDoc.Pages[0].Controls.Single(c => c.Type == "DataTable");
|
||||
made.Model.Props.SetText("Query", "SELECT 1 FROM DUAL");
|
||||
|
||||
var runner = new Services.MDataTableRunner(dsDoc.Document, null);
|
||||
Check("배선: 팔레트로 만든 데이터소스를 러너가 찾는다",
|
||||
runner.Names.Count == 1 && runner.Names[0] == made.Id,
|
||||
runner.Names.Count == 0 ? "0개 — 러너가 타입명을 잘못 보고 있다"
|
||||
: string.Join(",", runner.Names));
|
||||
|
||||
// 이름으로 가리키는 배선이 실제로 그 데이터소스에 닿는가(값은 DB 없이 못 내지만
|
||||
// '이 서식에 없습니다'로 떨어지면 안 된다)
|
||||
var spec = SheetMe.Core.Catalog.DataTableFieldSpec.Parse($"{made.Id}.Rows(0).Item(\"NAM\")");
|
||||
var reason = spec is null ? "(형식 파싱 실패)" : runner.Resolve(spec).Text;
|
||||
Check("배선: '이 서식에 없습니다'로 떨어지지 않는다",
|
||||
!reason.Contains("이 서식에 없습니다", StringComparison.Ordinal), reason);
|
||||
}
|
||||
|
||||
// 15-a00) 아이콘 기본색이 검정으로 떨어지지 않는다.
|
||||
//
|
||||
// 색도 리소스 키도 안 준 아이콘이 <c>Brushes.Black</c> 으로 칠해지고 있었다.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using SheetMe.Core.Catalog;
|
||||
using SheetMe.Core.Models;
|
||||
using SheetMe.Core.Serialization;
|
||||
using SheetMe.Data.Stores;
|
||||
|
||||
namespace SheetMe.Designer.Services;
|
||||
@@ -141,7 +142,13 @@ public sealed class MDataTableRunner : IDataFieldResolver
|
||||
{
|
||||
foreach (var control in controls)
|
||||
{
|
||||
if (control.Type == "MDataTable")
|
||||
// <b>중립 타입명으로 비교한다.</b> 여기가 레거시 클래스명("MDataTable")을 보고 있어서
|
||||
// 실사용 서식에서는 데이터소스를 <b>한 개도 못 모았다</b> — XML 에서 읽는 순간
|
||||
// LegacyTypeCatalog 가 이미 "DataTable" 로 바꿔 두기 때문이다.
|
||||
// 그 결과 모든 DataTableField 배선이 "데이터소스 X 이 이 서식에 없습니다"로 실패해
|
||||
// 미리보기·인쇄에서 빈칸이 됐고, 미리보기의 '데이터소스...' 버튼은 영영 회색이었다.
|
||||
// 진단들이 ControlElement 를 손으로 만들며 같은 레거시 이름을 써서 초록으로 통과하고 있었다.
|
||||
if (string.Equals(control.Type, LegacyTypeCatalog.DataTableType, StringComparison.Ordinal))
|
||||
{
|
||||
queries[control.Id] = control.Props.GetText("Query") ?? string.Empty;
|
||||
}
|
||||
|
||||
@@ -136,6 +136,30 @@ public abstract class ContainerViewModel : ControlViewModel
|
||||
/// <summary>레이어 목록에서 펼침 셰브론을 그릴지 — 자식이 있을 때만</summary>
|
||||
public override bool HasLayerChildren => Children.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// 레이어 목록에 붙는 자식 수 — 접혀 있어도 <b>무엇이 딸려 있는지</b> 알 수 있어야 한다.
|
||||
///
|
||||
/// 페이지 행에는 (7), 그룹 폴더에는 (3)이 이미 붙는데 컨테이너만 없었다.
|
||||
/// 그래서 접힌 패널이 3개를 품었는지 300개를 품었는지 펴 보기 전에는 알 수 없었고,
|
||||
/// 그 패널을 지우거나 옮기기 전에 무엇이 함께 가는지도 알 수 없었다.
|
||||
/// 중첩까지 세어 실제로 딸려 가는 수를 말한다.
|
||||
/// </summary>
|
||||
public string LayerChildCountText => $"({CountAll(Children)})";
|
||||
|
||||
private static int CountAll(IEnumerable<ControlViewModel> items)
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
total++;
|
||||
if (item is ContainerViewModel container)
|
||||
{
|
||||
total += CountAll(container.Children);
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
protected ContainerViewModel(ControlElement model) : base(model) { }
|
||||
|
||||
/// <summary>시각 재해석 — 자식에게 상속 컨텍스트 전파(WinForms Font/ForeColor 상속)</summary>
|
||||
|
||||
@@ -2611,6 +2611,19 @@ public sealed class DesignerViewModel : ViewModelBase
|
||||
=> total.TryGetValue(id, out var t) && t > 0
|
||||
&& selected.TryGetValue(id, out var s) && s == t;
|
||||
|
||||
// 조상 중에 '선택된 컨테이너'가 있는가 — 선택한 패널의 <b>범위</b>를 목록에 그리기 위한 판정
|
||||
static bool HasSelectedContainerAncestor(ControlViewModel viewModel)
|
||||
{
|
||||
for (var parent = viewModel.Parent; parent is not null; parent = parent.Parent)
|
||||
{
|
||||
if (parent.IsSelected)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var states = new LayerHighlight[LayerRows.Count];
|
||||
for (var i = 0; i < LayerRows.Count; i++)
|
||||
{
|
||||
@@ -2620,6 +2633,12 @@ public sealed class DesignerViewModel : ViewModelBase
|
||||
ControlViewModel c when c.Model.GroupId is { } gid && GroupFullySelected(gid) => LayerHighlight.Navy,
|
||||
ControlViewModel c when c.IsSelected => LayerHighlight.Blue,
|
||||
LayerGroupViewModel g when GroupFullySelected(g.GroupId) => LayerHighlight.Blue,
|
||||
// 선택한 컨테이너 안에 있는 것 — 그룹 판정보다 <b>뒤에</b> 둔다(그룹 색을 이기면 안 된다).
|
||||
//
|
||||
// 캔버스에서 패널을 끌면 자식이 전부 따라 움직이는데 목록은 그 사실을 말하지 않았다.
|
||||
// 밴드가 붙으면 "이걸 지우면 무엇이 같이 지워지나"가 선택 즉시 보인다 —
|
||||
// 접었다 펴 보지 않아도 된다.
|
||||
ControlViewModel c when HasSelectedContainerAncestor(c) => LayerHighlight.Navy,
|
||||
_ => LayerHighlight.None,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -283,13 +283,41 @@
|
||||
<ToggleButton DockPanel.Dock="Right" Style="{StaticResource LayerRowToggle}"
|
||||
IsChecked="{Binding IsHiddenFlag}" ToolTip="숨김"
|
||||
Content="{Binding HiddenIconSpec, Converter={StaticResource IconSpec}}"/>
|
||||
<!--
|
||||
컨테이너의 자식 수 — 접혀 있어도 무엇이 딸려 있는지 보여야 한다.
|
||||
페이지 행과 그룹 폴더에는 이미 개수가 붙는데 컨테이너만 없었다.
|
||||
중첩까지 세므로 "이걸 지우면 몇 개가 같이 지워지나"가 그대로 읽힌다.
|
||||
-->
|
||||
<TextBlock DockPanel.Dock="Right" VerticalAlignment="Center" FontSize="11"
|
||||
Margin="4,0,2,0" Text="{Binding LayerChildCountText}"
|
||||
Visibility="{Binding HasLayerChildren, Converter={StaticResource BoolToVisibility}}">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
|
||||
<Style.Triggers>
|
||||
<!-- 선택 밴드 위에서 B.Muted 는 1.03:1 — 개수가 통째로 사라진다 -->
|
||||
<DataTrigger Binding="{Binding Highlight}" Value="Blue">
|
||||
<Setter Property="Foreground" Value="{DynamicResource B.OnSelMuted}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<!-- 계층 들여쓰기: 페이지 직속 12, 그룹 멤버 32, 컨테이너 자식 +16 -->
|
||||
<Border DockPanel.Dock="Left" Width="{Binding LayerIndent}"/>
|
||||
<!-- 컨테이너(패널/그룹박스)만 자식 접기 셰브론을 갖는다 -->
|
||||
<ToggleButton DockPanel.Dock="Left" Style="{StaticResource MiniChevron}" Width="16"
|
||||
IsChecked="{Binding IsLayerExpanded}" ToolTip="자식 접기/펼치기"
|
||||
Visibility="{Binding HasLayerChildren, Converter={StaticResource BoolToVisibility}}"
|
||||
Content="{Binding LayerChevronSpec, Converter={StaticResource IconSpec}}"/>
|
||||
<!--
|
||||
컨테이너(패널/그룹박스)만 자식 접기 셰브론을 갖는다.
|
||||
<b>없을 때도 16px 칸은 남긴다.</b> 전에는 셰브론이 Collapsed 로 사라져
|
||||
폭 16 이 통째로 없어졌는데, 컨테이너 자식 들여쓰기도 정확히 16 이라
|
||||
<b>부모와 자식의 아이콘이 같은 x 에 놓였다</b> — 목록만 보면 형제로 읽힌다.
|
||||
계층을 나타내는 유일한 수단인 들여쓰기가 셰브론 폭에 정확히 상쇄되고 있었다.
|
||||
-->
|
||||
<Border DockPanel.Dock="Left" Width="16">
|
||||
<ToggleButton Style="{StaticResource MiniChevron}" Width="16"
|
||||
IsChecked="{Binding IsLayerExpanded}" ToolTip="자식 접기/펼치기"
|
||||
Visibility="{Binding HasLayerChildren, Converter={StaticResource BoolToVisibility}}"
|
||||
Content="{Binding LayerChevronSpec, Converter={StaticResource IconSpec}}"/>
|
||||
</Border>
|
||||
<ContentControl DockPanel.Dock="Left" Margin="0,0,7,0" VerticalAlignment="Center" IsTabStop="False"
|
||||
Content="{Binding LayerIconSpec, Converter={StaticResource IconSpec}}"/>
|
||||
<!-- 데이터 배선 배지 — 값이 조회/데이터소스에서 오는 컨트롤 -->
|
||||
|
||||
Reference in New Issue
Block a user