문제

저는 M-V-C와 M-V-VM을 결합한 일종의 M-V-MC(Model-View-ModelController)라고 부르는 M-V-VM을 사용하여 매우 큰 LOB 앱을 개발해 왔습니다.나는 게시했다 이 답변 MV-VM에서 뷰가 "라는 질문에 대해 인스턴스화되는 방법에 대해WPF 개발에서 가장 흔한 실수는 무엇입니까?".

내 답변에 대해 다음과 같은 의견을 제시했습니다.

그러면 후속 질문이 생성됩니다.뷰를 어떻게 만드나요?나는 릴레이 명령을 사용하여 뷰에서 뷰 모델로의 동작을 바인딩하므로 뷰는 조치가 발사되었음을 알지 못하고 새로운 뷰를 열어야한다는 것을 알지 못합니다.해결책:구독 할보기를 위해 VM에서 이벤트를 만듭니다.

처음 MV-VM 개발을 시작했을 때 모든 것이 ViewModel에 있어야 한다는 생각이 있었고 다음과 같은 사람들로부터 많은 예를 연구했습니다. 조쉬 스미스 그리고 칼 시플렛.그러나 나는 명령이 ViewModel에 있어야 하는 경우에 대한 좋은 예를 아직 제시하지 못했습니다.

예를 들어, 고객을 표시하는 ListView와 현재 선택된 고객을 편집할 수 있도록 클릭하는 버튼이 있다고 가정해 보겠습니다.ListView(View)는 CustomerVM(ViewModel)에 바인딩됩니다.버튼을 클릭하면 CustomerVM의 모든 속성을 편집할 수 있는 팝업 창이 열리는 EditCustomerCommand가 실행됩니다.이 EditCustomerCommand는 어디에 있습니까?창 열기(UI 기능)와 관련된 경우 뷰의 코드 숨김에 정의하면 안 되나요?alt text

View와 ViewModel에서 명령을 정의해야 하는 경우에 대한 예가 있는 사람이 있습니까?

매튜 라이트 아래 상태:

목록에서 새롭고 삭제하는 것이 좋은 예입니다.이 경우 빈 레코드가 추가되거나 현재 레코드가 뷰 모델에 의해 삭제됩니다.견해에 의해 취한 모든 조치는 발생하는 사건에 대한 응답이어야합니다.

그러면 새로 만들기 버튼을 클릭하면 어떻게 되나요?CustomerVM의 새 인스턴스가 상위 ViewModel에 의해 생성되어 해당 컬렉션에 추가됩니다.그렇다면 편집 화면은 어떻게 열리나요?뷰는 Customer ViewModel의 새 인스턴스를 생성하고 이를 ParentVM.Add(newlyCreatedVM) 메서드에 전달해야 합니다. 그렇죠?

VM에 있는 DeleteCommand를 통해 고객 기록을 삭제한다고 가정해 보겠습니다.VM은 비즈니스 계층을 호출하고 레코드 삭제를 시도합니다.그럴 수 없으므로 VM에 메시지를 반환합니다.이 메시지를 대화 상자에 표시하고 싶습니다.뷰는 명령 작업에서 메시지를 어떻게 가져오나요?

도움이 되었습니까?

해결책

내가 질문에 인용되는 것을 보게 될 것이라고는 결코 생각하지 못했습니다.

나는 이 질문에 대해 한동안 곰곰이 생각해 본 후 내 코드 기반에 대해 다소 실용적인 결정을 내렸습니다.

내 코드 베이스에서는 작업이 발생할 때 ViewModel이 호출되므로 이 상태를 유지하고 싶었습니다.또한 ViewModel이 뷰를 제어하는 ​​것을 원하지 않습니다.

내가 뭘 한거지?
탐색용 컨트롤러를 추가했습니다.

public interface INavigation
{
  void NewContent(ViewModel viewmodel);
  void NewWindow(ViewModel viewmodel);
}

이 컨트롤러에는 두 가지 작업이 포함되어 있습니다.NewContent()는 현재 창에 새 콘텐츠를 표시하고 NewWindow()는 새 창을 만들고 콘텐츠로 채워서 표시합니다.
물론 내 뷰 모델에는 어떤 뷰를 표시할지 전혀 알 수 없습니다.그러나 그들은 어떤 뷰 모델을 표시하고 싶은지 알고 있으므로 예제에 따르면 DeleteCommand가 실행될 때 탐색 서비스 기능을 호출합니다. NewWindow(새 ValidateCustomerDeletedViewModel()) '고객이 삭제되었습니다'라는 창을 표시합니다(이 간단한 메시지 상자에는 과잉이지만 간단한 메시지 상자에는 특별한 탐색 기능을 사용하는 것이 쉬울 것입니다).

뷰모델은 탐색 서비스를 어떻게 얻나요?

내 viewmodel 클래스에는 탐색 컨트롤러에 대한 속성이 있습니다.

public class ViewModel
{
  public INavigation Navigator { get; set; }
  [...]
}

뷰모델이 창(또는 뷰를 표시하는 모든 항목)에 연결되면 창은 Navigator 속성을 설정하므로 뷰모델이 이를 호출할 수 있습니다.

