2013-02-01 2 views
6

MVP로 구현 된 WinForms 응용 프로그램이 있습니다. 내 양식은 TextBox이며 해당 Text 속성을 모델의 속성에 databind하고 싶습니다. 보기에서 모델을 참조하고 싶지 않습니다.MVP winforms에서 데이터 바인딩

Google에서 검색 한 후 Model과 View를 결합하여 데이터 바인딩을하는 것이 좋지 않음을 알았습니다. 내 샘플 초기화 Model, ViewPresenter은 다음과 같습니다.

현재
class View : Form, IView 
{ 
    public View() 
    { 
     InitializeComponent(); 
     new Presenter(this); 
    } 
} 

class Presenter 
{ 
    public Presenter(IView) : this.Presenter(this, new Model()) 
    { 
    } 

    public Presenter(IView view) 
    { 
    } 
} 

class Model : IModel 
{ 
    public Model() 
    { 
    } 

} 

I는 Model, ViewPresenter 위해 3 개 각 프로젝트가있다. 보기에는 PresenterPresenter에 대한 참조가 있으며 Model에 대한 참조가 있습니다. 아무도 날 Model의 속성에 View에서 컨트롤에 데이터 바인딩을 형성하는 방법을 안내 할 수 있습니까?

편집

나는 그리드에서 일을 알고있다. 이 UI의 값을 반영합니다

_view.DataSource = _model.ListOfEmployees; 

때 모델의 ListOfEmployees 변화 : 우리는 같은 발표자의 List (유사하거나 무엇인가)에 그리드의 Datasource 속성을 할당 할 수 있습니다. 그러나 Text 속성을 노출하는 TextBox은 무엇입니까? MVP 아키텍처에서 어떻게 바인딩 할 수 있습니까?

+1

왜 모델을 뷰로 언급하는 것이 좋지 않은가요? 나는 모델을 IView에 바인딩하기 위해 표현 자에게 메서드를 추가하기 만하면됩니다. –

+1

@WiktorZychla : 프리젠터 내에서 뷰와 모델을 캡슐화합니다. 의도는 발표자 바깥에있는 어떤 물체도 모델을 포함하여보기가 어지럽 혀서는 안된다는 것입니다. 그렇지 않으면 다른 오브젝트가 뷰의 제어를 대신하는 함수 크립이 발생할 수 있습니다. – IAbstract

+0

그런 다음 발표자를보기 모델로 만듭니다. 즉, 발표자에서 모델을 흡수하고보기 모델과 마찬가지로보기의 발표자에 바인딩합니다. 이는 권장되는 접근 방법입니다. –

답변

8

내 권장 사항은보기 및 모델을 발표자에 캡슐화하는 것입니다. 이것은 주어진 View에 대한 전문 Presenter (대부분의 경우)를 의미합니다. 필자의 견해로는 대부분의 모델이 어쨌든 달라질 것이므로이 방법이 효과적입니다.

void SetGridDatasource() { 
    view.GridDatasource = model.SomeBindableData; 
} 

보기 구현 :

public object GridDatasource { 
    get { return myGridView.DataSource; } 
    set { myGridView.DataSource = value; } 
} 

interface IView { 
    object GridDataSource { get; set; } 
} 

당신의 발표자 몇 가지 방법을 추가

당신의 개의 IView에서
class Presenter { 
    readonly IView view; 
    readonly IModel model; 

    public Presenter() { 
     // if view needs ref. to presenter, pass into view ctor 
     view = new View(this); 
     model = new Model(); 
    } 

    // alternatively - with the model injected - my preference 
    public Presenter(IModel Model) { 
     // if view needs ref. to presenter, pass into view ctor 
     view = new View(this); 
     model = Model; 
    } 
} 

는, 컨트롤 또는 컨트롤의 데이터 소스 속성을 노출

N ote :
코드 조각은 테스트되지 않았으므로 시작 지점으로 권장됩니다. 의견에

업데이트 :
INotifyPropertyChangedIViewIModel 사이에 속성을 업데이트하기위한 매우 중요한 메커니즘이다.

대부분의 컨트롤에는 일종의 바인딩 기능이 있습니다. 가능한 경우 언제든지 DataBinding methods을 사용하는 것이 좋습니다. IView를 통해 해당 속성을 노출하고 발표자가 해당 바인딩을 IModel 속성으로 설정하게하십시오.

+0

무슨 뜻인지 아시면 간단히'{Binding}'코드에 너무 많은 코드가 있습니다. –

+2

MVP/MVC/MVVM에 쓸 코드가 항상 더 있습니다. ;) – IAbstract

+0

