2012-11-29 2 views
2

MVP 패턴을 사용하여 응용 프로그램을 개발하려고합니다.관습/반영을 통한 뷰, 모델 및 발표자의 동적 연결

문제는 모든 코드를 수동으로 배선하는 것입니다. 나는 필요한 코드를 줄이기를 바랬다. 다른 솔루션을 복사하려고했지만 작동하지 못했습니다. Winforms를 사용하고 있으며 소스로 사용하고 있던 솔루션이 WPF를 사용하고 있습니다.

그것은 몇 가지 규칙에 가지를 연결합니다 :

보기 이벤트는 이름으로 유선된다. 예 :보기의 "로드 됨"이벤트는 발표자의 "OnLoaded()"메서드에 연결됩니다. 단추 명령은 이름으로 연결됩니다. 예 : MoveNext "단추는"OnMoveNext() "메서드에 연결됩니다. 그리드 더블 클릭은 이름으로 연결됩니다 : 예 :"OnActionsChoosen (ToDoAction) "에"Actions "를 더블 클릭하십시오.

WPF의 작업 코드는 다음과 같습니다

private static void WireListBoxesDoubleClick(IPresenter presenter) 
    { 
     var presenterType = presenter.GetType(); 
     var methodsAndListBoxes = from method in GetActionMethods(presenterType) 
            where method.Name.EndsWith("Choosen") 
            where method.GetParameters().Length == 1 
            let elementName = method.Name.Substring(2, method.Name.Length - 2 /*On*/- 7 /*Choosen*/) 
            let matchingListBox = LogicalTreeHelper.FindLogicalNode(presenter.View, elementName) as ListBox 
            where matchingListBox != null 
            select new {method, matchingListBox}; 

     foreach (var methodAndEvent in methodsAndListBoxes) 
     { 
      var parameterType = methodAndEvent.method.GetParameters()[0].ParameterType; 
      var action = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(parameterType), 
               presenter, methodAndEvent.method); 

      methodAndEvent.matchingListBox.MouseDoubleClick += (sender, args) => 
      { 
       var item1 = ((ListBox)sender).SelectedItem; 
       if(item1 == null) 
        return; 
       action.DynamicInvoke(item1); 
      }; 
     } 
    } 

    private static void WireEvents(IPresenter presenter) 
    { 
     var viewType = presenter.View.GetType(); 
     var presenterType = presenter.GetType(); 
     var methodsAndEvents = 
       from method in GetParameterlessActionMethods(presenterType) 
       let matchingEvent = viewType.GetEvent(method.Name.Substring(2)) 
       where matchingEvent != null 
       where matchingEvent.EventHandlerType == typeof(RoutedEventHandler) 
       select new { method, matchingEvent }; 

     foreach (var methodAndEvent in methodsAndEvents) 
     { 
      var action = (Action)Delegate.CreateDelegate(typeof(Action), 
                  presenter, methodAndEvent.method); 

      var handler = (RoutedEventHandler)((sender, args) => action()); 
      methodAndEvent.matchingEvent.AddEventHandler(presenter.View, handler); 
     } 
    } 

    private static IEnumerable<MethodInfo> GetActionMethods(Type type) 
    { 
     return from method in type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) 
       where method.Name.StartsWith("On") 
       select method; 
    } 

    private static IEnumerable<MethodInfo> GetParameterlessActionMethods(Type type) 
    { 
     return from method in GetActionMethods(type) 
       where method.GetParameters().Length == 0 
       select method; 
    } 

어쨌든, 내가하는의 WinForm 응용 프로그램에 포트에 시도하지만 실패했습니다 내가 EventHandlersRoutedEventHandlers을 변경,하지만 무엇을 찾을 수 없습니다. LogicalTreeHelper에 대해 수행하십시오.

나는 이것에 막혔다. 나는 수동으로 할 수 있었지만,이 미니 프레임 워크는 너무 짜증나서 거의 미쳤다.

PS : 소스 편집 난 그냥 뭔가를 실현

http://msdn.microsoft.com/en-us/magazine/ee819139.aspx

입니다. 내가 바보가 아니에요, 위의 코드는 매우 친절한 테스트되지 않습니다, 그렇죠?

+0

+1 재미있는 아이디어입니다. 처음에는 네가 미쳤다고 생각했는데 이해가가는 군. – briantyler

답변

1

확인. 나는 그 일을 스스로 해냈다. 나는 다른 사람이 재미있는 것을 발견했기 때문에 대답을 게시하고 있습니다.

첫째,보기

public interface IBaseView 
{ 
    void Show(); 
    C Get<C>(string controlName) where C : Control; //Needed to later wire the events 
} 

public interface IView : IBaseView 
{ 
    TextBox ClientId { get; set; } //Need to expose this 
    Button SaveClient { get; set; } 
    ListBox MyLittleList { get; set; } 
} 

public partial class View : Form, IView 
{ 
    public TextBox ClientId //since I'm exposing it, my "concrete view" the controls are camelCased 
    { 
     get { return this.clientId; } 
     set { this.clientId = value; } 
    } 

    public Button SaveClient 
    { 
     get { return this.saveClient; } 
     set { this.saveClient = value; } 
    } 

    public ListBox MyLittleList 
    { 
     get { return this.myLittleList; } 
     set { this.myLittleList = value; } 
    } 

    //The view must also return the control to be wired. 
    public C Get<C>(string ControlName) where C : Control 
    { 
     var controlName = ControlName.ToLower(); 
     var underlyingControlName = controlName[0] + ControlName.Substring(1); 
     var underlyingControl = this.Controls.Find(underlyingControlName, true).FirstOrDefault(); 
     //It is strange because is turning PascalCase to camelCase. Could've used _Control for the controls on the concrete view instead 
     return underlyingControl as C; 
    } 
이제

발표자 :

public class Presenter : BasePresenter <ViewModel, View> 
{ 
    Client client; 
    IView view; 
    ViewModel viewModel; 