네비게이터는 뷰 모델에 대한 뷰를 어떻게 생성합니까?

어떤 뷰 모델에 대해 어떤 뷰를 만들 것인지 간단한 목록을 가질 수 있습니다. 제 경우에는 이름이 일치하므로 간단한 반사를 사용할 수 있습니다.

public static FrameworkElement CreateView(ViewModel viewmodel)
{
  Type vmt = viewmodel.GetType();
  // big bad dirty hack to get the name of the view, but it works *cough*
  Type vt = Type.GetType(vmt.AssemblyQualifiedName.Replace("ViewModel, ", "View, ")); 
  return (FrameworkElement)Activator.CreateInstance(vt, viewmodel);
}

물론 뷰에는 뷰모델을 매개변수로 받아들이는 생성자가 필요합니다.

public partial class ValidateCustomerDeletedView : UserControl
{
  public ValidateCustomerDeletedView(ValidateCustomerDeletedViewModel dac)
  {
    InitializeComponent();
    this.DataContext = dac;
  }
}

내 창문은 어떤 모습인가요?

단순한:내 기본 창은 INavigation 인터페이스를 구현하고 생성 시 시작 페이지를 표시합니다.직접 확인해보세요:

public partial class MainWindow : Window, INavigation
{
  public MainWindow()
  {
    InitializeComponent();
    NewContent(new StartPageViewModel());
  }

  public MainWindow(ViewModel newcontrol)
  {
    InitializeComponent();
    NewContent(newcontrol);
  }

  #region INavigation Member
  public void NewContent(ViewModel newviewmodel)
  {
    newviewmodel.Navigator = this;
    FrameworkElement ui = App.CreateView(newviewmodel);
    this.Content = ui;
    this.DataContext = ui.DataContext;
  }

  public void NewWindow(ViewModel viewModel)
  {
    MainWindow newwindow = new MainWindow(viewModel);
    newwindow.Show();
  }
  #endregion
}

(이것은 NavigationWindow와 동일하게 작동하며 뷰를 페이지로 래핑합니다.)

물론 이는 탐색 컨트롤러를 쉽게 모방할 수 있으므로 테스트 가능합니다.

이것이 완벽한 솔루션인지는 잘 모르겠지만 지금 당장은 제게는 잘 작동합니다.어떤 아이디어나 의견이라도 환영합니다!

다른 팁

삭제 메시지 박스 케이스의 경우 인터페이스를 통해 메시지 상자를 추상화합니다. 이것과 비슷합니다. 또한 WPF 앱 의이 인터페이스를 주입합니다.

건설자

    public MyViewModel(IMessage msg)
    {
      _msg = msg;
    }

그런 다음 뷰 모드에서 메소드 삭제 메소드에서 ...

    public void Delete()
    {
      if(CanDelete)
      {
        //do the delete 
      }
      else
      {
        _msg.Show("You can't delete this record");
      }
    }

이렇게하면 테스트 가능하면 실제로 메시지 상자를 표시하지 않는 다른 iMessage 구현을 연결할 수 있습니다. 테스트 목적으로 콘솔에 인쇄 할 수 있습니다. 분명히 WPF 앱에는 구현이있을 수 있습니다

public class MessageBoxQuestion : IMessage
{
   public void Show(string message)
   {
     MessageBox.Show(message);
   }
}

이렇게하면 다른 경로를 테스트하는 것이 매우 쉽고 간단하게 매우 쉽고 간단합니다. 삭제 확인을 상상할 수 있습니다. Imessage의 구체적인 인스턴스를 사용하여 확인을 위해 True/False를 반환하거나 테스트 중에 컨테이너를 조롱 할 수 있습니다.

[Test]
public void Can_Cancel_Delete()
{
  var vm = new ProductViewModel(_cancel);
  ...

}
[Test]
public void Can_Confirm_Delete()
{
  var vm = new ProductViewModel(_yes);
  ...

}

명령을 언제 사용 해야하는지에 대한 다른 질문에 대해서는 해당보기에서 새 추가 또는 세부 사항보기를 인스턴스화합니다. 당신의 예에서와 마찬가지로. 견해는 다른 견해에 의해서만 인스턴스화됩니다 우리 앱에서. 나는 그런 경우에 명령을 사용하지 않습니다. 그러나 부모보기의 뷰 모델 속성을 자식보기에 사용합니다.

public void Object_DoubleClick(object sender, EventArgs e)
{
  var detailView = new DetailView(ViewModel.Product);
  detailView.Show();
}

도움이 되었기를 바랍니다!

목록에서 새롭고 삭제하는 것이 좋은 예입니다. 이 경우 빈 레코드가 추가되거나 현재 레코드가 뷰 모델에 의해 삭제됩니다. 견해에 의해 취한 모든 조치는 발생하는 사건에 대한 응답이어야합니다.

한 가지 방법은 비즈니스 계층을 수정할 수있는 명령 매개 변수 개체를 사용하고 실행 된 명령 후에 VM이 처리 할 수있는 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top