71862c2986b52d7a9b4bb2907da11f219b80c927
[자격증명] 접속 문자열 우선순위를 환경변수 > appsettings.Development.json > appsettings.json > ServerInfo ini 로 세웠다. 병원 설치본은 설정 없이 [000]bin 의 MSYSTECH_ServerInfo.ini 를 자동으로 찾아 붙는다(레거시와 동일한 MD5 → 3DES-ECB 복호). - ServerInfoReader 를 [200]SheetMe 에서 복사 이식(vendoring — 두 저장소가 분리돼 있어 ProjectReference 불가, 레거시 암복호 규약은 변할 이유가 없는 고정 자산). 헤더에 동기화 의무 명시. - 실측 검증: ini 566라인/병원 엔트리 76개, CurrentServer=029.BSGH 복호 후 실제 접속 성공. - SaveMode 기본값을 File 로 되돌렸다. ini 의 CurrentServer 가 운영 병원 DB 를 가리키므로 DB 쓰기는 명시적으로 켠 단말에서만 활성화되어야 한다. - ConfigService 로 단일 소유화 — ConfigLoader.Load() 11회 호출이 ini 파싱 + 3DES 복호를 매번 반복하던 것을 1회로. 죽어 있던 Designer:GridSize/SnapThreshold 를 SnapEngine 에 배선하고, 쓰이지 않던 His:Provider 와 DefaultPaperWidth/Height 는 제거했다(후자는 설정이 아니라 레거시 패리티 상수다). [산출물] 8.6MB/34파일 → 7.2MB/26파일. - EmrDataContext 제거 — M.Framework.DBAccess/TableFramework/M.MW.Data.EMR 의 유일한 소비처였는데 그 클래스가 어디서도 인스턴스화되지 않았다. DB 접근은 전부 raw Oracle 클라이언트를 쓴다. - Microsoft.Web.WebView2 는 ExcludeAssets="runtime" — M.Framework.WPF 전이 의존일 뿐 소스 참조 0건. [000]bin 과의 이름 충돌 3건도 함께 사라진다. - Production.pubxml(win-x64, FDD, SatelliteResourceLanguages=ko). RID 를 csproj 가 아니라 pubxml 에 둔 이유는 csproj 에 넣으면 dotnet build/test 까지 RID 별 복원을 타기 때문이다. - tools/publish.ps1 — 비밀값 하드 게이트 + [000]bin 충돌 경고 + SHA256 매니페스트 + zip. 게이트는 역방향으로 검증했다(appsettings.json 에 실접속 정보를 넣고 실행 → 정상 차단). - nuget.config 신설 — 사내 피드가 개발자 개인 OneDrive 경로라 다른 머신에서 복원이 불가능했다. %MSYS_NUGET_FEED% 환경변수로 받게 해 최소한 실패 원인이 드러나게 했다. [배포 규약] docs/DEPLOYMENT.md. [000]bin 최상위 평면 복사를 금지한다 — 실측 결과 Oracle.ManagedDataAccess.dll 이 겹치고 (신규 .NET Core 5,434KB ↔ 기존 .NET FW 4,602KB), 덮으면 그 폴더의 레거시 EXE 187개가 전부 Oracle 접속 불능이 된다. [000]bin\SheetMe\ 하위 폴더에 둔다 — Information/Log/ OCR서식생성기/SpreadDesign 등 기존 앱들과 같은 방식이다. FDD 로 배포한다: [000]bin\OCR서식생성기 가 이미 net10.0 + WindowsDesktop.App 10.0.0 을 요구하며 운영 중이라 런타임 존재가 확인된다. 없는 단말이 나오면 -SelfContained 한 번이면 된다. [로깅] AppLog — LogManager 배선. 모든 호출을 try/catch 로 감싸 로깅 실패가 업무를 막지 않게 했다. - Redact 필수 — 접속 문자열을 값으로 들고 다니므로 예외 메시지에 자격증명이 섞일 수 있다. 기록 직전 1회 통과시킨다. - LogLevel 은 열거형이 아니라 문자열 속성이라 오타를 컴파일러가 못 잡고, 잘못된 값이면 FIXED 만 남고 나머지가 조용히 사라진다. LogType 열거값의 이름으로만 지정하게 했다. - 문서에 있는 HandleShutdown 은 6.0.0 DLL 에 실제로는 없어(XML 문서가 앞서 있음) 쓰지 않는다. 대신 기록이 비동기 배치라 스모크에서 짧게 폴링해 확인한다. - 로그 경로는 실행 폴더\logs\Designer, 쓰기 불가 시 %LocalAppData% 폴백(쓰기 프로브까지 확인). [전역 예외] Dispatcher/AppDomain/TaskScheduler 3종을 진단 분기보다 앞에 등록했다. UI 예외는 기록 후 계속 진행한다(편집 중 문서를 예외 하나로 잃지 않게) — 단 10초 내 5회면 무한 팝업 루프이므로 강제 종료한다. DialogService.ShowError 도입 — 우리가 던진 안내성 예외는 메시지를 그대로 보여주고, 그 외는 일반화 문구 + 오류 코드만 노출한다(코드가 로그 줄머리와 같아 전화 한 통으로 특정된다). ex.ToString() 전문을 그대로 띄우던 2곳을 정리했다. 로그인/권한거부/DB저장은 감사 이벤트(FIXED)로 남긴다. 검증: 테스트 70/70, edit-smoke 실패 0(마스킹 5건 + 로그 배선 1건 추가), 왕복 1,271건 diff 0/예외 0, db-save-smoke 제자리 갱신 통과. publish.ps1 정방향/역방향 모두 확인. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SheetMe 서식생성기 (C#/.NET 10 WPF)
MsystechHIS 레거시 서식생성기([003]EMR\[001]SheetDesigner, VB.NET WinForms)를 대체하는 신규 WPF 서식 디자이너.
레거시 XML 포맷을 읽기·쓰기 모두 무손실 호환하여, 여기서 만든 서식을 기존 EMR 런타임(EMRLoader)이 그대로 렌더링한다.
프로젝트 구성
| 프로젝트 | TFM | 역할 |
|---|---|---|
src/SheetMe.Core |
net10.0 | 포맷 중립 모델(FormDocument) · LegacyXmlSerializer(읽기+쓰기, PropBag 무손실 왕복) · FormJsonSerializer(부 포맷) · ControlRegistry(팔레트/속성 스키마) |
src/SheetMe.Data |
net10.0 | Oracle 저장소 — OracleLegacyFormStore(E_ShtMst/E_SdgMst/E_SctMst, 레거시 동일 저장 의미론) · SctMstXmlWalker · EmrDataContext(M.MW.Data.EMR) |
src/SheetMe.Designer |
net10.0-windows | WPF MVVM 디자이너(M.Framework.WPF) — 캔버스/팔레트/인스펙터/페이지/레이어 |
tests/SheetMe.Core.Tests |
net10.0 | MSTest 44건 — 실서식 XML 왕복 의미론 diff 0 게이트 포함 |
실행
dotnet build
dotnet run --project src/SheetMe.Designer
dotnet test
진단 모드 (자동 검증)
# 첫 페이지 오프스크린 렌더 → PNG (레거시 시각 대조용)
SheetMe.Designer.exe --render-smoke <서식.xml> <출력.png>
# 편집 코어 자동 검증(선택/이동/리사이즈/마퀴/복붙/Undo/왕복) → 리포트
SheetMe.Designer.exe --edit-smoke <리포트.txt>
주요 기능 (v1)
- 캔버스 편집: 클릭/Ctrl/Shift/마퀴 선택, 드래그 이동(스냅: 그리드+정렬 가이드, Alt=자유), 8핸들 리사이즈(다중선택 비례), 키보드 넛지, z-order, 정렬 6종
- Undo/Redo: 문서 스냅샷 메멘토 — 드래그=1스텝, 넛지 400ms 코얼레스
- 팔레트 14종: Label·TextBox·MaskedTextBox·CheckBox·RadioButton·ComboBox·ListBox·CheckList·DateTimePicker·Panel·GroupBox·Line·PictureBox·CalcBox — 미지원 레거시 컨트롤(Spread/MSign 등)은 자리표시로 무손실 보존(이동/리사이즈 가능, 저장 시 원본 속성 전부 유지)
- 인스펙터: 스키마 기반 속성 편집(다중선택 교집합), 폰트/색은 레거시 invariant 문자열 정합
- 페이지/레이어: 페이지 추가/삭제/용지 크기, 레이어 목록(잠금/숨김)
- 그룹: Ctrl+G/Ctrl+Shift+G — 그룹 멤버 클릭 시 그룹 전체가 한 단위로 선택/이동
- 컨테이너 자식 편집: Panel/GroupBox 내부 컨트롤을 캔버스에서 직접 클릭·이동·리사이즈·삭제(상대좌표 유지)
- 미리보기/인쇄: 편집 크롬 없는 렌더(캔버스와 동일 템플릿), FixedPage 1:1(DIP) 인쇄
- 파일: 레거시 .xml 열기/저장(TK_ExportXML 호환) + JSON 가져오기/내보내기(부 포맷)
- DB: E_ShtMst 검색 → 활성 디자인 열기 · DB 저장(레거시 동일: ShtCneYon='Y'→제자리 갱신 / 그 외 SdgDelYon 버저닝 + E_SctMst 재생성, 시퀀스→MAX+1 채번, 단일 트랜잭션) · 신규 서식 등록(미등록 코드 저장 시 자동 안내 — ShtTyp='D' 관행 기본값)
설정 (src/SheetMe.Designer/appsettings.json)
{
"ConnectionStrings": { "His": "Oracle 접속 문자열" },
"FormStore": { "SaveMode": "File" } // DB 저장은 "Db" 로 명시 변경 시에만 활성(안전 게이트)
}
레거시 호환 규약 (정합성 기준: bzDesignSheetLoader.LoadObject 가 오류 없이 복원)
- XML:
<Sheet name="SheetDesignInfo">루트에 페이지당<Object>(MDesignerHost) N개, 자식 Object 먼저 → Property 나중 - 타입: 레거시 AQN 원문 보존, 신규는
M.EMR.UserControl.{Cls}, M.EMR.UserControl, Version=1.0.0.0, ... - 속성값: WinForms TypeConverter invariant 포맷(
"120, 88","굴림, 11.25pt, style=Bold","224, 224, 224") - LocationOnBase 는 저장 시 재계산(부모 절대좌표 누적),
<Binary>는 verbatim 보존(신규 생성 안 함 — .NET 10 BinaryFormatter 제거) - z-order: XML 첫 Object = 최상위(WinForms Controls 규약) ↔ WPF 캔버스는 역순 렌더
실DB 검증 결과 (2026-07-16, 테스트DB 192.168.100.222/MSYSTECHHIS)
- 읽기: 전체 디자인 서식 1,271건 일괄 왕복(Read→Write) 의미론 diff 0 / 예외 0 — 미지원 컨트롤 보존 서식 242건 포함
- 쓰기: S999 무변경 재저장 — 신규 버전 생성(SdgKey 52241→52380), 재저장본 diff 0, E_SctMst 36행 워커 기대치 일치, 이전 버전
SdgDelYon='Y'이력 보존 확인 - 호환성 세부: 텍스트 내 CR 은
NewLineHandling.Entitize( 보존), 레거시 데이터의 제어문자(0x05)는CheckCharacters=false관용 파싱(구식 XmlTextReader 동등)
계획 범위(P0~P5) 전체 완료 — 후속 아이디어
- 레거시 SheetDesigner/EMR 미리보기로 S999 육안 확인 (권장)
- v2 후보: 미지원 컨트롤 네이티브 지원(Spread 표, MSign 서명), 계산박스 수식 평가, 컨트롤 간 자식 드래그 재배치(reparent), 자 눈금(룰러), REST API 경유 모드
설계 계획서: C:\Users\Msystech\.claude\plans\c-net-wpf-flickering-gizmo.md
Languages
C#
99.8%
PowerShell
0.2%