2012-11-29 24 views
2

我试图开发一个使用MVP模式的应用程序。按照惯例/反射动态布线视图,模型和Presenter

问题是手动连接所有代码。我希望能够减少所需的代码。我试图复制另一个解决方案,但我无法工作。我正在使用Winforms,并且我使用的解决方案是使用WPF。

这将连线上的一些约定的事情:

查看事件由名称接线。例如:视图上的“加载”事件将连线到演示者上的“OnLoaded()”方法 按钮命令按名称连线。例如:双击“动作”将被连接到“OnActionsChoosen(ToDoAction)”

在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程序,但我没有成功,我改变了RoutedEventHandlersEventHandlers,但找不到什么。做关于LogicalTreeHelper

我很困难。我可以手动完成,但是我发现这个迷你框架非常简洁,几乎是疯了。

PS:Source是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。

无论如何,我希望有人认为它有用。如果你想指出代码的味道,我完全可以。