    public Presenter(int clientId, IView viewParam, ViewModel viewModelParam) 
    { 
     this.view = viewParam; 
     this.viewModel = viewModelParam; 

     client = viewModel.FindById(clientId); 
     BindData(client); 
     wireEventsTo(view); //Implement on the base class 
    } 

    public void OnSaveClient(object sender, EventArgs e) 
    { 
     viewModel.Save(client); 
    } 

    public void OnEnter(object sender, EventArgs e) 
    { 
     MessageBox.Show("It works!"); 
    } 

    public void OnMyLittleListChanged(object sender, EventArgs e) 
    { 
     MessageBox.Show("Test"); 
    } 
} 

은 "마법"은 기본 클래스에서 발생합니다. wireEventsTo (IBaseView 뷰)에서

public abstract class BasePresenter 
    <VM, V> 
    where VM : BaseViewModel 
    where V : IBaseView, new() 
{ 

    protected void wireEventsTo(IBaseView view) 
    { 
     Type presenterType = this.GetType(); 
     Type viewType = view.GetType(); 

     foreach (var method in presenterType.GetMethods()) 
     { 
      var methodName = method.Name; 

      if (methodName.StartsWith("On")) 
      { 
       try 
       { 
        var presenterMethodName = methodName.Substring(2); 
        var nameOfMemberToMatch = presenterMethodName.Replace("Changed", ""); //ListBoxes wiring 

        var matchingMember = viewType.GetMember(nameOfMemberToMatch).FirstOrDefault(); 

        if (matchingMember == null) 
        { 
         return; 
        } 

        if (matchingMember.MemberType == MemberTypes.Event) 
        { 
         wireMethod(view, matchingMember, method);  
        } 

        if (matchingMember.MemberType == MemberTypes.Property) 
        { 
         wireMember(view, matchingMember, method);  
        } 

       } 
       catch (Exception ex) 
       { 
        continue; 
       } 
      } 
     } 
    } 

    private void wireMember(IBaseView view, MemberInfo match, MethodInfo method) 
    { 
     var matchingMemberType = ((PropertyInfo)match).PropertyType; 

     if (matchingMemberType == typeof(Button)) 
     { 
      var matchingButton = view.Get<Button>(match.Name); 

      var eventHandler = (EventHandler)EventHandler.CreateDelegate(typeof(EventHandler), this, method); 

      matchingButton.Click += eventHandler; 
     } 

     if (matchingMemberType == typeof(ListBox)) 
     { 
      var matchinListBox = view.Get<ListBox>(match.Name); 

      var eventHandler = (EventHandler)EventHandler.CreateDelegate(typeof(EventHandler), this, method); 

      matchinListBox.SelectedIndexChanged += eventHandler; 
     } 
    } 

    private void wireMethod(IBaseView view, MemberInfo match, MethodInfo method) 
    { 
     var viewType = view.GetType(); 

     var matchingEvent = viewType.GetEvent(match.Name); 

     if (matchingEvent != null) 
     { 
      if (matchingEvent.EventHandlerType == typeof(EventHandler)) 
      { 
       var eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), this, method); 
       matchingEvent.AddEventHandler(view, eventHandler); 
      } 

      if (matchingEvent.EventHandlerType == typeof(FormClosedEventHandler)) 
      { 
       var eventHandler = FormClosedEventHandler.CreateDelegate(typeof(FormClosedEventHandler), this, method); 
       matchingEvent.AddEventHandler(view, eventHandler); 
      } 
     } 
    } 
} 

나는 그대로이 기능을 사용하고 있습니다. Presenter의 EventHandler를 IView에있는 컨트롤의 기본 이벤트에 자동으로 연결합니다.

또한 메모에 BindData 메서드를 공유하려고합니다.

protected void BindData(Client client) 
    { 
     string nameOfPropertyBeingReferenced; 

     nameOfPropertyBeingReferenced = MVP.Controller.GetPropertyName(() => client.Id); 
     view.ClientId.BindTo(client, nameOfPropertyBeingReferenced); 

     nameOfPropertyBeingReferenced = MVP.Controller.GetPropertyName(() => client.FullName); 
     view.ClientName.BindTo(client, nameOfPropertyBeingReferenced); 
    } 

    public static void BindTo(this TextBox thisTextBox, object viewModelObject, string nameOfPropertyBeingReferenced) 
    { 
     Bind(viewModelObject, thisTextBox, nameOfPropertyBeingReferenced, "Text"); 
    } 

    private static void Bind(object sourceObject, Control destinationControl, string sourceObjectMember, string destinationControlMember) 
    { 
     Binding binding = new Binding(destinationControlMember, sourceObject, sourceObjectMember, true, DataSourceUpdateMode.OnPropertyChanged); 
     //Binding binding = new Binding(sourceObjectMember, sourceObject, destinationControlMember); 
     destinationControl.DataBindings.Clear(); 
     destinationControl.DataBindings.Add(binding); 
    } 

    public static string GetPropertyName<T>(Expression<Func<T>> exp) 
    { 
     return (((MemberExpression)(exp.Body)).Member).Name; 
    } 

이렇게하면 바인딩에서 "마법 문자열"이 제거됩니다. INotificationPropertyChanged에서도 사용할 수 있다고 생각합니다.

어쨌든 누군가가 유용하다고 생각합니다. 코드 냄새를 지적하고 싶다면 완전히 괜찮아.