하지만 텍스트 상자의 'Text'속성에 바인딩을 추가해야하는 경우 어떻게해야합니까? 데이터 소스를 노출하지 않습니다. 'Text' 간단한 텍스트 속성을 사용하면 할당하고 포인터를 바인딩하지 않습니다 ...... 어떤 포인터 ?? – Sandy

3

WinForms에서 MVP로 약간을 썼는데 해결해야 할 문제가 많이 있습니다. 대부분의 문제는 VS에 있다는 사실에서 비롯되며 디자이너 인 양식을 사용하여 양식을 쉽게 디자인 할 수 있다는 것이 좋습니다.

널리 사용되는 WebForms MVP 프로젝트에서 개발 한 WinForms MVP API를 사용해 보았습니다.하지만 Generics (예 : public class TheForm : UserControl)의 파일이면에 코드가 있기 때문에 양식을 디자인 할 능력이 없어졌습니다. 디자이너는 Generics를 처리하는 방법을 알고 있습니다.

IPresenter, IView, IViewModel과 같은 핵심 인터페이스가 끝났습니다. 추가 속성을 추가하지 않아도 특정 구현을위한 중간 인터페이스를 만들었습니다. 주로 나중에 추가 작업을 할 때 변경 사항을 수용하기가 더 쉽기 때문입니다. IPresenter는 유형 IView의 공동 변형 generice 형식을 사용합니다. 그래서 상속 체인 아래에서 특정 하위 뷰 유형의 발표자를 만들 수 있습니다. 대화를 만들어 결국 쇼 발표자를 인스턴스화하고 호출하여 수행됩니다

ISomePresenter<ISomeView> somePresenter = new SomeFactory.GetSomePresenter(); 
somePresenter.Show(); 

내보기는 IViewModel의 사본 보유 : 없음 다시 원래의 질문에

public void Show() 
{ 
    ISomeView theView = new V(); 
    theView.ViewModel = new SomePresenterViewModel(); 
    . 
    . 
    . 
} 

을 ... 을 SampleView는 ISampleViewModel에 대해 알 수 없기 때문에 어딘가에 캐스트를 넣지 않고 ViewModel에 표준 데이터 바인딩을 수행하는 것은 불가능합니다. 이 모든 것들을 개발 한 프로젝트에서 손을 떼었고 사람들은 BindingSource 마법사뿐만 아니라 이벤트 핸들러에서 온통 캐스팅되었습니다. MVP 전체가 사라졌습니다.

이제는 이벤트 처리 방법과 속성 (및 함께 제공되는 ISampleView 속성)을 공개 컨트롤로 설정하여 발표자가 볼 수있게하거나 단순히 중복 이벤트를 만들어서 다시 발생시키는 방법에 대해 엄격하게 다루었습니다. 발표자가 가져올 이벤트 전체 데이터 바인딩 수수께끼에 대해 생각해 보았습니다. 실제로 디자이너가 지원하지 않고 디자이너가 코드에서 수행하는 모든 작업을 Presenter 내부에서 수행하는 유일한 방법이 있습니다. Designer를 사용하여 .designer.cs 파일에서 자동 생성 된 코드를 가져오고 모든 코드를 Presenter로 잘라낼 수 있습니다. 어쩌면 문법 등을 얻고, 보일러 플레이트 코드를 배제하거나, 생성 된 내용을 기반으로 발췌 문장을 만들 수 있습니다. 바인딩을 지정하려면 뷰의 실제 컨트롤에 대한 액세스가 필요하므로 컨트롤 인스턴스를 반환하는 ISampleView에 속성을 추가하십시오. 또한 Presenter에 BindingSource 인스턴스를 배치하거나 Presenter가 인스턴스를 보유하는 다른 클래스에 배치하는 것이 좋습니다.

저는 가능한 한 디자이너를 사용하고 싶지만 때로는 휴식을 취할 필요가 있습니다. 내가 말했듯이 CodePlex의 WinForms MVP 프로젝트는 훌륭하지만 모든 폼 디자인은 코드로 수행됩니다. 내 시나리오에서는 코드에서 수행해야하는 데이터 바인딩 일뿐입니다. 어쨌든 실제로 시각적 인 것이 아니기 때문에 다루기가 더 쉽습니다.

또한 메모로 전체 데이터 바인딩을 지원하는 NotifyPropertyWeaver (IL 제직) 사용자. 보기 모델에서 자동 속성을 만들어서 각 속성에 NotifyPropertyChanging 등을 호출 할 필요없이 코드를 간결하고 읽기 쉽게 유지할 수 있다는 점에서 훌륭합니다. IL Weaving with Fody는 최종 빌드 출력 단계 전에 컴파일 된 모든 작업을 수행합니다. 매우 편리합니다.

