-
Notifications
You must be signed in to change notification settings - Fork 7
/
EventToCommand.cs
156 lines (137 loc) · 5.46 KB
/
EventToCommand.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
namespace Xamarin.Behaviors
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
/// <summary>
/// Invoked a command when an event raises
/// </summary>
public class EventToCommand : Behavior
{
public static readonly BindableProperty EventNameProperty = BindableProperty.Create<EventToCommand, string>(p => p.EventName, null);
public static readonly BindableProperty CommandProperty = BindableProperty.Create<EventToCommand, ICommand>(p => p.Command, null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create<EventToCommand, object>(p => p.CommandParameter, null);
public static readonly BindableProperty CommandNameProperty = BindableProperty.Create<EventToCommand, string>(p => p.CommandName, null);
public static readonly BindableProperty CommandNameContextProperty = BindableProperty.Create<EventToCommand, object>(p => p.CommandNameContext, null);
private Delegate handler;
private EventInfo eventInfo;
/// <summary>
/// Gets or sets the name of the event to subscribe
/// </summary>
/// <value>
/// The name of the event.
/// </value>
public string EventName
{
get { return (string)GetValue(EventNameProperty); }
set { SetValue(EventNameProperty, value); }
}
/// <summary>
/// Gets or sets the command to invoke when event raised
/// </summary>
/// <value>
/// The command.
/// </value>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
/// <summary>
/// Gets or sets the optional command parameter.
/// </summary>
/// <value>
/// The command parameter.
/// </value>
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
/// <summary>
/// Gets or sets the name of the relative command.
/// </summary>
/// <value>
/// The name of the command.
/// </value>
public string CommandName
{
get { return (string)GetValue(CommandNameProperty); }
set { SetValue(CommandNameProperty, value); }
}
/// <summary>
/// Gets or sets the relative context used with command name.
/// </summary>
/// <value>
/// The command name context.
/// </value>
public object CommandNameContext
{
get { return GetValue(CommandNameContextProperty); }
set { SetValue(CommandNameContextProperty, value); }
}
protected override void OnAttach()
{
var events = this.AssociatedObject.GetType().GetRuntimeEvents();
if (events.Any())
{
this.eventInfo = events.FirstOrDefault(e => e.Name == this.EventName);
if (this.eventInfo == null) throw new ArgumentException(string.Format("EventToCommand: Can't find any event named '{0}' on attached type"));
this.AddEventHandler(eventInfo, this.AssociatedObject, this.OnFired);
}
}
protected override void OnDetach()
{
if (this.handler != null) this.eventInfo.RemoveEventHandler(this.AssociatedObject, this.handler);
}
/// <summary>
/// Subscribes the event handler.
/// </summary>
/// <param name="eventInfo">The event information.</param>
/// <param name="item">The item.</param>
/// <param name="action">The action.</param>
private void AddEventHandler(EventInfo eventInfo, object item, Action action)
{
//Got inspiration from here: http://stackoverflow.com/questions/9753366/subscribing-an-action-to-any-event-type-via-reflection
//Maybe it is possible to pass Event arguments as CommanParameter
var mi = eventInfo.EventHandlerType.GetRuntimeMethods().First(rtm => rtm.Name == "Invoke");
List<ParameterExpression> parameters = mi.GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToList();
MethodInfo actionMethodInfo = action.GetMethodInfo();
Expression exp = Expression.Call(Expression.Constant(this), actionMethodInfo, null);
this.handler = Expression.Lambda(eventInfo.EventHandlerType, exp, parameters).Compile();
eventInfo.AddEventHandler(item, handler);
}
/// <summary>
/// Called when subscribed event fires
/// </summary>
private void OnFired()
{
if (!string.IsNullOrEmpty(this.CommandName))
{
if (this.Command == null) this.CreateRelativeBinding();
}
if (this.Command == null) throw new InvalidOperationException("No command available, Is Command properly properly set up?");
if (this.Command.CanExecute(this.CommandParameter))
{
this.Command.Execute(this.CommandParameter);
}
}
/// <summary>
/// Cretes a binding between relative context and provided Command name
/// </summary>
private void CreateRelativeBinding()
{
if (this.CommandNameContext == null) throw new ArgumentNullException("CommandNameContext property cannot be null when using CommandName property, consider using CommandNameContext={b:RelativeContext [ElementName]} markup markup extension.");
if (this.Command != null) throw new InvalidOperationException("Both Command and CommandName properties specified, only one mode supported.");
PropertyInfo pi = this.CommandNameContext.GetType().GetRuntimeProperty(this.CommandName);
if (pi == null) throw new ArgumentNullException(string.Format("Can't find a command named '{0}'", this.CommandName));
this.Command = pi.GetValue(this.CommandNameContext) as ICommand;
if (this.Command == null) throw new ArgumentNullException(string.Format("Can't create binding with CommandName '{0}'", this.CommandName));
}
}
}