[WPF] 상태 기반 UI 관리 라이브러리 제작에 대한 피드백을 받고 싶습니다(수정)

안녕하세요!
저는 현재 WPF를 사용해 테스트 자동화 프로그램을 만들고 있는 신입 WPF 개발자입니다.
자동화 프로그램을 개발하면서 어떻게 하면 UI 를 조금 더 체계적으로 관리/제어할 수 있을까라는 고민을 많이 했었습니다. 많이 부족하지만 스스로 한번 생각해본 UI 설계 방식이 있어 해당 내용을 공유하고 피드백을 받고 싶다는 생각에 이렇게 글을 작성합니다.

[개발 중인 테스트 자동화 프로그램]

[상태 기반 UI 관리 라이브러리 샘플 앱]

[비고]
저는 자바 SpringFramework 개발 1년, WPF 개발 3개월의 신입 개발자입니다. 기술적으로 아직 다른 분들과 비교했을 때 많이 부족하고 개발 경험도 정말 적습니다. 선배님들이 보시기에 많이 부족한 코드, 아이디어이겠지만 한번씩 피드백 해주시고 어떤 부분이 부족한지 지적해주신다면 저에게 정말 큰 도움이 될 것 같습니다. 많은 피드백 부탁드리겠습니다!

[문제 상황]
WPF로 하드웨터 부품에 대한 테스트 자동화 프로그램을 관리하면서 사실 가장 어려웠던 부분은 어떻게 하면 사용자 친화적인 UI 를 개발하는가였습니다. 가장 걱정했던 하드웨어 장치로부터 데이터를 가지고 오는 과정에 대한 로직은 이전 개발자분이 개발하신 Winform 코드와 API 문서들을 확인하며 생각보다 수월(?)하게 구현할 수 있었습니다. 하지만 사용자가 어떤 상황에서 어떤 선택을 할 수 있는지, 각 테스트 단계에서 사용자가 어떠한 선택을 할 수 있는지 제한하는 방식으로 UI 를 구성하는 것이 저 같은 초보 WPF 개발자에게는 꽤 어려운 일이었습니다.

제가 생각한 UI 구성 방식의 대원칙은 다음과 같았습니다.
" 사용자가 각 테스트 단계에서 어떠한 선택을 할 수 있는지 명확하게 제한함으로써 사용자가 테스트 단계에서 잘못된 선택을 하는 것을 방지한다"
가령, 테스트 장치들과 연결이 되지 않는 상태에서 사용자가 테스트 시작 버튼을 누르지 못하게 한다든가, 테스트 과정에서 사용자가 다른 버튼을 눌러 테스트 과정을 방해하는 것을 막는다든지 사용자가 각 단계에서 어떠한 행동을 할 수 있는지 명확하게 제공하는 것이 중요하다는 생각을 개발 초기에 많이 했었습니다. 꽤나 고가의 전자 부품을 테스트함에 있어 테스트 과정에서의 부주의함으로 인한 리스크를 최소화하고 싶었기 때문입니다.(사실 모르는 게 많아서 조금 불안했습니다…)

다만, 이러한 어떤 시나리오의 흐름 속에서 전체 UI를 관리한다는 것이 사실 쉽지 않았습니다. 다음은 제가 생각한 전체 프로그램의 실행 과정에 대한 간략한 흐름을 정리한 그림입니다.

이걸 실제 UI로 표현하면 이렇게 구현할 수 있었습니다.

