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

Language C# Lines 392
MD5 Hash 5098a00daed1bcaa3611eca6e6f7f498 Estimated Cost $4,986 (why?)
Repository https://bitbucket.org/VahidN/interlace.git View Raw File View Project SPDX
  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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#region Using Directives and Copyright Notice

// Copyright (c) 2007-2010, Computer Consultancy Pty Ltd
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the Computer Consultancy Pty Ltd nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
// ARE DISCLAIMED. IN NO EVENT SHALL COMPUTER CONSULTANCY PTY LTD BE LIABLE 
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Interlace.Binding
{
    using AutoBinders;
    using ViewConverters;
    using Views;

    /// <summary>
    /// A binder connects the properties of a single object (the model) with a number of properties
    /// of other objects (the views).
    /// </summary>
    /// <remarks>
    /// <para>
    /// A single binder handles a single one to many relationship between objects. The binder is attached 
    /// to one single object (the "bound-to" object) and can watch any number of properties. Each property
    /// is watched by a <see cref="BinderModel"/> instance, which can get and set the property and
    /// which subscribes to the change events of the property, if they exist. For details on how
    /// the event is found, see the <see cref="BinderModel"/> class documentation.
    /// </para>
    /// <para>
    /// The bound-to object can also be changed at runtime, and all views will be notified appropriately.
    /// Also, if the <see pref="BoundTo"/> property is set to null, views are notified and can
    /// change the screen to show that no object is bound. When the <see pref="BoundTo"/> property
    /// is modified, views are notified immediately.
    /// </para>
    /// <para>
    /// The <see cref="BinderController"/> connects the model instance and the multiple views that may be 
    /// attached to a property.
    /// </para>
    /// <para>
    /// There are several types of view, all derived from <see cref="BinderViewBase"/>. A view
    /// receives notifications when the model property it is connected to changes. Views may also
    /// send updates to the model if the objects connected to the view are modified; the
    /// binding can be read only, write only or bi-directional depending on the implementation
    /// of <see cref="BinderViewBase"/>.
    /// </para>
    /// </remarks>
    public class Binder
    {
        object _boundTo;
        string _autoBindViewPrefix = "";
        Dictionary<string, BinderController> _properties;

        static List<IAutoBindingFactory> _autoBindings;
        static List<IAutoConverterFactory> _autoConverters;

        static Binder()
        {
            _autoBindings = new List<IAutoBindingFactory>();
            _autoBindings.Add(new CheckBoxAutoBindingFactory());
            _autoBindings.Add(new TextBoxBaseAutoBindingFactory());
            _autoBindings.Add(new ComboBoxBaseAutoBindingFactory());

            _autoConverters = new List<IAutoConverterFactory>();
            _autoConverters.Add(new BasicAutoConverterFactory());
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Binder"/> class, with no initial bound-to
        /// object.
        /// </summary>
        public Binder()
        {
            _boundTo = null;
            _properties = new Dictionary<string, BinderController>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Binder"/> class, with <paramref name="boundTo"/>
        /// as the initial bound-to object.
        /// </summary>
        /// <param name="boundTo">The bound to.</param>
        public Binder(object boundTo)
        {
            _boundTo = boundTo;
            _properties = new Dictionary<string, BinderController>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Binder"/> class, and creates 
        /// a binding on an underlying binder to update this binders BoundTo property.
        /// </summary>
        /// <param name="boundToBinder">The binder to create the binding on.</param>
        /// <param name="propertyName">Name of the property to bind to.</param>
        public Binder(Binder boundToBinder, string propertyName)
        : this()
        {
            boundToBinder.AddBinding(propertyName, new PropertyView(this, "BoundTo", null));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Binder"/> class, and creates 
        /// a binding on an underlying binder to update this binders BoundTo property.
        /// </summary>
        /// <param name="boundToBinder">The binder to create the binding on.</param>
        /// <param name="propertyName">Name of the property to bind to.</param>
        /// <param name="readOnly">A boolean indicating whether the binding should be bidirectional or read only.</param>
        public Binder(Binder boundToBinder, string propertyName, bool readOnly)
        : this()
        {
            boundToBinder.AddBinding(propertyName, new PropertyView(this, "BoundTo", null, readOnly));
        }

        /// <summary>
        /// The prefix that is removed from the name of an auto-bound control.
        /// </summary>
        /// <remarks>
        /// If multiple fields on a form are auto-bound to fields with the same name, the
        /// names of the controls will clash. Each binder can have a prefix set
        /// to avoid name clashes. The prefix must be set when the auto-binding is made; 
        /// changing the prefix after bindings are created has no effect on the previously
        /// created bindings.
        /// </remarks>
        public string AutoBindViewPrefix
        {
            get { return _autoBindViewPrefix; }
            set { _autoBindViewPrefix = value; }
        }

        private string FixAutoBindName(string name)
        {
            string underscorelessName = name;

            if (!string.IsNullOrEmpty(name) && name[0] == '_')
            {
                underscorelessName = name.Substring(1, 1).ToUpper() + name.Substring(2);
            }

            if (underscorelessName.ToLower().StartsWith(_autoBindViewPrefix.ToLower()))
            {
                return underscorelessName.Substring(_autoBindViewPrefix.Length);
            }
            else
            {
                return underscorelessName;
            }
        }

        /// <summary>
        /// Creates a binding, making inferences based on the name and type of the control.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Auto-binding attempts to use the name and type of the control to determine
        /// the field to bind to and the type of view to use.
        /// </para>
        /// <para>
        /// The field name is usually taken from the name of the control (after some simple
        /// transformations), but other behaviours are possible. The name is transformed with the 
        /// following steps:
        /// </para>
        /// <list type="bullet">
        /// <item><description>If present, the single leading underscore is stripped
        /// off. The character immediately following the underscore is converted to an 
        /// uppercase character. For example, "_name" would be changed to "Name".</description></item>
        /// <item><description>If set and present at the beginning of the control name, 
        /// the auto-bind prefix is removed from the name. For example, "PersonAge" would
        /// be changed to "Person".</description></item>
        /// </list>
        /// <para>Auto-binding makes no attempt to check the bound object for existance of the
        /// property. In most cases this is not possible; the type of the bound object is 
        /// not always known when bindings are created, since it is possible to be unbound
        /// or bound to different types of objects.</para>
        /// </remarks>
        /// <param name="viewer">The view control to be bound.</param>
        /// <returns>The newly bound view.</returns>
        public BinderViewBase AutoBind(object viewer)
        {
            return AutoBind(viewer, ViewConverterBase.Null);
        }

        public BinderViewBase AutoBind(object viewer, ViewConverterBase converter)
        {
            foreach (IAutoBindingFactory factory in _autoBindings)
            {
                if (factory.CanAutoBind(viewer))
                {
                    BinderViewBase view = factory.CreateView(viewer);
                    
                    AddBinding(FixAutoBindName(factory.GetAutoBindName(viewer)), view, converter);

                    return view;
                }
            }

            throw new InvalidOperationException("No suitable autobinder was found.");
        }

        public BinderViewBase AutoBind(object viewer, BinderHint hint)
        {
            foreach (IAutoBindingFactory factory in _autoBindings)
            {
                if (factory.CanAutoBind(viewer))
                {
                    BinderViewBase view = factory.CreateView(viewer);

                    // Find a converter:
                    ViewConverterBase converter = ViewConverterBase.Null;

                    foreach (IAutoConverterFactory converterFactory in _autoConverters)
                    {
                        if (converterFactory.CanProvideConverter(view, hint))
                        {
                            converter = converterFactory.ProvideConverter(view, hint);

                            break;
                        }

                        // (If a converter can't be provided, just use the exact one.)
                    }
                    
                    AddBinding(FixAutoBindName(factory.GetAutoBindName(viewer)), view, converter);

                    return view;
                }
            }

            throw new InvalidOperationException("No suitable autobinder was found.");
        }

        public void EnsureControllerExistsForProperty(string propertyName)
        {
            if (!_properties.ContainsKey(propertyName))
            {
                IBinderModel model;

                switch (propertyName)
                {
                    case "@Count":
                        model = new CountBinderModel();
                        break;

                    default:
                        model = new PropertyBinderModel(propertyName);
                        break;
                }

                _properties[propertyName] = new BinderController(model);
            }
        }

        public void SetTracing(string propertyName, bool enabled)
        {
            EnsureControllerExistsForProperty(propertyName);

            _properties[propertyName].TracingEnabled = enabled;
        }

        public void AddBinding(string propertyName, BinderViewBase view)
        {
            AddBinding(propertyName, view, ViewConverterBase.Null);
        }

        public void AddBinding(string propertyName, BinderViewBase view,
            ViewConverterBase converter)
        {
            view.Converter = converter;

            EnsureControllerExistsForProperty(propertyName);

            BinderController controller = _properties[propertyName];
            controller.AddView(view);

            if (_boundTo != null) controller.ConnectBoundToObject(_boundTo);
        }

        public event EventHandler BoundToChanged;

        public object BoundTo
        {
            get { return _boundTo; }
            set
            {
                _boundTo = value;

                if (_boundTo != null)
                {
                    foreach (BinderController controller in _properties.Values)
                    {
                        controller.ConnectBoundToObject(_boundTo);
                    }
                }
                else
                {
                    foreach (BinderController controller in _properties.Values)
                    {
                        controller.DisconnectBoundToObject();
                    }
                }

                if (BoundToChanged != null) BoundToChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets a list of all views, ordered to some logical (and stable) ordering.
        /// </summary>
        /// <remarks>
        /// Views are assigned a partial ordering based on (for real controls) the tab
        /// order. The first control in the ordering is picked first when error processing
        /// is deciding which control to complain about first.
        /// </remarks>
        /// <returns>An ordered list of all views.</returns>
        private List<BinderViewBase> GetOrderedViewsList()
        {
            List<BinderViewBase> views = new List<BinderViewBase>();

            foreach (BinderController controller in _properties.Values)
            {
                views.AddRange(controller.Views);
            }

            views.Sort((Comparison<BinderViewBase>)delegate(BinderViewBase lhs, BinderViewBase rhs)
            {
                return lhs.OrderingIndex.CompareTo(rhs.OrderingIndex);
            });

            return views;
        }

        /// <summary>
        /// Finds the first view that is in an error state, and returns an error handle from it.
        /// </summary>
        /// <returns>
        /// An error handle, or null if no error has occurred. 
        /// </returns>
        public BinderViewError FirstError
        {
            get 
            {
                List<BinderViewBase> views = GetOrderedViewsList();

                foreach (BinderViewBase view in views)
                {
                    BinderViewError error = view.FindFirstError();

                    if (error != null) return error;
                }

                return null;
            }
        }

        /// <summary>
        /// Gets a value indicating whether any views in the binder have errors.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if one or more errors are present; otherwise, <c>false</c>.
        /// </value>
        public bool HasErrors
        {
            get
            {
                return FirstError != null;
            }
        }
    }
}
Back to Top