어쨌든이 두뇌 문제에 대한 개념을 두뇌가 누군가에게 가치 있기를 바랍니다. 나는 긴 시간을 그것을 밖으로 분류하는 데 썼다. 그러나 그것은 나를 위해 꽤 잘 돌아 간다.

스티브

편집 2014년 4월 23일

당신은 .NET 데이터 바인딩이 엉덩이에 엄청난 고통이 무엇인지 알고있다. 최근에 프로젝트에서 우리는 특정 컨트롤에 대한 자체 데이터 바인딩 코드를 롤업하는 결과를 낳았습니다.

더 많은 최근 경험을 다시 회고 해보면 핵심 모델이 완전히 분리되어 있어야합니다. 필자는 데이터베이스와 대화하고 DataBindable이며 View에서 볼 수있는 ViewModel을 작성하는 경향이 있습니다. 데이터 바인딩을 사용하면 특히 DateTimePicker의 ValueChanged와 같은 컨트롤 이벤트를 처리 할 때 많은 불만이 생깁니다. 한 시나리오에서 시작 날짜와 종료 날짜 선택기 및 시작 날짜 이후의 종료 날짜와 다른 범위 규칙을 고려해야 할 체크 상자가 있습니다. 일부 규칙을 기반으로 값을 변경할 때 VM에 구성된 데이터 바인딩을 사용하면 이벤트가 다시 실행되고 내가 선택한 것보다 우선합니다. 이벤트 처리기가 계속 진행되어야하는지, 아니면 잠재적 경쟁 조건이 있는지 또는 다른 스레드의 이벤트 처리기가 기다려야하는지 여부를 알지 못하게하는 데 도움이되는 bool 값을 넣어야합니다. 정말 지저분 해집니다.

내 접근법은 데이터베이스에 접근하는 큰 MODEL을 생성하고 캡처 된 데이터를 기반으로 유효성 검사 규칙을 확인할 수 있지만 데이터 바인딩을위한 속성 만 보유하고있는 작고 가벼운 버전을 만들 것입니다. 실제 모델과의 분리는 여전히 진행 중이며 Presenter/Controller가 응답하는 모든 이벤트는 양식 데이터 유효성 검사/데이터 지속 시간에 VM에서 주 모델로 복사 할 수 있습니다. 이벤트에 응답하는 경우 다음에 바인딩되는 VM 값을 설정하면 더 가벼운 VM을 만들 수 있습니다. 새 VM 인스턴스를 만들고 유효성 검사 결과를 다시 할당 한 다음이 새 VM 인스턴스를 .DataSource로 설정할 수 있습니다. 보기에있는 BindingSource는 준비가되면 이벤트 처리기가 엉망이되는 것을 방지합니다.

주 모델이 NotifyPropertyChanged 이벤트에 응답하여 변경 사항을 업데이트하거나 적절한시기에 발표자에게 알릴 수 있습니다.

그런데 Visual Studio 2012 및 2013에서는 디자이너의 일반 컨트롤이 매우 멋지게 처리되는 것 같습니다.

사이드 노트로서 저는 최근에 iOS 개발에 관심을 기울이고 있습니다. 한 가지 내가 감동했습니다. 프로세스의 일부로 MVC에서 구운 방법입니다. .NET과 달리 모든 방식의 해킹 방법을 생각해 낼 수 있습니다. 나는 이것으로부터 몇 가지 교훈을 얻었고 그것들을 .NET에 적용했다. 그리고 나의 두뇌가 너무 많이 깨지는 것을 발견했다. 특히 내가 좋아하는 한 가지는리스트 컨트롤이 작동하는 방식입니다. Qt (C++ 프레임 워크) MVC 컨트롤과 매우 비슷합니다. 객체의 모 놀리 식 백엔드 목록을 가질 수있는 능력은 보이는 영역에서만 필요한 것만 보유하고 있기 때문에 .NET이 기본 동작을 제어하는 ​​것보다 훨씬 더 좋습니다.

어쨌든 .NET 데이터 바인딩과 함께 행운을 빈다. 저는 개인적으로 새로운 사람들에게 권하고 싶습니다 ... 그것을 사용하지 말고 적절한 시간에 모든 값을 명시 적으로 할당하는 컨트롤러를 얻으십시오. 그러나 편안하고 짜증나는 뉘앙스를 이해한다면, 나는 누군가에게 도달했다고 말한 바가 있습니다.

+0

DataBinding의 경험을 미세 입자 동기화 (Fine Grained Synchronization) 문제로 해석 할 것이고, 중간 모델로의 전환은 Fowler가 http://martinfowler.com/eaaDev/MediatedSynchronization.html 및 http : //에서 언급 한 것처럼 현명한 방법 일 것입니다. /martinfowler.com/eaaDev/OrganizingPresentations.html#LayersBetween 네 경험과 실수를 공유해 주셔서 감사합니다. – wezzix