이러한 어떤 시나리오, 절차를 가진 프로그램의 UI를 제작함에 있어 어려웠던 점은 시나리오의 특정 절차에 도달했을 때 개별적인 UI 요소들(여러 버튼, 여러 화면 구성)이 특정 UI 표현력을 가지게 하기 위해서 일괄적으로 관리할 수 있는 방법이 부재한다는 점이었습니다. 만약 위의 UI 흐름도에서 테스트 시나리오가 다음 단계로 이동하게 된다면, 각각의 UI 요소들을 직접적으로, 개별적으로 변경해주어야 한다는 점이 가장 문제였습니다. ViewModel 과 View 가 가능한한 일대일로 매핑될 수 있게 전체 프로그램 구조를 관리하고 싶었던 차에, 이러한 모든 UI 요소를 동시다발적으로 관리하는 것은 너무나 복잡하고 코드의 양이 증폭되게 하는 문제를 낳았습니다.(혹시 제가 놓치고 있는 방법들이 있다면 피드백 부탁드리겠습니다…!) 테스트 시나리오가 흘러가는 과정에서 각 테스트 단계에 일치되게끔 각각의 UI 요소들을 직접적으로 ‘하나하나’ 제어하기에는 너무 번거롭고 헷갈린다는 결론을 내리게 되었습니다. 실수로 ‘저장’ 버튼을 ‘시험 대기’ 상태에서 클릭할 수 없게 IsEnabled를 false로 변경하는 로직을 잊어버린다면, 심각한 버그가 발생할 수 있는 문제가 있는 구조라고 생각했습니다>

[해결 방안 모색]
제가 생각했던 해결 방안은 중앙집중적이고 일괄적인 UI 관리 방법을 제공하는 것이었습니다. 즉, 테스트 시나리오 속에서 현재 프로그램이 어떠한 단계에 도달한다면, 각각의 UI 요소들이 직접 해당 단계에 맞는 UI를 스스로 갖추도록 하게 하는 것입니다. 즉, ViewModel에서 개별적으로 UI 요소들의 UI 상태를 일일이 하나하나 변경하는 것이 아니라, ViewModel에서 테스트의 단계/상태만을 관리하고 이 상태가 변경된다면 이 ViewModel의 UI 요소들이 직접 자기 자신의 UI를 변경할 수 있도록 이벤트/구독 형식의 UI 관리 방법을 제공하는 것입니다.

저는 우선 이러한 특정 상태에 기반하여 전체 UI 요소를 관리하기 위해서는 테스트 시나리오의 흐름에 맞춘 명확한 상태에 대한 규정과 명칭이 필요하다고 생각을 하였고, 각 테스트 시나리오의 단계에 대해서 다음과 같은 이름을 지어주었습니다.

그리고 최종적으로 이러한 흐름을 관리할 수 있는 StateEventManager라는 객체를 사용하여 ViewModel 에서 전체 테스트의 흐름을 제어할 수 있게끔 해주었습니다. 그리고 각각의 UI 요소들은 StateEventManager의 각 테스트 단계로의 전환(NotReady, Ready, Waiting, Running, Paused, Completed)에 해당하는 이벤트에 자기 자신의 UI 수정 로직을 담은 이벤트 핸들러를 등록할 수 있습니다.

구체적인 절차는 다음과 같습니다.

  1. 각 UI 요소들의 이벤트 핸들러에 미리 UI 로직 정의
  2. 각 UI 요소들은 StateEventManger의 각 상태에 대한 이벤트에 자기 자신의 이벤트 핸들러를 연결
  3. 상위 관리자가 StateEventManger의 상태를 변경(특정 상태에 대한 이벤트를 Invoke)
  4. 이벤트에 등록된 이벤트 핸들러들이 일괄적으로 동작하면서 등록된 UI 요소들의 UI 로직을 일괄적으로 수행
  5. 이렇게 미리 정의해 놓은 UI 관리 이벤트 핸들러를 각 상태의 이벤트에 등록해놓는다면, 다음과 같이 특정 Command 를 사용하여 StateEventManager의 상태를 변경함으로써 모든 UI 요소의 UI를 일괄적으로 제어할 수 있습니다.

[2부에서 계속…]
처음에 글을 작성할 때는 한 게시물에 전체적인 내용을 다 담을 수 있을 거라고 생각했는데, 제 표현력이 좋지 않아 아이디어를 다 담지는 못했습니다. 전체적인 개념에 대해서는 나름대로 표현했다고 생각하나 어디까지나 초안 수준의 아이디어로 개선해야 할 점이 많습니다. 앞으로 올릴 2부에서 조금 더 구체적이고 상세한 내용을 담을 수 있도록 하겠습니다. 긴 글 읽어주셔서 정말 감사합니다. 많은 피드백 부탁드리겠습니다. 감사합니다!

