PageRenderTime 45ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/source/library/Interlace/Binding/Binder.cs

https://bitbucket.org/VahidN/interlace
C# | 391 lines | 200 code | 53 blank | 138 comment | 18 complexity | 5098a00daed1bcaa3611eca6e6f7f498 MD5 | raw file
  1. #region Using Directives and Copyright Notice
  2. // Copyright (c) 2007-2010, Computer Consultancy Pty Ltd
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. // * Redistributions of source code must retain the above copyright
  8. // notice, this list of conditions and the following disclaimer.
  9. // * Redistributions in binary form must reproduce the above copyright
  10. // notice, this list of conditions and the following disclaimer in the
  11. // documentation and/or other materials provided with the distribution.
  12. // * Neither the name of the Computer Consultancy Pty Ltd nor the
  13. // names of its contributors may be used to endorse or promote products
  14. // derived from this software without specific prior written permission.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. // ARE DISCLAIMED. IN NO EVENT SHALL COMPUTER CONSULTANCY PTY LTD BE LIABLE
  20. // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  21. // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  22. // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  23. // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  24. // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  25. // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  26. // DAMAGE.
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Text;
  30. #endregion
  31. namespace Interlace.Binding
  32. {
  33. using AutoBinders;
  34. using ViewConverters;
  35. using Views;
  36. /// <summary>
  37. /// A binder connects the properties of a single object (the model) with a number of properties
  38. /// of other objects (the views).
  39. /// </summary>
  40. /// <remarks>
  41. /// <para>
  42. /// A single binder handles a single one to many relationship between objects. The binder is attached
  43. /// to one single object (the "bound-to" object) and can watch any number of properties. Each property
  44. /// is watched by a <see cref="BinderModel"/> instance, which can get and set the property and
  45. /// which subscribes to the change events of the property, if they exist. For details on how
  46. /// the event is found, see the <see cref="BinderModel"/> class documentation.
  47. /// </para>
  48. /// <para>
  49. /// The bound-to object can also be changed at runtime, and all views will be notified appropriately.
  50. /// Also, if the <see pref="BoundTo"/> property is set to null, views are notified and can
  51. /// change the screen to show that no object is bound. When the <see pref="BoundTo"/> property
  52. /// is modified, views are notified immediately.
  53. /// </para>
  54. /// <para>
  55. /// The <see cref="BinderController"/> connects the model instance and the multiple views that may be
  56. /// attached to a property.
  57. /// </para>
  58. /// <para>
  59. /// There are several types of view, all derived from <see cref="BinderViewBase"/>. A view
  60. /// receives notifications when the model property it is connected to changes. Views may also
  61. /// send updates to the model if the objects connected to the view are modified; the
  62. /// binding can be read only, write only or bi-directional depending on the implementation
  63. /// of <see cref="BinderViewBase"/>.
  64. /// </para>
  65. /// </remarks>
  66. public class Binder
  67. {
  68. object _boundTo;
  69. string _autoBindViewPrefix = "";
  70. Dictionary<string, BinderController> _properties;
  71. static List<IAutoBindingFactory> _autoBindings;
  72. static List<IAutoConverterFactory> _autoConverters;
  73. static Binder()
  74. {
  75. _autoBindings = new List<IAutoBindingFactory>();
  76. _autoBindings.Add(new CheckBoxAutoBindingFactory());
  77. _autoBindings.Add(new TextBoxBaseAutoBindingFactory());
  78. _autoBindings.Add(new ComboBoxBaseAutoBindingFactory());
  79. _autoConverters = new List<IAutoConverterFactory>();
  80. _autoConverters.Add(new BasicAutoConverterFactory());
  81. }
  82. /// <summary>
  83. /// Initializes a new instance of the <see cref="Binder"/> class, with no initial bound-to
  84. /// object.
  85. /// </summary>
  86. public Binder()
  87. {
  88. _boundTo = null;
  89. _properties = new Dictionary<string, BinderController>();
  90. }
  91. /// <summary>
  92. /// Initializes a new instance of the <see cref="Binder"/> class, with <paramref name="boundTo"/>
  93. /// as the initial bound-to object.
  94. /// </summary>
  95. /// <param name="boundTo">The bound to.</param>
  96. public Binder(object boundTo)
  97. {
  98. _boundTo = boundTo;
  99. _properties = new Dictionary<string, BinderController>();
  100. }
  101. /// <summary>
  102. /// Initializes a new instance of the <see cref="Binder"/> class, and creates
  103. /// a binding on an underlying binder to update this binders BoundTo property.
  104. /// </summary>
  105. /// <param name="boundToBinder">The binder to create the binding on.</param>
  106. /// <param name="propertyName">Name of the property to bind to.</param>
  107. public Binder(Binder boundToBinder, string propertyName)
  108. : this()
  109. {
  110. boundToBinder.AddBinding(propertyName, new PropertyView(this, "BoundTo", null));
  111. }
  112. /// <summary>
  113. /// Initializes a new instance of the <see cref="Binder"/> class, and creates
  114. /// a binding on an underlying binder to update this binders BoundTo property.
  115. /// </summary>
  116. /// <param name="boundToBinder">The binder to create the binding on.</param>
  117. /// <param name="propertyName">Name of the property to bind to.</param>
  118. /// <param name="readOnly">A boolean indicating whether the binding should be bidirectional or read only.</param>
  119. public Binder(Binder boundToBinder, string propertyName, bool readOnly)
  120. : this()
  121. {
  122. boundToBinder.AddBinding(propertyName, new PropertyView(this, "BoundTo", null, readOnly));
  123. }
  124. /// <summary>
  125. /// The prefix that is removed from the name of an auto-bound control.
  126. /// </summary>
  127. /// <remarks>
  128. /// If multiple fields on a form are auto-bound to fields with the same name, the
  129. /// names of the controls will clash. Each binder can have a prefix set
  130. /// to avoid name clashes. The prefix must be set when the auto-binding is made;
  131. /// changing the prefix after bindings are created has no effect on the previously
  132. /// created bindings.
  133. /// </remarks>
  134. public string AutoBindViewPrefix
  135. {
  136. get { return _autoBindViewPrefix; }
  137. set { _autoBindViewPrefix = value; }
  138. }
  139. private string FixAutoBindName(string name)
  140. {
  141. string underscorelessName = name;
  142. if (!string.IsNullOrEmpty(name) && name[0] == '_')
  143. {
  144. underscorelessName = name.Substring(1, 1).ToUpper() + name.Substring(2);
  145. }
  146. if (underscorelessName.ToLower().StartsWith(_autoBindViewPrefix.ToLower()))
  147. {
  148. return underscorelessName.Substring(_autoBindViewPrefix.Length);
  149. }
  150. else
  151. {
  152. return underscorelessName;
  153. }
  154. }
  155. /// <summary>
  156. /// Creates a binding, making inferences based on the name and type of the control.
  157. /// </summary>
  158. /// <remarks>
  159. /// <para>
  160. /// Auto-binding attempts to use the name and type of the control to determine
  161. /// the field to bind to and the type of view to use.
  162. /// </para>
  163. /// <para>
  164. /// The field name is usually taken from the name of the control (after some simple
  165. /// transformations), but other behaviours are possible. The name is transformed with the
  166. /// following steps:
  167. /// </para>
  168. /// <list type="bullet">
  169. /// <item><description>If present, the single leading underscore is stripped
  170. /// off. The character immediately following the underscore is converted to an
  171. /// uppercase character. For example, "_name" would be changed to "Name".</description></item>
  172. /// <item><description>If set and present at the beginning of the control name,
  173. /// the auto-bind prefix is removed from the name. For example, "PersonAge" would
  174. /// be changed to "Person".</description></item>
  175. /// </list>
  176. /// <para>Auto-binding makes no attempt to check the bound object for existance of the
  177. /// property. In most cases this is not possible; the type of the bound object is
  178. /// not always known when bindings are created, since it is possible to be unbound
  179. /// or bound to different types of objects.</para>
  180. /// </remarks>
  181. /// <param name="viewer">The view control to be bound.</param>
  182. /// <returns>The newly bound view.</returns>
  183. public BinderViewBase AutoBind(object viewer)
  184. {
  185. return AutoBind(viewer, ViewConverterBase.Null);
  186. }
  187. public BinderViewBase AutoBind(object viewer, ViewConverterBase converter)
  188. {
  189. foreach (IAutoBindingFactory factory in _autoBindings)
  190. {
  191. if (factory.CanAutoBind(viewer))
  192. {
  193. BinderViewBase view = factory.CreateView(viewer);
  194. AddBinding(FixAutoBindName(factory.GetAutoBindName(viewer)), view, converter);
  195. return view;
  196. }
  197. }
  198. throw new InvalidOperationException("No suitable autobinder was found.");
  199. }
  200. public BinderViewBase AutoBind(object viewer, BinderHint hint)
  201. {
  202. foreach (IAutoBindingFactory factory in _autoBindings)
  203. {
  204. if (factory.CanAutoBind(viewer))
  205. {
  206. BinderViewBase view = factory.CreateView(viewer);
  207. // Find a converter:
  208. ViewConverterBase converter = ViewConverterBase.Null;
  209. foreach (IAutoConverterFactory converterFactory in _autoConverters)
  210. {
  211. if (converterFactory.CanProvideConverter(view, hint))
  212. {
  213. converter = converterFactory.ProvideConverter(view, hint);
  214. break;
  215. }
  216. // (If a converter can't be provided, just use the exact one.)
  217. }
  218. AddBinding(FixAutoBindName(factory.GetAutoBindName(viewer)), view, converter);
  219. return view;
  220. }
  221. }
  222. throw new InvalidOperationException("No suitable autobinder was found.");
  223. }
  224. public void EnsureControllerExistsForProperty(string propertyName)
  225. {
  226. if (!_properties.ContainsKey(propertyName))
  227. {
  228. IBinderModel model;
  229. switch (propertyName)
  230. {
  231. case "@Count":
  232. model = new CountBinderModel();
  233. break;
  234. default:
  235. model = new PropertyBinderModel(propertyName);
  236. break;
  237. }
  238. _properties[propertyName] = new BinderController(model);
  239. }
  240. }
  241. public void SetTracing(string propertyName, bool enabled)
  242. {
  243. EnsureControllerExistsForProperty(propertyName);
  244. _properties[propertyName].TracingEnabled = enabled;
  245. }
  246. public void AddBinding(string propertyName, BinderViewBase view)
  247. {
  248. AddBinding(propertyName, view, ViewConverterBase.Null);
  249. }
  250. public void AddBinding(string propertyName, BinderViewBase view,
  251. ViewConverterBase converter)
  252. {
  253. view.Converter = converter;
  254. EnsureControllerExistsForProperty(propertyName);
  255. BinderController controller = _properties[propertyName];
  256. controller.AddView(view);
  257. if (_boundTo != null) controller.ConnectBoundToObject(_boundTo);
  258. }
  259. public event EventHandler BoundToChanged;
  260. public object BoundTo
  261. {
  262. get { return _boundTo; }
  263. set
  264. {
  265. _boundTo = value;
  266. if (_boundTo != null)
  267. {
  268. foreach (BinderController controller in _properties.Values)
  269. {
  270. controller.ConnectBoundToObject(_boundTo);
  271. }
  272. }
  273. else
  274. {
  275. foreach (BinderController controller in _properties.Values)
  276. {
  277. controller.DisconnectBoundToObject();
  278. }
  279. }
  280. if (BoundToChanged != null) BoundToChanged(this, EventArgs.Empty);
  281. }
  282. }
  283. /// <summary>
  284. /// Gets a list of all views, ordered to some logical (and stable) ordering.
  285. /// </summary>
  286. /// <remarks>
  287. /// Views are assigned a partial ordering based on (for real controls) the tab
  288. /// order. The first control in the ordering is picked first when error processing
  289. /// is deciding which control to complain about first.
  290. /// </remarks>
  291. /// <returns>An ordered list of all views.</returns>
  292. private List<BinderViewBase> GetOrderedViewsList()
  293. {
  294. List<BinderViewBase> views = new List<BinderViewBase>();
  295. foreach (BinderController controller in _properties.Values)
  296. {
  297. views.AddRange(controller.Views);
  298. }
  299. views.Sort((Comparison<BinderViewBase>)delegate(BinderViewBase lhs, BinderViewBase rhs)
  300. {
  301. return lhs.OrderingIndex.CompareTo(rhs.OrderingIndex);
  302. });
  303. return views;
  304. }
  305. /// <summary>
  306. /// Finds the first view that is in an error state, and returns an error handle from it.
  307. /// </summary>
  308. /// <returns>
  309. /// An error handle, or null if no error has occurred.
  310. /// </returns>
  311. public BinderViewError FirstError
  312. {
  313. get
  314. {
  315. List<BinderViewBase> views = GetOrderedViewsList();
  316. foreach (BinderViewBase view in views)
  317. {
  318. BinderViewError error = view.FindFirstError();
  319. if (error != null) return error;
  320. }
  321. return null;
  322. }
  323. }
  324. /// <summary>
  325. /// Gets a value indicating whether any views in the binder have errors.
  326. /// </summary>
  327. /// <value>
  328. /// <c>true</c> if one or more errors are present; otherwise, <c>false</c>.
  329. /// </value>
  330. public bool HasErrors
  331. {
  332. get
  333. {
  334. return FirstError != null;
  335. }
  336. }
  337. }
  338. }