5

설명 :

당신이 모델의에 텍스트 상자 및 기본 속성을 데이터 바인딩하려면 :

첫째 : 많은 사람들이 당신의 재산 INotifyPropertyChanged 인터페이스를 구현해야한다 포함 무엇이든지, 상태가 좋아하는 그렇게 할 때 개체 속성이 변경되면 필요한 이벤트가 발생하여 변경 내용이 적용되었음을 알립니다. 이 관점에서 뷰 모델링을 사용하여 특정 속성을 캡슐화하여 뷰를 바인딩 할 수 있습니다.

두 번째 : IView에 View에서 구현해야하는 viewmodel 속성이 포함됩니다.

셋째 : View는 각 텍스트 상자를 dto 속성에 바인딩하기 위해 viewmodel 객체에 설정된 접근 자만 사용하여 IView 속성을 구현합니다. 아래 예제에서,로드를 한 후에 다시 수동으로 텍스트 상자를 설정하는 방법을 확인하십시오. 텍스트 상자.기본 모델의 viewmodel 속성이 변경되면 텍스트 값이 업데이트됩니다. 이것은 두 가지 방식으로 작동합니다 (2 방향 데이터 바인딩). 사용자 입력으로 텍스트 상자를 편집하면 기본 모델의 dto 속성 값이 변경됩니다.

넷째 : 발표자가보기로드시 IView의 속성을 모델의 속성으로 한 번만 설정합니다.

예 : 이것은 매우 단순화 된 거친 예제이며 OP가 사용하는 것처럼 모델 추상화가 없지만 Winforms MVP에서 텍스트 상자 데이터 바인딩에 대한 좋은 출발점을 제공해야합니다. 프로덕션 앱에서 변경 될 수있는 또 다른 사항은 Model을 stateless로 만들고 뷰 모델 (사람)을 발표자로 이동시키는 것입니다.

//VIEWMODEL 
public class Person : INotifyPropertyChanged 
{ 
    string _firstName; 
    string _lastName; 
    public string FirstName 
    { 
     get { return _firstName; } 
     set 
     { 
      if(value != _firstName) 
      { 
       _firstName = value; 
       NotifyPropertyChanged("FirstName"); 
      } 
     } 
    } 
    public string LastName 
    { 
     get { return _lastName; } 
     set 
     { 
      if (value != _lastName) 
      { 
       _lastName = value; 
       NotifyPropertyChanged("LastName"); 
      } 
     } 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 

    private void NotifyPropertyChanged(String info) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(info)); 
     } 
    } 

} 

//MODEL 
class Model 
{ 
    Person _person; 
    public Person Person { get { return _person; } } 

    public Model() 
    { 
     //Set default value 
     _person = new Person(){ FirstName = "Test", LastName = "Subject" }; 
    } 

    public void ChangePerson() 
    { 
     //When presenter calls this method, it will change the underlying source field and will reflect the changes in the View. 
     _person.FirstName = "Homer"; 
     _person.LastName = "Simpson"; 
    } 
} 

//PRESENTER 
class Presenter 
{ 
    readonly View _view; 
    readonly Model _model; 
    public Presenter(View view) 
    { 
     _view = view; 
     _model = new Model(); 

     _view.OnViewLoad += Load; 
     _view.OnChangePerson += ChangePerson; 
    } 

    private void Load() 
    { 
     _view.Person = _model.Person; 
    } 

    private void ChangePerson() 
    { 
     _model.ChangePerson(); 
    } 
} 

//IVIEW 
interface IView 
{ 
    Person person { set; } 

    event Action OnViewLoad; 
    event Action OnChangePerson; 
} 

//VIEW 
public partial class View : IView 
{ 
    public View() 
    { 
     Presenter presenter = new Presenter(this); 
     this.Load += (s, e) => OnViewLoad(); //Shorthand event delegate 
     this.btnChange.Click += (s, e) => OnChangePerson(); //Shorthand event delegate 
    } 

    public event Action OnViewLoad; 
    public event Action OnChangePerson; 

    public Person person 
    { //This is how you set textbox two-way databinding 
     set 
     { 
       //Databinding syntax: property of control, source, source property, enable formatting, when to update datasource, null value 
       txtFirstName.DataBindings.Add(new Binding("Text", value, "FirstName", true, DataSourceUpdateMode.OnPropertyChanged, string.Empty)); 
       txtLastName.DataBindings.Add(new Binding("Text", value, "LastName", true, DataSourceUpdateMode.OnPropertyChanged, string.Empty)); 
     } 
    } 

} 
관련 문제