6개의 좋아요

내용이 길어 세세하게 읽어보진 못했지만 요약하자면 사용자 입력실수를 UI에서 하나하나 체크가 번거롭고 복잡하다는 말씀같네요. 맞는지요?? 이곳의 다른분께서는 기술적으로 답을 주시겠지만 저의 같은 경우에는 뭔가 사용자가 조작을 못 하게 해야 한다면 그냥 패널로 전체 UI를 덮어버려 입력이 안되게끔 합니다. 제일 깔끔하고 확실한 방법이더군요 ㅎㅎ

1개의 좋아요

저의 경우에도 작성자님처럼 단일 버튼식에서 상태 기반으로 변경했는데요.
적어주신것처럼 UI처리 부분이 복잡할수록 viewmodel 부분이 복잡해 지는 문제가 있죠

이미 잘 아실 수도 있겠지만 다양한 버튼들을 처리하신다면 상태를 Binding 받아 Converter를 사용한다면 viewmodel부분과 UI처리 로직이 분리되면서 더욱 깔끔하고 보기 편해질 수 있습니다.

1개의 좋아요

올려주신 코드를 살펴보면 MVVM 패턴에 대한 이해가 조금 더 필요해 보입니다.

아마도 이전의 웹 개발 경험이 현재 구조에 영향을 미친게 아닌가 추측해봅니다.

위에 언급하신 부분은 Command의 CanExecute에 의해 깔끔하게 해결할 수 있는데 이벤트에 의존하여 구현되어 있는 것 같습니다.

    MakeUntargetedButtonUi = (object? obj, EventArgs args) =>
    {
        Opacity = 0.6;
        IsEnabled = false;
    };

    MakeTargetedButtonUi = (object? obj, EventArgs args) =>
    {
        Opacity = 1;
        IsEnabled = true;
    };

특히, OpacityIsEnabled와 같은 UI 속성 상태를 코드에서 직접 제어하고 계신데, 이는 MVVM 관점에서 권장되지 않는 방식입니다. 예시로 제시하신 코드처럼, UIModel이라는 생소한 형태를 통해 UI 상태를 직접 조작하는 방식은 View와 ViewModel 간의 책임을 모호하게 만들 수 있습니다.

이와 같은 접근 방식에서 발생하는 가장 큰 문제는, MainViewModel이 애플리케이션 전반의 비즈니스 로직 흐름을 명확히 관장하지 못하고, UI 요소 단위의 단편적인 구현이 개별 UIModel에 분산된다는 점입니다.

현재 구조에서는 상태 전이에 따른 UI 반응이 StateEventManager와 각 UIModel 내부의 이벤트 핸들러를 통해 제어되고 있어, MainViewModel은 상태 흐름을 선언적으로 주도하지 못하고 단순한 중계자로 기능하고 있습니다.

이로 인해 상태나 흐름을 변경하거나 확장하려면 여러 UIModel 내부 로직을 수정해야 하며, 전체 동작을 이해하거나 추적하는 데도 불필요한 복잡성이 발생합니다.

Opacity 속성은 로직이 아니라 IsEnabled 속성 값에 의해 결정되는 스타일의 영역입니다. 이러한 처리는 View 단에서 Style, DataTrigger, DataTemplate 등을 통해 선언적으로 정의하는 것이 바람직합니다.
또한 IsEnabled 속성의 경우 Command의 CanExecute 로직을 통해 표현되도록 구조를 재편해야 할 것으로 보입니다.

2개의 좋아요

댓글 달아주셔서 감사합니다! 맞습니다. 자동화 프로그램을 만들면서 고민했던 부분이 프로그램의 각 단계에서 어떠한 UI를 제공할 수 있을지 조금 더 분명하게 할 수 있는 방법이 없을까 고민하였고, 그러한 고민의 가장 큰 이유가 사용자 입력 실수를 방지하기 위함이었습니다 ㅎㅎ Tokhi님의 댓글을 보니… 전체 UI 위에 로딩창 같은 패널을 하나 씌우는 방법도 활용할 수 있는 곳이 아주 많을 것 같습니다. 좋은 의견 감사합니다!

