2 // BindingExpressionBase.cs
5 // Moonlight List (moonlight-list@lists.ximian.com)
7 // Copyright 2008 Novell, Inc.
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System
.Collections
;
30 using System
.ComponentModel
;
31 using System
.Reflection
;
33 using System
.Collections
.Generic
;
34 using System
.Windows
.Controls
;
38 namespace System
.Windows
.Data
{
40 public abstract class BindingExpressionBase
: Expression
44 INotifyPropertyChanged cachedSource
;
50 internal Binding Binding
{
54 FrameworkElement Target
{
58 internal bool UpdatingSource
{
59 get { return updatingSource; }
62 DependencyProperty Property
{
66 bool TwoWayTextBoxText
{
67 get { return Target is TextBox && Property == TextBox.TextProperty && Binding.Mode == BindingMode.TwoWay; }
70 // This is the object we're databound to
71 internal object DataSource
{
74 if (Binding
.Source
!= null)
75 source
= Binding
.Source
;
77 // If DataContext is bound, then we need to read the parents datacontext or use null
78 if (source
== null && Target
!= null) {
79 if (Property
== FrameworkElement
.DataContextProperty
) {
80 FrameworkElement e
= Target
.Parent
as FrameworkElement
;
82 source
= e
.DataContext
;
85 source
= Target
.DataContext
;
89 // If the datasource has changed, disconnect from the old object and reconnect
91 if (source
!= cachedSource
) {
92 if (cachedSource
!= null)
93 cachedSource
.PropertyChanged
-= PropertyChanged
;
94 cachedSource
= source
as INotifyPropertyChanged
;
95 if (cachedSource
!= null)
96 cachedSource
.PropertyChanged
+= PropertyChanged
;
102 PropertyInfo PropertyInfo
{
106 info
= GetPropertyInfo ();
113 // This is the object at the end of the PropertyPath
114 internal object PropertySource
{
118 internal int PropertyIndexer
{
122 internal BindingExpressionBase (Binding binding
, FrameworkElement target
, DependencyProperty property
)
128 if (TwoWayTextBoxText
)
129 ((TextBox
) target
).LostFocus
+= TextBoxLostFocus
;
131 PropertyIndexer
= -1;
134 internal override void Dispose ()
136 if (TwoWayTextBoxText
)
137 ((TextBox
) Target
).LostFocus
-= TextBoxLostFocus
;
139 if (cachedSource
!= null)
140 cachedSource
.PropertyChanged
-= PropertyChanged
;
145 PropertyInfo
GetPropertyInfo ()
147 object source
= DataSource
;
152 // FIXME: What if the path is invalid? A.B....C, AB.C£$.D etc
153 // Can you have an explicit interface implementation? Probably not.
154 string[] parts
= Binding
.Path
.Path
.Split (new char[] { '.' }
);
155 if (parts
.Length
== 0) {
158 for (int i
= 0; i
< parts
.Length
; i
++) {
159 string prop_name
= parts
[i
];
160 string indexer
= null;
161 PropertyInfo p
= null;
164 int close
= parts
[i
].LastIndexOf (']');
166 int open
= parts
[i
].LastIndexOf ("[");
167 prop_name
= parts
[i
].Substring (0, open
);
168 indexer
= parts
[i
].Substring (open
+ 1, close
- open
- 1);
172 p
= source
.GetType ().GetProperty (prop_name
);
174 // The property does not exist, so abort.
178 if (!p
.DeclaringType
.IsVisible
)
179 throw new MethodAccessException (string.Format ("Property {0} cannot be accessed", p
.Name
));
181 if (indexer
!= null) {
182 if (!Int32
.TryParse (indexer
, out idx
))
183 throw new ArgumentException ("Invalid value for indexer.");
186 if (i
!= (parts
.Length
- 1)) {
187 if (indexer
!= null) {
188 IList list
= p
.GetValue (source
, null) as IList
;
190 throw new ArgumentException ("Indexer on non list type.");
193 source
= p
.GetValue (source
, null);
200 PropertySource
= source
;
201 PropertyIndexer
= idx
;
205 throw new Exception ("Should not be reached");
208 public void Invalidate ()
216 internal override object GetValue (DependencyProperty dp
)
222 if (DataSource
== null) {
223 cachedValue
= dp
.DefaultValue
;
226 else if (string.IsNullOrEmpty (Binding
.Path
.Path
)) {
227 // If the path is empty, return the active DataSource
228 cachedValue
= DataSource
;
230 else if (PropertyInfo
== null) {
231 cachedValue
= dp
.DefaultValue
;
235 cachedValue
= GetPropertyValue ();
238 cachedValue
= ConvertToType (dp
, cachedValue
);
240 cachedValue
= dp
.DefaultValue
;
246 internal void SetValue (object value)
249 // TextBox.Text only updates a two way binding if it is *not* focused.
250 if (TwoWayTextBoxText
&& System
.Windows
.Input
.FocusManager
.GetFocusedElement () == Target
)
253 if (updatingSource
|| PropertyInfo
== null)
256 if (Binding
.Converter
!= null)
257 value = Binding
.Converter
.ConvertBack (value,
258 PropertyInfo
.PropertyType
,
259 Binding
.ConverterParameter
,
260 Binding
.ConverterCulture
?? Helper
.DefaultCulture
);
262 value = MoonlightTypeConverter
.ConvertObject (PropertyInfo
, value, Target
.GetType ());
263 if (cachedValue
== null) {
267 else if (cachedValue
.Equals (value)) {
271 updatingSource
= true;
272 SetPropertyValue (value);
274 } catch (Exception ex
) {
275 if (Binding
.NotifyOnValidationError
&& Binding
.ValidatesOnExceptions
) {
276 Target
.RaiseBindingValidationError (new ValidationErrorEventArgs (ValidationErrorEventAction
.Added
, new ValidationError (ex
)));
280 updatingSource
= false;
284 void PropertyChanged (object sender
, PropertyChangedEventArgs e
)
287 updatingSource
= true;
288 if (string.IsNullOrEmpty (Binding
.Path
.Path
)) {
289 Target
.SetValueImpl (Property
, ConvertToType (Property
, DataSource
));
290 } else if (PropertyInfo
== null) {
292 } else if (PropertyInfo
.Name
.Equals (e
.PropertyName
)) {
293 object value = ConvertToType (Property
, GetPropertyValue ());
294 Target
.SetValueImpl (Property
, value);
297 //Type conversion exceptions are silently swallowed
299 updatingSource
= false;
303 object ConvertToType (DependencyProperty dp
, object value)
305 if (Binding
.Converter
!= null) {
306 value = Binding
.Converter
.Convert (value,
307 Property
.PropertyType
,
308 Binding
.ConverterParameter
,
309 Binding
.ConverterCulture
?? Helper
.DefaultCulture
);
311 return MoonlightTypeConverter
.ConvertObject (dp
, value, Target
.GetType ());
314 void TextBoxLostFocus (object sender
, RoutedEventArgs e
)
316 SetValue (((TextBox
) sender
).Text
);
319 object GetPropertyValue ()
321 object res
= PropertyInfo
.GetValue (PropertySource
, null);
323 if (res
!= null && PropertyIndexer
!= -1) {
324 IList list
= res
as IList
;
326 throw new ArgumentException ("Indexer on non list type");
327 res
= list
[PropertyIndexer
];
333 void SetPropertyValue (object value)
335 if (PropertyIndexer
== -1) {
336 PropertyInfo
.SetValue (PropertySource
, value, null);
340 object source
= PropertyInfo
.GetValue (PropertySource
, null);
342 if (source
!= null && PropertyIndexer
!= -1) {
343 IList list
= source
as IList
;
345 throw new ArgumentException ("Indexer on non list type");
346 list
[PropertyIndexer
] = value;