1개의 좋아요

솔직히 Converter를 이렇게 UI 제어에 사용할 수 있을지는 몰랐습니다… ㅎㅎ 저 같은 경우 그냥 bool 값이나 int 값을 다른 문자열로 변경해주는 용도로 사용했는데 UI 처리 로직에도 사용할 수 있겠다는 생각을 처음 하게되었습니다!! 다만 제 지식에 한계에 따르면 Converter를 사용하면 하나의 UI 속성을 처리하기 위해 하나의 Converter가 필요할 것 같다는 생각이 들어 Converter 수가 엄청나게 늘어날 것 같다는 생각을 했는데, 혹시 제가 놓친 게 있는지 알고 싶습니다! 좋은 말씀 정말 감사합니다!

1개의 좋아요

감사하다는 말씀 먼저 드려야 될 것 같습니다 ㅎㅎ
사실 정말 신기했습니다. 많이 부족한 글인데 이 글을 통해서 제 의도를 너무나 정확히 간파하셔서 신기했습니다.

이 말씀도 정말 정확하십니다. 사실 아직도 MVVM에 대한 정확한 감을 찾기가 너무 어렵고, MVC 패턴의 백엔드 개발에 조금 더 익숙하다 보니 이렇게 UI 와 비지니스 로직이 결합된 애플리케이션이 매우 어렵게 느껴집니다…

이러한 아이디어도 제 나름대로 해법을 찾아내보고자 생각한 것인데 사실 al6uiz님의 글을 보니 그냥 제 공부가 부족했다는 생각이 들었습니다. 처음에 xaml이 어렵게 느껴져서 조금 뒤로 미루다보니 Style, DataTrigger, DataTemplate로 UI를 관리할 수 있다는 사실을 놓치고 말았습니다. 그리고 MVVM 패턴에 대한 이해의 부족으로 ViewModel에서 UI를 변경하는 코드를 넣었었는데, al6uiz 님의 말씀대로 ui와 관련된 로직들은 xaml에 모조리 집어넣고 코드 상에서는 ui 로직을 제외하는 것이 정말 바람직하다는 것을 느끼게 되었습니다. 덕분에 MVVM에 대한 이해가 더 분명해진 것 같아서 정말 감사하게 생각하고 있습니다.

생소하다고 하셨던 UI Model이라는 개념을 제가 떠오르게 되었던 이유는 view가 복잡해질수록 하나의 ViewModel에 너무 많은 프로퍼티와 메소드가 생기는 것 같아서 View를 작은 Ui 요소들로 나누고 그에 따른 UIModel이라는 객체들을 추가해보면 어떨까라는 생각했기 때문입니다. (마치 리액트의 컴포넌트와 비슷하다고 생각을 했습니다!) 막상 그렇게 UIModel을 만들고 나니, UI 로직들을 이 요소들 안에 집약적으로 넣어놓으면 관리하기가 매우 편하겠다라는 생각이 들었습니다. 하지만 제가 놓쳤던 부분은 UIModel에 비지니스 로직과 UI 로직들이 들어가기 시작하면서 책임이 분리되지 못했다는 점이었습니다. 그리고 xaml와 코드 상에 ui 로직들이 이중으로 산재되면서 코드가 난잡해지는 문제가 발생했습니다.

살짝 아쉽긴 하지만, 이렇게 코드 상에서 UI를 관리하는 기법은 쓰지 못하겠다라는 생각이 강하게 듭니다… UI 관리는 View의 xaml가 전담하게 수정해야 할 것 같습니다. 다만, 상태 관리라는 기법 자체는 조금 더 활용을 해보려고 합니다. 가령, ViewModel에서 ThisState라는 프로퍼티를 가지게 하고, 이 프로퍼티를 xaml의 datatrigger에 바인딩시켜서 상태가 변하게 되면 전체 UI가 일괄적이게 바뀌게 하는 방식을 사용할 수 있지 않을까 생각합니다…! ㅎㅎㅎ

이벤트를 기반으로 한 UIModel들을 제어하는 구조는 조금 다른 방식으로 사용할 수 없는지 한번 고민해봐야 할 것 같습니다… 예를 들어 상태를 변화시킬 때 각 UI 요소들이 특정한 행동을 수행할 수 있도록 한다든지, 특별한 로직을 처리할 수 있게끔 하는 방식으로 사용해볼 수 있지 않을까 싶습니다!

[궁금한 점]
그리고 UIModel이라는 개념을 사용하지 않는다면 복잡한 View를 가진 ViewModel을 어떻게 관리하는지 여쭤보고 싶습니다…! 제 프로젝트에서는 ViewModel이 너무 복잡해지는 문제를 피하기 위해서 UiModel을 만들어 사용했는데 다른 분들은 어떻게 관리하시는지 많이 궁금합니다…!

마지막으로 부족한 글이었는데 너무나 꼼꼼히 읽어봐주셔서 감사합니다. 정말 많이 배우고 생각할 수 있는 시간이었습니다. 감사합니다!

3개의 좋아요

솔직히 말씀드리자면 저 모양이면 굳이 WPF로 해야 했나 의문이 드네요
윈폼 MVC패턴으로 충분하고 WPF를 써서 윈폼처럼 만든 이유를 모르겠습니다.

어떤 방식이든 본인의 구상 대로 구현을 해보는 것도 나쁘지 않을 것 같습니다.
특히 범용적으로 사용하고 싶다면, MVVM을 고려하지 않는 게 오히려 좋은 선택일 것입니다.

코드에서 눈에 띄는 부분을 말씀드려보면,

이벤트

EventHandler (와 아무런 정보도 없는 EventArgs)를 사용하고 있는데, 이보다는 EventHandler<T> 를 사용하는 게 더 구체적입니다.

public class StateEventManager
{
   // EventHandler<State> 형식:  void (object?, State)
   public event EventHandler<State> StateHasChanged;
}

안티 패턴

참고로, 원본 StateEventManagerChangeState 메서드는 안티 패턴이라 할 수 있습니다.

public class StateEventManager
{
   //Event
   public event EventHandler<State> StateHasChanged;
   public void ChangeState(State state) => 
      StateHasChanged?.Invoke(this, state);
}

닷넷의 event 키워드는 발행자-구독자 패턴의 언어적 지원으로 "이벤트 소유자만이 이벤트를 전파함"을 보증합니다. 이로인해, 이벤트 소유자 외부에서 이벤트를 전파를 시도하면 에러가 발생합니다.

var manager = new StateEventManager();
var any = new object();
var args = State.Ready;
manager.StateHasChanged?.Invoke(any, args); // 컴파일 에러

ChangeState 메서드는 "발행자만이 발행할 수 있다"는 규칙을 무력화하는 우회 통로입니다.

manager.ChangeState(args); // 에러 없음

이 우회로로 인해 StateEventManager 객체에 접근할 수 있는 모든 객체가 이벤트를 전파할 수 있게 되는데, 여기에는 이벤트 구독자도 포함됩니다.

    public class FirstUiModel : ABaseUiModel
    {
        // ...
        public FirstUiModel (StateEventManager stateEventManager) 
        {
           // ...
           // 이벤트 구독자가 이벤트를 발행할 수 있음 
           // 발행자가 FirstUiModel 임을 숨김.
           stateEventManager.ChangeState(State.NotReady); 
        }

이벤트 브로커를 도입할 때는 이러한 위험이 없도록 설계되어야 하는데, StateEventManager는 그러지 못하고 있는 것 같습니다.

이러한 위험이 없도록 설계하려면, 이벤트 브로커가 발행하는 이벤트와 개별 요소가 발행하는 이벤트를 분리해야 합니다.
이러한 분리 구조에서는, 개별 요소는 전역 이벤트에 대한 구독자가 되고, 이벤트 브로커는 개별 요소가 발행하는 이벤트의 구독자가 됩니다. 예를 들면,

public class StateEventManager
{
   // 전역 상태 이벤트
   public event EventHandler<State> GlobalStateHasChanged;

   // public void ChangeState(State state) => 
   //    StateHasChanged?.Invoke(this, state);

   // 로컬 이벤트 구독
   public void Subscribe(EventHandler localEvent) => 
      localEvent += HandleLocalEvent;

   // 로컬 이벤트 구독 취소 (memory leak 방지)
   public void Unsubscribe(EventHandler localEvent) => 
      localEvent -= HandleLocalEvent;

   // 로컬 이벤트에 따른 전역 이벤트 발행 결정.
   void HandleLocalEvent(object? s, EventArgs e)
   {
      if (s is IDeviceFinder finder && finder.IsSuccess)
         GlobalStateHasChanged?.Invoke(this, State.Ready);

      if (s is ITester tester)
      {
         // ...
   } 
}

도메인 세분화

EventHandler<T> 또한 범용 타입이라, 좀 더 강한 형식성을 부여하는 것이 좋습니다.

public delegate void StateChangedHandler(object? sender, StateChangedEventArgs args);

public class StateChangedEventArgs : EventArgs
{
   public State Current { get; }
   public State Former { get; }
   public string Message { get; }
   protected StateChangedEventArgs(State former, State current, string message) =>
      (Former, Current, Message) = (former, current, message);
   
   public static StateChangedEventArgs New(State former, State current, string message) 
   {
      if (former == current)
         throw new ArgementException("State has not changed from {former}");
      return new(former, current, message);
   }
}

"강한 형식성"의 의미는 EventHandler<StateChangedEventArgs> 대리자와 StateChangedHandler 대리자는 외형(Method Signature) 이 동일해도 호환되지 않음을 의미합니다. 이러한 성질은 실수의 가능성을 예방합니다.

이벤트 자체에 충분한 의미를 담았으므로, 사실 StateEventManager는 그다지 필요가 없다할 수 있습니다.

다른 분들도 언급하셨지만, StateChangedHandler 는 전역적인 View 의 State를 전파하기 위한 것이기 때문에, 만약 저라면, 아래와 같이 Entry View 인 MainWindow 를 발행자로 지정하는 간편한 방법을 택할 것 같습니다.

public partial class MainWinodw
{
   IDeviceFinder _finder;
   IDevice? device;
   public static event StateChangedHandler StateHasChanged;
   async Task OnSearchClicked(object? sender, EventArgs e)
   {
      var device = await _finder.FindAsync();
      if (device is not null)
      {
         _device = device;
         var args =  StateChangedEventArgs.New(State.NotReady, State.Ready, "장치 탐색 성공");
         StateHasChanged?.Invoke(this, args);
      }
   }
   // ...
}

이 이벤트의 구독자는 MainWindow 의 자식 뷰가 됩니다.

public partial class ControlA : UserControl, IDisposable
{
   public ControlA()
   {
      //...
      MainWindow.StateHasChanged += OnStateHasChanged;
   }
   void OnStateHasChanged(object? sender, StateChangedEventArgs args)
   {
      // handles with args
   }
   public void Dispose() => 
      MainWindow.StateHasChanged -= OnStateHasChanged;
}

수명주기 패턴

이 부분은 제가 애용하는 블레이저에서 가끔 사용하는 방식을 설명드리는 것입니다.
WPF 에는 적용해 본적이 없어, 코파일럿의 답변을 토대로 코드를 적었다는 점을 참고하세요.

StateHasChanged 이벤트를 구독하는 MainWindow의 자식 콘트롤을 정의할 때 이벤트 처리 코드가 반복되는데, 아래와 같이 정형화하여 반복 코드를 최소화할 수 있습니다.

abstract class ChildControlBase : Control, IDisposable
{
   protected ChildControlBase()
   {
      //...
      MainWindow.StateHasChanged += OnStateHasChanged;
   }
   void OnStateHasChanged(object? sender, StateChangedEventArgs args)
   {
       switch (args.Current)
       {
          case State.Ready:
             OnReady(args.Former, args.Message);  break;
          case State.Running:
             OnRunning(args.Former, args.Message); break;
         // ...
       }
   }
   public void Dispose()
   {
       MainWindow.StateHasChanged -= OnStateHasChanged;
       Dispose(true);    
       GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing) {   }

   protected virtual void OnReady(State formerState, string message) { }
   protected virtual void OnRnnning(State formerState, string message) { }
   // 모든 State 처리 메서드 추가
}

자식 콘트롤

// ControlA.xaml.cs
partial class ControlA : ChildControlBase
{
   public ControlA () : base()
   { 
      InitializeComponents();
   }
   
   // 관심있는 State 만 선별적으로 처리
   protected override void OnReady(State formerState, string message) 
   { 
      // State.Ready 에 맞게 View 를 변화.
   }
}
4개의 좋아요

안녕하세요 선생님
너무 늦었지만 꼭 감사 인사를 드리고 싶어서 댓글 드립니다.
최근 여행과 회사 일정이 조금 겹쳐지면서 정신 없다는 핑계로… 너무 늦어졌지만 그래도 부족한 글에 이렇게 댓글을 남겨주셔서 감사해요.

사실, BigSquare님의 글에 담긴 메세지가 제가 단번에 이해하기는 어려워서 여러차례 고민을 했었습니다. 첫 번째로 느낀 것은, 아직 제가 정말 부족한 점이 많다고 느꼈다는 점입니다. 언어를 사용함에 있어 단순히 문법을 사용하는 것만이 아닌 이렇게 고려해야 할 사항들이 많이 있고 아직 제가 모르는 게 많음을 느낄 수 있었습니다. 특히 이벤트 발행 구독자가 누구이냐에 따라 문제가 발생할 수 있다는 사실은 미처 몰랐습니다… 작성해주신 글을 정독하면서 다시 한번 차근차근 따라가보겠습니다.

StateEventManger라는 객체의 존재에 대해서 많이들 말씀해주셨는데, 저는 하나의 ViewModel 에서 두 개의 상태를 체계적으로 관리하기 위해선 StateEventManger가 있으면 좋겠다는 생각을 했습니다.
하나의 ViewModel에서 두 개의 장치와의 연결 상태를 보여준다고 할 때, 각 장치마다 두 개의 독립된 상태가 있으면 좋겠다는 생각이었고 이에 따라 StateEventManger를 새로 Create해서 사용하는 방법을 생각했었습니다. 하지만 생각해보면 두 개의 Event를 각각 발행해서 사용하는 간단한 방법도 있을 것 같습니다.

말씀해주신 문법의 기본적인 소양들과 여러 아이디어들은 찬찬히 다시 정독하여 소화할 수 있도록 하겠습니다. 소중한 시간 내어 댓글 달아주셔서 감사합니다!

4개의 좋아요

그라목손님 소중한 시간 내어 읽어주셔서 감사합니다!!!

제가 아직 프레임워크와 MVVM에 대한 이해가 많이 부족한 것 같습니다. 다른 분들도 지적해주셨지만 제가 만든 구조가 xaml을 사용하는 목적에서 많이 벗어났음을 느끼고 있습니다.

처음 WPF를 사용해보면서 XAML의 여러가지 기능들이 조금 어렵게 느껴졌습니다. 그러다보니… 최대한 XAML의 사용을 줄이고 C# 코드로 문제들을 해결하려고 했고 그런 결과 MVVM에서 벗어난 구조가 만들어져 버린 것 같습니다. 솔직히 말씀드리면 저 구조를 만들고 났을 때는 이게 MVVM에서 벗어났는지도 잘 모르기도 했구요… 바인딩만 잘 해주면 그게 MVVM을 지키는 것인 줄 알았습니다.

정말 다행히도 커뮤니티의 많은 분들의 도움으로 제가 얼마나 부족한지 , 그리고 MVVM 이 무엇인지 조금이나마 더 이해하게 된 것 같습니다…!

다음에는 뭔가 제가 만든 것을 보여드리는 자리가 아닌, 다른 분들께 더 여쭤보고 궁금한 것을 질문하는 방식으로 찾아오겠습니다. 앞으로도 진심 어린 답글 많이 부탁드릴게요!!

1개의 좋아요

따로 안남겨놨지만…응원하고 있었습니다.
다른분들의 의견은 참고는 하시면서
@불바다 님이 추구하시는 방향…그대로 갔으면 좋겠어요