From bede1ad98d2fa75f07cf3362e0f17b61aaca77f9 Mon Sep 17 00:00:00 2001 From: Robert Kozak Date: Tue, 18 Jan 2011 12:52:25 -0800 Subject: [PATCH] alpha 0.1 --- Application/DisposableObject.cs | 51 + Application/PropertyNotifier.cs | 154 + Application/TinyIoC.cs | 3109 +++++++++++++++++ Application/TinyMessenger.cs | 792 +++++ Binding/Binding.cs | 61 + Binding/BindingBase.cs | 44 + Binding/BindingExpression.cs | 186 + Binding/BindingMode.cs | 41 + Binding/BindingOperations.cs | 215 ++ Binding/IBindable.cs | 42 + Binding/IBindingExpression.cs | 45 + Binding/IValueConverter.cs | 41 + Binding/PropertyPath.cs | 15 + Binding/TypeExtensions.cs | 40 + ChangeLog | 47 + Collections/EnumBinder.cs | 69 + Collections/EnumCollection.cs | 116 + .../ObservableCollection.cs | 100 + .../INotifyCollectionChanged.cs | 35 + .../NotifyCollectionChangedAction.cs | 37 + .../NotifyCollectionChangedEventArgs.cs | 102 + .../NotifyCollectionChangedEventHandler.cs | 31 + Commands/ICommand.cs | 42 + Commands/ReflectiveCommand.cs | 86 + Commands/UICommand.cs | 74 + Converters/EnumConverter.cs | 64 + Converters/EnumItemsConverter.cs | 91 + Converters/EnumerableConverter.cs | 50 + Converters/MoneyConverter.cs | 52 + Converters/PercentConverter.cs | 53 + Converters/ViewConverter.cs | 58 + Dialog/DialogViewController.cs | 667 ++++ Dialog/Elements/ActivityElement.cs | 42 + Dialog/Elements/BaseBooleanImageElement.cs | 128 + Dialog/Elements/BoolElement.cs | 48 + Dialog/Elements/BooleanElement.cs | 86 + Dialog/Elements/BooleanImageElement.cs | 59 + Dialog/Elements/ButtonElement.cs | 91 + Dialog/Elements/CheckboxElement.cs | 79 + Dialog/Elements/DateElement.cs | 60 + Dialog/Elements/DateTimeElement.cs | 172 + Dialog/Elements/Element.cs | 324 ++ Dialog/Elements/ElementBadge.cs | 168 + Dialog/Elements/EntryElement.cs | 283 ++ Dialog/Elements/FloatElement.cs | 96 + Dialog/Elements/HtmlElement.cs | 140 + Dialog/Elements/ImageElement.cs | 229 ++ Dialog/Elements/ImageStringElement.cs | 81 + Dialog/Elements/ListBoxElement.cs | 29 + Dialog/Elements/LoadMoreElement.cs | 184 + Dialog/Elements/MapViewElement.cs | 160 + Dialog/Elements/MultilineElement.cs | 61 + Dialog/Elements/OwnerDrawnElement.cs | 111 + Dialog/Elements/RadioElement.cs | 113 + Dialog/Elements/RootElement.cs | 639 ++++ Dialog/Elements/Section.cs | 427 +++ Dialog/Elements/StringElement.cs | 102 + Dialog/Elements/StyledMultilineElement.cs | 53 + Dialog/Elements/StyledStringElement.cs | 76 + Dialog/Elements/TimeElement.cs | 58 + Dialog/Elements/UIViewElement.cs | 111 + Dialog/Elements/ViewElement.cs | 536 +++ Dialog/Group.cs | 47 + Dialog/IElementSizing.cs | 40 + Dialog/IRoot.cs | 59 + Dialog/ITappable.cs | 39 + Dialog/RadioGroup.cs | 53 + Dialog/Reflection API/AlignmentAttribute.cs | 44 + Dialog/Reflection API/BindAttribute.cs | 108 + Dialog/Reflection API/BindingContext.cs | 635 ++++ Dialog/Reflection API/ButtonAttribute.cs | 58 + Dialog/Reflection API/CaptionAttribute.cs | 43 + Dialog/Reflection API/CheckboxAttribute.cs | 38 + Dialog/Reflection API/Color.cs | 70 + Dialog/Reflection API/DateAttribute.cs | 38 + Dialog/Reflection API/EditButtonAttribute.cs | 17 + Dialog/Reflection API/EntryAttribute.cs | 50 + Dialog/Reflection API/HtmlAttribute.cs | 38 + Dialog/Reflection API/InlineAttribute.cs | 39 + Dialog/Reflection API/ListAttribute.cs | 39 + Dialog/Reflection API/MapAttribute.cs | 17 + Dialog/Reflection API/MulitlineAttribute.cs | 38 + Dialog/Reflection API/OrderAttribute.cs | 45 + Dialog/Reflection API/PasswordAttribute.cs | 41 + .../Reflection API/PopOnSelectionAttribute.cs | 38 + Dialog/Reflection API/RangeAttribute.cs | 44 + Dialog/Reflection API/RootAttribute.cs | 47 + Dialog/Reflection API/SectionAttribute.cs | 55 + Dialog/Reflection API/SkipAttribute.cs | 38 + Dialog/Reflection API/TimeAttribute.cs | 38 + Dialog/Reflection API/ToolbarButton.cs | 17 + Dialog/RefreshTableHeaderView.cs | 199 ++ Dialog/UITableViewElementCell.cs | 75 + EnumValueConverter.cs | 20 + Extensions/EnumExtensions.cs | 66 + Extensions/ExpressionExtensions.cs | 138 + Extensions/TypeExtensions.cs | 63 + Images/arrow.png | Bin 0 -> 1600 bytes Model/IModel.cs | 11 + Model/Model.cs | 16 + MonoTouch.Dialog.pidb | Bin 0 -> 158359 bytes MonoTouch.MVVM.csproj | 219 ++ MonoTouch.MVVM.pidb | Bin 0 -> 302263 bytes MonoTouch.MVVM.sln | 50 + MonoTouch.MVVM.userprefs | 62 + README.markdown | 164 + Samples/AppDelegateIPad.cs | 39 + Samples/AppDelegateIPhone.cs | 38 + Samples/Converters/CurrentMovieConverter.cs | 27 + Samples/Converters/FontConverter.cs | 27 + Samples/Converters/MovieViewModelConverter.cs | 9 + Samples/Info.plist | 13 + Samples/Main.cs | 14 + Samples/MainMenu.cs | 37 + Samples/MainWindowIPad.xib | 482 +++ Samples/MainWindowIPad.xib.designer.cs | 33 + Samples/MainWindowIPhone.xib | 303 ++ Samples/MainWindowIPhone.xib.designer.cs | 47 + Samples/Models/MovieDataModel.cs | 30 + Samples/Samples.csproj | 119 + Samples/Samples.pidb | Bin 0 -> 28875 bytes Samples/ViewModels/AddressViewModel.cs | 38 + Samples/ViewModels/InterestingViewModel.cs | 26 + Samples/ViewModels/MovieViewModel.cs | 92 + Samples/Views/AddressView.cs | 50 + Samples/Views/InterestingView.cs | 60 + Samples/Views/MovieElement.cs | 63 + Samples/Views/MovieListView.cs | 77 + Samples/Views/MovieView.cs | 84 + Samples/Views/View1.cs | 33 + View/IView.cs | 45 + View/View.cs | 152 + ViewModel/IViewModel.cs | 40 + ViewModel/ViewModel.cs | 44 + 134 files changed, 16334 insertions(+) create mode 100644 Application/DisposableObject.cs create mode 100644 Application/PropertyNotifier.cs create mode 100644 Application/TinyIoC.cs create mode 100644 Application/TinyMessenger.cs create mode 100644 Binding/Binding.cs create mode 100644 Binding/BindingBase.cs create mode 100644 Binding/BindingExpression.cs create mode 100644 Binding/BindingMode.cs create mode 100644 Binding/BindingOperations.cs create mode 100644 Binding/IBindable.cs create mode 100644 Binding/IBindingExpression.cs create mode 100644 Binding/IValueConverter.cs create mode 100644 Binding/PropertyPath.cs create mode 100644 Binding/TypeExtensions.cs create mode 100644 ChangeLog create mode 100644 Collections/EnumBinder.cs create mode 100644 Collections/EnumCollection.cs create mode 100644 Collections/System.Collections.ObjectModel/ObservableCollection.cs create mode 100644 Collections/System.Collections.Specialized/INotifyCollectionChanged.cs create mode 100644 Collections/System.Collections.Specialized/NotifyCollectionChangedAction.cs create mode 100644 Collections/System.Collections.Specialized/NotifyCollectionChangedEventArgs.cs create mode 100644 Collections/System.Collections.Specialized/NotifyCollectionChangedEventHandler.cs create mode 100644 Commands/ICommand.cs create mode 100644 Commands/ReflectiveCommand.cs create mode 100644 Commands/UICommand.cs create mode 100644 Converters/EnumConverter.cs create mode 100644 Converters/EnumItemsConverter.cs create mode 100644 Converters/EnumerableConverter.cs create mode 100644 Converters/MoneyConverter.cs create mode 100644 Converters/PercentConverter.cs create mode 100644 Converters/ViewConverter.cs create mode 100644 Dialog/DialogViewController.cs create mode 100644 Dialog/Elements/ActivityElement.cs create mode 100644 Dialog/Elements/BaseBooleanImageElement.cs create mode 100644 Dialog/Elements/BoolElement.cs create mode 100644 Dialog/Elements/BooleanElement.cs create mode 100644 Dialog/Elements/BooleanImageElement.cs create mode 100644 Dialog/Elements/ButtonElement.cs create mode 100644 Dialog/Elements/CheckboxElement.cs create mode 100644 Dialog/Elements/DateElement.cs create mode 100644 Dialog/Elements/DateTimeElement.cs create mode 100644 Dialog/Elements/Element.cs create mode 100644 Dialog/Elements/ElementBadge.cs create mode 100644 Dialog/Elements/EntryElement.cs create mode 100644 Dialog/Elements/FloatElement.cs create mode 100644 Dialog/Elements/HtmlElement.cs create mode 100644 Dialog/Elements/ImageElement.cs create mode 100644 Dialog/Elements/ImageStringElement.cs create mode 100644 Dialog/Elements/ListBoxElement.cs create mode 100644 Dialog/Elements/LoadMoreElement.cs create mode 100644 Dialog/Elements/MapViewElement.cs create mode 100644 Dialog/Elements/MultilineElement.cs create mode 100644 Dialog/Elements/OwnerDrawnElement.cs create mode 100644 Dialog/Elements/RadioElement.cs create mode 100644 Dialog/Elements/RootElement.cs create mode 100644 Dialog/Elements/Section.cs create mode 100644 Dialog/Elements/StringElement.cs create mode 100644 Dialog/Elements/StyledMultilineElement.cs create mode 100644 Dialog/Elements/StyledStringElement.cs create mode 100644 Dialog/Elements/TimeElement.cs create mode 100644 Dialog/Elements/UIViewElement.cs create mode 100644 Dialog/Elements/ViewElement.cs create mode 100644 Dialog/Group.cs create mode 100644 Dialog/IElementSizing.cs create mode 100644 Dialog/IRoot.cs create mode 100644 Dialog/ITappable.cs create mode 100644 Dialog/RadioGroup.cs create mode 100644 Dialog/Reflection API/AlignmentAttribute.cs create mode 100644 Dialog/Reflection API/BindAttribute.cs create mode 100644 Dialog/Reflection API/BindingContext.cs create mode 100644 Dialog/Reflection API/ButtonAttribute.cs create mode 100644 Dialog/Reflection API/CaptionAttribute.cs create mode 100644 Dialog/Reflection API/CheckboxAttribute.cs create mode 100644 Dialog/Reflection API/Color.cs create mode 100644 Dialog/Reflection API/DateAttribute.cs create mode 100644 Dialog/Reflection API/EditButtonAttribute.cs create mode 100644 Dialog/Reflection API/EntryAttribute.cs create mode 100644 Dialog/Reflection API/HtmlAttribute.cs create mode 100644 Dialog/Reflection API/InlineAttribute.cs create mode 100644 Dialog/Reflection API/ListAttribute.cs create mode 100644 Dialog/Reflection API/MapAttribute.cs create mode 100644 Dialog/Reflection API/MulitlineAttribute.cs create mode 100644 Dialog/Reflection API/OrderAttribute.cs create mode 100644 Dialog/Reflection API/PasswordAttribute.cs create mode 100644 Dialog/Reflection API/PopOnSelectionAttribute.cs create mode 100644 Dialog/Reflection API/RangeAttribute.cs create mode 100644 Dialog/Reflection API/RootAttribute.cs create mode 100644 Dialog/Reflection API/SectionAttribute.cs create mode 100644 Dialog/Reflection API/SkipAttribute.cs create mode 100644 Dialog/Reflection API/TimeAttribute.cs create mode 100644 Dialog/Reflection API/ToolbarButton.cs create mode 100644 Dialog/RefreshTableHeaderView.cs create mode 100644 Dialog/UITableViewElementCell.cs create mode 100644 EnumValueConverter.cs create mode 100644 Extensions/EnumExtensions.cs create mode 100644 Extensions/ExpressionExtensions.cs create mode 100644 Extensions/TypeExtensions.cs create mode 100644 Images/arrow.png create mode 100644 Model/IModel.cs create mode 100644 Model/Model.cs create mode 100644 MonoTouch.Dialog.pidb create mode 100644 MonoTouch.MVVM.csproj create mode 100644 MonoTouch.MVVM.pidb create mode 100644 MonoTouch.MVVM.sln create mode 100644 MonoTouch.MVVM.userprefs create mode 100644 README.markdown create mode 100644 Samples/AppDelegateIPad.cs create mode 100644 Samples/AppDelegateIPhone.cs create mode 100644 Samples/Converters/CurrentMovieConverter.cs create mode 100644 Samples/Converters/FontConverter.cs create mode 100644 Samples/Converters/MovieViewModelConverter.cs create mode 100644 Samples/Info.plist create mode 100644 Samples/Main.cs create mode 100644 Samples/MainMenu.cs create mode 100644 Samples/MainWindowIPad.xib create mode 100644 Samples/MainWindowIPad.xib.designer.cs create mode 100644 Samples/MainWindowIPhone.xib create mode 100644 Samples/MainWindowIPhone.xib.designer.cs create mode 100644 Samples/Models/MovieDataModel.cs create mode 100644 Samples/Samples.csproj create mode 100644 Samples/Samples.pidb create mode 100644 Samples/ViewModels/AddressViewModel.cs create mode 100644 Samples/ViewModels/InterestingViewModel.cs create mode 100644 Samples/ViewModels/MovieViewModel.cs create mode 100644 Samples/Views/AddressView.cs create mode 100644 Samples/Views/InterestingView.cs create mode 100644 Samples/Views/MovieElement.cs create mode 100644 Samples/Views/MovieListView.cs create mode 100644 Samples/Views/MovieView.cs create mode 100644 Samples/Views/View1.cs create mode 100644 View/IView.cs create mode 100644 View/View.cs create mode 100644 ViewModel/IViewModel.cs create mode 100644 ViewModel/ViewModel.cs diff --git a/Application/DisposableObject.cs b/Application/DisposableObject.cs new file mode 100644 index 0000000..52064c9 --- /dev/null +++ b/Application/DisposableObject.cs @@ -0,0 +1,51 @@ +// +// DisposableObject.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + + public abstract class DisposableObject : IDisposable + { + ~DisposableObject() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool calledExplicitly) + { + } + } +} diff --git a/Application/PropertyNotifier.cs b/Application/PropertyNotifier.cs new file mode 100644 index 0000000..37284c2 --- /dev/null +++ b/Application/PropertyNotifier.cs @@ -0,0 +1,154 @@ +// +// PropertyNotifier.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Linq.Expressions; + using MonoTouch.Dialog; + + public class PropertyNotifier : DisposableObject, INotifyPropertyChanged + { + private IDictionary _PropertyMap = new Dictionary(); + + public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { }; + + protected IDictionary PropertyMap { get { return _PropertyMap; } set { _PropertyMap = value; } } + + public PropertyNotifier() + { + } + + public void NotifyPropertyChanged(string propertyName) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + + public void NotifyPropertyChanged(Expression> property) + { + NotifyPropertyChanged(property.PropertyName()); + } + + public T Get(Expression> property) + { + return Get(property, default(T)); + } + + public T Get(Expression> property, T defaultValue) + { + return Get(property, () => defaultValue); + } + + public T Get(Expression> property, Func defaultValue) + { + if (property == null) + { + throw new ArgumentException("cannot be null", "property"); + } + + var name = property.PropertyName(); + var isFunction = name.Contains(".") || string.IsNullOrEmpty(name); + + // Either property is of form: ()=>Foo.Bar or property is a function: ()=>CalculateValue(); + if (isFunction) + { + object source = this; + var propertyInfo = this.GetType ().GetNestedProperty (ref source, name, false); + if (source != this) { + return (T)propertyInfo.GetValue (source, new object[] { }); + } + } + + + if (_PropertyMap.ContainsKey(name)) + return (T)_PropertyMap[name]; + + if (defaultValue != null) + { + T result = defaultValue.Invoke(); + if (!isFunction) + { + Set(property, result); + } + + return result; + } + + return default(T); + } + + public void Set(Expression> property, T value) + { + if (property == null) + { + throw new ArgumentException("cannot be null", "property"); + } + + var name = property.PropertyName(); + + T oldValue = default(T); + if(name.Contains(".")) + { + object source = this; + var propertyInfo = this.GetType().GetNestedProperty(ref source, name, false); + if (source != this) + { + propertyInfo.SetValue(source, value, new object[] { }); + name = propertyInfo.Name; + } + } + else + { + object tryValue = null; + + if (_PropertyMap.TryGetValue(name, out tryValue)) + { + oldValue = (T)tryValue; + + if (oldValue == null && value == null) + return; + + if (oldValue != null && oldValue.Equals(value)) + return; + + _PropertyMap[name] = value; + } + else + { + _PropertyMap.Add(name, value); + } + } + + NotifyPropertyChanged(name); + } + } +} + diff --git a/Application/TinyIoC.cs b/Application/TinyIoC.cs new file mode 100644 index 0000000..8683cb2 --- /dev/null +++ b/Application/TinyIoC.cs @@ -0,0 +1,3109 @@ +//=============================================================================== +// TinyIoC +// +// An easy to use, hassle free, Inversion of Control Container for small projects +// and beginners alike. +// +// http://hg.grumpydev.com/tinyioc +//=============================================================================== +// Copyright © Steven Robbins. All rights reserved. +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY +// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT +// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE. +//=============================================================================== + +// -- MONOTOUCH IMPORTANT -- // +// If you are intending to use MonoTouch you *must* define MONOTOUCH or the code +// will not compile. +// -- MONOTOUCH IMPORTANT -- // +#define MONOTOUCH + +#region Preprocessor Directives +// Uncomment this line if you want the container to automatically +// register the TinyMessenger messenger/event aggregator +//#define TINYMESSENGER + +// Preprocessor directives for enabling/disabling functionality +// depending on platform features. If the platform has an appropriate +// #DEFINE then these should be set automatically below. +#define EXPRESSIONS // Platform supports System.Linq.Expressions +#define APPDOMAIN_GETASSEMBLIES // Platform supports getting all assemblies from the AppDomain object +#define UNBOUND_GENERICS_GETCONSTRUCTORS // Platform supports GetConstructors on unbound generic types +#define GETPARAMETERS_OPEN_GENERICS // Platform supports GetParameters on open generics +#define ASPNET // Adds ASP.Net pre-request singleton support + +// CompactFramework / Windows Phone 7 +// By default does not support System.Linq.Expressions. +// AppDomain object does not support enumerating all assemblies in the app domain. +#if PocketPC || WINDOWS_PHONE +#undef EXPRESSIONS +#undef APPDOMAIN_GETASSEMBLIES +#undef UNBOUND_GENERICS_GETCONSTRUCTORS +#undef ASPNET +#endif + +// PocketPC has a bizarre limitation on enumerating parameters on unbound generic methods. +// We need to use a slower workaround in that case. +#if PocketPC +#undef GETPARAMETERS_OPEN_GENERICS +#endif + +#if SILVERLIGHT +#undef APPDOMAIN_GETASSEMBLIES +#undef ASPNET +#endif + +#if MONOTOUCH +#undef ASPNET +#endif +#endregion +namespace TinyIoC +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; +#if EXPRESSIONS + using System.Linq.Expressions; +#endif +#if ASPNET + using System.Web; +#endif + + #region SafeDictionary + public class SafeDictionary : IDisposable + { + private readonly object _Padlock = new object(); + private readonly Dictionary _Dictionary = new Dictionary(); + + public TValue this[TKey key] + { + set + { + lock (_Padlock) + { + TValue current; + if (_Dictionary.TryGetValue(key, out current)) + { + var disposable = current as IDisposable; + + if (disposable != null) + disposable.Dispose(); + } + + _Dictionary[key] = value; + } + } + } + + public bool TryGetValue(TKey key, out TValue value) + { + lock (_Padlock) + { + return _Dictionary.TryGetValue(key, out value); + } + } + + public bool Remove(TKey key) + { + lock (_Padlock) + { + return _Dictionary.Remove(key); + } + } + + public void Clear() + { + lock (_Padlock) + { + _Dictionary.Clear(); + } + } + + public IEnumerable Keys + { + get + { + return _Dictionary.Keys; + } + } + #region IDisposable Members + + public void Dispose() + { + lock (_Padlock) + { + var disposableItems = from item in _Dictionary.Values + where item is IDisposable + select item as IDisposable; + + foreach (var item in disposableItems) + { + item.Dispose(); + } + } + + GC.SuppressFinalize(this); + } + + #endregion + } + #endregion + + #region Extensions + public static class TypeExtensions + { + /// + /// Gets a generic method from a type given the method name, binding flags, generic types and parameter types + /// + /// Source type + /// Binding flags + /// Name of the method + /// Generic types to use to make the method generic + /// Method parameters + /// MethodInfo or null if no matches found + /// + /// + public static MethodInfo GetGenericMethod(this Type sourceType, System.Reflection.BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes) + { +#if GETPARAMETERS_OPEN_GENERICS + var methods = sourceType.GetMethods(bindingFlags) + .Where(mi => string.Equals(methodName, mi.Name, StringComparison.InvariantCulture)) + .Where(mi => mi.ContainsGenericParameters) + .Where(mi => mi.GetGenericArguments().Length == genericTypes.Length) + .Where(mi => mi.GetParameters().Length == parameterTypes.Length) + .Select(mi => mi.MakeGenericMethod(genericTypes)) + .Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)) + .ToList(); +#else + var validMethods = from method in sourceType.GetMethods(bindingFlags) + where method.Name == methodName + where method.IsGenericMethod + where method.GetGenericArguments().Length == genericTypes.Length + let genericMethod = method.MakeGenericMethod(genericTypes) + where genericMethod.GetParameters().Count() == parameterTypes.Length + where genericMethod.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes) + select genericMethod; + + var methods = validMethods.ToList(); +#endif + if (methods.Count > 1) + throw new AmbiguousMatchException(); + + var actualMethod = methods.FirstOrDefault(); + + return actualMethod; + } + } + #endregion + + #region TinyIoC Exception Types + public class TinyIoCResolutionException : Exception + { + private const string ERROR_TEXT = "Unable to resolve type: {0}"; + + public TinyIoCResolutionException(Type type) + : base(String.Format(ERROR_TEXT, type.FullName)) + { + } + + public TinyIoCResolutionException(Type type, Exception innerException) + : base(String.Format(ERROR_TEXT, type.FullName), innerException) + { + } + } + + public class TinyIoCRegistrationTypeException : Exception + { + private const string REGISTER_ERROR_TEXT = "Cannot register type {0} - abstract classes or interfaces are not valid implementation types for {1}."; + + public TinyIoCRegistrationTypeException(Type type, string factory) + : base(String.Format(REGISTER_ERROR_TEXT, type.FullName, factory)) + { + } + + public TinyIoCRegistrationTypeException(Type type, string factory, Exception innerException) + : base(String.Format(REGISTER_ERROR_TEXT, type.FullName, factory), innerException) + { + } + } + + public class TinyIoCRegistrationException : Exception + { + private const string CONVERT_ERROR_TEXT = "Cannot convert current registration of {0} to {1}"; + private const string GENERIC_CONSTRAINT_ERROR_TEXT = "Type {1} is not valid for a registration of type {0}"; + + public TinyIoCRegistrationException(Type type, string method) + : base(String.Format(CONVERT_ERROR_TEXT, type.FullName, method)) + { + } + + public TinyIoCRegistrationException(Type type, string method, Exception innerException) + : base(String.Format(CONVERT_ERROR_TEXT, type.FullName, method), innerException) + { + } + + public TinyIoCRegistrationException(Type registerType, Type implementationType) + : base(String.Format(GENERIC_CONSTRAINT_ERROR_TEXT, registerType.FullName, implementationType.FullName)) + { + } + + public TinyIoCRegistrationException(Type registerType, Type implementationType, Exception innerException) + : base(String.Format(GENERIC_CONSTRAINT_ERROR_TEXT, registerType.FullName, implementationType.FullName), innerException) + { + } + } + + public class TinyIoCWeakReferenceException : Exception + { + private const string ERROR_TEXT = "Unable to instantiate {0} - referenced object has been reclaimed"; + + public TinyIoCWeakReferenceException(Type type) + : base(String.Format(ERROR_TEXT, type.FullName)) + { + } + + public TinyIoCWeakReferenceException(Type type, Exception innerException) + : base(String.Format(ERROR_TEXT, type.FullName), innerException) + { + } + } + + public class TinyIoCConstructorResolutionException : Exception + { + private const string ERROR_TEXT = "Unable to resolve constructor for {0} using provided Expression."; + + public TinyIoCConstructorResolutionException(Type type) + : base(String.Format(ERROR_TEXT, type.FullName)) + { + } + + public TinyIoCConstructorResolutionException(Type type, Exception innerException) + : base(String.Format(ERROR_TEXT, type.FullName), innerException) + { + } + + public TinyIoCConstructorResolutionException(string message, Exception innerException) + : base(message, innerException) + { + } + + public TinyIoCConstructorResolutionException(string message) + : base(message) + { + } + } + + public class TinyIoCAutoRegistrationException : Exception + { + private const string ERROR_TEXT = "Duplicate implementation of type {0} found ({1})."; + + public TinyIoCAutoRegistrationException(Type registerType, IEnumerable types) + : base(String.Format(ERROR_TEXT, registerType, GetTypesString(types))) + { + } + + public TinyIoCAutoRegistrationException(Type registerType, IEnumerable types, Exception innerException) + : base(String.Format(ERROR_TEXT, registerType, GetTypesString(types)), innerException) + { + } + + private static string GetTypesString(IEnumerable types) + { + var typeNames = from type in types + select type.FullName; + + return string.Join(",", typeNames.ToArray()); + } + } + #endregion + + #region Public Setup / Settings Classes + /// + /// Name/Value pairs for specifying "user" parameters when resolving + /// + public sealed class NamedParameterOverloads : Dictionary + { + public static NamedParameterOverloads FromIDictionary(IDictionary data) + { + return data as NamedParameterOverloads ?? new NamedParameterOverloads(data); + } + + public NamedParameterOverloads() + { + } + + public NamedParameterOverloads(IDictionary data) + : base(data) + { + } + + private static readonly NamedParameterOverloads _Default = new NamedParameterOverloads(); + + public static NamedParameterOverloads Default + { + get + { + return _Default; + } + } + } + + public enum UnregisteredResolutionActions + { + /// + /// Attempt to resolve type, even if the type isn't registered. + /// + /// Registered types/options will always take precedence. + /// + AttemptResolve, + + /// + /// Fail resolution if type not explicitly registered + /// + Fail, + + /// + /// Attempt to resolve unregistered type if requested type is generic + /// and no registration exists for the specific generic parameters used. + /// + /// Registered types/options will always take precedence. + /// + GenericsOnly + } + + public enum NamedResolutionFailureActions + { + AttemptUnnamedResolution, + Fail + } + + /// + /// Resolution settings + /// + public sealed class ResolveOptions + { + private static readonly ResolveOptions _Default = new ResolveOptions(); + private static readonly ResolveOptions _FailUnregisteredAndNameNotFound = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.Fail, UnregisteredResolutionAction = UnregisteredResolutionActions.Fail }; + private static readonly ResolveOptions _FailUnregisteredOnly = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.AttemptUnnamedResolution, UnregisteredResolutionAction = UnregisteredResolutionActions.Fail }; + private static readonly ResolveOptions _FailNameNotFoundOnly = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.Fail, UnregisteredResolutionAction = UnregisteredResolutionActions.AttemptResolve }; + + private UnregisteredResolutionActions _UnregisteredResolutionAction = UnregisteredResolutionActions.AttemptResolve; + public UnregisteredResolutionActions UnregisteredResolutionAction + { + get { return _UnregisteredResolutionAction; } + set { _UnregisteredResolutionAction = value; } + } + + private NamedResolutionFailureActions _NamedResolutionFailureAction = NamedResolutionFailureActions.Fail; + public NamedResolutionFailureActions NamedResolutionFailureAction + { + get { return _NamedResolutionFailureAction; } + set { _NamedResolutionFailureAction = value; } + } + + /// + /// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found) + /// + public static ResolveOptions Default + { + get + { + return _Default; + } + } + + /// + /// Preconfigured option for attempting resolution of unregistered types and failing on named resolution if name not found + /// + public static ResolveOptions FailNameNotFoundOnly + { + get + { + return _FailNameNotFoundOnly; + } + } + + /// + /// Preconfigured option for failing on resolving unregistered types and on named resolution if name not found + /// + public static ResolveOptions FailUnregisteredAndNameNotFound + { + get + { + return _FailUnregisteredAndNameNotFound; + } + } + + /// + /// Preconfigured option for failing on resolving unregistered types, but attempting unnamed resolution if name not found + /// + public static ResolveOptions FailUnregisteredOnly + { + get + { + return _FailUnregisteredOnly; + } + } + } + #endregion + + public sealed class TinyIoCContainer : IDisposable + { + #region "Fluent" API + /// + /// Registration options for "fluent" API + /// + public sealed class RegisterOptions + { + private TinyIoCContainer _Container; + private TypeRegistration _Registration; + + public RegisterOptions(TinyIoCContainer container, TypeRegistration registration) + { + _Container = container; + _Registration = registration; + } + + /// + /// Make registration a singleton (single instance) if possible + /// + /// RegisterOptions + /// + public RegisterOptions AsSingleton() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "singleton"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.SingletonVariant); + } + +#if ASPNET + public RegisterOptions AsPerRequestSingleton() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "singleton"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.PerRequestSingletonVariant); + } +#endif + + /// + /// Make registration multi-instance if possible + /// + /// RegisterOptions + /// + public RegisterOptions AsMultiInstance() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "multi-instance"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.MultiInstanceVariant); + } + + /// + /// Make registration hold a weak reference if possible + /// + /// RegisterOptions + /// + public RegisterOptions WithWeakReference() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "weak reference"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.WeakReferenceVariant); + } + + /// + /// Make registration hold a strong reference if possible + /// + /// RegisterOptions + /// + public RegisterOptions WithStrongReference() + { + var currentFactory = _Container.GetCurrentFactory(_Registration); + + if (currentFactory == null) + throw new TinyIoCRegistrationException(_Registration.Type, "strong reference"); + + return _Container.AddUpdateRegistration(_Registration, currentFactory.StrongReferenceVariant); + } + +#if EXPRESSIONS + public RegisterOptions UsingConstructor(Expression> constructor) + { + var lambda = constructor as LambdaExpression; + if (lambda == null) + throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); + + var newExpression = lambda.Body as NewExpression; + if (newExpression == null) + throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); + + var constructorInfo = newExpression.Constructor; + if (constructorInfo == null) + throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); + + var currentFactory = _Container.GetCurrentFactory(_Registration); + if (currentFactory == null) + throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); + + currentFactory.SetConstructor(constructorInfo); + + return this; + } +#endif + } + + /// + /// Registration options for "fluent" API when registering multiple implementations + /// + public sealed class MultiRegisterOptions + { + private IEnumerable _RegisterOptions; + + /// + /// Initializes a new instance of the MultiRegisterOptions class. + /// + /// Registration options + public MultiRegisterOptions(IEnumerable registerOptions) + { + _RegisterOptions = registerOptions; + } + + /// + /// Make registration a singleton (single instance) if possible + /// + /// RegisterOptions + /// + public MultiRegisterOptions AsSingleton() + { + _RegisterOptions = ExecuteOnAllRegisterOptions(ro => ro.AsSingleton()); + return this; + } + + /// + /// Make registration multi-instance if possible + /// + /// MultiRegisterOptions + /// + public MultiRegisterOptions AsMultiInstance() + { + _RegisterOptions = ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance()); + return this; + } + + private IEnumerable ExecuteOnAllRegisterOptions(Func action) + { + var newRegisterOptions = new List(); + + foreach (var registerOption in _RegisterOptions) + { + newRegisterOptions.Add(action(registerOption)); + } + + return newRegisterOptions; + } + } + #endregion + + #region Public API + #region Child Containers + public TinyIoCContainer GetChildContainer() + { + return new TinyIoCContainer(this); + } + #endregion + + #region Registration + /// + /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. + /// + /// If more than one class implements an interface then only one implementation will be registered + /// although no error will be thrown. + /// + public void AutoRegister() + { +#if APPDOMAIN_GETASSEMBLIES + AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), true); +#else + AutoRegisterInternal(new Assembly[] {this.GetType().Assembly}, true); +#endif + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. + /// + /// Whether to ignore duplicate implementations of an interface/base class. False=throw an exception + /// + public void AutoRegister(bool ignoreDuplicateImplementations) + { +#if APPDOMAIN_GETASSEMBLIES + AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), ignoreDuplicateImplementations); +#else + AutoRegisterInternal(new Assembly[] { this.GetType().Assembly }, ignoreDuplicateImplementations); +#endif + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the specified assembly + /// + /// If more than one class implements an interface then only one implementation will be registered + /// although no error will be thrown. + /// + /// Assembly to process + public void AutoRegister(Assembly assembly) + { + AutoRegisterInternal(new Assembly[] { assembly }, true); + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the specified assembly + /// + /// Assembly to process + /// Whether to ignore duplicate implementations of an interface/base class. False=throw an exception + /// + public void AutoRegister(Assembly assembly, bool ignoreDuplicateImplementations) + { + AutoRegisterInternal(new Assembly[] { assembly }, ignoreDuplicateImplementations); + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies + /// + /// If more than one class implements an interface then only one implementation will be registered + /// although no error will be thrown. + /// + /// Assemblies to process + public void AutoRegister(IEnumerable assemblies) + { + AutoRegisterInternal(assemblies, true); + } + + /// + /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies + /// + /// Assemblies to process + /// Whether to ignore duplicate implementations of an interface/base class. False=throw an exception + /// + public void AutoRegister(IEnumerable assemblies, bool ignoreDuplicateImplementations) + { + AutoRegisterInternal(assemblies, ignoreDuplicateImplementations); + } + + /// + /// Creates/replaces a container class registration with default options. + /// + /// Type to register + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerImplementation) + { + return ExecuteGenericRegister(new Type[] { registerImplementation }, new Type[] { }, null); + } + + /// + /// Creates/replaces a named container class registration with default options. + /// + /// Type to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerImplementation, string name) + { + return ExecuteGenericRegister(new Type[] { registerImplementation }, new Type[] { typeof(string) }, new object[] { name }); + } + + /// + /// Creates/replaces a container class registration with a given implementation and default options. + /// + /// Type to register + /// Type to instantiate that implements RegisterType + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Type registerImplementation) + { + return ExecuteGenericRegister(new Type[] { registerType, registerImplementation }, new Type[] { }, null); + } + + /// + /// Creates/replaces a named container class registration with a given implementation and default options. + /// + /// Type to register + /// Type to instantiate that implements RegisterType + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Type registerImplementation, string name) + { + return ExecuteGenericRegister(new Type[] { registerType, registerImplementation }, new Type[] { typeof(string) }, new object[] { name }); + } + + /// + /// Creates/replaces a container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Instance of RegisterType to register + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerImplementation, object instance) + { + return ExecuteGenericRegister(new Type[] { registerImplementation }, new Type[] { registerImplementation }, new object[] { instance }); + } + + /// + /// Creates/replaces a named container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Instance of RegisterType to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerImplementation, object instance, string name) + { + return ExecuteGenericRegister(new Type[] { registerImplementation }, new Type[] { registerImplementation, typeof(string) }, new object[] { instance, name }); + } + + /// + /// Creates/replaces a container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Type of instance to register that implements RegisterType + /// Instance of RegisterImplementation to register + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Type implementationType, object instance) + { + return ExecuteGenericRegister(new Type[] { registerType, implementationType }, new Type[] { implementationType }, new object[] { instance }); + } + + /// + /// Creates/replaces a named container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Type of instance to register that implements RegisterType + /// Instance of RegisterImplementation to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(Type registerType, Type implementationType, object instance, string name) + { + return ExecuteGenericRegister(new Type[] { registerType, implementationType }, new Type[] { implementationType, typeof(string) }, new object[] { instance, name }); + } + + /// + /// Creates/replaces a container class registration with default options. + /// + /// Type to register + /// RegisterOptions for fluent API + public RegisterOptions Register() + where RegisterType : class + { + return RegisterInternal(typeof(RegisterType), string.Empty, GetDefaultObjectFactory()); + } + + /// + /// Creates/replaces a named container class registration with default options. + /// + /// Type to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(string name) + where RegisterType : class + { + return RegisterInternal(typeof(RegisterType), name, GetDefaultObjectFactory()); + } + + /// + /// Creates/replaces a container class registration with a given implementation and default options. + /// + /// Type to register + /// Type to instantiate that implements RegisterType + /// RegisterOptions for fluent API + public RegisterOptions Register() + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + return RegisterInternal(typeof(RegisterType), string.Empty, GetDefaultObjectFactory()); + } + + /// + /// Creates/replaces a named container class registration with a given implementation and default options. + /// + /// Type to register + /// Type to instantiate that implements RegisterType + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(string name) + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + return RegisterInternal(typeof(RegisterType), name, GetDefaultObjectFactory()); + } + + /// + /// Creates/replaces a container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Instance of RegisterType to register + /// RegisterOptions for fluent API + public RegisterOptions Register(RegisterType instance) + where RegisterType : class + { + return RegisterInternal(typeof(RegisterType), string.Empty, new InstanceFactory(instance)); + } + + /// + /// Creates/replaces a named container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Instance of RegisterType to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(RegisterType instance, string name) + where RegisterType : class + { + return RegisterInternal(typeof(RegisterType), name, new InstanceFactory(instance)); + } + + /// + /// Creates/replaces a container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Type of instance to register that implements RegisterType + /// Instance of RegisterImplementation to register + /// RegisterOptions for fluent API + public RegisterOptions Register(RegisterImplementation instance) + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + return RegisterInternal(typeof(RegisterType), string.Empty, new InstanceFactory(instance)); + } + + /// + /// Creates/replaces a named container class registration with a specific, strong referenced, instance. + /// + /// Type to register + /// Type of instance to register that implements RegisterType + /// Instance of RegisterImplementation to register + /// Name of registration + /// RegisterOptions for fluent API + public RegisterOptions Register(RegisterImplementation instance, string name) + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + return RegisterInternal(typeof(RegisterType), name, new InstanceFactory(instance)); + } + + /// + /// Creates/replaces a container class registration with a user specified factory + /// + /// Type to register + /// Factory/lambda that returns an instance of RegisterType + /// RegisterOptions for fluent API + public RegisterOptions Register(Func factory) + where RegisterType : class + { + return RegisterInternal(typeof(RegisterType), string.Empty, new DelegateFactory(factory)); + } + + /// + /// Creates/replaces a named container class registration with a user specified factory + /// + /// Type to register + /// Factory/lambda that returns an instance of RegisterType + /// Name of registation + /// RegisterOptions for fluent API + public RegisterOptions Register(Func factory, string name) + where RegisterType : class + { + return RegisterInternal(typeof(RegisterType), name, new DelegateFactory(factory)); + } + + /// + /// Register multiple implementations of a type. + /// + /// Internally this registers each implementation using the full name of the class as its registration name. + /// + /// Type that each implementation implements + /// Types that implement RegisterType + /// MultiRegisterOptions for the fluent API + public MultiRegisterOptions RegisterMultiple(IEnumerable types) + { + if (types == null) + throw new ArgumentNullException("types", "types is null."); + + foreach (var type in types) + if (!typeof(RegisterType).IsAssignableFrom(type)) + throw new ArgumentException(String.Format("types: The type {0} is not assignable from {1}", typeof(RegisterType).FullName, type.FullName)); + + if (types.Count() != types.Distinct().Count()) + throw new ArgumentException("types: The same implementation type cannot be specificed multiple times"); + + var registerOptions = new List(); + + foreach (var type in types) + { + registerOptions.Add(Register(typeof(RegisterType), type, type.FullName)); + } + + return new MultiRegisterOptions(registerOptions); + } + #endregion + + #region Resolution + /// + /// Attempts to resolve a type using default options. + /// + /// Type to resolve + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType) + { + return ResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, ResolveOptions.Default); + } + + /// + /// Attempts to resolve a type using specified options. + /// + /// Type to resolve + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, ResolveOptions options) + { + return ResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, string name) + { + return ResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, ResolveOptions.Default); + } + + /// + /// Attempts to resolve a type using supplied options and name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, string name, ResolveOptions options) + { + return ResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, NamedParameterOverloads parameters) + { + return ResolveInternal(new TypeRegistration(resolveType), parameters, ResolveOptions.Default); + } + + /// + /// Attempts to resolve a type using specified options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options) + { + return ResolveInternal(new TypeRegistration(resolveType), parameters, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied constructor parameters and name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Name of registration + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, string name, NamedParameterOverloads parameters) + { + return ResolveInternal(new TypeRegistration(resolveType, name), parameters, ResolveOptions.Default); + } + + /// + /// Attempts to resolve a named type using specified options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public object Resolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options) + { + return ResolveInternal(new TypeRegistration(resolveType, name), parameters, options); + } + + /// + /// Attempts to resolve a type using default options. + /// + /// Type to resolve + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve() + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType)); + } + + /// + /// Attempts to resolve a type using specified options. + /// + /// Type to resolve + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(ResolveOptions options) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), options); + } + + /// + /// Attempts to resolve a type using default options and the supplied name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(string name) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), name); + } + + /// + /// Attempts to resolve a type using supplied options and name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(string name, ResolveOptions options) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), name, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(NamedParameterOverloads parameters) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), parameters); + } + + /// + /// Attempts to resolve a type using specified options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(NamedParameterOverloads parameters, ResolveOptions options) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), parameters, options); + } + + /// + /// Attempts to resolve a type using default options and the supplied constructor parameters and name. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// User specified constructor parameters + /// Name of registration + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(string name, NamedParameterOverloads parameters) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), name, parameters); + } + + /// + /// Attempts to resolve a named type using specified options and the supplied constructor parameters. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Instance of type + /// Unable to resolve the type. + public ResolveType Resolve(string name, NamedParameterOverloads parameters, ResolveOptions options) + where ResolveType : class + { + return (ResolveType)Resolve(typeof(ResolveType), name, parameters, options); + } + + /// + /// Attempts to predict whether a given type can be resolved with default options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType) + { + return CanResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, ResolveOptions.Default); + } + + /// + /// Attempts to predict whether a given named type can be resolved with default options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Bool indicating whether the type can be resolved + private bool CanResolve(Type resolveType, string name) + { + return CanResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, ResolveOptions.Default); + } + + /// + /// Attempts to predict whether a given type can be resolved with the specified options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, ResolveOptions options) + { + return CanResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, options); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the specified options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, string name, ResolveOptions options) + { + return CanResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, options); + } + + /// + /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters and default options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// User supplied named parameter overloads + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, NamedParameterOverloads parameters) + { + return CanResolveInternal(new TypeRegistration(resolveType), parameters, ResolveOptions.Default); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters and default options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// User supplied named parameter overloads + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, string name, NamedParameterOverloads parameters) + { + return CanResolveInternal(new TypeRegistration(resolveType, name), parameters, ResolveOptions.Default); + } + + /// + /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// User supplied named parameter overloads + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options) + { + return CanResolveInternal(new TypeRegistration(resolveType), parameters, options); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// User supplied named parameter overloads + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options) + { + return CanResolveInternal(new TypeRegistration(resolveType, name), parameters, options); + } + + /// + /// Attempts to predict whether a given type can be resolved with default options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Bool indicating whether the type can be resolved + public bool CanResolve() + where ResolveType : class + { + return CanResolve(typeof(ResolveType)); + } + + /// + /// Attempts to predict whether a given named type can be resolved with default options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Bool indicating whether the type can be resolved + public bool CanResolve(string name) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), name); + } + + /// + /// Attempts to predict whether a given type can be resolved with the specified options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(ResolveOptions options) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), options); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the specified options. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(string name, ResolveOptions options) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), name, options); + } + + /// + /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters and default options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// User supplied named parameter overloads + /// Bool indicating whether the type can be resolved + public bool CanResolve(NamedParameterOverloads parameters) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), parameters); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters and default options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// User supplied named parameter overloads + /// Bool indicating whether the type can be resolved + public bool CanResolve(string name, NamedParameterOverloads parameters) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), name, parameters); + } + + /// + /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// User supplied named parameter overloads + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(NamedParameterOverloads parameters, ResolveOptions options) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), parameters, options); + } + + /// + /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options. + /// + /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). + /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. + /// + /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. + /// + /// Type to resolve + /// Name of registration + /// User supplied named parameter overloads + /// Resolution options + /// Bool indicating whether the type can be resolved + public bool CanResolve(string name, NamedParameterOverloads parameters, ResolveOptions options) + where ResolveType : class + { + return CanResolve(typeof(ResolveType), name, parameters, options); + } + + /// + /// Attemps to resolve a type using the default options + /// + /// Type to resolve + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the given options + /// + /// Type to resolve + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, ResolveOptions options, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and given name + /// + /// Type to resolve + /// Name of registration + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, string name, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, name); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the given options and name + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, string name, ResolveOptions options, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, name, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and supplied constructor parameters + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, NamedParameterOverloads parameters, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, parameters); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and supplied name and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, string name, NamedParameterOverloads parameters, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, name, parameters); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the supplied options and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, parameters, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the supplied name, options and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options, out object resolvedType) + { + try + { + resolvedType = Resolve(resolveType, name, parameters, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = null; + return false; + } + } + + /// + /// Attemps to resolve a type using the default options + /// + /// Type to resolve + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the given options + /// + /// Type to resolve + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(ResolveOptions options, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and given name + /// + /// Type to resolve + /// Name of registration + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(string name, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(name); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the given options and name + /// + /// Type to resolve + /// Name of registration + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(string name, ResolveOptions options, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(name, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and supplied constructor parameters + /// + /// Type to resolve + /// User specified constructor parameters + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(NamedParameterOverloads parameters, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(parameters); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the default options and supplied name and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(string name, NamedParameterOverloads parameters, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(name, parameters); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the supplied options and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(NamedParameterOverloads parameters, ResolveOptions options, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(parameters, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Attemps to resolve a type using the supplied name, options and constructor parameters + /// + /// Type to resolve + /// Name of registration + /// User specified constructor parameters + /// Resolution options + /// Resolved type or default if resolve fails + /// True if resolved sucessfully, false otherwise + public bool TryResolve(string name, NamedParameterOverloads parameters, ResolveOptions options, out ResolveType resolvedType) + where ResolveType : class + { + try + { + resolvedType = Resolve(name, parameters, options); + return true; + } + catch (TinyIoCResolutionException) + { + resolvedType = default(ResolveType); + return false; + } + } + + /// + /// Returns all registrations of a type + /// + /// Type to resolveAll + /// Whether to include un-named (default) registrations + /// IEnumerable + public IEnumerable ResolveAll(Type resolveType, bool includeUnnamed) + { + return ResolveAllInternal(resolveType, includeUnnamed).Select(o => o); + } + + /// + /// Returns all registrations of a type, both named and unnamed + /// + /// Type to resolveAll + /// IEnumerable + public IEnumerable ResolveAll(Type resolveType) + { + return ResolveAll(resolveType, false); + } + + /// + /// Returns all registrations of a type + /// + /// Type to resolveAll + /// Whether to include un-named (default) registrations + /// IEnumerable + public IEnumerable ResolveAll(bool includeUnnamed) + where ResolveType : class + { + foreach (var resolvedType in ResolveAll(typeof(ResolveType), includeUnnamed)) + { + yield return resolvedType as ResolveType; + } + } + + /// + /// Returns all registrations of a type, both named and unnamed + /// + /// Type to resolveAll + /// Whether to include un-named (default) registrations + /// IEnumerable + public IEnumerable ResolveAll() + where ResolveType : class + { + return ResolveAll(true); + } + + /// + /// Attempts to resolve all public property dependencies on the given object. + /// + /// Object to "build up" + public void BuildUp(object input) + { + BuildUpInternal(input, ResolveOptions.Default); + } + + /// + /// Attempts to resolve all public property dependencies on the given object using the given resolve options. + /// + /// Object to "build up" + /// Resolve options to use + public void BuildUp(object input, ResolveOptions resolveOptions) + { + BuildUpInternal(input, resolveOptions); + } + #endregion + #endregion + + #region Object Factories + private abstract class ObjectFactoryBase + { + /// + /// Whether to assume this factory sucessfully constructs its objects + /// + /// Generally set to true for delegate style factories as CanResolve cannot delve + /// into the delegates they contain. + /// + public virtual bool AssumeConstruction { get { return false; } } + + /// + /// The type the factory instantiates + /// + public abstract Type CreatesType { get; } + + /// + /// Constructor to use, if specified + /// + public ConstructorInfo Constructor { get; protected set; } + + /// + /// Create the type + /// + /// Container that requested the creation + /// Any user parameters passed + /// + public abstract object GetObject(TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options); + + public virtual ObjectFactoryBase SingletonVariant + { + get + { + throw new TinyIoCRegistrationException(this.GetType(), "singleton"); + } + } +#if ASPNET + public virtual TinyIoCContainer.ObjectFactoryBase PerRequestSingletonVariant + { + get + { + throw new TinyIoCRegistrationException(this.GetType(), "per-request-singleton"); + } + } +#endif + public virtual ObjectFactoryBase MultiInstanceVariant + { + get + { + throw new TinyIoCRegistrationException(this.GetType(), "multi-instance"); + } + } + + public virtual ObjectFactoryBase StrongReferenceVariant + { + get + { + throw new TinyIoCRegistrationException(this.GetType(), "strong reference"); + } + } + + public virtual ObjectFactoryBase WeakReferenceVariant + { + get + { + throw new TinyIoCRegistrationException(this.GetType(), "weak reference"); + } + } + + public virtual void SetConstructor(ConstructorInfo constructor) + { + Constructor = constructor; + } + + public virtual ObjectFactoryBase GetFactoryForChildContainer(TinyIoCContainer parent, TinyIoCContainer child) + { + return this; + } + } + + /// + /// IObjectFactory that creates new instances of types for each resolution + /// + /// Registered type + /// Type to construct to fullful request for RegisteredType + private class MultiInstanceFactory : ObjectFactoryBase + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + public override Type CreatesType { get { return typeof(RegisterImplementation); } } + + public MultiInstanceFactory() + { + if (typeof(RegisterImplementation).IsAbstract || typeof(RegisterImplementation).IsInterface) + throw new TinyIoCRegistrationTypeException(typeof(RegisterImplementation), "MultiInstanceFactory"); + } + + public override object GetObject(TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + try + { + return container.ConstructType(typeof(RegisterImplementation), Constructor, parameters, options); + } + catch (TinyIoCResolutionException ex) + { + throw new TinyIoCResolutionException(typeof(RegisterImplementation), ex); + } + } + + public override ObjectFactoryBase SingletonVariant + { + get + { + return new SingletonFactory(); + } + } + +#if ASPNET + public override ObjectFactoryBase PerRequestSingletonVariant + { + get + { + return new PerRequestSingletonFactory(); + } + } +#endif + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return this; + } + } + } + + /// + /// IObjectFactory that invokes a specified delegate to construct the object + /// + /// Registered type to be constructed + private class DelegateFactory : ObjectFactoryBase + where RegisterType : class + { + private Func _factory; + + public override bool AssumeConstruction { get { return true; } } + + public override Type CreatesType { get { return typeof(RegisterType); } } + + public override object GetObject(TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + try + { + return _factory.Invoke(container, parameters); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(typeof(RegisterType), ex); + } + } + + public DelegateFactory(Func factory) + { + if (factory == null) + throw new ArgumentNullException("factory"); + + _factory = factory; + } + + public override ObjectFactoryBase WeakReferenceVariant + { + get + { + return new WeakDelegateFactory(_factory); + } + } + + public override ObjectFactoryBase StrongReferenceVariant + { + get + { + return this; + } + } + + public override void SetConstructor(ConstructorInfo constructor) + { + throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for delegate factory registrations"); + } + } + + /// + /// IObjectFactory that invokes a specified delegate to construct the object + /// + /// Holds the delegate using a weak reference + /// + /// Registered type to be constructed + private class WeakDelegateFactory : ObjectFactoryBase + where RegisterType : class + { + private WeakReference _factory; + + public override bool AssumeConstruction { get { return true; } } + + public override Type CreatesType { get { return typeof(RegisterType); } } + + public override object GetObject(TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + var factory = _factory.Target as Func; + + if (factory == null) + throw new TinyIoCWeakReferenceException(typeof(RegisterType)); + + try + { + return factory.Invoke(container, parameters); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(typeof(RegisterType), ex); + } + } + + public WeakDelegateFactory(Func factory) + { + if (factory == null) + throw new ArgumentNullException("factory"); + + _factory = new WeakReference(factory); + } + + public override ObjectFactoryBase StrongReferenceVariant + { + get + { + var factory = _factory.Target as Func; + + if (factory == null) + throw new TinyIoCWeakReferenceException(typeof(RegisterType)); + + return new DelegateFactory(factory); + } + } + + public override ObjectFactoryBase WeakReferenceVariant + { + get + { + return this; + } + } + + public override void SetConstructor(ConstructorInfo constructor) + { + throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for delegate factory registrations"); + } + } + + /// + /// Stores an particular instance to return for a type + /// + /// Registered type + /// Type of the instance + private class InstanceFactory : ObjectFactoryBase, IDisposable + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + private RegisterImplementation _instance; + + public override bool AssumeConstruction { get { return true; } } + + public InstanceFactory(RegisterImplementation instance) + { + _instance = instance; + } + + public override Type CreatesType + { + get { return typeof(RegisterImplementation); } + } + + public override object GetObject(TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + return _instance; + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return new MultiInstanceFactory(); + } + } + + public override ObjectFactoryBase WeakReferenceVariant + { + get + { + return new WeakInstanceFactory(_instance); + } + } + + public override ObjectFactoryBase StrongReferenceVariant + { + get + { + return this; + } + } + + public override void SetConstructor(ConstructorInfo constructor) + { + throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for instance factory registrations"); + } + + public void Dispose() + { + var disposable = _instance as IDisposable; + + if (disposable != null) + disposable.Dispose(); + } + } + + /// + /// Stores an particular instance to return for a type + /// + /// Stores the instance with a weak reference + /// + /// Registered type + /// Type of the instance + private class WeakInstanceFactory : ObjectFactoryBase, IDisposable + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + private WeakReference _instance; + + public WeakInstanceFactory(RegisterImplementation instance) + { + _instance = new WeakReference(instance); + } + + public override Type CreatesType + { + get { return typeof(RegisterImplementation); } + } + + public override object GetObject(TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + var instance = _instance.Target as RegisterImplementation; + + if (instance == null) + throw new TinyIoCWeakReferenceException(typeof(RegisterType)); + + return instance; + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return new MultiInstanceFactory(); + } + } + + public override ObjectFactoryBase WeakReferenceVariant + { + get + { + return this; + } + } + + public override ObjectFactoryBase StrongReferenceVariant + { + get + { + var instance = _instance.Target as RegisterImplementation; + + if (instance == null) + throw new TinyIoCWeakReferenceException(typeof(RegisterType)); + + return new InstanceFactory(instance); + } + } + + public override void SetConstructor(ConstructorInfo constructor) + { + throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for instance factory registrations"); + } + + public void Dispose() + { + var disposable = _instance.Target as IDisposable; + + if (disposable != null) + disposable.Dispose(); + } + } + + /// + /// A factory that lazy instantiates a type and always returns the same instance + /// + /// Registered type + /// Type to instantiate + private class SingletonFactory : ObjectFactoryBase, IDisposable + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + private readonly object SingletonLock = new object(); + private RegisterImplementation _Current; + + public SingletonFactory() + { + if (typeof(RegisterImplementation).IsAbstract || typeof(RegisterImplementation).IsInterface) + throw new TinyIoCRegistrationTypeException(typeof(RegisterImplementation), "SingletonFactory"); + } + + public override Type CreatesType + { + get { return typeof(RegisterImplementation); } + } + + public override object GetObject(TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters.Count != 0) + throw new ArgumentException("Cannot specify parameters for singleton types"); + + lock (SingletonLock) + if (_Current == null) + _Current = container.ConstructType(typeof(RegisterImplementation), Constructor, options) as RegisterImplementation; + + return _Current; + } + + public override ObjectFactoryBase SingletonVariant + { + get + { + return this; + } + } + +#if ASPNET + public override ObjectFactoryBase PerRequestSingletonVariant + { + get + { + return new PerRequestSingletonFactory(); + } + } +#endif + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return new MultiInstanceFactory(); + } + } + + public override ObjectFactoryBase GetFactoryForChildContainer(TinyIoCContainer parent, TinyIoCContainer child) + { + // We make sure that the singleton is constructed before the child container takes the factory. + // Otherwise the results would vary depending on whether or not the parent container had resolved + // the type before the child container does. + GetObject(parent, NamedParameterOverloads.Default, ResolveOptions.Default); + return this; + } + + public void Dispose() + { + if (_Current != null) + { + var disposable = _Current as IDisposable; + + if (disposable != null) + disposable.Dispose(); + } + } + } + +#if ASPNET + /// + /// A factory that lazy instantiates a type and always returns the same instance + /// + /// Registered type + /// Type to instantiate + private class PerRequestSingletonFactory : ObjectFactoryBase, IDisposable + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + private readonly string _KeyName = String.Format("TinyIoC.{0}.{1}", typeof(RegisterType).FullName, Guid.NewGuid()); + private readonly object SingletonLock = new object(); + + public PerRequestSingletonFactory() + { + if (typeof(RegisterImplementation).IsAbstract || typeof(RegisterImplementation).IsInterface) + throw new TinyIoCRegistrationTypeException(typeof(RegisterImplementation), "SingletonFactory"); + } + + public override Type CreatesType + { + get { return typeof(RegisterImplementation); } + } + + public override object GetObject(TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters.Count != 0) + throw new ArgumentException("Cannot specify parameters for singleton types"); + + RegisterImplementation current; + + lock (SingletonLock) + { + current = HttpContext.Current.Items[_KeyName] as RegisterImplementation; + if (current == null) + { + current = container.ConstructType(typeof(RegisterImplementation), Constructor, options) as RegisterImplementation; + HttpContext.Current.Items[_KeyName] = current; + } + } + + return current; + } + + public override ObjectFactoryBase SingletonVariant + { + get + { + return new SingletonFactory(); + } + } + + public override ObjectFactoryBase PerRequestSingletonVariant + { + get + { + return this; + } + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return new MultiInstanceFactory(); + } + } + + public override ObjectFactoryBase GetFactoryForChildContainer(TinyIoCContainer parent, TinyIoCContainer child) + { + // We make sure that the singleton is constructed before the child container takes the factory. + // Otherwise the results would vary depending on whether or not the parent container had resolved + // the type before the child container does. + GetObject(parent, NamedParameterOverloads.Default, ResolveOptions.Default); + return this; + } + + public void Dispose() + { + var current = HttpContext.Current.Items[_KeyName] as RegisterImplementation; + if (current != null) + { + var disposable = current as IDisposable; + + if (disposable != null) + disposable.Dispose(); + } + } + } +#endif + #endregion + + #region Singleton Container + private static readonly TinyIoCContainer _Current = new TinyIoCContainer(); + + static TinyIoCContainer() + { + } + + /// + /// Lazy created Singleton instance of the container for simple scenarios + /// + public static TinyIoCContainer Current + { + get + { + return _Current; + } + } + #endregion + + #region Type Registrations + public sealed class TypeRegistration + { + public Type Type { get; private set; } + public string Name { get; private set; } + + public TypeRegistration(Type type) + : this(type, string.Empty) + { + } + + public TypeRegistration(Type type, string name) + { + Type = type; + Name = name; + } + + public override bool Equals(object obj) + { + var typeRegistration = obj as TypeRegistration; + + if (typeRegistration == null) + return false; + + if (Type != typeRegistration.Type) + return false; + + if (String.Compare(Name, typeRegistration.Name, StringComparison.Ordinal) != 0) + return false; + + return true; + } + + public override int GetHashCode() + { + return String.Format("{0}|{1}", Type.FullName, Name).GetHashCode(); + } + } + private readonly SafeDictionary _RegisteredTypes; + #endregion + + #region Constructors + public TinyIoCContainer() + { + _RegisteredTypes = new SafeDictionary(); + + RegisterDefaultTypes(); + } + + TinyIoCContainer _Parent; + private TinyIoCContainer(TinyIoCContainer parent) + : this() + { + _Parent = parent; + } + #endregion + + #region Internal Methods + private readonly object _AutoRegisterLock = new object(); + private void AutoRegisterInternal(IEnumerable assemblies, bool ignoreDuplicateImplementations) + { + lock (_AutoRegisterLock) + { + var defaultFactoryMethod = this.GetType().GetMethod("GetDefaultObjectFactory", BindingFlags.NonPublic | BindingFlags.Instance); + + var types = assemblies.SelectMany(a => a.GetTypes()).Where(t => !IsIgnoredType(t)).ToList(); + + var concreteTypes = from type in types + where (type.IsClass == true) && (type.IsAbstract == false) && (type != this.GetType() && (type.DeclaringType != this.GetType()) && (!type.IsGenericTypeDefinition)) + select type; + + foreach (var type in concreteTypes) + { + Type[] genericTypes = { type, type }; + var genericDefaultFactoryMethod = defaultFactoryMethod.MakeGenericMethod(genericTypes); + try + { + RegisterInternal(type, string.Empty, genericDefaultFactoryMethod.Invoke(this, null) as ObjectFactoryBase); + } + catch (MethodAccessException) + { + // Ignore methods we can't access - added for Silverlight + } + } + + var abstractInterfaceTypes = from type in types + where ((type.IsInterface == true || type.IsAbstract == true) && (type.DeclaringType != this.GetType()) && (!type.IsGenericTypeDefinition)) + select type; + + foreach (var type in abstractInterfaceTypes) + { + var implementations = from implementationType in concreteTypes + where implementationType.GetInterfaces().Contains(type) || implementationType.BaseType == type + select implementationType; + + if (!ignoreDuplicateImplementations && implementations.Count() > 1) + throw new TinyIoCAutoRegistrationException(type, implementations); + + var firstImplementation = implementations.FirstOrDefault(); + if (firstImplementation != null) + { + Type[] genericTypes = { type, firstImplementation }; + var genericDefaultFactoryMethod = defaultFactoryMethod.MakeGenericMethod(genericTypes); + try + { + RegisterInternal(type, string.Empty, genericDefaultFactoryMethod.Invoke(this, null) as ObjectFactoryBase); + } + catch (MethodAccessException) + { + // Ignore methods we can't access - added for Silverlight + } + } + } + } + } + + private bool IsIgnoredAssembly(Assembly assembly) + { + // TODO - find a better way to remove "system" assemblies from the auto registration + var ignoreChecks = new List>() + { + asm => asm.FullName.StartsWith("Microsoft.", StringComparison.InvariantCulture), + asm => asm.FullName.StartsWith("System.", StringComparison.InvariantCulture), + asm => asm.FullName.StartsWith("System,", StringComparison.InvariantCulture), + asm => asm.FullName.StartsWith("CR_ExtUnitTest", StringComparison.InvariantCulture), + asm => asm.FullName.StartsWith("mscorlib,", StringComparison.InvariantCulture), + asm => asm.FullName.StartsWith("CR_VSTest", StringComparison.InvariantCulture), + asm => asm.FullName.StartsWith("DevExpress.CodeRush", StringComparison.InvariantCulture), + }; + + foreach (var check in ignoreChecks) + { + if (check(assembly)) + return true; + } + + return false; + } + + private bool IsIgnoredType(Type type) + { + // TODO - find a better way to remove "system" types from the auto registration + var ignoreChecks = new List>() + { + t => t.FullName.StartsWith("System.", StringComparison.InvariantCulture), + t => t.FullName.StartsWith("Microsoft.", StringComparison.InvariantCulture), + t => t.IsPrimitive, +#if !UNBOUND_GENERICS_GETCONSTRUCTORS + t => t.IsGenericTypeDefinition, +#endif + t => (t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0) && !(t.IsInterface || t.IsAbstract), + }; + + foreach (var check in ignoreChecks) + { + if (check(type)) + return true; + } + + return false; + } + + private void RegisterDefaultTypes() + { + Register(this); + +#if TINYMESSENGER + // Only register the TinyMessenger singleton if we are the root container + if (_Parent == null) + Register(); +#endif + } + + private ObjectFactoryBase GetCurrentFactory(TypeRegistration registration) + { + ObjectFactoryBase current = null; + + _RegisteredTypes.TryGetValue(registration, out current); + + return current; + } + + private RegisterOptions RegisterInternal(Type registerType, string name, ObjectFactoryBase factory) + { + var typeRegistration = new TypeRegistration(registerType, name); + + return AddUpdateRegistration(typeRegistration, factory); + } + + private RegisterOptions AddUpdateRegistration(TypeRegistration typeRegistration, ObjectFactoryBase factory) + { + _RegisteredTypes[typeRegistration] = factory; + + return new RegisterOptions(this, typeRegistration); + } + + private void RemoveRegistration(TypeRegistration typeRegistration) + { + _RegisteredTypes.Remove(typeRegistration); + } + + private ObjectFactoryBase GetDefaultObjectFactory() + where RegisterType : class + where RegisterImplementation : class, RegisterType + { + if (typeof(RegisterType).IsInterface || typeof(RegisterType).IsAbstract) + return new SingletonFactory(); + + return new MultiInstanceFactory(); + } + + private bool CanResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + + Type checkType = registration.Type; + string name = registration.Name; + + ObjectFactoryBase factory; + if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType, name), out factory)) + { + if (factory.AssumeConstruction) + return true; + + if (factory.Constructor == null) + return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; + else + return CanConstruct(factory.Constructor, parameters, options); + } + + // Fail if requesting named resolution and settings set to fail if unresolved + // Or bubble up if we have a parent + if (!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) + return (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; + + // Attemped unnamed fallback container resolution if relevant and requested + if (!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) + { + if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType), out factory)) + { + if (factory.AssumeConstruction) + return true; + + return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; + } + } + + // Check if type is an automatic lazy factory request + if (IsAutomaticLazyFactoryRequest(checkType)) + return true; + + // Check if type is an IEnumerable + if (IsIEnumerableRequest(registration.Type)) + return true; + + // Attempt unregistered construction if possible and requested + // If we cant', bubble if we have a parent + if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (checkType.IsGenericType && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) + return (GetBestConstructor(checkType, parameters, options) != null) ? true : (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; + + // Bubble resolution up the container tree if we have a parent + if (_Parent != null) + return _Parent.CanResolveInternal(registration, parameters, options); + + return false; + } + + private bool IsIEnumerableRequest(Type type) + { + if (!type.IsGenericType) + return false; + + Type genericType = type.GetGenericTypeDefinition(); + + if (genericType == typeof(IEnumerable<>)) + return true; + + return false; + } + + private bool IsAutomaticLazyFactoryRequest(Type type) + { + if (!type.IsGenericType) + return false; + + Type genericType = type.GetGenericTypeDefinition(); + + // Just a func + if (genericType == typeof(Func<>)) + return true; + + // 2 parameter func with string as first parameter (name) + if ((genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(string))) + return true; + + // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters) + if ((genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) && type.GetGenericArguments()[1] == typeof(IDictionary))) + return true; + + return false; + } + + private ObjectFactoryBase GetParentObjectFactory(TypeRegistration registration) + { + if (_Parent == null) + return null; + + ObjectFactoryBase factory; + if (_Parent._RegisteredTypes.TryGetValue(registration, out factory)) + { + return factory.GetFactoryForChildContainer(_Parent, this); + } + + return _Parent.GetParentObjectFactory(registration); + } + + private object ResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options) + { + ObjectFactoryBase factory; + + // Attempt container resolution + if (_RegisteredTypes.TryGetValue(registration, out factory)) + { + try + { + return factory.GetObject(this, parameters, options); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + + // Attempt to get a factory from parent if we can + var bubbledObjectFactory = GetParentObjectFactory(registration); + if (bubbledObjectFactory != null) + { + try + { + return bubbledObjectFactory.GetObject(this, parameters, options); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + + // Fail if requesting named resolution and settings set to fail if unresolved + if (!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) + throw new TinyIoCResolutionException(registration.Type); + + // Attemped unnamed fallback container resolution if relevant and requested + if (!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) + { + if (_RegisteredTypes.TryGetValue(new TypeRegistration(registration.Type, string.Empty), out factory)) + { + try + { + return factory.GetObject(this, parameters, options); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + } + +#if EXPRESSIONS + // Attempt to construct an automatic lazy factory if possible + if (IsAutomaticLazyFactoryRequest(registration.Type)) + return GetLazyAutomaticFactoryRequest(registration.Type); +#endif + if (IsIEnumerableRequest(registration.Type)) + return GetIEnumerableRequest(registration.Type); + + // Attempt unregistered construction if possible and requested + if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (registration.Type.IsGenericType && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) + { + if (!registration.Type.IsAbstract && !registration.Type.IsInterface) + return ConstructType(registration.Type, parameters, options); + } + + // Unable to resolve - throw + throw new TinyIoCResolutionException(registration.Type); + } + +#if EXPRESSIONS + private object GetLazyAutomaticFactoryRequest(Type type) + { + if (!type.IsGenericType) + return null; + + Type genericType = type.GetGenericTypeDefinition(); + Type[] genericArguments = type.GetGenericArguments(); + + // Just a func + if (genericType == typeof(Func<>)) + { + Type returnType = genericArguments[0]; + + MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { }); + resolveMethod = resolveMethod.MakeGenericMethod(returnType); + + var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod); + + var resolveLambda = Expression.Lambda(resolveCall).Compile(); + + return resolveLambda; + } + + // 2 parameter func with string as first parameter (name) + if ((genericType == typeof(Func<,>)) && (genericArguments[0] == typeof(string))) + { + Type returnType = genericArguments[1]; + + MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { typeof(String) }); + resolveMethod = resolveMethod.MakeGenericMethod(returnType); + + ParameterExpression[] resolveParameters = new ParameterExpression[] { Expression.Parameter(typeof(String), "name") }; + var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod, resolveParameters); + + var resolveLambda = Expression.Lambda(resolveCall, resolveParameters).Compile(); + + return resolveLambda; + } + + // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters) + if ((genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) && type.GetGenericArguments()[1] == typeof(IDictionary))) + { + Type returnType = genericArguments[2]; + + var name = Expression.Parameter(typeof(string), "name"); + var parameters = Expression.Parameter(typeof(IDictionary), "parameters"); + + MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { typeof(String), typeof(NamedParameterOverloads) }); + resolveMethod = resolveMethod.MakeGenericMethod(returnType); + + var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod, name, Expression.Call(typeof(NamedParameterOverloads), "FromIDictionary", null, parameters)); + + var resolveLambda = Expression.Lambda(resolveCall, name, parameters).Compile(); + + return resolveLambda; + } + + throw new TinyIoCResolutionException(type); + } +#endif + private object GetIEnumerableRequest(Type type) + { + var genericResolveAllMethod = this.GetType().GetGenericMethod(BindingFlags.Public | BindingFlags.Instance, "ResolveAll", type.GetGenericArguments(), new Type[] { }); + +//#if GETPARAMETERS_OPEN_GENERICS +// // Using MakeGenericMethod (slow) because we need to +// // cast the IEnumerable or constructing the type wil fail. +// // We may as well use the ResolveAll public +// // method to do this. +// var resolveAllMethod = this.GetType().GetMethod("ResolveAll", new Type[] { }); +// var genericResolveAllMethod = resolveAllMethod.MakeGenericMethod(type.GetGenericArguments()[0]); +//#else +// var resolveAllMethods = from member in this.GetType().GetMembers() +// where member.MemberType == MemberTypes.Method +// where member.Name == "ResolveAll" +// let method = member as MethodInfo +// where method.IsGenericMethod +// let genericMethod = method.MakeGenericMethod(type.GetGenericArguments()[0]) +// where genericMethod.GetParameters().Count() == 0 +// select genericMethod; + +// var genericResolveAllMethod = resolveAllMethods.First(); +//#endif + return genericResolveAllMethod.Invoke(this, new object[] { }); + } + + private bool CanConstruct(ConstructorInfo ctor, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + + foreach (var parameter in ctor.GetParameters()) + { + if (string.IsNullOrEmpty(parameter.Name)) + return false; + + var isParameterOverload = parameters.ContainsKey(parameter.Name); + + if (parameter.ParameterType.IsPrimitive && !isParameterOverload) + return false; + + if (!isParameterOverload && !CanResolveInternal(new TypeRegistration(parameter.ParameterType), NamedParameterOverloads.Default, options)) + return false; + } + + return true; + } + + private ConstructorInfo GetBestConstructor(Type type, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + + if (type.IsValueType) + return null; + + // Get constructors in reverse order based on the number of parameters + // i.e. be as "greedy" as possible so we satify the most amount of dependencies possible + var ctors = from ctor in type.GetConstructors() + orderby ctor.GetParameters().Count() descending + select ctor; + + foreach (var ctor in ctors) + { + if (CanConstruct(ctor, parameters, options)) + return ctor; + } + + return null; + } + + private object ConstructType(Type type, ResolveOptions options) + { + return ConstructType(type, null, NamedParameterOverloads.Default, options); + } + + private object ConstructType(Type type, ConstructorInfo constructor, ResolveOptions options) + { + return ConstructType(type, constructor, NamedParameterOverloads.Default, options); + } + + private object ConstructType(Type type, NamedParameterOverloads parameters, ResolveOptions options) + { + return ConstructType(type, null, parameters, options); + } + + private object ConstructType(Type type, ConstructorInfo constructor, NamedParameterOverloads parameters, ResolveOptions options) + { + if (constructor == null) + constructor = GetBestConstructor(type, parameters, options); + + if (constructor == null) + throw new TinyIoCResolutionException(type); + + var ctorParams = constructor.GetParameters(); + object[] args = new object[ctorParams.Count()]; + + for (int parameterIndex = 0; parameterIndex < ctorParams.Count(); parameterIndex++) + { + var currentParam = ctorParams[parameterIndex]; + + args[parameterIndex] = parameters.ContainsKey(currentParam.Name) ? parameters[currentParam.Name] : ResolveInternal(new TypeRegistration(currentParam.ParameterType), NamedParameterOverloads.Default, options); + } + + try + { + return constructor.Invoke(args); + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(type, ex); + } + } + + private void BuildUpInternal(object input, ResolveOptions resolveOptions) + { + var properties = from property in input.GetType().GetProperties() + where (property.GetGetMethod() != null) && (property.GetSetMethod() != null) && !property.PropertyType.IsValueType + select property; + + foreach (var property in properties) + { + if (property.GetValue(input, null) == null) + { + try + { + property.SetValue(input, ResolveInternal(new TypeRegistration(property.PropertyType), NamedParameterOverloads.Default, resolveOptions), null); + } + catch (TinyIoCResolutionException) + { + // Catch any resolution errors and ignore them + } + } + } + } + + private IEnumerable GetParentRegistrationsForType(Type resolveType) + { + if (_Parent == null) + return new TypeRegistration[] { }; + + var registrations = _Parent._RegisteredTypes.Keys.Where(tr => tr.Type == resolveType); + + return registrations.Concat(_Parent.GetParentRegistrationsForType(resolveType)); + } + + private IEnumerable ResolveAllInternal(Type resolveType, bool includeUnnamed) + { + var registrations = _RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(GetParentRegistrationsForType(resolveType)); + + if (!includeUnnamed) + registrations = registrations.Where(tr => tr.Name != string.Empty); + + foreach (var registration in registrations) + { + yield return ResolveInternal(registration, NamedParameterOverloads.Default, ResolveOptions.Default); + } + } + + private RegisterOptions ExecuteGenericRegister(Type[] genericParameterTypes, Type[] methodParameterTypes, object[] methodParameters) + { + try + { + var method = this.GetType().GetGenericMethod(BindingFlags.Instance | BindingFlags.Public, "Register", genericParameterTypes, methodParameterTypes); + + return (RegisterOptions)method.Invoke(this, methodParameters); + } + catch (ArgumentException ex) + { + var registrationType = genericParameterTypes[0]; + var implementationType = genericParameterTypes[1]; + + if (genericParameterTypes.Length == 2) + implementationType = genericParameterTypes[2]; + + throw new TinyIoCRegistrationException(registrationType, implementationType, ex); + } + } + #endregion + + #region IDisposable Members + bool disposed = false; + public void Dispose() + { + if (!disposed) + { + disposed = true; + + _RegisteredTypes.Dispose(); + + GC.SuppressFinalize(this); + } + } + + #endregion + } +} diff --git a/Application/TinyMessenger.cs b/Application/TinyMessenger.cs new file mode 100644 index 0000000..4106d58 --- /dev/null +++ b/Application/TinyMessenger.cs @@ -0,0 +1,792 @@ +//=============================================================================== +// TinyIoC - TinyMessenger +// +// A simple messenger/event aggregator. +// +// http://hg.grumpydev.com/tinyioc +//=============================================================================== +// Copyright © Steven Robbins. All rights reserved. +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY +// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT +// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE. +//=============================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TinyMessenger +{ + #region Message Types / Interfaces + /// + /// A TinyMessage to be published/delivered by TinyMessenger + /// + public interface ITinyMessage + { + /// + /// The sender of the message, or null if not supported by the message implementation. + /// + object Sender { get; } + } + + /// + /// Base class for messages that provides weak refrence storage of the sender + /// + public abstract class TinyMessageBase : ITinyMessage + { + /// + /// Store a WeakReference to the sender just in case anyone is daft enough to + /// keep the message around and prevent the sender from being collected. + /// + private WeakReference _Sender; + public object Sender + { + get + { + return (_Sender == null) ? null : _Sender.Target; + } + } + + /// + /// Initializes a new instance of the MessageBase class. + /// + /// Message sender (usually "this") + public TinyMessageBase(object sender) + { + if (sender == null) + throw new ArgumentNullException("sender"); + + _Sender = new WeakReference(sender); + } + } + + /// + /// Generic message with user specified content + /// + /// Content type to store + public class GenericTinyMessage : TinyMessageBase + { + /// + /// Contents of the message + /// + public TContent Content { get; protected set; } + + /// + /// Create a new instance of the GenericTinyMessage class. + /// + /// Message sender (usually "this") + /// Contents of the message + public GenericTinyMessage(object sender, TContent content) + : base(sender) + { + Content = content; + } + } + + /// + /// Basic "cancellable" generic message + /// + /// Content type to store + public class CancellableGenericTinyMessage : TinyMessageBase + { + /// + /// Cancel action + /// + public Action Cancel { get; protected set; } + + /// + /// Contents of the message + /// + public TContent Content { get; protected set; } + + /// + /// Create a new instance of the CancellableGenericTinyMessage class. + /// + /// Message sender (usually "this") + /// Contents of the message + /// Action to call for cancellation + public CancellableGenericTinyMessage(object sender, TContent content, Action cancelAction) + : base(sender) + { + if (cancelAction == null) + throw new ArgumentNullException("cancelAction"); + + Content = content; + Cancel = cancelAction; + } + } + + /// + /// Represents an active subscription to a message + /// + public sealed class TinyMessageSubscriptionToken : IDisposable + { + private WeakReference _Hub; + private Type _MessageType; + + /// + /// Initializes a new instance of the TinyMessageSubscriptionToken class. + /// + public TinyMessageSubscriptionToken(ITinyMessengerHub hub, Type messageType) + { + if (hub == null) + throw new ArgumentNullException("hub"); + + if (!typeof(ITinyMessage).IsAssignableFrom(messageType)) + throw new ArgumentOutOfRangeException("messageType"); + + _Hub = new WeakReference(hub); + _MessageType = messageType; + } + + public void Dispose() + { + if (_Hub.IsAlive) + { + var hub = _Hub.Target as ITinyMessengerHub; + + if (hub != null) + { + var unsubscribeMethod = typeof(ITinyMessengerHub).GetMethod("Unsubscribe", new Type[] {typeof(TinyMessageSubscriptionToken)}); + unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(_MessageType); + unsubscribeMethod.Invoke(hub, new object[] {this}); + } + } + + GC.SuppressFinalize(this); + } + } + + /// + /// Represents a message subscription + /// + public interface ITinyMessageSubscription + { + /// + /// Token returned to the subscribed to reference this subscription + /// + TinyMessageSubscriptionToken SubscriptionToken { get; } + + /// + /// Whether delivery should be attempted. + /// + /// Message that may potentially be delivered. + /// True - ok to send, False - should not attempt to send + bool ShouldAttemptDelivery(ITinyMessage message); + + /// + /// Deliver the message + /// + /// Message to deliver + void Deliver(ITinyMessage message); + } + + /// + /// Message proxy definition. + /// + /// A message proxy can be used to intercept/alter messages and/or + /// marshall delivery actions onto a particular thread. + /// + public interface ITinyMessageProxy + { + void Deliver(ITinyMessage message, ITinyMessageSubscription subscription); + } + + /// + /// Default "pass through" proxy. + /// + /// Does nothing other than deliver the message. + /// + public sealed class DefaultTinyMessageProxy : ITinyMessageProxy + { + private static readonly DefaultTinyMessageProxy _Instance = new DefaultTinyMessageProxy(); + + static DefaultTinyMessageProxy() + { + } + + /// + /// Singleton instance of the proxy. + /// + public static DefaultTinyMessageProxy Instance + { + get + { + return _Instance; + } + } + + private DefaultTinyMessageProxy() + { + } + + public void Deliver(ITinyMessage message, ITinyMessageSubscription subscription) + { + subscription.Deliver(message); + } + } + #endregion + + #region Exceptions + /// + /// Thrown when an exceptions occurs while subscribing to a message type + /// + public class TinyMessengerSubscriptionException : Exception + { + private const string ERROR_TEXT = "Unable to add subscription for {0} : {1}"; + + public TinyMessengerSubscriptionException(Type messageType, string reason) + : base(String.Format(ERROR_TEXT, messageType, reason)) + { + + } + + public TinyMessengerSubscriptionException(Type messageType, string reason, Exception innerException) + : base(String.Format(ERROR_TEXT, messageType, reason), innerException) + { + + } + } + #endregion + + #region Hub Interface + /// + /// Messenger hub responsible for taking subscriptions/publications and delivering of messages. + /// + public interface ITinyMessengerHub + { + /// + /// Subscribe to a message type with the given destination and delivery action. + /// All references are held with WeakReferences + /// + /// All messages of this type will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// TinyMessageSubscription used to unsubscribing + TinyMessageSubscriptionToken Subscribe(Action deliveryAction) where TMessage : class, ITinyMessage; + + /// + /// Subscribe to a message type with the given destination and delivery action. + /// Messages will be delivered via the specified proxy. + /// All references (apart from the proxy) are held with WeakReferences + /// + /// All messages of this type will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Proxy to use when delivering the messages + /// TinyMessageSubscription used to unsubscribing + TinyMessageSubscriptionToken Subscribe(Action deliveryAction, ITinyMessageProxy proxy) where TMessage : class, ITinyMessage; + + /// + /// Subscribe to a message type with the given destination and delivery action. + /// + /// All messages of this type will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Use strong references to destination and deliveryAction + /// TinyMessageSubscription used to unsubscribing + TinyMessageSubscriptionToken Subscribe(Action deliveryAction, bool useStrongReferences) where TMessage : class, ITinyMessage; + + /// + /// Subscribe to a message type with the given destination and delivery action. + /// Messages will be delivered via the specified proxy. + /// + /// All messages of this type will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Use strong references to destination and deliveryAction + /// Proxy to use when delivering the messages + /// TinyMessageSubscription used to unsubscribing + TinyMessageSubscriptionToken Subscribe(Action deliveryAction, bool useStrongReferences, ITinyMessageProxy proxy) where TMessage : class, ITinyMessage; + + /// + /// Subscribe to a message type with the given destination and delivery action with the given filter. + /// All references are held with WeakReferences + /// + /// Only messages that "pass" the filter will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// TinyMessageSubscription used to unsubscribing + TinyMessageSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter) where TMessage : class, ITinyMessage; + + /// + /// Subscribe to a message type with the given destination and delivery action with the given filter. + /// Messages will be delivered via the specified proxy. + /// All references (apart from the proxy) are held with WeakReferences + /// + /// Only messages that "pass" the filter will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Proxy to use when delivering the messages + /// TinyMessageSubscription used to unsubscribing + TinyMessageSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter, ITinyMessageProxy proxy) where TMessage : class, ITinyMessage; + + /// + /// Subscribe to a message type with the given destination and delivery action with the given filter. + /// All references are held with WeakReferences + /// + /// Only messages that "pass" the filter will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Use strong references to destination and deliveryAction + /// TinyMessageSubscription used to unsubscribing + TinyMessageSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter, bool useStrongReferences) where TMessage : class, ITinyMessage; + + /// + /// Subscribe to a message type with the given destination and delivery action with the given filter. + /// Messages will be delivered via the specified proxy. + /// All references are held with WeakReferences + /// + /// Only messages that "pass" the filter will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Use strong references to destination and deliveryAction + /// Proxy to use when delivering the messages + /// TinyMessageSubscription used to unsubscribing + TinyMessageSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter, bool useStrongReferences, ITinyMessageProxy proxy) where TMessage : class, ITinyMessage; + + /// + /// Unsubscribe from a particular message type. + /// + /// Does not throw an exception if the subscription is not found. + /// + /// Type of message + /// Subscription token received from Subscribe + void Unsubscribe(TinyMessageSubscriptionToken subscriptionToken) where TMessage : class, ITinyMessage; + + /// + /// Publish a message to any subscribers + /// + /// Type of message + /// Message to deliver + void Publish(TMessage message) where TMessage : class, ITinyMessage; + + /// + /// Publish a message to any subscribers asynchronously + /// + /// Type of message + /// Message to deliver + void PublishAsync(TMessage message) where TMessage : class, ITinyMessage; + + /// + /// Publish a message to any subscribers asynchronously + /// + /// Type of message + /// Message to deliver + /// AsyncCallback called on completion + void PublishAsync(TMessage message, AsyncCallback callback) where TMessage : class, ITinyMessage; + } + #endregion + + #region Hub Implementation + /// + /// Messenger hub responsible for taking subscriptions/publications and delivering of messages. + /// + public sealed class TinyMessengerHub : ITinyMessengerHub + { + #region Private Types and Interfaces + private class WeakTinyMessageSubscription : ITinyMessageSubscription + where TMessage : class, ITinyMessage + { + protected TinyMessageSubscriptionToken _SubscriptionToken; + protected WeakReference _DeliveryAction; + protected WeakReference _MessageFilter; + + public TinyMessageSubscriptionToken SubscriptionToken + { + get { return _SubscriptionToken; } + } + + public bool ShouldAttemptDelivery(ITinyMessage message) + { + if (!(message is TMessage)) + return false; + + if (!_DeliveryAction.IsAlive) + return false; + + if (!_MessageFilter.IsAlive) + return false; + + return ((Func)_MessageFilter.Target).Invoke(message as TMessage); + } + + public void Deliver(ITinyMessage message) + { + if (!(message is TMessage)) + throw new ArgumentException("Message is not the correct type"); + + if (!_DeliveryAction.IsAlive) + return; + + ((Action)_DeliveryAction.Target).Invoke(message as TMessage); + } + + /// + /// Initializes a new instance of the WeakTinyMessageSubscription class. + /// + /// Destination object + /// Delivery action + /// Filter function + public WeakTinyMessageSubscription(TinyMessageSubscriptionToken subscriptionToken, Action deliveryAction, Func messageFilter) + { + if (subscriptionToken == null) + throw new ArgumentNullException("subscriptionToken"); + + if (deliveryAction == null) + throw new ArgumentNullException("deliveryAction"); + + if (messageFilter == null) + throw new ArgumentNullException("messageFilter"); + + _SubscriptionToken = subscriptionToken; + _DeliveryAction = new WeakReference(deliveryAction); + _MessageFilter = new WeakReference(messageFilter); + } + } + + private class StrongTinyMessageSubscription : ITinyMessageSubscription + where TMessage : class, ITinyMessage + { + protected TinyMessageSubscriptionToken _SubscriptionToken; + protected Action _DeliveryAction; + protected Func _MessageFilter; + + public TinyMessageSubscriptionToken SubscriptionToken + { + get { return _SubscriptionToken; } + } + + public bool ShouldAttemptDelivery(ITinyMessage message) + { + if (!(message is TMessage)) + return false; + + return _MessageFilter.Invoke(message as TMessage); + } + + public void Deliver(ITinyMessage message) + { + if (!(message is TMessage)) + throw new ArgumentException("Message is not the correct type"); + + _DeliveryAction.Invoke(message as TMessage); + } + + /// + /// Initializes a new instance of the TinyMessageSubscription class. + /// + /// Destination object + /// Delivery action + /// Filter function + public StrongTinyMessageSubscription(TinyMessageSubscriptionToken subscriptionToken, Action deliveryAction, Func messageFilter) + { + if (subscriptionToken == null) + throw new ArgumentNullException("subscriptionToken"); + + if (deliveryAction == null) + throw new ArgumentNullException("deliveryAction"); + + if (messageFilter == null) + throw new ArgumentNullException("messageFilter"); + + _SubscriptionToken = subscriptionToken; + _DeliveryAction = deliveryAction; + _MessageFilter = messageFilter; + } + } + #endregion + + #region Subscription dictionary + private class SubscriptionItem + { + public ITinyMessageProxy Proxy { get; private set; } + public ITinyMessageSubscription Subscription { get; private set; } + + public SubscriptionItem(ITinyMessageProxy proxy, ITinyMessageSubscription subscription) + { + Proxy = proxy; + Subscription = subscription; + } + } + + private readonly object _SubscriptionsPadlock = new object(); + private readonly Dictionary> _Subscriptions = new Dictionary>(); + #endregion + + #region Public API + /// + /// Subscribe to a message type with the given destination and delivery action. + /// All references are held with strong references + /// + /// All messages of this type will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// TinyMessageSubscription used to unsubscribing + public TinyMessageSubscriptionToken Subscribe(Action deliveryAction) where TMessage : class, ITinyMessage + { + return AddSubscriptionInternal(deliveryAction, (m) => true, true, DefaultTinyMessageProxy.Instance); + } + + /// + /// Subscribe to a message type with the given destination and delivery action. + /// Messages will be delivered via the specified proxy. + /// All references (apart from the proxy) are held with strong references + /// + /// All messages of this type will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Proxy to use when delivering the messages + /// TinyMessageSubscription used to unsubscribing + public TinyMessageSubscriptionToken Subscribe(Action deliveryAction, ITinyMessageProxy proxy) where TMessage : class, ITinyMessage + { + return AddSubscriptionInternal(deliveryAction, (m) => true, true, proxy); + } + + /// + /// Subscribe to a message type with the given destination and delivery action. + /// + /// All messages of this type will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Use strong references to destination and deliveryAction + /// TinyMessageSubscription used to unsubscribing + public TinyMessageSubscriptionToken Subscribe(Action deliveryAction, bool useStrongReferences) where TMessage : class, ITinyMessage + { + return AddSubscriptionInternal(deliveryAction, (m) => true, useStrongReferences, DefaultTinyMessageProxy.Instance); + } + + /// + /// Subscribe to a message type with the given destination and delivery action. + /// Messages will be delivered via the specified proxy. + /// + /// All messages of this type will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Use strong references to destination and deliveryAction + /// Proxy to use when delivering the messages + /// TinyMessageSubscription used to unsubscribing + public TinyMessageSubscriptionToken Subscribe(Action deliveryAction, bool useStrongReferences, ITinyMessageProxy proxy) where TMessage : class, ITinyMessage + { + return AddSubscriptionInternal(deliveryAction, (m) => true, useStrongReferences, proxy); + } + + /// + /// Subscribe to a message type with the given destination and delivery action with the given filter. + /// All references are held with WeakReferences + /// + /// Only messages that "pass" the filter will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// TinyMessageSubscription used to unsubscribing + public TinyMessageSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter) where TMessage : class, ITinyMessage + { + return AddSubscriptionInternal(deliveryAction, messageFilter, true, DefaultTinyMessageProxy.Instance); + } + + /// + /// Subscribe to a message type with the given destination and delivery action with the given filter. + /// Messages will be delivered via the specified proxy. + /// All references (apart from the proxy) are held with WeakReferences + /// + /// Only messages that "pass" the filter will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Proxy to use when delivering the messages + /// TinyMessageSubscription used to unsubscribing + public TinyMessageSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter, ITinyMessageProxy proxy) where TMessage : class, ITinyMessage + { + return AddSubscriptionInternal(deliveryAction, messageFilter, true, proxy); + } + + /// + /// Subscribe to a message type with the given destination and delivery action with the given filter. + /// All references are held with WeakReferences + /// + /// Only messages that "pass" the filter will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Use strong references to destination and deliveryAction + /// TinyMessageSubscription used to unsubscribing + public TinyMessageSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter, bool useStrongReferences) where TMessage : class, ITinyMessage + { + return AddSubscriptionInternal(deliveryAction, messageFilter, useStrongReferences, DefaultTinyMessageProxy.Instance); + } + + /// + /// Subscribe to a message type with the given destination and delivery action with the given filter. + /// Messages will be delivered via the specified proxy. + /// All references are held with WeakReferences + /// + /// Only messages that "pass" the filter will be delivered. + /// + /// Type of message + /// Action to invoke when message is delivered + /// Use strong references to destination and deliveryAction + /// Proxy to use when delivering the messages + /// TinyMessageSubscription used to unsubscribing + public TinyMessageSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter, bool useStrongReferences, ITinyMessageProxy proxy) where TMessage : class, ITinyMessage + { + return AddSubscriptionInternal(deliveryAction, messageFilter, useStrongReferences, proxy); + } + + /// + /// Unsubscribe from a particular message type. + /// + /// Does not throw an exception if the subscription is not found. + /// + /// Type of message + /// Subscription token received from Subscribe + public void Unsubscribe(TinyMessageSubscriptionToken subscriptionToken) where TMessage : class, ITinyMessage + { + RemoveSubscriptionInternal(subscriptionToken); + } + + /// + /// Publish a message to any subscribers + /// + /// Type of message + /// Message to deliver + public void Publish(TMessage message) where TMessage : class, ITinyMessage + { + PublishInternal(message); + } + + /// + /// Publish a message to any subscribers asynchronously + /// + /// Type of message + /// Message to deliver + public void PublishAsync(TMessage message) where TMessage : class, ITinyMessage + { + PublishAsyncInternal(message, null); + } + + /// + /// Publish a message to any subscribers asynchronously + /// + /// Type of message + /// Message to deliver + /// AsyncCallback called on completion + public void PublishAsync(TMessage message, AsyncCallback callback) where TMessage : class, ITinyMessage + { + PublishAsyncInternal(message, callback); + } + #endregion + + #region Internal Methods + private TinyMessageSubscriptionToken AddSubscriptionInternal(Action deliveryAction, Func messageFilter, bool strongReference, ITinyMessageProxy proxy) + where TMessage : class, ITinyMessage + { + if (deliveryAction == null) + throw new ArgumentNullException("deliveryAction"); + + if (messageFilter == null) + throw new ArgumentNullException("messageFilter"); + + if (proxy == null) + throw new ArgumentNullException("proxy"); + + lock (_SubscriptionsPadlock) + { + List currentSubscriptions; + + if (!_Subscriptions.TryGetValue(typeof(TMessage), out currentSubscriptions)) + { + currentSubscriptions = new List(); + _Subscriptions[typeof(TMessage)] = currentSubscriptions; + } + + var subscriptionToken = new TinyMessageSubscriptionToken(this, typeof(TMessage)); + + ITinyMessageSubscription subscription; + if (strongReference) + subscription = new StrongTinyMessageSubscription(subscriptionToken, deliveryAction, messageFilter); + else + subscription = new WeakTinyMessageSubscription(subscriptionToken, deliveryAction, messageFilter); + + currentSubscriptions.Add(new SubscriptionItem(proxy, subscription)); + + return subscriptionToken; + } + } + + private void RemoveSubscriptionInternal(TinyMessageSubscriptionToken subscriptionToken) + where TMessage : class, ITinyMessage + { + if (subscriptionToken == null) + throw new ArgumentNullException("subscriptionToken"); + + lock (_SubscriptionsPadlock) + { + List currentSubscriptions; + if (!_Subscriptions.TryGetValue(typeof(TMessage), out currentSubscriptions)) + return; + + var currentlySubscribed = (from sub in currentSubscriptions + where object.ReferenceEquals(sub.Subscription.SubscriptionToken, subscriptionToken) + select sub).ToList(); + + currentlySubscribed.ForEach(sub => currentSubscriptions.Remove(sub)); + } + } + + private void PublishInternal(TMessage message) + where TMessage : class, ITinyMessage + { + if (message == null) + throw new ArgumentNullException("message"); + + List currentlySubscribed; + lock (_SubscriptionsPadlock) + { + List currentSubscriptions; + if (!_Subscriptions.TryGetValue(typeof(TMessage), out currentSubscriptions)) + return; + + currentlySubscribed = (from sub in currentSubscriptions + where sub.Subscription.ShouldAttemptDelivery(message) + select sub).ToList(); + } + + currentlySubscribed.ForEach(sub => + { + try + { + sub.Proxy.Deliver(message, sub.Subscription); + } + catch (Exception) + { + // Ignore any errors and carry on + // TODO - add to a list of erroring subs and remove them? + } + }); + } + + private void PublishAsyncInternal(TMessage message, AsyncCallback callback) where TMessage : class, ITinyMessage + { + Action publishAction = () => { PublishInternal(message); }; + + publishAction.BeginInvoke(callback, null); + } + #endregion + } + #endregion +} diff --git a/Binding/Binding.cs b/Binding/Binding.cs new file mode 100644 index 0000000..1fd01b8 --- /dev/null +++ b/Binding/Binding.cs @@ -0,0 +1,61 @@ +// +// Binding.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Globalization; + using System.ComponentModel; + + public class Binding: BindingBase + { + public Binding(): this(null, null) + { + } + + public Binding(string sourcePath, string targetPath) + { + SourcePath = sourcePath; + TargetPath = targetPath; + Mode = BindingMode.TwoWay; + } + + public string TargetPath { get; set; } + public object Target { get; set;} + public string SourcePath { get; set; } + public object Source { get; set; } + + public IValueConverter Converter { get; set; } + public CultureInfo ConverterCulture { get; set; } + public object ConverterParameter { get; set; } + public BindingMode Mode { get; set; } + + } +} + diff --git a/Binding/BindingBase.cs b/Binding/BindingBase.cs new file mode 100644 index 0000000..66aa681 --- /dev/null +++ b/Binding/BindingBase.cs @@ -0,0 +1,44 @@ +// +// BindingBase.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + + public abstract class BindingBase + { + protected BindingBase() + { + } + + public object FallbackValue { get; set; } + public object TargetNullValue { get; set;} + } +} + diff --git a/Binding/BindingExpression.cs b/Binding/BindingExpression.cs new file mode 100644 index 0000000..73ac244 --- /dev/null +++ b/Binding/BindingExpression.cs @@ -0,0 +1,186 @@ +// +// BindingExpression.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Reflection; + using System.Globalization; + using MonoTouch.UIKit; + using System.Collections.Generic; + using MonoTouch.Dialog; + + public class BindingExpression : IBindingExpression + { + struct PendingUpdateKey + { + public UITableViewElementCell Cell; + public PropertyInfo TargetProperty; + public object Target; + } + + public PropertyInfo SourceProperty; + public PropertyInfo TargetProperty; + public Element Element { get; set; } + + public Binding Binding { get; private set; } + + public BindingExpression(Binding binding, PropertyInfo targetProperty, object target) + { + if (binding == null) + throw new ArgumentNullException("binding"); + + if (targetProperty == null) + throw new ArgumentNullException("targetProperty"); + + if (target == null) + throw new ArgumentNullException("target"); + + Binding = binding; + Binding.Target = target; + TargetProperty = targetProperty; + if(string.IsNullOrEmpty(binding.TargetPath)) + { + binding.TargetPath = targetProperty.Name; + } + + object source = Binding.Source; + SourceProperty = source.GetType().GetNestedProperty(ref source, Binding.SourcePath, true); + Binding.Source = source; + } + + public void UpdateSource() + { + UpdateSource(GetTargetValue()); + } + + public void UpdateSource(object targetValue) + { + if (SourceProperty != null && SourceProperty.CanWrite && Binding.Mode != BindingMode.OneTime) + { + try + { + var convertedTargetValue = ConvertbackValue(targetValue); + if (Convert.GetTypeCode(convertedTargetValue) != TypeCode.Object) + convertedTargetValue = Convert.ChangeType(convertedTargetValue, SourceProperty.PropertyType); + + SourceProperty.SetValue(Binding.Source, convertedTargetValue, null); + } + catch (NotImplementedException) + { + } + } + } + + public void UpdateTarget() + { + UpdateTarget(GetSourceValue()); + } + + public void UpdateTarget(object sourceValue) + { + if (TargetProperty != null && TargetProperty.CanWrite && Binding.Mode == BindingMode.TwoWay) + { + if (sourceValue == null) + sourceValue = Binding.TargetNullValue; + try + { + var convertedSourceValue = ConvertValue(sourceValue); + + if (Convert.GetTypeCode(convertedSourceValue) != TypeCode.Object) + convertedSourceValue = Convert.ChangeType(convertedSourceValue, TargetProperty.PropertyType); + + if (Element != null && Element.Cell != null && Element.Cell.Element == Element) + { + TargetProperty.SetValue(Binding.Target, convertedSourceValue, null); + } + + } + catch (InvalidCastException ex) + { + Console.WriteLine(ex.Message + " : "+ex.StackTrace); + } + catch (NotImplementedException ex) + { + } + } + } + + public object ConvertValue(object value) + { + object convertedValue = value; + + if (Binding.Converter != null) + { + object parameter = Element; + if (Binding.ConverterParameter != null) + parameter = Binding.ConverterParameter; + + convertedValue = Binding.Converter.Convert(value, TargetProperty.PropertyType, parameter, CultureInfo.CurrentUICulture); + } + + return convertedValue; + } + + public object ConvertbackValue(object value) + { + object convertedValue = value; + + if (Binding.Converter != null) + { + object parameter = Element; + if (Binding.ConverterParameter != null) + parameter = Binding.ConverterParameter; + + convertedValue = Binding.Converter.ConvertBack(value, SourceProperty.PropertyType, parameter, CultureInfo.CurrentUICulture); + } + + return convertedValue; + } + + protected virtual object GetSourceValue() + { + if (Binding.Source != null) + { + var property = Binding.Source.GetType().GetProperty(Binding.SourcePath, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + + if (property != null) + return property.GetValue(Binding.Source, null); + } + + return Binding.FallbackValue; + } + + protected virtual object GetTargetValue() + { + return TargetProperty.GetValue(Binding.Target, null); + } + } +} + diff --git a/Binding/BindingMode.cs b/Binding/BindingMode.cs new file mode 100644 index 0000000..645b35e --- /dev/null +++ b/Binding/BindingMode.cs @@ -0,0 +1,41 @@ +// +// BindingMode.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + + public enum BindingMode + { + OneWay = 1, + OneTime = 2, + TwoWay = 3, + } +} + diff --git a/Binding/BindingOperations.cs b/Binding/BindingOperations.cs new file mode 100644 index 0000000..f3bf344 --- /dev/null +++ b/Binding/BindingOperations.cs @@ -0,0 +1,215 @@ +// +// BindingOperations.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Linq; + using System.Reflection; + using MonoTouch.Dialog; + + public static class BindingOperations + { + private class PropertyBinder + { + public IBindable Object; + public string Property; + } + + private static bool _UpdatingBindings { get; set; } + private static Dictionary _Bindings = new Dictionary(); + private static List _BindingExpressions; + + public static void ClearAllBindings() + { + _BindingExpressions.Clear(); + _Bindings.Clear(); + } + + public static void ClearBinding(object source, string property) + { + var bindingExpression = GetBindingExpression(source, property); + if (bindingExpression != null) + _BindingExpressions.Remove(bindingExpression); + + var binding = _Bindings.SingleOrDefault((kvp)=>kvp.Key.Object == source && kvp.Key.Property == property); + if (binding.Key != null) + _Bindings.Remove(binding.Key); + } + + public static Binding GetBinding(object target, string property) + { + var bindingExpression = GetBindingExpression(target, property); + if (bindingExpression != null) + return bindingExpression.Binding; + + return null; + } + + public static IBindingExpression GetBindingExpression(object target, string property) + { + UpdateBindings(); + if (_BindingExpressions != null) + { + return _BindingExpressions.SingleOrDefault((b)=>b.Binding.TargetPath == property && b.Binding.Target == target); + } + + return null; + } + + public static IBindingExpression[] GetBindingExpressionsForElement(object element) + { + UpdateBindings(); + if (_BindingExpressions != null) + { + return _BindingExpressions.Where((b)=>b.Element == element).ToArray(); + } + + return null; + } + + private static IBindingExpression[] GetBindingExpressions(string property) + { + UpdateBindings(); + if (_BindingExpressions != null) + { + return _BindingExpressions.Where((b)=>b.Binding.SourcePath == property).ToArray(); + } + + return null; + } + + public static bool IsDataBound(IBindable target, string property) { return false; } + + public static IBindingExpression SetBinding(IBindable target, string targetProperty, Binding binding) + { + if (target == null) + throw new ArgumentNullException("target"); + + if (string.IsNullOrEmpty(targetProperty)) + throw new ArgumentNullException("targetProperty"); + + + if (binding == null) + throw new ArgumentNullException("binding"); + + var binderKey =_Bindings.SingleOrDefault((kvp)=>kvp.Key.Object == target && kvp.Key.Property == targetProperty).Key; + + IBindingExpression bindingExpression = null; + + object nestedTarget = target; + var element = target as Element; + + PropertyInfo propertyInfo = target.GetType().GetNestedProperty(ref nestedTarget, targetProperty, false); + var targetReady = propertyInfo != null && nestedTarget != null; + + if (targetReady) + { + if (_BindingExpressions == null) + _BindingExpressions = new List(); + + bindingExpression = GetBindingExpression(target, targetProperty); + + if (bindingExpression == null) + { + bindingExpression = new BindingExpression(binding, propertyInfo, nestedTarget) { Element = element }; + + _BindingExpressions.Add(bindingExpression); + + var INPC = bindingExpression.Binding.Source as INotifyPropertyChanged; + if (INPC != null) + { + INPC.PropertyChanged -= HandleDataContextPropertyChanged; + INPC.PropertyChanged += HandleDataContextPropertyChanged; + } + } + } + else + { + if (binderKey == null) + _Bindings.Add(new PropertyBinder() { Object = target, Property = targetProperty }, binding); + else + _Bindings[binderKey] = binding; + } + + return bindingExpression; + } + + private static void UpdateBindings() + { + if(!_UpdatingBindings) + { + try + { + _UpdatingBindings = true; + + var bindingsToRemove = new List(); + + if (_Bindings.Count > 0) + { + var keys = _Bindings.Keys.ToArray(); + for (var i = 0; i < keys.Length; i++) + { + var binding = _Bindings[keys[i]]; + var bindingExpression = SetBinding(keys[i].Object, keys[i].Property, binding); + if(bindingExpression != null) + bindingsToRemove.Add(keys[i]); + } + + if (bindingsToRemove.Count > 0) + { + foreach(var bindingKey in bindingsToRemove) + { + _Bindings.Remove(bindingKey); + } + } + + bindingsToRemove.Clear(); + } + } + finally + { + _UpdatingBindings = false; + } + } + } + + private static void HandleDataContextPropertyChanged(object sender, PropertyChangedEventArgs e) + { + IBindingExpression[] bindingExpressions = GetBindingExpressions(e.PropertyName); + foreach (var bindingExpression in bindingExpressions) + { + bindingExpression.UpdateTarget(); + } + } + } +} + diff --git a/Binding/IBindable.cs b/Binding/IBindable.cs new file mode 100644 index 0000000..b9e2f21 --- /dev/null +++ b/Binding/IBindable.cs @@ -0,0 +1,42 @@ +// +// IBindable.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using MonoTouch.Dialog; + + public interface IBindable + { + IBindingExpression SetBinding(IBindable target, string targetProperty, Binding binding); + IBindingExpression SetBinding(string targetProperty, Binding binding); + IBindingExpression GetBindingExpression(string targetProperty); + } +} + diff --git a/Binding/IBindingExpression.cs b/Binding/IBindingExpression.cs new file mode 100644 index 0000000..1c15f19 --- /dev/null +++ b/Binding/IBindingExpression.cs @@ -0,0 +1,45 @@ +// +// IBindingExpression.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using MonoTouch.Dialog; + + public interface IBindingExpression + { + Binding Binding { get; } + object ConvertValue(object value); + object ConvertbackValue(object value); + Element Element { get; } + + void UpdateTarget(); + void UpdateSource(); + } +} diff --git a/Binding/IValueConverter.cs b/Binding/IValueConverter.cs new file mode 100644 index 0000000..7dfbf49 --- /dev/null +++ b/Binding/IValueConverter.cs @@ -0,0 +1,41 @@ +// +// IValueConverter.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Globalization; + + public interface IValueConverter + { + object Convert(object value, Type targetType, object parameter, CultureInfo culture); + object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture); + } +} + diff --git a/Binding/PropertyPath.cs b/Binding/PropertyPath.cs new file mode 100644 index 0000000..b1599dd --- /dev/null +++ b/Binding/PropertyPath.cs @@ -0,0 +1,15 @@ +namespace MonoTouch.MVVM +{ + using System; + + public sealed class PropertyPath + { + public string Path { get; internal set; } + + public PropertyPath(string path) + { + Path = path; + } + } +} + diff --git a/Binding/TypeExtensions.cs b/Binding/TypeExtensions.cs new file mode 100644 index 0000000..917b75f --- /dev/null +++ b/Binding/TypeExtensions.cs @@ -0,0 +1,40 @@ +// +// TypeExtensions.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Reflection; + + public static class TypeExtensions + { + + } +} + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..ea244a0 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,47 @@ +2010-06-16 Eric Maupin + + * Elements.cs: Added support on FloatElement for an optional + caption (default is still no caption). + + * Reflect.cs: Added [Range] optional captions support. + +2010-06-18 Eric Maupin + + * Element.cs: EntryElement: Fixed an error with FetchValue + if the text field hadn't yet been generated. + + StringElement: SelectionStyle is only blue when there's a + tap handler registered. + +2010-06-17 Eric Maupin + + * Reflect.cs: EntryAttribute: Removed placeholder requirement + and added KeyboardType support. + +2010-05-16 Miguel de Icaza + + * DialogViewController.cs (StartSearch, FinishSearch, + PerformFilter): Made public these methods. They allow consumers + to start, stop and trigger a filter operation programatically. + + (OnSearchTextChanged): New virtual method that subclasses can hook + up into to check for changes in the search bar. + + (SearchTextChanged): Event to listen to changes in the search. + + (Selected): New virtual method that actually dispatches to the + Selected method in the element. This wasy subclasses can catch + the Selected method without having to hook up hundreds of + methods. + + * Element.cs (Section.Insert): the overload that takes an + IEnumerable now returns the number of items that were inserted. + + (Section.Remove): Add new overload to remove an element from the + section. + + (RootElement): New constructor that can take a function that + creates the nested DialogViewController, this prevents code from + having to derive a new class just to push a new kind of + UIViewController. + diff --git a/Collections/EnumBinder.cs b/Collections/EnumBinder.cs new file mode 100644 index 0000000..174c2e4 --- /dev/null +++ b/Collections/EnumBinder.cs @@ -0,0 +1,69 @@ +// +// EnumBinder.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + public class EnumBinder : PropertyNotifier + { + public string GroupName + { + get { return Get(() => GroupName); } + set { Set(() => GroupName, value); } + } + + public string FieldName + { + get { return Get(() => FieldName); } + set { Set(() => FieldName, value); } + } + + public int Index + { + get { return Get(() => Index); } + set { Set(() => Index, value); } + } + + public string Description + { + get { return Get(() => Description); } + set { Set(() => Description, value); } + } + + public bool IsChecked + { + get { return Get(() => IsChecked); } + set { Set(() => IsChecked, value); } + } + + public override string ToString() + { + return Description; + } + } +} diff --git a/Collections/EnumCollection.cs b/Collections/EnumCollection.cs new file mode 100644 index 0000000..0483bb5 --- /dev/null +++ b/Collections/EnumCollection.cs @@ -0,0 +1,116 @@ +// +// EnumCollection.cs: Generic Collection of Enum Values +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + public class EnumCollection : EnumCollection where T : struct + { + public EnumCollection() : base(typeof(T)) + { + } + } + + public class EnumCollection : ObservableCollection, IDisposable + { + public Type EnumType { get; set; } + + public ICollection TrueValues + { + get { return this.Where(binder => binder.IsChecked).ToList(); } + } + + public ICollection FalseValues + { + get { return this.Where(binder => !binder.IsChecked).ToList(); } + } + + public ICollection AllValues + { + get { return this.ToList(); } + } + + private bool _IsOrdered { get; set; } + + public EnumCollection(Type type) : this(type, false) { } + + public EnumCollection(Type type, bool isOrdered) + { + _IsOrdered = isOrdered; + + if (type == null || !type.IsEnum) + throw new ArgumentException("This class only supports Enum types"); + + EnumType = type; + + var enumItems = from field in EnumType.GetFields() + where field.IsLiteral + select field.Name; + + if (_IsOrdered) + enumItems = from item in enumItems + orderby item + select item; + + foreach (var item in enumItems) + { + Add(new EnumBinder() + { + GroupName = EnumType.FullName, + Index = (int)Enum.Parse(EnumType, item, true), + FieldName = item, + Description = EnumExtensions.GetDescriptionValue(item, EnumType) + }); + } + } + + ~EnumCollection() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool calledExplicitly) + { + foreach (var item in Items) + { + item.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Collections/System.Collections.ObjectModel/ObservableCollection.cs b/Collections/System.Collections.ObjectModel/ObservableCollection.cs new file mode 100644 index 0000000..53b2aec --- /dev/null +++ b/Collections/System.Collections.ObjectModel/ObservableCollection.cs @@ -0,0 +1,100 @@ +// +// ObservableCollection.cs +// +// Contact: +// Moonlight List (moonlight-list@lists.ximian.com) +// +// Copyright 2008 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace System.Collections.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + + public class ObservableCollection : Collection, INotifyCollectionChanged, INotifyPropertyChanged + { + public ObservableCollection() + { + } + + public ObservableCollection(IEnumerable collection) + { + throw new NotImplementedException(); + } + + public ObservableCollection(List list) + { + throw new NotImplementedException(); + } + + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + protected override void InsertItem(int index, T item) + { + base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (CollectionChanged != null) + CollectionChanged(this, e); + } + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (PropertyChanged != null) + PropertyChanged(this, e); + } + + protected override void RemoveItem(int index) + { + T oldItem = this[index]; + + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Remove, oldItem, index)); + } + + protected override void SetItem(int index, T item) + { + T oldItem = this[index]; + base.SetItem(index, item); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, item, oldItem, index)); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + protected event PropertyChangedEventHandler PropertyChanged; + + event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged + { + add { PropertyChanged += value; } + remove { PropertyChanged -= value; } + } + } +} \ No newline at end of file diff --git a/Collections/System.Collections.Specialized/INotifyCollectionChanged.cs b/Collections/System.Collections.Specialized/INotifyCollectionChanged.cs new file mode 100644 index 0000000..cc7cfac --- /dev/null +++ b/Collections/System.Collections.Specialized/INotifyCollectionChanged.cs @@ -0,0 +1,35 @@ +// +// INotifyCollectionChanged.cs +// +// Contact: +// Moonlight List (moonlight-list@lists.ximian.com) +// +// Copyright 2008 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace System.Collections.Specialized +{ + + public interface INotifyCollectionChanged + { + event NotifyCollectionChangedEventHandler CollectionChanged; + } +} \ No newline at end of file diff --git a/Collections/System.Collections.Specialized/NotifyCollectionChangedAction.cs b/Collections/System.Collections.Specialized/NotifyCollectionChangedAction.cs new file mode 100644 index 0000000..f3f122f --- /dev/null +++ b/Collections/System.Collections.Specialized/NotifyCollectionChangedAction.cs @@ -0,0 +1,37 @@ +// +// NotifyCollectionChangedAction.cs +// +// Contact: +// Moonlight List (moonlight-list@lists.ximian.com) +// +// Copyright 2008 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace System.Collections.Specialized +{ + public enum NotifyCollectionChangedAction + { + Add, + Remove, + Replace, + Reset = 4 + } +} \ No newline at end of file diff --git a/Collections/System.Collections.Specialized/NotifyCollectionChangedEventArgs.cs b/Collections/System.Collections.Specialized/NotifyCollectionChangedEventArgs.cs new file mode 100644 index 0000000..8c5a6e5 --- /dev/null +++ b/Collections/System.Collections.Specialized/NotifyCollectionChangedEventArgs.cs @@ -0,0 +1,102 @@ +// +// NotifyCollectionChangedEventArgs.cs +// +// Contact: +// Moonlight List (moonlight-list@lists.ximian.com) +// +// Copyright 2008 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System.Collections.Generic; + +namespace System.Collections.Specialized +{ + + public sealed class NotifyCollectionChangedEventArgs : EventArgs + { + private List _NewItems, _OldItems; + + public NotifyCollectionChangedAction Action { get; private set; } + + public IList NewItems + { + get { return _NewItems; } + } + + public IList OldItems + { + get { return _OldItems; } + } + + public int NewStartingIndex { get; private set; } + public int OldStartingIndex { get; private set; } + + public NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction action) + { + if (action != NotifyCollectionChangedAction.Reset) + throw new NotSupportedException (); + + Action = action; + NewStartingIndex = -1; + OldStartingIndex = -1; + } + + public NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction action, object changedItem, int index) + { + switch (action) + { + case NotifyCollectionChangedAction.Add: + _NewItems = new List(); + _NewItems.Add(changedItem); + NewStartingIndex = index; + OldStartingIndex = -1; + break; + case NotifyCollectionChangedAction.Remove: + _OldItems = new List(); + _OldItems.Add(changedItem); + OldStartingIndex = index; + NewStartingIndex = -1; + break; + default: + throw new NotSupportedException(); + } + + Action = action; + } + + public NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction action, object newItem, object oldItem, int index) + { + if (action != NotifyCollectionChangedAction.Replace) + throw new NotSupportedException(); + + Action = action; + + _NewItems = new List(); + _NewItems.Add(newItem); + + _OldItems = new List(); + _OldItems.Add(oldItem); + + NewStartingIndex = index; + OldStartingIndex = -1; + } + } +} \ No newline at end of file diff --git a/Collections/System.Collections.Specialized/NotifyCollectionChangedEventHandler.cs b/Collections/System.Collections.Specialized/NotifyCollectionChangedEventHandler.cs new file mode 100644 index 0000000..78e1929 --- /dev/null +++ b/Collections/System.Collections.Specialized/NotifyCollectionChangedEventHandler.cs @@ -0,0 +1,31 @@ +// +// NotifyCollectionChangedEventHandler.cs +// +// Contact: +// Moonlight List (moonlight-list@lists.ximian.com) +// +// Copyright 2008 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace System.Collections.Specialized +{ + public delegate void NotifyCollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs e); +} \ No newline at end of file diff --git a/Commands/ICommand.cs b/Commands/ICommand.cs new file mode 100644 index 0000000..7ad10b8 --- /dev/null +++ b/Commands/ICommand.cs @@ -0,0 +1,42 @@ +// +// ICommand.cs: ICommand interface +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + + public interface ICommand + { + event EventHandler CanExecuteChanged; + + bool CanExecute(object parameter); + void Execute(object parameter); + } +} + diff --git a/Commands/ReflectiveCommand.cs b/Commands/ReflectiveCommand.cs new file mode 100644 index 0000000..3db28c5 --- /dev/null +++ b/Commands/ReflectiveCommand.cs @@ -0,0 +1,86 @@ +// +// ReflectiveCommand.cs: ICommand implementation using a MethodInfo for Execute method +// and PropertyInfo for CanExecute +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Reflection; + using System.ComponentModel; + using System.Linq; + + public class ReflectiveCommand : ICommand + { + private readonly PropertyInfo _CanExecute; + private readonly MethodInfo _Execute; + private readonly object _ViewModel; + + public event EventHandler CanExecuteChanged = (sender, e) => { }; + + public ReflectiveCommand(object viewModel, MethodInfo execute, PropertyInfo canExecute) + { + if (execute == null) + throw new ArgumentNullException("execute"); + + _ViewModel = viewModel; + _Execute = execute; + _CanExecute = canExecute; + + var notifier = viewModel as INotifyPropertyChanged; + if (notifier != null && _CanExecute != null) + { + notifier.PropertyChanged += (s, e) => + { + if (e.PropertyName == _CanExecute.Name) + CanExecuteChanged(this, EventArgs.Empty); + }; + } + } + + public bool CanExecute(object parameter) + { + if (_CanExecute != null) + return (bool)_CanExecute.GetValue(_ViewModel, null); + + return true; + } + + public void Execute(object parameter) + { + if (CanExecute(parameter)) + { + if (_Execute.GetParameters().Any()) + _Execute.Invoke(_ViewModel, new object[] { parameter }); + else + _Execute.Invoke(_ViewModel, null); + } + } + } +} + diff --git a/Commands/UICommand.cs b/Commands/UICommand.cs new file mode 100644 index 0000000..1d4f283 --- /dev/null +++ b/Commands/UICommand.cs @@ -0,0 +1,74 @@ +// +// UICommand.cs: Generic Command implementation +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + + public class UICommand: ICommand + { + private Action _ExecuteMethod { get ; set; } + private Func _CanExecuteMethod { get; set;} + + public UICommand(Action executeMethod, Func canExecuteMethod) + { + if (executeMethod == null) + throw new ArgumentNullException("Execute method cannot be null"); + + _ExecuteMethod = executeMethod; + _CanExecuteMethod = canExecuteMethod; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(T parameter) + { + if (_CanExecuteMethod == null) + return true; + + return _CanExecuteMethod(parameter); + } + + public void Execute(T parameter) + { + _ExecuteMethod(parameter); + } + + bool ICommand.CanExecute(object parameter) + { + return CanExecute((T)parameter); + } + + void ICommand.Execute(object parameter) + { + Execute((T)parameter); + } + } +} + diff --git a/Converters/EnumConverter.cs b/Converters/EnumConverter.cs new file mode 100644 index 0000000..62ea45d --- /dev/null +++ b/Converters/EnumConverter.cs @@ -0,0 +1,64 @@ +// +// EnumConverter.cs: +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Globalization; + using System.Linq; + + public class EnumConverter : IValueConverter + { + public Type PropertyType { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return value; + + if (targetType == typeof(string)) + { + return ((Enum)value).GetDescription(); + } + + return (int)value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value.GetType() == typeof(string)) + { + return Enum.Parse(PropertyType, (string)value, true); + } + + var values = Enum.GetValues(PropertyType); + return values.GetValue((int)value); + } + } +} diff --git a/Converters/EnumItemsConverter.cs b/Converters/EnumItemsConverter.cs new file mode 100644 index 0000000..e5afa23 --- /dev/null +++ b/Converters/EnumItemsConverter.cs @@ -0,0 +1,91 @@ +// +// EnumItemsConverter.cs: +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + + public class EnumItemsConverter : DisposableObject, IValueConverter + { + private EnumCollection _EnumCollection; + + public EnumCollection EnumCollection { get { return _EnumCollection; } } + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value == null) + return value; + + Type valueType = value.GetType(); + + if (!typeof(EnumCollection).IsAssignableFrom(valueType)) + throw new ArgumentException("value must be an EnumCollection"); + + var convertedValue = 0; + foreach(var item in ((EnumCollection)value).TrueValues) + { + convertedValue |= (1 << item.Index); + } + + return convertedValue; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (!typeof(EnumCollection).IsAssignableFrom(targetType)) + throw new ArgumentException("target must be an EnumCollection"); + + var intValue = System.Convert.ToInt64(value); + if (intValue == -1 ) + intValue = 0; + + var collectionType = typeof(EnumCollection<>); + var enumType = targetType.GetGenericArguments()[0]; + Type[] generic = { enumType }; + + var collection = Activator.CreateInstance(collectionType.MakeGenericType(generic)); + _EnumCollection = (EnumCollection)collection; + + foreach (var item in _EnumCollection) + { + item.IsChecked = (intValue & (1 << item.Index)) != 0; + } + + return collection; + } + + protected override void Dispose(bool calledExplicitly) + { + if (_EnumCollection != null) + _EnumCollection.Dispose(); + + base.Dispose(calledExplicitly); + } + } +} diff --git a/Converters/EnumerableConverter.cs b/Converters/EnumerableConverter.cs new file mode 100644 index 0000000..7604b98 --- /dev/null +++ b/Converters/EnumerableConverter.cs @@ -0,0 +1,50 @@ +// +// EnumerableConverter.cs: +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Globalization; + using MonoTouch.Dialog; + + public class EnumerableConverter : IValueConverter + { + //RK: Temporarily not implemented until I decide how to handle this case + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} + diff --git a/Converters/MoneyConverter.cs b/Converters/MoneyConverter.cs new file mode 100644 index 0000000..c4c50fd --- /dev/null +++ b/Converters/MoneyConverter.cs @@ -0,0 +1,52 @@ +// +// MoneyConverter.cs: +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Globalization; + using MonoTouch.Dialog; + + public class MoneyConverter : IValueConverter + { + public object Convert (object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return value; + + return string.Format("{0:C}", value); + } + + public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) + { + return ((string)value).Replace("$", ""); + } + } +} + diff --git a/Converters/PercentConverter.cs b/Converters/PercentConverter.cs new file mode 100644 index 0000000..38b7193 --- /dev/null +++ b/Converters/PercentConverter.cs @@ -0,0 +1,53 @@ +// +// PercentConverter.cs: +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MonoTouch.MVVM +{ + using System; + using System.Globalization; + using MonoTouch.Dialog; + + public class PercentConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return value; + + return string.Format("{0}%", value); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + var result = ((string)value).Replace("%", ""); + return result; + } + } +} diff --git a/Converters/ViewConverter.cs b/Converters/ViewConverter.cs new file mode 100644 index 0000000..d13ecda --- /dev/null +++ b/Converters/ViewConverter.cs @@ -0,0 +1,58 @@ +// +// ViewConverter.cs: +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.Globalization; + using MonoTouch.Dialog; + + public class ViewConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return value; + + var view = value as IView; + if (!(view is IView)) + { + throw new ArgumentException("value must be an IView"); + } + + return view.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value; + } + } +} + diff --git a/Dialog/DialogViewController.cs b/Dialog/DialogViewController.cs new file mode 100644 index 0000000..0907bc6 --- /dev/null +++ b/Dialog/DialogViewController.cs @@ -0,0 +1,667 @@ +// +// DialogViewController.cs: drives MonoTouch.Dialog +// +// Author: +// Miguel de Icaza +// +// Code to support pull-to-refresh based on Martin Bowling's TweetTableView +// which is based in turn in EGOTableViewPullRefresh code which was created +// by Devin Doty and is Copyrighted 2009 enormego and released under the +// MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using MonoTouch.UIKit; +using System.Drawing; +using System.Collections.Generic; +using MonoTouch.Foundation; + +namespace MonoTouch.Dialog +{ + public class DialogViewController : UITableViewController + { + public UITableViewStyle Style = UITableViewStyle.Grouped; + UISearchBar searchBar; + UITableView tableView; + RefreshTableHeaderView refreshView; + IRoot root; + bool pushing; + bool dirty; + bool reloading; + + /// + /// The root element displayed by the DialogViewController, the value can be changed during runtime to update the contents. + /// + public IRoot Root { + get { + return root; + } + set { + if (root == value) + return; + if (root != null) + root.Dispose (); + + root = value; + root.TableView = tableView; + ReloadData (); + } + } + + EventHandler refreshRequested; + /// + /// If you assign a handler to this event before the view is shown, the + /// DialogViewController will have support for pull-to-refresh UI. + /// + public event EventHandler RefreshRequested { + add { + if (tableView != null) + throw new ArgumentException ("You should set the handler before the controller is shown"); + refreshRequested += value; + } + remove { + refreshRequested -= value; + } + } + + // If the value is 1, we are enabled, used in the source for quick computation + bool enableSearch; + public bool EnableSearch { + get { + return enableSearch; + } + set { + if (enableSearch == value) + return; + + // After MonoTouch 3.0, we can allow for the search to be enabled/disable + if (tableView != null) + throw new ArgumentException ("You should set EnableSearch before the controller is shown"); + enableSearch = value; + } + } + + // If set, we automatically scroll the content to avoid showing the search bar until + // the user manually pulls it down. + public bool AutoHideSearch { get; set; } + + public string SearchPlaceholder { get; set; } + + /// + /// Invoke this method to trigger a data refresh. + /// + /// + /// This will invoke the RerfeshRequested event handler, the code attached to it + /// should start the background operation to fetch the data and when it completes + /// it should call ReloadComplete to restore the control state. + /// + public void TriggerRefresh () + { + TriggerRefresh (false); + } + + void TriggerRefresh (bool showStatus) + { + if (refreshRequested == null) + return; + + if (reloading) + return; + + reloading = true; + if (refreshView != null) + refreshView.SetActivity (true); + refreshRequested (this, EventArgs.Empty); + + if (showStatus && refreshView != null){ + UIView.BeginAnimations ("reloadingData"); + UIView.SetAnimationDuration (0.2); + TableView.ContentInset = new UIEdgeInsets (60, 0, 0, 0); + UIView.CommitAnimations (); + } + } + + /// + /// Invoke this method to signal that a reload has completed, this will update the UI accordingly. + /// + public void ReloadComplete () + { + if (refreshView != null) + refreshView.LastUpdate = DateTime.Now; + if (!reloading) + return; + + reloading = false; + if (refreshView == null) + return; + + refreshView.SetActivity (false); + refreshView.Flip (false); + UIView.BeginAnimations ("doneReloading"); + UIView.SetAnimationDuration (0.3f); + TableView.ContentInset = new UIEdgeInsets (0, 0, 0, 0); + refreshView.SetStatus (RefreshViewStatus.PullToReload); + UIView.CommitAnimations (); + } + + /// + /// Controls whether the DialogViewController should auto rotate + /// + public bool Autorotate { get; set; } + + public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation) + { + return Autorotate || toInterfaceOrientation == UIInterfaceOrientation.Portrait; + } + + public override void DidRotate (UIInterfaceOrientation fromInterfaceOrientation) + { + base.DidRotate (fromInterfaceOrientation); + ReloadData (); + } + + ISection [] originalSections; + Element [][] originalElements; + + /// + /// Allows caller to programatically activate the search bar and start the search process + /// + public void StartSearch () + { + if (originalSections != null) + return; + + searchBar.BecomeFirstResponder (); + originalSections = Root.Sections.ToArray (); + originalElements = new Element [originalSections.Length][]; + for (int i = 0; i < originalSections.Length; i++) + originalElements [i] = originalSections [i].Elements.ToArray (); + } + + /// + /// Allows the caller to programatically stop searching. + /// + public virtual void FinishSearch () + { + if (originalSections == null) + return; + + Root.Sections = new List (originalSections); + originalSections = null; + originalElements = null; + searchBar.ResignFirstResponder (); + ReloadData (); + } + + public delegate void SearchTextEventHandler (object sender, SearchChangedEventArgs args); + public event SearchTextEventHandler SearchTextChanged; + + public virtual void OnSearchTextChanged (string text) + { + if (SearchTextChanged != null) + SearchTextChanged (this, new SearchChangedEventArgs (text)); + } + + public void PerformFilter (string text) + { + if (originalSections == null) + return; + + OnSearchTextChanged (text); + + var newSections = new List (); + + for (int sidx = 0; sidx < originalSections.Length; sidx++){ + ISection newSection = null; + var section = originalSections [sidx]; + Element [] elements = originalElements [sidx]; + + for (int eidx = 0; eidx < elements.Length; eidx++){ + if (elements [eidx].Matches (text)){ + if (newSection == null){ + newSection = new Section (section.Header, section.Footer){ + FooterView = section.FooterView, + HeaderView = section.HeaderView + }; + newSections.Add (newSection); + } + newSection.Add (elements [eidx]); + } + } + } + + Root.Sections = newSections; + + ReloadData (); + } + + public virtual void SearchButtonClicked (string text) + { + } + + class SearchDelegate : UISearchBarDelegate { + DialogViewController container; + + public SearchDelegate (DialogViewController container) + { + this.container = container; + } + + public override void OnEditingStarted (UISearchBar searchBar) + { + searchBar.ShowsCancelButton = true; + container.StartSearch (); + } + + public override void OnEditingStopped (UISearchBar searchBar) + { + searchBar.ShowsCancelButton = false; + container.FinishSearch (); + } + + public override void TextChanged (UISearchBar searchBar, string searchText) + { + container.PerformFilter (searchText ?? ""); + } + + public override void CancelButtonClicked (UISearchBar searchBar) + { + searchBar.ShowsCancelButton = false; + container.FinishSearch (); + searchBar.ResignFirstResponder (); + } + + public override void SearchButtonClicked (UISearchBar searchBar) + { + container.SearchButtonClicked (searchBar.Text); + } + } + + public class Source : UITableViewSource { + const float yboundary = 65; + protected DialogViewController Container; + protected IRoot Root; + bool checkForRefresh; + + public Source (DialogViewController container) + { + this.Container = container; + Root = container.root; + } + + public override int RowsInSection (UITableView tableview, int section) + { + var s = Root.Sections [section]; + var count = s.Elements.Count; + + return count; + } + + public override int NumberOfSections (UITableView tableView) + { + return Root.Sections.Count; + } + + public override string TitleForHeader (UITableView tableView, int section) + { + return Root.Sections [section].Caption; + } + + public override string TitleForFooter (UITableView tableView, int section) + { + return Root.Sections [section].Footer; + } + + public override UITableViewCell GetCell (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) + { + var section = Root.Sections [indexPath.Section]; + var element = section.Elements [indexPath.Row]; + + return element.GetCell (tableView) as UITableViewElementCell; + } + + public override void RowSelected (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) + { + Container.Selected (indexPath); + } + + public override UIView GetViewForHeader (UITableView tableView, int sectionIdx) + { + var section = Root.Sections [sectionIdx]; + return section.HeaderView; + } + + public override float GetHeightForHeader (UITableView tableView, int sectionIdx) + { + var section = Root.Sections [sectionIdx]; + if (section.HeaderView == null) + return -1; + return section.HeaderView.Frame.Height; + } + + public override UIView GetViewForFooter (UITableView tableView, int sectionIdx) + { + var section = Root.Sections [sectionIdx]; + return section.FooterView; + } + + public override float GetHeightForFooter (UITableView tableView, int sectionIdx) + { + var section = Root.Sections [sectionIdx]; + if (section.FooterView == null) + return -1; + return section.FooterView.Frame.Height; + } + + #region Pull to Refresh support + public override void Scrolled (UIScrollView scrollView) + { + if (!checkForRefresh) + return; + if (Container.reloading) + return; + var view = Container.refreshView; + if (view == null) + return; + + var point = Container.TableView.ContentOffset; + + if (view.IsFlipped && point.Y > -yboundary && point.Y < 0){ + view.Flip (true); + view.SetStatus (RefreshViewStatus.PullToReload); + } else if (!view.IsFlipped && point.Y < -yboundary){ + view.Flip (true); + view.SetStatus (RefreshViewStatus.ReleaseToReload); + } + } + + public override void DraggingStarted (UIScrollView scrollView) + { + checkForRefresh = true; + } + + public override void DraggingEnded (UIScrollView scrollView, bool willDecelerate) + { + if (Container.refreshView == null) + return; + + checkForRefresh = false; + if (Container.TableView.ContentOffset.Y > -yboundary) + return; + Container.TriggerRefresh (true); + } + #endregion + } + + // + // Performance trick, if we expose GetHeightForRow, the UITableView will + // probe *every* row for its size; Avoid this by creating a separate + // model that is used only when we have items that require resizing + // + public class SizingSource : Source { + public SizingSource (DialogViewController controller) : base (controller) {} + + public override float GetHeightForRow (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) + { + var section = Root.Sections [indexPath.Section]; + var element = section.Elements [indexPath.Row]; + + var sizable = element as IElementSizing; + if (sizable == null) + return tableView.RowHeight; + return sizable.GetHeight (tableView, indexPath); + } + } + + /// + /// Activates a nested view controller from the DialogViewController. + /// If the view controller is hosted in a UINavigationController it + /// will push the result. Otherwise it will show it as a modal + /// dialog + /// + public void ActivateController (UIViewController controller, DialogViewController oldController) + { + dirty = true; + + if (typeof(DialogViewController) == controller.GetType()){ + var dialog= (DialogViewController)controller; + dialog.TableView.BackgroundColor = oldController.TableView.BackgroundColor; + } + + var parent = ParentViewController; + var nav = parent as UINavigationController; + + // We can not push a nav controller into a nav controller + if (nav != null && !(controller is UINavigationController)) + nav.PushViewController (controller, true); + else + PresentModalViewController (controller, true); + } + + /// + /// Dismisses the view controller. It either pops or dismisses + /// based on the kind of container we are hosted in. + /// + public void DeactivateController (bool animated) + { + var parent = ParentViewController; + var nav = parent as UINavigationController; + + if (nav != null) + nav.PopViewControllerAnimated (animated); + else + DismissModalViewControllerAnimated (animated); + } + + void SetupSearch () + { + if (enableSearch){ + searchBar = new UISearchBar (new RectangleF (0, 0, tableView.Bounds.Width, 44)) { + Delegate = new SearchDelegate (this) + }; + if (SearchPlaceholder != null) + searchBar.Placeholder = this.SearchPlaceholder; + tableView.TableHeaderView = searchBar; + } else { + // Does not work with current Monotouch, will work with 3.0 + // tableView.TableHeaderView = null; + } + } + + public virtual void Selected (NSIndexPath indexPath) + { + var section = root.Sections [indexPath.Section]; + var element = section.Elements [indexPath.Row]; + + element.Selected(this, tableView, indexPath); + } + + public virtual UITableView MakeTableView (RectangleF bounds, UITableViewStyle style) + { + return new UITableView (bounds, style); + } + + public override void LoadView () + { + tableView = MakeTableView (UIScreen.MainScreen.Bounds, Style); + tableView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleTopMargin; + tableView.AutosizesSubviews = true; + + UpdateSource (); + View = tableView; + SetupSearch (); + ConfigureTableView (); + + if (root == null) + return; + root.TableView = tableView; + } + + void ConfigureTableView () + { + if (refreshRequested != null){ + // The dimensions should be large enough so that even if the user scrolls, we render the + // whole are with the background color. + var bounds = View.Bounds; + refreshView = MakeRefreshTableHeaderView (new RectangleF (0, -bounds.Height, bounds.Width, bounds.Height)); + if (reloading) + refreshView.SetActivity (true); + TableView.AddSubview (refreshView); + } + } + + public virtual RefreshTableHeaderView MakeRefreshTableHeaderView (RectangleF rect) + { + return new RefreshTableHeaderView (rect); + } + + public override void ViewWillAppear (bool animated) + { + base.ViewWillAppear (animated); + if (AutoHideSearch){ + if (enableSearch){ + if (TableView.ContentOffset.Y < 44) + TableView.ContentOffset = new PointF (0, 44); + } + } + if (root == null) + return; + + root.Prepare (); + + NavigationItem.HidesBackButton = !pushing; + if (root.Caption != null) + NavigationItem.Title = root.Caption; + if (dirty){ + tableView.ReloadData (); + dirty = false; + } + + var rootElement = root as IRoot; + if (rootElement != null) + { + if(rootElement.ItemIndex >-1) + { + var path = rootElement.PathForRadio(rootElement.ItemIndex); + if (path != null) + tableView.ScrollToRow(path, UITableViewScrollPosition.Top, false); + } + } + } + + public virtual Source CreateSizingSource (bool unevenRows) + { + return unevenRows ? new SizingSource (this) : new Source (this); + } + + Source TableSource; + + void UpdateSource () + { + if (root == null) + return; + + TableSource = CreateSizingSource (root.UnevenRows); + tableView.Source = TableSource; + } + + public void ReloadData () + { + if (root == null) + return; + + root.Prepare (); + if (tableView != null){ + UpdateSource (); + tableView.ReloadData (); + } + dirty = false; + } + + public event EventHandler ViewDissapearing; + + public override void ViewWillDisappear (bool animated) + { + base.ViewWillDisappear (animated); + if (ViewDissapearing != null) + ViewDissapearing (this, EventArgs.Empty); + } + + protected void PrepareRoot (IRoot root) + { + this.root = root; + if (root != null) + root.Prepare (); + } + + protected DialogViewController (bool pushing) : base(UITableViewStyle.Grouped) { + this.pushing = pushing; + } + + protected DialogViewController (UITableViewStyle style, bool pushing) : base(style) { + this.pushing = pushing; + Style = style; + } + + public DialogViewController (BindingContext binding, bool pushing) : base(UITableViewStyle.Grouped) + { + if(binding == null) + throw new ArgumentNullException("binding"); + this.pushing = pushing; + PrepareRoot(binding.Root); + } + + public DialogViewController (IRoot root) : base (UITableViewStyle.Grouped) + { + PrepareRoot (root); + } + + public DialogViewController (UITableViewStyle style, IRoot root) : base (style) + { + PrepareRoot (root); + } + + /// + /// Creates a new DialogViewController from a IRoot and sets the push status + /// + /// + /// The containing the information to render. + /// + /// + /// A describing whether this is being pushed + /// (NavigationControllers) or not. If pushing is true, then the back button + /// will be shown, allowing the user to go back to the previous controller + /// + public DialogViewController (IRoot root, bool pushing) : base (UITableViewStyle.Grouped) + { + this.pushing = pushing; + PrepareRoot (root); + } + + public DialogViewController (UITableViewStyle style, IRoot root, bool pushing) : base (style) + { + this.pushing = pushing; + Style = style; + PrepareRoot (root); + } + } + +} diff --git a/Dialog/Elements/ActivityElement.cs b/Dialog/Elements/ActivityElement.cs new file mode 100644 index 0000000..4da6b25 --- /dev/null +++ b/Dialog/Elements/ActivityElement.cs @@ -0,0 +1,42 @@ +namespace MonoTouch.Dialog +{ + using System.Drawing; + using MonoTouch.UIKit; + + public class ActivityElement : UIViewElement, IElementSizing + { + public ActivityElement() : base ("", new UIActivityIndicatorView(UIActivityIndicatorViewStyle.Gray), false) + { + var sbounds = UIScreen.MainScreen.Bounds; + var uia = Value as UIActivityIndicatorView; + + uia.StartAnimating(); + + var vbounds = Value.Bounds; + Value.Frame = new RectangleF((sbounds.Width-vbounds.Width)/2, 4, vbounds.Width, vbounds.Height + 0); + Value.AutoresizingMask = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleRightMargin; + } + + public bool Animating { + get + { + return ((UIActivityIndicatorView)Value).IsAnimating; + } + set + { + var activity = Value as UIActivityIndicatorView; + + if (value) + activity.StartAnimating(); + else + activity.StopAnimating(); + } + } + + public override float GetHeight (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) + { + return base.GetHeight(tableView, indexPath)+ 8; + } + } +} + diff --git a/Dialog/Elements/BaseBooleanImageElement.cs b/Dialog/Elements/BaseBooleanImageElement.cs new file mode 100644 index 0000000..91119fb --- /dev/null +++ b/Dialog/Elements/BaseBooleanImageElement.cs @@ -0,0 +1,128 @@ +// +// BaseBooleanImageElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System.Drawing; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + /// + /// This class is used to render a string + a state in the form + /// of an image. + /// + /// + /// It is abstract to avoid making this element + /// keep two pointers for the state images, saving 8 bytes per + /// slot. The more derived class "BooleanImageElement" shows + /// one way to implement this by keeping two pointers, a better + /// implementation would return pointers to images that were + /// preloaded and are static. + /// + /// A subclass only needs to implement the GetImage method. + /// + public abstract class BaseBooleanImageElement : BoolElement + { + public class TextWithImageCellView : UITableViewElementCell + { + const int fontSize = 17; + static UIFont font = UIFont.BoldSystemFontOfSize(fontSize); + BaseBooleanImageElement parent; + UILabel label; + UIButton button; + const int ImageSpace = 32; + const int Padding = 8; + + public TextWithImageCellView(BaseBooleanImageElement parent, NSString Id) : base(UITableViewCellStyle.Value1, Id) + { + this.parent = parent; + label = new UILabel { TextAlignment = UITextAlignment.Left, Text = parent.Caption, Font = font }; + button = UIButton.FromType(UIButtonType.Custom); + button.TouchDown += delegate + { + parent.Value = !parent.Value; + UpdateImage(); + if (parent.Tapped != null) + parent.Tapped(); + }; + ContentView.Add(label); + ContentView.Add(button); + UpdateImage(); + } + + void UpdateImage() + { + button.SetImage(parent.GetImage(), UIControlState.Normal); + } + + public override void LayoutSubviews() + { + base.LayoutSubviews(); + var full = ContentView.Bounds; + var frame = full; + frame.Height = 22; + frame.X = Padding; + frame.Y = (full.Height - frame.Height) / 2; + frame.Width -= ImageSpace + Padding; + label.Frame = frame; + + button.Frame = new RectangleF(full.Width - ImageSpace, -3, ImageSpace, 48); + } + + public void UpdateFrom(BaseBooleanImageElement newParent) + { + parent = newParent; + UpdateImage(); + label.Text = parent.Caption; + SetNeedsDisplay(); + } + } + + public BaseBooleanImageElement(string caption, bool value) : base(caption, value) + { + Id = new NSString("BooleanImageElement"); + } + + public event NSAction Tapped; + + protected abstract UIImage GetImage(); + + public override UITableViewElementCell GetCell(UITableView tableView) + { + var cell = tableView.DequeueReusableCell(Id) as TextWithImageCellView; + if (cell == null) + cell = new TextWithImageCellView(this, Id); + else + cell.UpdateFrom(this); + Cell = cell; + return cell as UITableViewElementCell; + } + } +} + diff --git a/Dialog/Elements/BoolElement.cs b/Dialog/Elements/BoolElement.cs new file mode 100644 index 0000000..894a469 --- /dev/null +++ b/Dialog/Elements/BoolElement.cs @@ -0,0 +1,48 @@ +// +// BoolElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + public abstract class BoolElement : Element + { + public BoolElement(string caption, bool value) : base(caption) + { + Value = value; + } + + public BoolElement(string caption) : this(caption, false) + { + } + + public override string ToString() + { + return Value ? "On" : "Off"; + } + } +} \ No newline at end of file diff --git a/Dialog/Elements/BooleanElement.cs b/Dialog/Elements/BooleanElement.cs new file mode 100644 index 0000000..d1eb7c2 --- /dev/null +++ b/Dialog/Elements/BooleanElement.cs @@ -0,0 +1,86 @@ +// +// BooleanElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using MonoTouch.UIKit; + + /// + /// Used to display switch on the screen. + /// + public class BooleanElement : BoolElement + { + private UISwitch Switch; + + public BooleanElement(string caption) : base(caption) + { + } + + public BooleanElement(string caption, bool value) : base(caption, value) + { + } + + public BooleanElement(string caption, bool value, string key) : base(caption, value) + { + } + + public override void InitializeCell(UITableView tableView) + { + RemoveTag(1); + if (Switch == null) + { + Switch = new UISwitch { BackgroundColor = UIColor.Clear, Tag = 1, On = Value }; + Switch.AddTarget(delegate + { + Value = Switch.On; + }, UIControlEvent.ValueChanged); + } + + Cell.TextLabel.Text = Caption; + Cell.AccessoryView = Switch; + } + + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Switch.Dispose(); + Switch = null; + } + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + if (Switch != null && Switch.On != Value) + Switch.On = Value; + } + } +} \ No newline at end of file diff --git a/Dialog/Elements/BooleanImageElement.cs b/Dialog/Elements/BooleanImageElement.cs new file mode 100644 index 0000000..3db137b --- /dev/null +++ b/Dialog/Elements/BooleanImageElement.cs @@ -0,0 +1,59 @@ +// +// BoolImageElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using MonoTouch.UIKit; + + public class BooleanImageElement : BaseBooleanImageElement + { + UIImage onImage, offImage; + + public BooleanImageElement(string caption, bool value, UIImage onImage, UIImage offImage) : base(caption, value) + { + this.onImage = onImage; + this.offImage = offImage; + } + + protected override UIImage GetImage() + { + if (Value) + return onImage; + else + return offImage; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + onImage = offImage = null; + } + } +} + diff --git a/Dialog/Elements/ButtonElement.cs b/Dialog/Elements/ButtonElement.cs new file mode 100644 index 0000000..ec43ecd --- /dev/null +++ b/Dialog/Elements/ButtonElement.cs @@ -0,0 +1,91 @@ +// +// ButtonElement.cs +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using MonoTouch.Foundation; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + + /// + /// The button element can be used to render a cell as a button that responds to Tap events + /// + public class ButtonElement : Element, ITappable + { + public UIFont Font; + public UIColor TextColor; + public UIColor BackgroundColor; + + public ICommand Command + { + get; + set; + } + public object CommandParameter + { + get; + set; + } + + public ButtonElement(string caption) : base(caption) + { + } + + public override void InitializeCell(UITableView tableView) + { + Cell.SelectionStyle = UITableViewCellSelectionStyle.Blue; + Cell.TextLabel.TextAlignment = UITextAlignment.Center; + + Cell.Accessory = UITableViewCellAccessory.None; + + Cell.BackgroundColor = BackgroundColor == null ? UIColor.White : BackgroundColor; + + + var textLabel = Cell.TextLabel; + textLabel.Text = Caption; + textLabel.TextColor = TextColor == null ? UIColor.Black : TextColor; + textLabel.Font = Font == null ? UIFont.BoldSystemFontOfSize(17) : Font; + textLabel.Lines = 0; + } + + public override string ToString() + { + return Caption; + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath) + { + if (Command != null && Command.CanExecute(CommandParameter)) + Command.Execute(CommandParameter); + + tableView.DeselectRow(indexPath, true); + } + } +} + diff --git a/Dialog/Elements/CheckboxElement.cs b/Dialog/Elements/CheckboxElement.cs new file mode 100644 index 0000000..6152314 --- /dev/null +++ b/Dialog/Elements/CheckboxElement.cs @@ -0,0 +1,79 @@ +// +// CheckboxElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class CheckboxElement : BooleanElement + { + public string Group; + + public CheckboxElement(string caption) : base(caption, false) + { + } + public CheckboxElement(string caption, bool value) : base(caption, value) + { + } + + public CheckboxElement(string caption, bool value, string @group) : this(caption, value) + { + Group = @group; + } + + public override void InitializeCell(UITableView tableView) + { + Cell.AccessoryView = null; + Cell.Accessory = Value ? UITableViewCellAccessory.Checkmark : UITableViewCellAccessory.None; + Cell.TextLabel.Text = Caption; + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + Value = !Value; + InitializeCell(tableView); + + base.Selected(dvc, tableView, path); + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + if (Cell != null) + Cell.Accessory = Value ? UITableViewCellAccessory.Checkmark : UITableViewCellAccessory.None; + } + + public override string ToString() + { + return Caption; + } + } +} + diff --git a/Dialog/Elements/DateElement.cs b/Dialog/Elements/DateElement.cs new file mode 100644 index 0000000..ce52d36 --- /dev/null +++ b/Dialog/Elements/DateElement.cs @@ -0,0 +1,60 @@ +// +// DateElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class DateElement : DateTimeElement + { + public DateElement(string caption) : base(caption) + { + } + + public DateElement(string caption, DateTime date) : base(caption, date) + { + fmt.DateStyle = NSDateFormatterStyle.Medium; + } + + public override string FormatDate(DateTime dt) + { + return fmt.ToString(dt); + } + + public override UIDatePicker CreatePicker() + { + var picker = base.CreatePicker(); + picker.Mode = UIDatePickerMode.Date; + return picker; + } + } +} + diff --git a/Dialog/Elements/DateTimeElement.cs b/Dialog/Elements/DateTimeElement.cs new file mode 100644 index 0000000..4064486 --- /dev/null +++ b/Dialog/Elements/DateTimeElement.cs @@ -0,0 +1,172 @@ +// +// DateTimeElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Drawing; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class DateTimeElement : Element + { + public UIDatePicker DatePicker; + protected NSDateFormatter fmt = new NSDateFormatter { DateStyle = NSDateFormatterStyle.Short }; + private UILabel Label; + + public DateTimeElement(string caption) : base(caption) + { + } + public DateTimeElement(string caption, DateTime date) : base(caption) + { + Value = date; + } + + public override UITableViewElementCell NewCell () + { + return new UITableViewElementCell(UITableViewCellStyle.Value1, Id); + } + + public override void InitializeCell(UITableView tableView) + { + Cell.Accessory = UITableViewCellAccessory.None; + Cell.TextLabel.Text = Caption; + + // The check is needed because the cell might have been recycled. + if (Cell.DetailTextLabel != null) + { + Label = Cell.DetailTextLabel; + Label.Text = FormatDate(Value).ToString(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + fmt.Dispose(); + fmt = null; + if (DatePicker != null) + { + DatePicker.Dispose(); + DatePicker = null; + } + } + } + + public virtual string FormatDate(DateTime dt) + { + return fmt.ToString(dt) + " " + dt.ToLocalTime().ToShortTimeString(); + } + + public virtual UIDatePicker CreatePicker() + { + var picker = new UIDatePicker(RectangleF.Empty) { AutoresizingMask = UIViewAutoresizing.FlexibleWidth, Mode = UIDatePickerMode.Date, Date = Value }; + return picker; + } + + private static RectangleF PickerFrameWithSize(SizeF size) + { + var screenRect = UIScreen.MainScreen.ApplicationFrame; + float fY = 0, fX = 0; + + switch (UIApplication.SharedApplication.StatusBarOrientation) + { + case UIInterfaceOrientation.LandscapeLeft: + case UIInterfaceOrientation.LandscapeRight: + fX = (screenRect.Height - size.Width) / 2; + fY = (screenRect.Width - size.Height) / 2 - 17; + break; + + case UIInterfaceOrientation.Portrait: + case UIInterfaceOrientation.PortraitUpsideDown: + fX = (screenRect.Width - size.Width) / 2; + fY = (screenRect.Height - size.Height) / 2 - 25; + break; + } + + return new RectangleF(fX, fY, size.Width, size.Height); + } + + private class MyViewController : UIViewController + { + DateTimeElement container; + + public MyViewController(DateTimeElement container) + { + this.container = container; + } + + public override void ViewWillDisappear(bool animated) + { + base.ViewWillDisappear(animated); + container.Value = container.DatePicker.Date; + } + + public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation) + { + base.DidRotate(fromInterfaceOrientation); + container.DatePicker.Frame = PickerFrameWithSize(container.DatePicker.SizeThatFits(SizeF.Empty)); + } + + public bool Autorotate + { + get; + set; + } + + public override bool ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation toInterfaceOrientation) + { + return Autorotate; + } + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + var vc = new MyViewController(this) { Autorotate = dvc.Autorotate }; + DatePicker = CreatePicker(); + DatePicker.Frame = PickerFrameWithSize(DatePicker.SizeThatFits(SizeF.Empty)); + vc.View.BackgroundColor = UIColor.Black; + vc.View.AddSubview(DatePicker); + dvc.ActivateController(vc, dvc); + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + if (DatePicker != null) + DatePicker.Date = Value; + + if (Label != null) + Label.Text = FormatDate(Value); + } + } +} + diff --git a/Dialog/Elements/Element.cs b/Dialog/Elements/Element.cs new file mode 100644 index 0000000..23777d9 --- /dev/null +++ b/Dialog/Elements/Element.cs @@ -0,0 +1,324 @@ +// +// Element.cs: +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Collections.Generic; + using MonoTouch.Foundation; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + + /// + /// Base class for all elements in MonoTouch.Dialog + /// + public class Element : DisposableObject, IBindable + { + public UITableViewElementCell Cell { get; set; } + + public UILabel TextLabel {get; set;} + + public NSString Id { get; set; } + public int Order { get; set; } + public int Index { get; set; } + + /// + /// Handle to the container object. + /// + /// + /// For sections this points to a IRoot, for every + /// other object this points to a Section and it is null + /// for the root IRoot. + /// + public Element Parent { get; set; } + + /// + /// The caption to display for this given element + /// + private string _Caption; + public string Caption + { + get {return _Caption;} + set {_Caption = value;} + } + + /// + /// Initializes the element with the given caption. + /// + /// + /// The caption. + /// + public Element(string caption) + { + Id = new NSString(GetType().FullName); + Caption = caption; + } + + protected override void Dispose(bool disposing) + { + } + public virtual UITableViewElementCell NewCell() + { + var cell = new UITableViewElementCell(UITableViewCellStyle.Default, Id); + cell.SelectionStyle = UITableViewCellSelectionStyle.None; + + return cell; + } + + public virtual UITableViewElementCell GetCell(UITableView tableView) + { + Cell = tableView.DequeueReusableCell(Id) as UITableViewElementCell; + if (Cell == null) + { + Cell = NewCell(); + } + if (Cell.Element != this) + InitializeCell(tableView); + + Cell.Element = this; + TextLabel = Cell.TextLabel; + UpdateTargets(); + + return Cell; + } + + public virtual void InitializeControls(UITableView tableView) + { + } + + public virtual void InitializeCell(UITableView tableView) + { + InitializeControls(tableView); + } + + public IBindingExpression SetBinding(IBindable target, string targetProperty, Binding binding) + { + return BindingOperations.SetBinding(target, targetProperty, binding); + } + + public IBindingExpression SetBinding(string targetProperty, Binding binding) + { + return SetBinding(this, targetProperty, binding); + } + + public IBindingExpression GetBindingExpression(string targetProperty) + { + return BindingOperations.GetBindingExpression(this, targetProperty); + } + + protected virtual void UpdateTarget(string property) + { + var bindingExpression = BindingOperations.GetBindingExpression(this, property); + + if (bindingExpression != null) + { + bindingExpression.UpdateTarget(); + } + } + + protected virtual void UpdateSource(string property) + { + var bindingExpression = BindingOperations.GetBindingExpression(this, property); + + if (bindingExpression != null) + { + bindingExpression.UpdateSource(); + } + } + + protected virtual void UpdateTargets() + { + var bindingExpressions = BindingOperations.GetBindingExpressionsForElement(this); + if(bindingExpressions != null) + { + foreach (var bindingExpression in bindingExpressions) + { + bindingExpression.UpdateTarget(); + } + } + } + + protected virtual void UpdateSources() + { + var bindingExpressions = BindingOperations.GetBindingExpressionsForElement(this); + if (bindingExpressions != null) + { + foreach (var bindingExpression in bindingExpressions) + { + bindingExpression.UpdateSource(); + } + } + } + + protected void RemoveTag(int tag) + { + var viewToRemove = Cell.ContentView.ViewWithTag(tag); + if (viewToRemove != null) + { + viewToRemove.RemoveFromSuperview(); + } + } + + /// + /// Returns a summary of the value represented by this object, suitable + /// for rendering as the result of a IRoot with child objects. + /// + /// + /// The return value must be a short description of the value. + /// + public override string ToString() + { + return ""; + } + + /// + /// Invoked when the given element has been tapped by the user. + /// + /// + /// The where the selection took place + /// + /// + /// The that contains the element. + /// + /// + /// The that contains the Section and Row for the element. + /// + public virtual void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + } + + /// + /// Returns the IndexPath of a given element. This is only valid for leaf elements, + /// it does not work for a toplevel IRoot or a Section of if the Element has + /// not been attached yet. + /// + public NSIndexPath IndexPath + { + get { + var section = Parent as ISection; + if (section == null) + return null; + var root = section.Parent as IRoot; + if (root == null) + return null; + + int row = 0; + foreach (var element in section.Elements) + { + if (element == this) + { + int nsect = 0; + foreach (var sect in root.Sections) + { + if (section == sect) + { + return NSIndexPath.FromRowSection(row, nsect); + } + nsect++; + } + } + row++; + } + return null; + } + } + + /// + /// Method invoked to determine if the cell matches the given text, never invoked with a null value or an empty string. + /// + public virtual bool Matches(string text) + { + if (Caption == null) + return false; + return Caption.IndexOf(text, StringComparison.CurrentCultureIgnoreCase) != -1; + } + } + + public abstract class Element : Element + { + public event EventHandler ValueChanged; + + private T _Value; + public virtual T Value + { + get { return _Value; } + set { SetValue(value); } + } + + public Element(string caption) : base(caption) + { + } + + protected virtual void SetValue(T value) + { + if (!EqualityComparer.Default.Equals(_Value, value)) + { + _Value = value; + + if (string.IsNullOrEmpty(Caption) && value != null) + { + Caption = value.ToString(); + } + + UpdateSources(); + OnValueChanged(); + } + } + + protected virtual void OnValueChanged() + { + if (ValueChanged != null) + ValueChanged(this, EventArgs.Empty); + } + + protected virtual T FormatValue(T value) + { + var expression = GetBindingExpression("Value"); + if (expression != null) + { + value = (T)expression.ConvertValue(value); + } + + return value; + } + + public override string ToString() + { + if(Value == null) + return Caption; + + return FormatValue(Value).ToString(); + } + +// protected virtual T FromString(string value) +// { +// return default(T); +// } + } +} \ No newline at end of file diff --git a/Dialog/Elements/ElementBadge.cs b/Dialog/Elements/ElementBadge.cs new file mode 100644 index 0000000..664c864 --- /dev/null +++ b/Dialog/Elements/ElementBadge.cs @@ -0,0 +1,168 @@ +// +// ElementBadge.cs: defines the Badge Element. +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Drawing; + using MonoTouch.CoreGraphics; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + /// + /// This element can be used to show an image with some text + /// + /// + /// The font can be configured after the element has been created + /// by assignign to the Font property; If you want to render + /// multiple lines of text, set the MultiLine property to true. + /// + /// If no font is specified, it will default to Helvetica 17. + /// + /// A static method MakeCalendarBadge is provided that can + /// render a calendar badge like the iPhone OS. It will compose + /// the text on top of the image which is expected to be 57x57 + /// + public class BadgeElement : Element, IElementSizing + { + public event NSAction Tapped; + public UILineBreakMode LineBreakMode = UILineBreakMode.TailTruncation; + public UIViewContentMode ContentMode = UIViewContentMode.Left; + public int Lines = 1; + public UITableViewCellAccessory Accessory = UITableViewCellAccessory.None; + private UIImage image; + private UIFont font; + + public BadgeElement (UIImage badgeImage, string cellText) + : this (badgeImage, cellText, null) + { + } + + public BadgeElement (UIImage badgeImage, string cellText, NSAction tapped) : base (cellText) + { + if (badgeImage == null) + throw new ArgumentNullException ("badgeImage"); + + image = badgeImage; + if (tapped != null) + Tapped += tapped; + } + + public UIFont Font + { + get + { + if (font == null) + font = UIFont.FromName ("Helvetica", 17f); + return font; + } + set + { + if (font != null) + font.Dispose (); + font = value; + } + } + + public override void InitializeCell(UITableView tableView) + { + Cell.SelectionStyle = UITableViewCellSelectionStyle.Blue; + + Cell.Accessory = Accessory; + + var textLabel = Cell.TextLabel; + textLabel.Text = Caption; + textLabel.Font = Font; + textLabel.LineBreakMode = LineBreakMode; + textLabel.Lines = Lines; + textLabel.ContentMode = ContentMode; + + Cell.ImageView.Image = image; + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + } + + public float GetHeight (UITableView tableView, NSIndexPath indexPath) + { + SizeF size = new SizeF (280, float.MaxValue); + float height = tableView.StringSize (Caption, Font, size, LineBreakMode).Height + 10; + + // Image is 57 pixels tall, add some padding + return Math.Max (height, 63); + } + + public override void Selected (DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + if (Tapped != null) + Tapped (); + tableView.DeselectRow (path, true); + } + + public static UIImage MakeCalendarBadge (UIImage template, string smallText, string bigText) + { + using (var cs = CGColorSpace.CreateDeviceRGB ()){ + using (var context = new CGBitmapContext (IntPtr.Zero, 57, 57, 8, 57*4, cs, CGImageAlphaInfo.PremultipliedLast)){ + //context.ScaleCTM (0.5f, -1); + context.TranslateCTM (0, 0); + context.DrawImage (new RectangleF (0, 0, 57, 57), template.CGImage); + context.SetRGBFillColor (1, 1, 1, 1); + + context.SelectFont ("Helvetica", 10f, CGTextEncoding.MacRoman); + + // Pretty lame way of measuring strings, as documented: + var start = context.TextPosition.X; + context.SetTextDrawingMode (CGTextDrawingMode.Invisible); + context.ShowText (smallText); + var width = context.TextPosition.X - start; + + context.SetTextDrawingMode (CGTextDrawingMode.Fill); + context.ShowTextAtPoint ((57-width)/2, 46, smallText); + + // The big string + context.SelectFont ("Helvetica-Bold", 32, CGTextEncoding.MacRoman); + start = context.TextPosition.X; + context.SetTextDrawingMode (CGTextDrawingMode.Invisible); + context.ShowText (bigText); + width = context.TextPosition.X - start; + + context.SetRGBFillColor (0, 0, 0, 1); + context.SetTextDrawingMode (CGTextDrawingMode.Fill); + context.ShowTextAtPoint ((57-width)/2, 9, bigText); + + context.StrokePath (); + + return UIImage.FromImage (context.ToImage ()); + } + } + } + } +} \ No newline at end of file diff --git a/Dialog/Elements/EntryElement.cs b/Dialog/Elements/EntryElement.cs new file mode 100644 index 0000000..cf0844f --- /dev/null +++ b/Dialog/Elements/EntryElement.cs @@ -0,0 +1,283 @@ +// +// EntryElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Drawing; + using MonoTouch.UIKit; + + /// + /// An element that can be used to enter text. + /// + /// + /// This element can be used to enter text both regular and password protected entries. + /// + /// The Text fields in a given section are aligned with each other. + /// + public class EntryElement : Element + { + /// + /// The type of keyboard used for input, you can change + /// this to use this for numeric input, email addressed, + /// urls, phones. + /// + public UIKeyboardType KeyboardType = UIKeyboardType.Default; + + private string _Placeholder; + private static UIFont _Font = UIFont.BoldSystemFontOfSize(17); + + private bool _IsPassword; + public bool IsPassword + { + get + { + return _IsPassword; + } + set + { + _IsPassword = value; + if (Entry != null && Entry.SecureTextEntry != _IsPassword) + { + Entry.SecureTextEntry = _IsPassword; + } + } + } + + public bool JustifyText { get; set; } + public UITextField Entry { get; set; } + + /// + /// Constructs an EntryElement with the given caption, placeholder and initial value. + /// + /// + /// The caption to use + /// + /// + /// Placeholder to display. + public EntryElement(string caption, string placeholder) : base(caption) + { + _Placeholder = placeholder; + } + + /// + /// Constructs an EntryElement with the given caption, placeholder and initial value. + /// + /// + /// The caption to use + /// + /// + /// Placeholder to display. + /// + /// + /// Initial value. + /// + public EntryElement(string caption, string placeholder, string value) : base(caption) + { + Value = value; + _Placeholder = placeholder; + } + + /// + /// Constructs an EntryElement for password entry with the given caption, placeholder and initial value. + /// + /// + /// The caption to use + /// + /// + /// Placeholder to display. + /// + /// True if this should be used to enter a password. + /// + public EntryElement(string caption, string placeholder, bool isPassword) : base(caption) + { + _IsPassword = isPassword; + _Placeholder = placeholder; + } + + /// + /// Constructs an EntryElement for password entry with the given caption, placeholder and initial value. + /// + /// + /// The caption to use + /// + /// + /// Placeholder to display. + /// + /// + /// Initial value. + /// + /// + /// True if this should be used to enter a password. + /// + public EntryElement(string caption, string placeholder, string value, bool isPassword) : base(caption) + { + Value = value; + _IsPassword = isPassword; + _Placeholder = placeholder; + } + + // + // Computes the X position for the entry by aligning all the entries in the Section + // + private SizeF ComputeEntryPosition(UITableView tv, UITableViewCell cell) + { + SizeF max = new SizeF(-1, -1); + var element = this as Element; + ISection s = Parent as ISection; + + if (JustifyText) + { + if (s.EntryAlignment.Width != 0) + return s.EntryAlignment; + + foreach (var e in s.Elements) + { + element = e as EntryElement; + if (element != null) + break; + } + } + + var size = tv.StringSize(element.Caption, _Font); + if (size.Width > max.Width) + max = size; + + s.EntryAlignment = new SizeF(25 + Math.Min(max.Width, 160), max.Height); + return s.EntryAlignment; + } + + public override void InitializeControls(UITableView tableView) + { + if (Entry == null) + { + SizeF size = ComputeEntryPosition(tableView, Cell); + var _entry = new UITextField(new RectangleF(size.Width, (Cell.ContentView.Bounds.Height - size.Height) / 2 - 1, 290 - size.Width, size.Height)) { Tag = 1, Placeholder = _Placeholder ?? "", SecureTextEntry = _IsPassword }; + _entry.AddTarget(delegate { Value = _entry.Text; }, UIControlEvent.ValueChanged); + + Entry = _entry; + + Entry.Ended += delegate + { + Value = Entry.Text; + }; + + Entry.ShouldReturn += delegate + { + EntryElement focus = null; + foreach (var e in (Parent as ISection).Elements) + { + if (e == this) + focus = this; + else if (focus != null && e is EntryElement) + focus = e as EntryElement; + } + + if (focus != this) + { + tableView.ScrollToRow(focus.IndexPath, UITableViewScrollPosition.Middle, true); + focus.Entry.BecomeFirstResponder(); + + } + else + focus.Entry.ResignFirstResponder(); + + return true; + }; + + Entry.Started += delegate + { + var bindingExpression = GetBindingExpression("Value"); + + if (bindingExpression != null) + { + Entry.Text = (string)bindingExpression.ConvertbackValue(Value); + } + else + Entry.Text = Value; + + EntryElement self = null; + var returnType = UIReturnKeyType.Done; + + foreach (var e in (Parent as ISection).Elements) + { + if (e == this) + self = this; else if (self != null && e is EntryElement) + returnType = UIReturnKeyType.Next; + } + + Entry.ReturnKeyType = returnType; + }; + } + + Entry.KeyboardType = KeyboardType; + Entry.TextAlignment = UITextAlignment.Right; + + Entry.Text = Value; + + Cell.TextLabel.Text = Caption; + + Cell.ContentView.AddSubview(Entry); + } + + public override void InitializeCell(UITableView tableView) + { + RemoveTag(1); + + Cell.TextLabel.Font = _Font; + + base.InitializeCell(tableView); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Entry.Dispose(); + Entry = null; + } + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + + if (Entry != null) + { + Entry.Text = Value; + } + } + + public override bool Matches(string text) + { + return (Value != null ? Value.IndexOf(text, StringComparison.CurrentCultureIgnoreCase) != -1 : false) || base.Matches(text); + } + } +} + diff --git a/Dialog/Elements/FloatElement.cs b/Dialog/Elements/FloatElement.cs new file mode 100644 index 0000000..55ca09d --- /dev/null +++ b/Dialog/Elements/FloatElement.cs @@ -0,0 +1,96 @@ +// +// FloatElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System.Drawing; + using MonoTouch.UIKit; + + /// + /// Used to display a slider on the screen. + /// + public class FloatElement : Element + { + public bool ShowCaption; + public float MinValue, MaxValue; + private UISlider slider; + + public FloatElement(float value) : base(null) + { + MinValue = 0; + MaxValue = 1; + Value = value; + } + + public FloatElement() : base(null) + { + MinValue = 0; + MaxValue = 1; + } + public override void InitializeCell(UITableView tableView) + { + RemoveTag(1); + + SizeF captionSize = new SizeF(0, 0); + if (Caption != null && ShowCaption) + { + Cell.TextLabel.Text = Caption; + captionSize = Cell.TextLabel.StringSize(Caption, UIFont.FromName(Cell.TextLabel.Font.Name, UIFont.LabelFontSize)); + captionSize.Width += 10; + // Spacing + } + + if (slider == null) + { + slider = new UISlider(new RectangleF(10f + captionSize.Width, 12f, 280f - captionSize.Width, 7f)) { BackgroundColor = UIColor.Clear, MinValue = this.MinValue, MaxValue = this.MaxValue, Continuous = true, Value = this.Value, Tag = 1 }; + slider.ValueChanged += delegate { Value = slider.Value; }; + } + + Cell.ContentView.AddSubview(slider); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + slider.Dispose(); + slider = null; + } + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + if (slider != null) + slider.Value = Value; + } + } + +} + diff --git a/Dialog/Elements/HtmlElement.cs b/Dialog/Elements/HtmlElement.cs new file mode 100644 index 0000000..851f015 --- /dev/null +++ b/Dialog/Elements/HtmlElement.cs @@ -0,0 +1,140 @@ +// +// HtmlElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + /// + /// Used to display a cell that will launch a web browser when selected. + /// + public class HtmlElement : Element + { + private NSUrl nsUrl; + private UIWebView web; + + public HtmlElement(string caption) : base(caption) + { + } + + public HtmlElement(string caption, string url) : base(caption) + { + Url = url; + } + + public HtmlElement(string caption, NSUrl url) : base(caption) + { + nsUrl = url; + } + + public string Url + { + get { return nsUrl.ToString(); } + set { nsUrl = new NSUrl(value); } + } + + public override void InitializeCell(UITableView tableView) + { + Cell.SelectionStyle = UITableViewCellSelectionStyle.Blue; + + Cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; + Cell.TextLabel.Text = Caption; + } + + static bool NetworkActivity + { + set { UIApplication.SharedApplication.NetworkActivityIndicatorVisible = value; } + } + + // We use this class to dispose the web control when it is not + // in use, as it could be a bit of a pig, and we do not want to + // wait for the GC to kick-in. + class WebViewController : UIViewController + { + HtmlElement container; + + public WebViewController(HtmlElement container) : base() + { + this.container = container; + } + + public override void ViewWillDisappear(bool animated) + { + base.ViewWillDisappear(animated); + NetworkActivity = false; + if (container.web == null) + return; + + container.web.StopLoading(); + container.web.Dispose(); + container.web = null; + } + + public bool Autorotate + { + get; + set; + } + + public override bool ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation toInterfaceOrientation) + { + return Autorotate; + } + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + var vc = new WebViewController(this) { Autorotate = dvc.Autorotate }; + web = new UIWebView(UIScreen.MainScreen.ApplicationFrame) { BackgroundColor = UIColor.White, ScalesPageToFit = true, AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight }; + web.LoadStarted += delegate { NetworkActivity = true; }; + web.LoadFinished += delegate { NetworkActivity = false; }; + web.LoadError += (webview, args) => + { + NetworkActivity = false; + if (web != null) + web.LoadHtmlString(String.Format("
An error occurred:
{0}
", args.Error.LocalizedDescription), null); + }; + vc.NavigationItem.Title = Caption; + vc.View.AddSubview(web); + + dvc.ActivateController(vc, dvc); + web.LoadRequest(NSUrlRequest.FromUrl(nsUrl)); + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + if (Value != null) + Url = FormatValue(Value); + } + } +} + diff --git a/Dialog/Elements/ImageElement.cs b/Dialog/Elements/ImageElement.cs new file mode 100644 index 0000000..1b3200e --- /dev/null +++ b/Dialog/Elements/ImageElement.cs @@ -0,0 +1,229 @@ +// +// ImageElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Drawing; + using MonoTouch.CoreGraphics; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class ImageElement : Element + { + private static RectangleF rect = new RectangleF(0, 0, dimx, dimy); + private UIImage scaled; + private UIPopoverController popover; + + // Apple leaks this one, so share across all. + private static UIImagePickerController picker; + + // Height for rows + private const int dimx = 48; + private const int dimy = 43; + + // radius for rounding + private const int rad = 10; + + private static UIImage MakeEmpty() + { + using (var cs = CGColorSpace.CreateDeviceRGB()) + { + using (var bit = new CGBitmapContext(IntPtr.Zero, dimx, dimy, 8, 0, cs, CGImageAlphaInfo.PremultipliedFirst)) + { + bit.SetRGBStrokeColor(1, 0, 0, 0.5f); + bit.FillRect(new RectangleF(0, 0, dimx, dimy)); + + return UIImage.FromImage(bit.ToImage()); + } + } + } + + private UIImage Scale(UIImage source) + { + UIGraphics.BeginImageContext(new SizeF(dimx, dimy)); + var ctx = UIGraphics.GetCurrentContext(); + + var img = source.CGImage; + ctx.TranslateCTM(0, dimy); + if (img.Width > img.Height) + ctx.ScaleCTM(1, -img.Width / dimy); + else + ctx.ScaleCTM(img.Height / dimx, -1); + + ctx.DrawImage(rect, source.CGImage); + + var ret = UIGraphics.GetImageFromCurrentImageContext(); + UIGraphics.EndImageContext(); + return ret; + } + + public ImageElement() : base("") + { + } + + public ImageElement(UIImage image) : base("") + { + Value = image; + } + + public override UIImage Value + { + get + { + return base.Value; + } + set + { + if (value == null) + { + base.Value = MakeEmpty(); + scaled = Value; + } + else + { + base.Value = value; + scaled = Scale(Value); + } + } + } + + public override void InitializeCell(UITableView tableView) + { + if (scaled != null) + { + ISection psection = Parent as ISection; + bool roundTop = psection.Elements[0] == this; + bool roundBottom = psection.Elements[psection.Elements.Count - 1] == this; + + using (var cs = CGColorSpace.CreateDeviceRGB()) + { + using (var bit = new CGBitmapContext(IntPtr.Zero, dimx, dimy, 8, 0, cs, CGImageAlphaInfo.PremultipliedFirst)) + { + // Clipping path for the image, different on top, middle and bottom. + if (roundBottom) + { + bit.AddArc(rad, rad, rad, (float)Math.PI, (float)(3 * Math.PI / 2), false); + + } + else + { + bit.MoveTo(0, rad); + bit.AddLineToPoint(0, 0); + } + bit.AddLineToPoint(dimx, 0); + bit.AddLineToPoint(dimx, dimy); + + if (roundTop) + { + bit.AddArc(rad, dimy - rad, rad, (float)(Math.PI / 2), (float)Math.PI, false); + bit.AddLineToPoint(0, rad); + + } + else + { + bit.AddLineToPoint(0, dimy); + } + bit.Clip(); + bit.DrawImage(rect, scaled.CGImage); + + Cell.ImageView.Image = UIImage.FromImage(bit.ToImage()); + } + } + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + scaled.Dispose(); + Value.Dispose(); + } + base.Dispose(disposing); + } + + class MyDelegate : UIImagePickerControllerDelegate + { + ImageElement container; + UITableView table; + NSIndexPath path; + + public MyDelegate(ImageElement container, UITableView table, NSIndexPath path) + { + this.container = container; + this.table = table; + this.path = path; + } + + public override void FinishedPickingImage(UIImagePickerController picker, UIImage image, NSDictionary editingInfo) + { + container.Picked(image); + table.ReloadRows(new NSIndexPath[] { path }, UITableViewRowAnimation.None); + } + } + + void Picked(UIImage image) + { + Value = image; + scaled = Scale(image); + currentController.DismissModalViewControllerAnimated(true); + + } + + UIViewController currentController; + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + if (picker == null) + picker = new UIImagePickerController(); + picker.Delegate = new MyDelegate(this, tableView, path); + + switch (UIDevice.CurrentDevice.UserInterfaceIdiom) + { + case UIUserInterfaceIdiom.Pad: + RectangleF useRect; + popover = new UIPopoverController(picker); + var cell = tableView.CellAt(path); + if (cell == null) + useRect = rect; + else + useRect = cell.Frame; + popover.PresentFromRect(useRect, dvc.View, UIPopoverArrowDirection.Any, true); + break; + default: + + case UIUserInterfaceIdiom.Phone: + dvc.ActivateController(picker, dvc); + break; + } + currentController = dvc; + } + } +} + diff --git a/Dialog/Elements/ImageStringElement.cs b/Dialog/Elements/ImageStringElement.cs new file mode 100644 index 0000000..0c64a10 --- /dev/null +++ b/Dialog/Elements/ImageStringElement.cs @@ -0,0 +1,81 @@ +// +// ImageStringElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using MonoTouch.UIKit; + + public class ImageStringElement : StringElement + { + private UIImage image; + + public UITableViewCellAccessory Accessory { get; set; } + + public ImageStringElement() : this("") + { + } + + public ImageStringElement(string caption) : base(caption) + { + this.Accessory = UITableViewCellAccessory.None; + } + + public ImageStringElement(string caption, UIImage image) : base(caption) + { + this.image = image; + this.Accessory = UITableViewCellAccessory.None; + } + + public ImageStringElement(string caption, string value, UIImage image) : base(caption, value) + { + this.image = image; + this.Accessory = UITableViewCellAccessory.None; + } + + public override UITableViewElementCell NewCell() + { + return new UITableViewElementCell(Value == null ? UITableViewCellStyle.Default : UITableViewCellStyle.Subtitle, Id); + + } + public override void InitializeCell(UITableView tableView) + { + Cell.SelectionStyle = UITableViewCellSelectionStyle.Blue; + Cell.Accessory = Accessory; + Cell.TextLabel.Text = Caption; + Cell.TextLabel.TextAlignment = Alignment; + + Cell.ImageView.Image = image; + + // The check is needed because the cell might have been recycled. + if (Cell.DetailTextLabel != null) + Cell.DetailTextLabel.Text = Value == null ? "" : Value; + } + } +} + diff --git a/Dialog/Elements/ListBoxElement.cs b/Dialog/Elements/ListBoxElement.cs new file mode 100644 index 0000000..f11f805 --- /dev/null +++ b/Dialog/Elements/ListBoxElement.cs @@ -0,0 +1,29 @@ +using System; +using MonoTouch.UIKit; +namespace MonoTouch.Dialog +{ + public class ListBoxElement : Section + { + public ListBoxElement() : base("") + { + } + + public ListBoxElement(string caption) : base(caption) + { + } + + public ListBoxElement(string caption, string footer) : base(caption, footer) + { + } + + public ListBoxElement(UIView header) : base(header) + { + } + + public ListBoxElement(UIView header, UIView footer) : base(header, footer) + { + } + } +} + + diff --git a/Dialog/Elements/LoadMoreElement.cs b/Dialog/Elements/LoadMoreElement.cs new file mode 100644 index 0000000..42a70b1 --- /dev/null +++ b/Dialog/Elements/LoadMoreElement.cs @@ -0,0 +1,184 @@ +// +// LoadMoreElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// This cell does not perform cell recycling, do not use as +// sample code for new elements. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Drawing; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class LoadMoreElement : Element, IElementSizing + { + public string NormalCaption { get; set; } + public string LoadingCaption { get; set; } + + private Action _Tapped = null; + + private UITableViewElementCell cell; + private UIActivityIndicatorView activityIndicator; + private UILabel caption; + private UIFont _Font; + + public LoadMoreElement (string normalCaption, string loadingCaption, Action tapped) : this (normalCaption, loadingCaption, tapped, UIFont.BoldSystemFontOfSize (16), UIColor.Black) + { + } + + public LoadMoreElement (string normalCaption, string loadingCaption, Action tapped, UIFont font, UIColor textColor) : base ("") + { + NormalCaption = normalCaption; + LoadingCaption = loadingCaption; + _Tapped = tapped; + _Font = font; + + cell = new UITableViewElementCell(UITableViewCellStyle.Default, Id); + + activityIndicator = new UIActivityIndicatorView() + { + ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray, + Hidden = true + }; + + activityIndicator.StopAnimating(); + + caption = new UILabel() + { + Font = font, + Text = NormalCaption, + TextColor = textColor, + BackgroundColor = UIColor.Clear, + TextAlignment = UITextAlignment.Center, + AdjustsFontSizeToFitWidth = false, + }; + + Layout(); + + cell.ContentView.AddSubview(caption); + cell.ContentView.AddSubview(activityIndicator); + } + + public bool Animating + { + get + { + return activityIndicator.IsAnimating; + } + set + { + if (value) + { + caption.Text = LoadingCaption; + activityIndicator.Hidden = false; + activityIndicator.StartAnimating(); + } + else + { + activityIndicator.StopAnimating(); + activityIndicator.Hidden = true; + caption.Text = NormalCaption; + } + Layout (); + } + } + + public override UITableViewElementCell GetCell(UITableView tv) + { + Layout(); + return cell as UITableViewElementCell; + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + tableView.DeselectRow(path, true); + + if (Animating) + return; + + if (_Tapped != null) + { + Animating = true; + Layout (); + _Tapped(this); + } + } + + private SizeF GetTextSize() + { + return new NSString(caption.Text).StringSize(_Font, UIScreen.MainScreen.Bounds.Width, UILineBreakMode.TailTruncation); + } + + private const int pad = 10; + private const int isize = 20; + + public float GetHeight(UITableView tableView, NSIndexPath indexPath) + { + return GetTextSize().Height + 2*pad; + } + + private void Layout() + { + var sbounds = cell.ContentView.Bounds; + + var size = GetTextSize(); + + if (!activityIndicator.Hidden) + activityIndicator.Frame = new RectangleF((sbounds.Width-size.Width)/2-isize*2, pad, isize, isize); + + caption.Frame = new RectangleF(10, pad, sbounds.Width-20, size.Height); + } + + public UITextAlignment Alignment + { + get + { + return caption.TextAlignment; + } + set + { + caption.TextAlignment = value; + } + } + public UITableViewCellAccessory Accessory + { + get + { + return cell.Accessory; + } + set + { + cell.Accessory = value; + } + } + } +} + diff --git a/Dialog/Elements/MapViewElement.cs b/Dialog/Elements/MapViewElement.cs new file mode 100644 index 0000000..1373884 --- /dev/null +++ b/Dialog/Elements/MapViewElement.cs @@ -0,0 +1,160 @@ +// +// MapElements.cs: Used to render a Google Map +// +// Author: +// Eduardo Scoz (contact@escoz.com) +// +// Copyright 2010, Eduardo Scoz +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using MonoTouch.CoreLocation; + using MonoTouch.Dialog; + using MonoTouch.Foundation; + using MonoTouch.MapKit; + using MonoTouch.UIKit; + + public class MapElement : StringElement + { + public virtual CLLocationCoordinate2D Location + { + get; + set; + } + + public MapElement(string caption, string value, CLLocationCoordinate2D location) : base(caption, value) + { + Location = location; + } + + public override void InitializeCell (UITableView tableView) + { + Cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + var mapViewController = new MapViewController(Location) { Title = Caption }; + dvc.ActivateController(mapViewController, dvc); + } + + private class MapViewController : UIViewController + { + private MKMapView _MapView; + private CLLocationCoordinate2D _CurrLocation; + + public MKAnnotation GeocodeAnnotation; + + public MapViewController(CLLocationCoordinate2D newLocation) + { + _MapView = CreateMapView(); + _CurrLocation = newLocation; + } + + public override void ViewDidLoad() + { + base.ViewDidLoad(); + View = _MapView; + } + + public override void ViewWillAppear(bool animated) + { + base.ViewWillAppear(animated); + UpdateLocation(_CurrLocation, false); + } + + public void UpdateLocation(CLLocationCoordinate2D newLocation, bool animated) + { + var span = new MKCoordinateSpan(0.1, 0.1); + var region = new MKCoordinateRegion(newLocation, span); + + _MapView.SetRegion(region, animated); + + if (GeocodeAnnotation != null) + _MapView.RemoveAnnotation(GeocodeAnnotation); + + GeocodeAnnotation = new MapViewAnnotation(newLocation); + + _MapView.AddAnnotationObject(GeocodeAnnotation); + } + + private MKMapView CreateMapView() + { + var map = new MKMapView() + { + Delegate = new MapViewDelegate(), + ZoomEnabled = true, + ScrollEnabled = true, + ShowsUserLocation = true, + + MapType = MonoTouch.MapKit.MKMapType.Standard, + + UserInteractionEnabled = true, + MultipleTouchEnabled = true, + + ClearsContextBeforeDrawing = true, + ClipsToBounds = true, + AutosizesSubviews = true, + }; + + return map; + } + } + + private class MapViewDelegate : MKMapViewDelegate + { + public override MKAnnotationView GetViewForAnnotation(MKMapView mapView, NSObject annotation) + { + var anv = mapView.DequeueReusableAnnotation("thislocation"); + + if (anv == null) + { + var pinanv = new MKPinAnnotationView(annotation, "thislocation"); + pinanv.AnimatesDrop = true; + pinanv.PinColor = MKPinAnnotationColor.Green; + pinanv.CanShowCallout = false; + anv = pinanv; + } else + { + anv.Annotation = annotation; + } + return anv; + } + } + + private class MapViewAnnotation : MKAnnotation + { + public override CLLocationCoordinate2D Coordinate + { + get; + set; + } + + public MapViewAnnotation(CLLocationCoordinate2D coord) : base() + { + Coordinate = coord; + } + } + } +} \ No newline at end of file diff --git a/Dialog/Elements/MultilineElement.cs b/Dialog/Elements/MultilineElement.cs new file mode 100644 index 0000000..8b0ebd8 --- /dev/null +++ b/Dialog/Elements/MultilineElement.cs @@ -0,0 +1,61 @@ +// +// MultilineElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System.Drawing; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class MultilineElement : StringElement, IElementSizing + { + public MultilineElement(string caption) : base(caption) + { + } + + public MultilineElement(string caption, string value) : base(caption, value) + { + } + + public override void InitializeCell(UITableView tableView) + { + var textLabel = Cell.TextLabel; + textLabel.LineBreakMode = UILineBreakMode.WordWrap; + textLabel.Lines = 0; + } + + public virtual float GetHeight(UITableView tableView, NSIndexPath indexPath) + { + SizeF size = new SizeF(280, float.MaxValue); + using (var font = UIFont.FromName("Helvetica", 17f)) + return tableView.StringSize(Caption, font, size, UILineBreakMode.WordWrap).Height + 10; + } + } +} + diff --git a/Dialog/Elements/OwnerDrawnElement.cs b/Dialog/Elements/OwnerDrawnElement.cs new file mode 100644 index 0000000..6ae73c4 --- /dev/null +++ b/Dialog/Elements/OwnerDrawnElement.cs @@ -0,0 +1,111 @@ +using System; +using System.Drawing; +using MonoTouch.UIKit; +using MonoTouch.CoreFoundation; +using MonoTouch.CoreGraphics; +using MonoTouch.Foundation; + +namespace MonoTouch.Dialog +{ + public abstract class OwnerDrawnElement : Element, IElementSizing + { + public UITableViewCellStyle Style + { + get; + set; + } + + public OwnerDrawnElement(UITableViewCellStyle style) : base("") + { + Style = style; + } + + public float GetHeight(UITableView tableView, NSIndexPath indexPath) + { + return Height(tableView.Bounds); + } + + public override UITableViewElementCell NewCell() + { + Cell = new OwnerDrawnCell(this, Style, Id); + + return Cell; + } + + public abstract void Draw(RectangleF bounds, CGContext context, UIView view); + + public abstract float Height(RectangleF bounds); + + private class OwnerDrawnCell : UITableViewElementCell + { + private OwnerDrawnCellView _View; + + public OwnerDrawnCell(OwnerDrawnElement element, UITableViewCellStyle style, string cellReuseIdentifier) : base(style, cellReuseIdentifier) + { + Element = element; + } + + public new OwnerDrawnElement Element + { + get + { + return _View.Element; + } + set + { + if (_View == null) + { + _View = new OwnerDrawnCellView(value); + ContentView.Add(_View); + } else + { + _View.Element = value; + } + + + } + } + + public override void LayoutSubviews() + { + base.LayoutSubviews(); + + _View.Frame = ContentView.Bounds; + } + } + + class OwnerDrawnCellView : UIView + { + private OwnerDrawnElement _Element; + + public OwnerDrawnCellView(OwnerDrawnElement element) + { + Element = element; + } + + public OwnerDrawnElement Element + { + get + { + return _Element; + } + set + { + _Element = value; + if (_Element != null) + SetNeedsDisplay(); + } + } + + public override void Draw(RectangleF rect) + { + if (_Element == null) + return; + + CGContext context = UIGraphics.GetCurrentContext(); + _Element.Draw(rect, context, this); + } + } + } +} + diff --git a/Dialog/Elements/RadioElement.cs b/Dialog/Elements/RadioElement.cs new file mode 100644 index 0000000..a1b119c --- /dev/null +++ b/Dialog/Elements/RadioElement.cs @@ -0,0 +1,113 @@ +// +// RadioElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class RadioElement : BooleanElement + { + internal bool PopOnSelect; + + public RadioElement(string caption, bool popOnSelect) : base(caption) + { + PopOnSelect = popOnSelect; + } + + public RadioElement(string caption) : base(caption) + { + } + + public override void InitializeCell(UITableView tableView) + { + var root = (IRoot)Parent.Parent; + bool selected = false; + var radioGroup = root.Group as RadioGroup; + + if (radioGroup == null) + throw new Exception ("The IRoot's Group is null or is not a RadioGroup"); + else + selected = Index == radioGroup.Selected; + + Cell.Accessory = selected ? UITableViewCellAccessory.Checkmark : UITableViewCellAccessory.None; + Cell.TextLabel.Text = Caption; + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath) + { + if (Parent != null) + { + var root = (IRoot)Parent.Parent; + if (root != null) + { + var section = root.Sections[indexPath.Section]; + foreach (var e in section.Elements) + if (e is RadioElement) + ((RadioElement)e).Value = false; + } + } + + Value = true; + + base.Selected(dvc, tableView, indexPath); + + if (PopOnSelect) + dvc.NavigationController.PopViewControllerAnimated(true); + } + + protected override void OnValueChanged() + { + if (Cell != null) + Cell.Accessory = Value ? UITableViewCellAccessory.Checkmark : UITableViewCellAccessory.None; + + if (Parent != null) + { + var root = (IRoot)Parent.Parent; + if(root != null) + { + var radioGroup = root.Group as RadioGroup; + if (radioGroup != null && Value) + { + radioGroup.Selected = Index; + } + } + } + + base.OnValueChanged(); + } + + public override string ToString() + { + return Caption; + } + } +} + diff --git a/Dialog/Elements/RootElement.cs b/Dialog/Elements/RootElement.cs new file mode 100644 index 0000000..b88d525 --- /dev/null +++ b/Dialog/Elements/RootElement.cs @@ -0,0 +1,639 @@ +// +// RootElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using MonoTouch.Foundation; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + + /// + /// RootElements are responsible for showing a full configuration page. + /// + /// + /// At least one RootElement is required to start the MonoTouch.Dialogs + /// process. + /// + /// RootElements can also be used inside Sections to trigger + /// loading a new nested configuration page. When used in this mode + /// the caption provided is used while rendered inside a section and + /// is also used as the Title for the subpage. + /// + /// If a RootElement is initialized with a section/element value then + /// this value is used to locate a child Element that will provide + /// a summary of the configuration which is rendered on the right-side + /// of the display. + /// + /// RootElements are also used to coordinate radio elements. The + /// RadioElement members can span multiple Sections (for example to + /// implement something similar to the ring tone selector and separate + /// custom ring tones from system ringtones). + /// + /// Sections are added by calling the Add method which supports the + /// C# 4.0 syntax to initialize a RootElement in one pass. + /// + public class RootElement : Element, IEnumerable, IRoot + { + public Type ElementType { get; set; } + public List ToolbarButtons { get; set; } + public UIBarButtonItem EditButton { get; set; } + + private int summarySection, summaryElement; + private Func createOnSelected; + private UILabel DetailLabel; + + public UITableViewCellStyle CellStyle { get; set; } + + public Group Group { get; set; } + public bool UnevenRows { get; set; } + public UITableView TableView { get; set; } + + public RootElement(): this("") + { + } + + /// + /// Initializes a RootSection with a caption + /// + /// + /// The caption to render. + /// + public RootElement(string caption) : base(caption) + { + summarySection = -1; + Sections = new List(); + } + + /// + /// Initializes a RootSection with a caption and a callback that will + /// create the nested UIViewController that is activated when the user + /// taps on the element. + /// + /// + /// The caption to render. + /// + public RootElement(string caption, Func createOnSelected) : this(caption) + { + summarySection = -1; + this.createOnSelected = createOnSelected; + Sections = new List(); + } + + /// + /// Initializes a RootElement with a caption with a summary fetched from the specified section and leement + /// + /// + /// The caption to render cref="System.String"/> + /// + /// + /// The section that contains the element with the summary. + /// + /// + /// The element index inside the section that contains the summary for this RootSection. + /// + public RootElement(string caption, int section, int element) : this(caption) + { + summarySection = section; + summaryElement = element; + } + + /// + /// Initializes a RootElement that renders the summary based on the radio settings of the contained elements. + /// + /// + /// The caption to ender + /// + /// + /// The group that contains the checkbox or radio information. This is used to display + /// the summary information when a RootElement is rendered inside a section. + /// + public RootElement(string caption, Group @group) : this(caption) + { + Group = @group; + } + + public List Sections { get; set; } + + public NSIndexPath PathForRadio(int idx) + { + RadioGroup radio = Group as RadioGroup; + if (radio == null) + return null; + + uint current = 0, section = 0; + foreach (ISection s in Sections) + { + uint row = 0; + + foreach (Element e in s.Elements) + { + if (!(e is RadioElement)) + continue; + + if (current == idx) + { + return NSIndexPath.Create(section, row); + } + row++; + current++; + } + section++; + } + return null; + } + + public int Count + { + get { return Sections.Count; } + } + + public ISection this[int idx] + { + get { return Sections[idx]; } + } + + public int IndexOf(ISection target) + { + int idx = 0; + foreach (ISection s in Sections) + { + if (s == target) + return idx; + idx++; + } + return -1; + } + + public void Prepare() + { + foreach (ISection s in Sections) + { + foreach (Element e in s.Elements) + { + if (UnevenRows == false && e is IElementSizing) + UnevenRows = true; + } + } + } + + /// + /// Adds a new section to this RootElement + /// + /// + /// The section to add, if the root is visible, the section is inserted with no animation + /// + public void Add(ISection section) + { + if (section == null) + return; + + Sections.Add(section); + section.Parent = this; + if (TableView == null) + return; + + TableView.InsertSections(MakeIndexSet(Sections.Count - 1, 1), UITableViewRowAnimation.None); + } + + // + // This makes things LINQ friendly; You can now create RootElements + // with an embedded LINQ expression, like this: + // new RootElement ("Title") { + // from x in names + // select new Section (x) { new StringElement ("Sample") } + // + public void Add(IEnumerable sections) + { + foreach (var s in sections) + Add(s); + } + + private NSIndexSet MakeIndexSet(int start, int count) + { + NSRange range; + range.Location = start; + range.Length = count; + return NSIndexSet.FromNSRange(range); + } + + /// + /// Inserts a new section into the RootElement + /// + /// + /// The index where the section is added + /// + /// + /// The type. + /// + /// + /// A list of sections to insert + /// + /// + /// This inserts the specified list of sections (a params argument) into the + /// root using the specified animation. + /// + public void Insert(int idx, UITableViewRowAnimation anim, params ISection[] newSections) + { + if (idx < 0 || idx > Sections.Count) + return; + if (newSections == null) + return; + + if (TableView != null) + TableView.BeginUpdates(); + + int pos = idx; + foreach (var s in newSections) + { + s.Parent = this; + Sections.Insert(pos++, s); + } + + if (TableView == null) + return; + + TableView.InsertSections(MakeIndexSet(idx, newSections.Length), anim); + TableView.EndUpdates(); + } + + /// + /// Inserts a new section into the RootElement + /// + /// + /// The index where the section is added + /// + /// + /// A list of sections to insert + /// + /// + /// This inserts the specified list of sections (a params argument) into the + /// root using the Fade animation. + /// + public void Insert(int idx, ISection section) + { + Insert(idx, UITableViewRowAnimation.None, section); + } + + /// + /// Removes a section at a specified location + /// + public void RemoveAt(int idx) + { + RemoveAt(idx, UITableViewRowAnimation.Fade); + } + + /// + /// Removes a section at a specified location using the specified animation + /// + /// + /// A + /// + /// + /// A + /// + public void RemoveAt(int idx, UITableViewRowAnimation anim) + { + if (idx < 0 || idx >= Sections.Count) + return; + + Sections.RemoveAt(idx); + + if (TableView == null) + return; + + TableView.DeleteSections(NSIndexSet.FromIndex(idx), anim); + } + + public void Remove(ISection s) + { + if (s == null) + return; + int idx = Sections.IndexOf(s); + if (idx == -1) + return; + RemoveAt(idx, UITableViewRowAnimation.Fade); + } + + public void Remove(ISection s, UITableViewRowAnimation anim) + { + if (s == null) + return; + int idx = Sections.IndexOf(s); + if (idx == -1) + return; + RemoveAt(idx, anim); + } + + public void Clear() + { + foreach (var s in Sections) + s.Dispose(); + Sections = new List(); + if (TableView != null) + TableView.ReloadData(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (Sections == null) + return; + + TableView = null; + Clear(); + Sections = null; + } + } + + /// + /// Enumerator that returns all the sections in the RootElement. + /// + /// + /// A + /// + public IEnumerator GetEnumerator() + { + foreach (var s in Sections) + yield return s; + } + + /// + /// The currently selected Radio item in the whole Root. + /// + public int ItemIndex + { + get { + var radio = Group as RadioGroup; + if (radio != null) + return radio.Selected; + return -1; + } + set { + var radio = Group as RadioGroup; + if (radio != null) + radio.Selected = value; + } + } + + + public override UITableViewElementCell GetCell(UITableView tableView) + { + if(ElementType != null) + { + var element = Activator.CreateInstance(ElementType, Caption) as Element; + element.GetCell(tableView); + Cell = element.Cell; + + if (Cell.Element != element) + { + element.InitializeCell(tableView); + Cell.Element = element; + } + + TextLabel = Cell.TextLabel; + // Cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; + + UpdateTargets(); + + return Cell; + } + + return base.GetCell(tableView); + } + + public override UITableViewElementCell NewCell() + { + var style = summarySection == -1 ? CellStyle : UITableViewCellStyle.Value1; + + var cell = new UITableViewElementCell(style, Id); + cell.SelectionStyle = UITableViewCellSelectionStyle.Blue; + + return cell; + } + + public override void InitializeCell(UITableView tableView) + { + Cell.TextLabel.Text = Caption; + var radio = Group as RadioGroup; + var section = Sections.FirstOrDefault(); + if (radio != null && section != null && !section.IsMultiselect) + { + foreach(var s in Sections) + { + foreach(var e in s.Elements) + { + var radioElement = e as RadioElement; + if (radioElement != null && radioElement.Value) + { + Value = (T)Convert.ChangeType(radioElement.Index, Value.GetType()); + radio.Selected = radioElement.Index; + break; + } + } + } + + Cell.DetailTextLabel.Text = EnumExtensions.GetDescriptionValue(Enum.GetNames(radio.EnumType)[radio.Selected], radio.EnumType); + } + else if (summarySection != -1 && summarySection < Sections.Count) + { + var s = Sections[summarySection]; + if (summaryElement < s.Elements.Count) + Cell.DetailTextLabel.Text = s.Elements[summaryElement].ToString(); + } + else if (section != null && section.IsMultiselect) + { + var count = 0; + var val = 0; + var index = 0; + + foreach (var s in Sections) + { + foreach (var e in s.Elements) + { + index++; + + var ce = e as CheckboxElement; + if (ce != null) + { + if (ce.Value) + { + count++; + val |= (1 << index - 1); + } + continue; + } + var be = e as BoolElement; + if (be != null) + { + if (be.Value) + count++; + continue; + } + } + } + + if(Cell.DetailTextLabel != null) + Cell.DetailTextLabel.Text = count.ToString(); + } + + Cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; + + DetailLabel = Cell.DetailTextLabel; + + var view = Value as View; + + if (DetailLabel != null && view != null) + { + DetailLabel.Text = view.ToString(); + } + + if (CellStyle == UITableViewCellStyle.Default && view != null && string.IsNullOrEmpty(Caption)) + { + Cell.TextLabel.Text = view.ToString(); + } + } + + /// + /// This method does nothing by default, but gives a chance to subclasses to + /// customize the UIViewController before it is presented + /// + protected virtual void PrepareDialogViewController(UIViewController dvc) + { + } + + /// + /// Creates the UIViewController that will be pushed by this RootElement + /// + protected virtual UIViewController MakeViewController() + { + if (createOnSelected != null) + return createOnSelected(this); + + var dvc = new DialogViewController(this, true) { Autorotate = true }; + + if(ToolbarButtons != null) + dvc.SetToolbarItems(ToolbarButtons.ToArray(), true); + + // dvc.EditButtonItem = EditButton; + return dvc; + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + tableView.DeselectRow(path, false); + var newDvc = MakeViewController(); + PrepareDialogViewController(newDvc); + dvc.ActivateController(newDvc, dvc); + } + + public void Reload(ISection section, UITableViewRowAnimation animation) + { + if (section == null) + throw new ArgumentNullException("section"); + if (section.Parent == null || section.Parent != this) + throw new ArgumentException("Section is not attached to this root"); + + int idx = 0; + foreach (var sect in Sections) + { + if (sect == section) + { + TableView.ReloadSections(new NSIndexSet((uint)idx), animation); + return; + } + idx++; + } + } + + public void Reload(Element element, UITableViewRowAnimation animation) + { + if (element == null) + throw new ArgumentNullException("element"); + var section = element.Parent as ISection; + if (section == null) + throw new ArgumentException("Element is not attached to this root"); + var root = section.Parent as IRoot; + if (root == null) + throw new ArgumentException("Element is not attached to this root"); + var path = element.IndexPath; + if (path == null) + return; + TableView.ReloadRows(new NSIndexPath[] { path }, animation); + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + + if (Value is IView) + { + var binding = new BindingContext(Value, Caption); + Sections = binding.Root.Sections; + } + var radio = Group as RadioGroup; + var section = Sections.FirstOrDefault(); + if (radio != null && section != null && !section.IsMultiselect) + { + radio.Selected = Convert.ToInt32(Value); + ((RadioElement)section[radio.Selected]).Value = true; + } + + if (Group != null && DetailLabel != null) + { + DetailLabel.Text = ToString(); + } + } + + public override string ToString() + { + var view = Value as IView; + if (view != null) + { + return view.ToString(); + } + + int value = Convert.ToInt32(Value); + if (value != -1) + return Sections[0][value].ToString(); + + return string.Empty; + } + + protected int FromString(string value) + { + var element = Sections[0].Elements.SingleOrDefault((e)=>e.ToString() == value); + return Sections[0].Elements.IndexOf(element); + } + } +} diff --git a/Dialog/Elements/Section.cs b/Dialog/Elements/Section.cs new file mode 100644 index 0000000..49ef18d --- /dev/null +++ b/Dialog/Elements/Section.cs @@ -0,0 +1,427 @@ +// +// Section.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Drawing; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public interface ISection : IDisposable + { + List Elements { get; set; } + string Caption { get; set; } + int Order { get; set; } + Element Parent { get; set; } + bool IsMultiselect { get; set; } + + SizeF EntryAlignment { get; set; } + string Header { get; set; } + string Footer { get; set; } + UIView HeaderView { get; set; } + UIView FooterView { get; set; } + + void Add(Element element ); + int Add(IEnumerable elements); + void Add(UIView view); + void Add(IEnumerable views); + + void Insert(int idx, UITableViewRowAnimation anim, params Element[] newElements); + + int Insert(int idx, UITableViewRowAnimation anim, IEnumerable newElements); + void Insert(int index, params Element[] newElements); + + void Remove(Element e); + void Remove(int idx); + void RemoveRange(int start, int count); + void RemoveRange(int start, int count, UITableViewRowAnimation anim); + + IEnumerator GetEnumerator(); + + int Count { get; } + Element this[int idx] { get; } + + void Clear(); + } + + /// + /// Generic base version of Section + /// + public class Section : Element, ISection, IEnumerable + { + private object header, footer; + public List Elements { get; set; } + + // X corresponds to the alignment, Y to the height of the password + public SizeF EntryAlignment { get; set; } + public bool IsMultiselect { get; set; } + + /// + /// Constructs a Section without header or footers. + /// + public Section() : base(null) + { + Elements = new List(); + } + + /// + /// Constructs a Section with the specified header + /// + /// + /// The header to display + /// + public Section(string caption) : base(caption) + { + Elements = new List(); + } + + /// + /// Constructs a Section with a header and a footer + /// + /// + /// The caption to display (or null to not display a caption) + /// + /// + /// The footer to display. + /// + public Section(string caption, string footer) : base(caption) + { + Footer = footer; + Elements = new List(); + } + + public Section(UIView header) : base(null) + { + HeaderView = header; + Elements = new List(); + } + + public Section(UIView header, UIView footer) : base(null) + { + HeaderView = header; + FooterView = footer; + Elements = new List(); + } + + /// + /// The section header, as a string + /// + public string Header + { + get { return header as string; } + set { footer = value; } + } + + /// + /// The section footer, as a string. + /// + public string Footer + { + get { return footer as string; } + + set { footer = value; } + } + + /// + /// The section's header view. + /// + public UIView HeaderView + { + get { return header as UIView; } + set { header = value; } + } + + /// + /// The section's footer view. + /// + public UIView FooterView + { + get { return footer as UIView; } + set { footer = value; } + } + + /// + /// Adds a new child Element to the Section + /// + /// + /// An element to add to the section. + /// + public void Add(Element element) + { + if (element == null) + return; + + Elements.Add(element); + element.Parent = this; + + if (Parent != null) + InsertVisual(Elements.Count - 1, UITableViewRowAnimation.None, 1); + } + + /// + /// Add version that can be used with LINQ + /// + /// + /// An enumerable list that can be produced by something like: + /// from x in ... select (Element) new MyElement (...) + /// + public int Add(IEnumerable elements) + { + int count = 0; + foreach (var e in elements) + { + Add(e); + count++; + } + return count; + } + + /// + /// Use to add a UIView to a section, it makes the section opaque, to + /// get a transparent one, you must manually call UIViewElement + public void Add(UIView view) + { + if (view == null) + return; + Add(new UIViewElement(null, view, false)); + } + + /// + /// Adds the UIViews to the section. + /// fparent + /// + /// An enumarable list that can be produced by something like: + /// from x in ... select (UIView) new UIFoo (); + /// + public void Add(IEnumerable views) + { + foreach (var v in views) + Add(v); + } + + /// + /// Inserts a series of elements into the Section using the specified animation + /// + /// + /// The index where the elements are inserted + /// + /// + /// The animation to use + /// + /// + /// A series of elements. + /// + public void Insert(int idx, UITableViewRowAnimation anim, params Element[] newElements) + { + if (newElements == null) + return; + + int pos = idx; + foreach (var e in newElements) + { + Elements.Insert(pos++, e); + e.Parent = this; + } + var root = Parent as IRoot; + if (Parent != null && root.TableView != null) + { + if (anim == UITableViewRowAnimation.None) + root.TableView.ReloadData(); + else + InsertVisual(idx, anim, newElements.Length); + } + } + + public int Insert(int idx, UITableViewRowAnimation anim, IEnumerable newElements) + { + if (newElements == null) + return 0; + + int pos = idx; + int count = 0; + foreach (var e in newElements) + { + Elements.Insert(pos++, e); + e.Parent = this; + count++; + } + var root = Parent as IRoot; + if (root != null && root.TableView != null) + { + if (anim == UITableViewRowAnimation.None) + root.TableView.ReloadData(); + else + InsertVisual(idx, anim, pos - idx); + } + return count; + } + + void InsertVisual(int idx, UITableViewRowAnimation anim, int count) + { + var root = Parent as IRoot; + + if (root == null || root.TableView == null) + return; + + int sidx = root.IndexOf(this as ISection); + var paths = new NSIndexPath[count]; + for (int i = 0; i < count; i++) + paths[i] = NSIndexPath.FromRowSection(idx + i, sidx); + + root.TableView.InsertRows(paths, anim); + } + + public void Insert(int index, params Element[] newElements) + { + Insert(index, UITableViewRowAnimation.None, newElements); + } + + public void Remove(Element e) + { + if (e == null) + return; + for (int i = Elements.Count; i > 0;) + { + i--; + if (Elements[i] == e) + { + RemoveRange(i, 1); + return; + } + } + } + + public void Remove(int idx) + { + RemoveRange(idx, 1); + } + + /// + /// Removes a range of elements from the Section + /// + /// + /// Starting position + /// + /// + /// Number of elements to remove from the section + /// + public void RemoveRange(int start, int count) + { + RemoveRange(start, count, UITableViewRowAnimation.Fade); + } + + /// + /// Remove a range of elements from the section with the given animation + /// + /// + /// Starting position + /// + /// + /// Number of elements to remove form the section + /// + /// + /// The animation to use while removing the elements + /// + public void RemoveRange(int start, int count, UITableViewRowAnimation anim) + { + if (start < 0 || start >= Elements.Count) + return; + if (count == 0) + return; + + var root = Parent as IRoot; + + if (start + count > Elements.Count) + count = Elements.Count - start; + + Elements.RemoveRange(start, count); + + if (root == null || root.TableView == null) + return; + + int sidx = root.IndexOf(this as ISection); + var paths = new NSIndexPath[count]; + for (int i = 0; i < count; i++) + paths[i] = NSIndexPath.FromRowSection(start + i, sidx); + root.TableView.DeleteRows(paths, anim); + } + + /// + /// Enumerator to get all the elements in the Section. + /// + /// + /// A + /// + public IEnumerator GetEnumerator() + { + foreach (var e in Elements) + yield return e; + } + + public int Count + { + get { return Elements.Count; } + } + + public Element this[int idx] + { + get { return Elements[idx]; } + } + + public void Clear() + { + foreach (var e in Elements) + e.Dispose(); + Elements = new List(); + + var root = Parent as IRoot; + if (root != null && root.TableView != null) + root.TableView.ReloadData(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Parent = null; + Clear(); + Elements = null; + } + } + + public override void InitializeCell(UITableView tableView) + { + Cell.TextLabel.Text = "Section was used for Element"; + } + } +} + diff --git a/Dialog/Elements/StringElement.cs b/Dialog/Elements/StringElement.cs new file mode 100644 index 0000000..8ce032e --- /dev/null +++ b/Dialog/Elements/StringElement.cs @@ -0,0 +1,102 @@ +// +// StringElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.Foundation; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + + /// + /// The string element can be used to render some text in a cell + /// that can optionally respond to tap events. + /// + public class StringElement : Element, ITappable + { + public UITextAlignment Alignment = UITextAlignment.Left; + private UILabel Label { get; set; } + + public ICommand Command { get; set; } + public object CommandParameter { get; set; } + + public StringElement(string caption) : base(caption) + { + } + + public StringElement(string caption, string value) : base(caption) + { + Value = value; + } + + public override UITableViewElementCell NewCell() + { + return new UITableViewElementCell(Value == null ? UITableViewCellStyle.Default : UITableViewCellStyle.Value1, Id); + } + + public override void InitializeCell(UITableView tableView) + { + Cell.SelectionStyle = (Command != null) ? UITableViewCellSelectionStyle.Blue : UITableViewCellSelectionStyle.None; + + Cell.Accessory = UITableViewCellAccessory.None; + + TextLabel = Cell.TextLabel; + TextLabel.Text = Caption; + TextLabel.TextAlignment = Alignment; + + // The check is needed because the cell might have been recycled. + if (Cell.DetailTextLabel != null) + { + Label = Cell.DetailTextLabel; + Label.Text = Value == null ? "" : Value; + } + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath) + { + if (Command != null && Command.CanExecute(CommandParameter)) + Command.Execute(CommandParameter); + + tableView.DeselectRow(indexPath, true); + } + + public override bool Matches(string text) + { + return (Value != null ? Value.IndexOf(text, StringComparison.CurrentCultureIgnoreCase) != -1 : false) || base.Matches(text); + } + + protected override void OnValueChanged() + { + base.OnValueChanged(); + if (Label != null) + Label.Text = Value; + } + } +} + diff --git a/Dialog/Elements/StyledMultilineElement.cs b/Dialog/Elements/StyledMultilineElement.cs new file mode 100644 index 0000000..442f968 --- /dev/null +++ b/Dialog/Elements/StyledMultilineElement.cs @@ -0,0 +1,53 @@ +// +// StyledMultilineElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System.Drawing; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class StyledMultilineElement : StyledStringElement, IElementSizing + { + public StyledMultilineElement(string caption) : base(caption) + { + } + public StyledMultilineElement(string caption, string value) : base(caption, value) + { + } + + public virtual float GetHeight(UITableView tableView, NSIndexPath indexPath) + { + SizeF size = new SizeF(280, float.MaxValue); + using (var font = UIFont.FromName("Helvetica", 17f)) + return tableView.StringSize(Caption, font, size, LineBreakMode).Height; + } + } +} + diff --git a/Dialog/Elements/StyledStringElement.cs b/Dialog/Elements/StyledStringElement.cs new file mode 100644 index 0000000..4235141 --- /dev/null +++ b/Dialog/Elements/StyledStringElement.cs @@ -0,0 +1,76 @@ +// +// StyledStringElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + /// + /// A version of the StringElement that can be styled with a number of options. + /// + public class StyledStringElement : StringElement + { + public StyledStringElement(string caption) : base(caption) + { + } + public StyledStringElement(string caption, string value) : base(caption, value) + { + } + + public UIFont Font; + public UIColor TextColor; + public UIColor BackgroundColor; + public UILineBreakMode LineBreakMode = UILineBreakMode.WordWrap; + public int Lines = 1; + public UITableViewCellAccessory Accessory = UITableViewCellAccessory.None; + + public override UITableViewElementCell NewCell() + { + return new UITableViewElementCell(Value == null ? UITableViewCellStyle.Default : UITableViewCellStyle.Value1, Id); + } + public override void InitializeCell(UITableView tableView) + { + Cell.SelectionStyle = UITableViewCellSelectionStyle.Blue; + + var textLabel = Cell.TextLabel; + textLabel.Text = Caption; + textLabel.TextAlignment = Alignment; + textLabel.TextColor = TextColor == null ? UIColor.Black : TextColor; + textLabel.Font = Font == null ? UIFont.SystemFontOfSize(14) : Font; + textLabel.LineBreakMode = LineBreakMode; + textLabel.Lines = 0; + + // The check is needed because the cell might have been recycled. + if (Cell.DetailTextLabel != null) + Cell.DetailTextLabel.Text = Value == null ? "" : Value; + } + } +} + diff --git a/Dialog/Elements/TimeElement.cs b/Dialog/Elements/TimeElement.cs new file mode 100644 index 0000000..7581efa --- /dev/null +++ b/Dialog/Elements/TimeElement.cs @@ -0,0 +1,58 @@ +// +// TimeElement.cs: +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.UIKit; + + public class TimeElement : DateTimeElement + { + public TimeElement(string caption) : base(caption) + { + } + + public TimeElement(string caption, DateTime date) : base(caption, date) + { + } + + public override string FormatDate(DateTime dt) + { + return dt.ToLocalTime().ToShortTimeString(); + } + + public override UIDatePicker CreatePicker() + { + var picker = base.CreatePicker(); + picker.Mode = UIDatePickerMode.Time; + return picker; + } + } +} + diff --git a/Dialog/Elements/UIViewElement.cs b/Dialog/Elements/UIViewElement.cs new file mode 100644 index 0000000..3c65dac --- /dev/null +++ b/Dialog/Elements/UIViewElement.cs @@ -0,0 +1,111 @@ +// +// UIViewElement.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System.Drawing; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + /// + /// This element can be used to insert an arbitrary UIView + /// + /// + /// There is no cell reuse here as we have a 1:1 mapping + /// in this case from the UIViewElement to the cell that + /// holds our view. + /// + public class UIViewElement : Element, IElementSizing + { + public CellFlags Flags; + + public enum CellFlags + { + Transparent = 1, + DisableSelection = 2 + } + + /// + /// Constructor + /// + /// + /// The caption, only used for IRoot that might want to summarize results + /// + /// + /// The view to display + /// + /// + /// If this is set, then the view is responsible for painting the entire area, + /// otherwise the default cell paint code will be used. + /// + public UIViewElement(string caption, UIView view, bool transparent) : base(caption) + { + Value = view; + Flags = transparent ? CellFlags.Transparent : 0; + } + + public override void InitializeCell(UITableView tableView) + { + if ((Flags & CellFlags.Transparent) != 0) + { + Cell.BackgroundColor = UIColor.Clear; + + // + // This trick is necessary to keep the background clear, otherwise + // it gets painted as black + // + Cell.BackgroundView = new UIView(RectangleF.Empty) { BackgroundColor = UIColor.Clear }; + } + if ((Flags & CellFlags.DisableSelection) != 0) + Cell.SelectionStyle = UITableViewCellSelectionStyle.None; + + if (Value != null) + Cell.ContentView.AddSubview(Value); + } + + public virtual float GetHeight(UITableView tableView, NSIndexPath indexPath) + { + if (Value != null) + return Value.Bounds.Height; + + return 0; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + Value.Dispose(); + Value = null; + } + } + } +} + diff --git a/Dialog/Elements/ViewElement.cs b/Dialog/Elements/ViewElement.cs new file mode 100644 index 0000000..754db61 --- /dev/null +++ b/Dialog/Elements/ViewElement.cs @@ -0,0 +1,536 @@ +using System.Collections; +using System.Collections.Generic; +using MonoTouch.Foundation; +// +// ViewElement.cs +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + + public class ViewElement: StringElement, IRoot + { + public object DataContext{ get; set; } + public UITableViewCellStyle CellStyle { get; set; } + public List
Sections { get; set; } + public bool UnevenRows { get; set; } + public UITableView TableView { get; set; } + public Group Group { get; set; } + public Func createOnSelected; + + + public ViewElement(string caption) : base(caption) + { + CellStyle = UITableViewCellStyle.Value1; + Sections= new List
(); + } + + public override UITableViewElementCell NewCell() + { + var cell = new UITableViewElementCell(CellStyle, Id); + cell.SelectionStyle = UITableViewCellSelectionStyle.Blue; + + return cell; + } + + public override void InitializeCell (UITableView tableView) + { + base.InitializeCell(tableView); + + Cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; + Cell.TextLabel.Text = Caption; + Cell.DetailTextLabel.Text = ToString(); + } + + public void Add(Section section) + { + if (section == null) + return; + + Sections.Add(section); + section.Parent = this; + if (TableView == null) + return; + + TableView.InsertSections(MakeIndexSet(Sections.Count - 1, 1), UITableViewRowAnimation.None); + } + + public void Add(IEnumerable
sections) + { + foreach (var s in sections) + Add(s); + } + + public void Clear() + { + foreach (var s in Sections) + s.Dispose(); + Sections = new List
(); + if (TableView != null) + TableView.ReloadData(); + } + + public int IndexOf(Section target) + { + int idx = 0; + foreach (Section s in Sections) + { + if (s == target) + return idx; + idx++; + } + return -1; + } + + public int Count + { + get { return Sections.Count; } + } + + public void Prepare() + { + foreach (Section s in Sections) + { + foreach (Element e in s.Elements) + { + if (UnevenRows == false && e is IElementSizing) + UnevenRows = true; + } + } + } + public override string ToString() + { + var value = string.Empty; + var view = DataContext as IView; + + if (view != null) + value = view.ToString(); + + return value; + } + + private NSIndexSet MakeIndexSet(int start, int count) + { + NSRange range; + range.Location = start; + range.Length = count; + return NSIndexSet.FromNSRange(range); + } + + protected virtual void PrepareDialogViewController(UIViewController dvc) + { + } + + protected virtual UIViewController MakeViewController() + { + if (createOnSelected != null) + return createOnSelected(this); + + return new DialogViewController(this, true) { Autorotate = true }; + } + + public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) + { + tableView.DeselectRow(path, false); + var newDvc = MakeViewController(); + PrepareDialogViewController(newDvc); + dvc.ActivateController(newDvc, dvc); + } + } + +// public class BaseRootElement : Element, IEnumerable +// { +// int summarySection, summaryElement; +// public bool UnevenRows; +// public Func createOnSelected; +// internal UITableView TableView; +// private UILabel DetailLabel; +// +// +// public BaseRootElement(string caption) : base(caption) +// { +// summarySection = -1; +// Sections = new List
(); +// } +// +// /// +// /// Initializes a RootSection with a caption and a callback that will +// /// create the nested UIViewController that is activated when the user +// /// taps on the element. +// /// +// /// +// /// The caption to render. +// /// +// public BaseRootElement(string caption, Func createOnSelected) : base(caption) +// { +// summarySection = -1; +// this.createOnSelected = createOnSelected; +// Sections = new List
(); +// } +// +// /// +// /// Initializes a RootElement with a caption with a summary fetched from the specified section and leement +// /// +// /// +// /// The caption to render cref="System.String"/> +// /// +// /// +// /// The section that contains the element with the summary. +// /// +// /// +// /// The element index inside the section that contains the summary for this RootSection. +// /// +// public BaseRootElement(string caption, int section, int element) : base(caption) +// { +// summarySection = section; +// summaryElement = element; +// } +// +// internal List
Sections = new List
(); +// +// public int Count +// { +// get { return Sections.Count; } +// } +// +// public Section this[int idx] +// { +// get { return Sections[idx]; } +// } +// +// internal int IndexOf(Section target) +// { +// int idx = 0; +// foreach (Section s in Sections) +// { +// if (s == target) +// return idx; +// idx++; +// } +// return -1; +// } +// +// internal virtual void Prepare() +// { +// int current = 0; +// foreach (Section s in Sections) +// { +// foreach (Element e in s.Elements) +// { +// if (UnevenRows == false && e is IElementSizing) +// UnevenRows = true; +// } +// } +// } +// +// /// +// /// Adds a new section to this RootElement +// /// +// /// +// /// The section to add, if the root is visible, the section is inserted with no animation +// /// +// public void Add(Section section) +// { +// if (section == null) +// return; +// +// Sections.Add(section); +// section.Parent = this; +// if (TableView == null) +// return; +// +// TableView.InsertSections(MakeIndexSet(Sections.Count - 1, 1), UITableViewRowAnimation.None); +// } +// +// // +// // This makes things LINQ friendly; You can now create RootElements +// // with an embedded LINQ expression, like this: +// // new RootElement ("Title") { +// // from x in names +// // select new Section (x) { new StringElement ("Sample") } +// // +// public void Add(IEnumerable
sections) +// { +// foreach (var s in sections) +// Add(s); +// } +// +// private NSIndexSet MakeIndexSet(int start, int count) +// { +// NSRange range; +// range.Location = start; +// range.Length = count; +// return NSIndexSet.FromNSRange(range); +// } +// +// /// +// /// Inserts a new section into the RootElement +// /// +// /// +// /// The index where the section is added +// /// +// /// +// /// The type. +// /// +// /// +// /// A list of sections to insert +// /// +// /// +// /// This inserts the specified list of sections (a params argument) into the +// /// root using the specified animation. +// /// +// public void Insert(int idx, UITableViewRowAnimation anim, params Section[] newSections) +// { +// if (idx < 0 || idx > Sections.Count) +// return; +// if (newSections == null) +// return; +// +// if (TableView != null) +// TableView.BeginUpdates(); +// +// int pos = idx; +// foreach (var s in newSections) +// { +// s.Parent = this; +// Sections.Insert(pos++, s); +// } +// +// if (TableView == null) +// return; +// +// TableView.InsertSections(MakeIndexSet(idx, newSections.Length), anim); +// TableView.EndUpdates(); +// } +// +// /// +// /// Inserts a new section into the RootElement +// /// +// /// +// /// The index where the section is added +// /// +// /// +// /// A list of sections to insert +// /// +// /// +// /// This inserts the specified list of sections (a params argument) into the +// /// root using the Fade animation. +// /// +// public void Insert(int idx, Section section) +// { +// Insert(idx, UITableViewRowAnimation.None, section); +// } +// +// /// +// /// Removes a section at a specified location +// /// +// public void RemoveAt(int idx) +// { +// RemoveAt(idx, UITableViewRowAnimation.Fade); +// } +// +// /// +// /// Removes a section at a specified location using the specified animation +// /// +// /// +// /// A +// /// +// /// +// /// A +// /// +// public void RemoveAt(int idx, UITableViewRowAnimation anim) +// { +// if (idx < 0 || idx >= Sections.Count) +// return; +// +// Sections.RemoveAt(idx); +// +// if (TableView == null) +// return; +// +// TableView.DeleteSections(NSIndexSet.FromIndex(idx), anim); +// } +// +// public void Remove(Section s) +// { +// if (s == null) +// return; +// int idx = Sections.IndexOf(s); +// if (idx == -1) +// return; +// RemoveAt(idx, UITableViewRowAnimation.Fade); +// } +// +// public void Remove(Section s, UITableViewRowAnimation anim) +// { +// if (s == null) +// return; +// int idx = Sections.IndexOf(s); +// if (idx == -1) +// return; +// RemoveAt(idx, anim); +// } +// +// public void Clear() +// { +// foreach (var s in Sections) +// s.Dispose(); +// Sections = new List
(); +// if (TableView != null) +// TableView.ReloadData(); +// } +// +// protected override void Dispose(bool disposing) +// { +// if (disposing) +// { +// if (Sections == null) +// return; +// +// TableView = null; +// Clear(); +// Sections = null; +// } +// } +// +// /// +// /// Enumerator that returns all the sections in the RootElement. +// /// +// /// +// /// A +// /// +// public IEnumerator GetEnumerator() +// { +// foreach (var s in Sections) +// yield return s; +// } +// +// public override UITableViewElementCell NewCell () +// { +// var style = summarySection == -1 ? UITableViewCellStyle.Default : UITableViewCellStyle.Value1; +// +// var cell = new UITableViewElementCell(style, Id); +// cell.SelectionStyle = UITableViewCellSelectionStyle.Blue; +// +// return cell; +// } +// +// public override void InitializeCell(UITableView tableView) +// { +// Cell.TextLabel.Text = Caption; +// if (summarySection != -1 && summarySection < Sections.Count) +// { +// var s = Sections[summarySection]; +// if (summaryElement < s.Elements.Count) +// Cell.DetailTextLabel.Text = s.Elements[summaryElement].ToString(); +// } +// +// Cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; +// +// DetailLabel = Cell.DetailTextLabel; +// +//// if (DetailLabel != null) +//// Value = FromString(DetailLabel.Text); +// } +// +// /// +// /// This method does nothing by default, but gives a chance to subclasses to +// /// customize the UIViewController before it is presented +// /// +// protected virtual void PrepareDialogViewController(UIViewController dvc) +// { +// } +// +// /// +// /// Creates the UIViewController that will be pushed by this RootElement +// /// +// protected virtual UIViewController MakeViewController() +// { +// if (createOnSelected != null) +// return createOnSelected(this); +// +// return new DialogViewController(this as BaseRootElement, true) { Autorotate = true }; +// } +// +// public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath path) +// { +// tableView.DeselectRow(path, false); +// var newDvc = MakeViewController(); +// PrepareDialogViewController(newDvc); +// dvc.ActivateController(newDvc, dvc); +// } +// +// public void Reload(Section section, UITableViewRowAnimation animation) +// { +// if (section == null) +// throw new ArgumentNullException("section"); +// if (section.Parent == null || section.Parent != this) +// throw new ArgumentException("Section is not attached to this root"); +// +// int idx = 0; +// foreach (var sect in Sections) +// { +// if (sect == section) +// { +// TableView.ReloadSections(new NSIndexSet((uint)idx), animation); +// return; +// } +// idx++; +// } +// } +// +// public void Reload(Element element, UITableViewRowAnimation animation) +// { +// if (element == null) +// throw new ArgumentNullException("element"); +// var section = element.Parent as Section; +// if (section == null) +// throw new ArgumentException("Element is not attached to this root"); +// var root = section.Parent as RootElement; +// if (root == null) +// throw new ArgumentException("Element is not attached to this root"); +// var path = element.IndexPath; +// if (path == null) +// return; +// TableView.ReloadRows(new NSIndexPath[] { path }, animation); +// } +// +// protected override void OnValueChanged() +// { +// base.OnValueChanged(); +// if (DetailLabel != null) +// DetailLabel.Text = ToString(); +// } +// +// } +} + + diff --git a/Dialog/Group.cs b/Dialog/Group.cs new file mode 100644 index 0000000..3fdb8a8 --- /dev/null +++ b/Dialog/Group.cs @@ -0,0 +1,47 @@ +// +// Group.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + /// + /// Used by root elements to fetch information when they need to + /// render a summary (Checkbox count or selected radio group). + /// + public class Group + { + public string Key; + public Group(string key) + { + Key = key; + } + } +} + diff --git a/Dialog/IElementSizing.cs b/Dialog/IElementSizing.cs new file mode 100644 index 0000000..3dc41dd --- /dev/null +++ b/Dialog/IElementSizing.cs @@ -0,0 +1,40 @@ +// +// IElementSizing.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public interface IElementSizing + { + float GetHeight(UITableView tableView, NSIndexPath indexPath); + } +} + diff --git a/Dialog/IRoot.cs b/Dialog/IRoot.cs new file mode 100644 index 0000000..cc9c645 --- /dev/null +++ b/Dialog/IRoot.cs @@ -0,0 +1,59 @@ +// +// IRoot.cs +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Collections.Generic; + using MonoTouch.UIKit; + using MonoTouch.Foundation; + + public interface IRoot: IDisposable + { + UITableViewCellStyle CellStyle { get; set; } + Type ElementType {get; set;} + + List ToolbarButtons { get; set; } + + List Sections { get; set; } + bool UnevenRows { get; set;} + string Caption { get; set; } + Group Group { get; set; } + UITableView TableView { get; set; } + int Count { get; } + int ItemIndex { get; set; } + NSIndexPath PathForRadio(int idx); + + void Prepare(); + void Add(IEnumerable sections); + void Add(ISection section); + void Clear(); + int IndexOf(ISection section); + } +} \ No newline at end of file diff --git a/Dialog/ITappable.cs b/Dialog/ITappable.cs new file mode 100644 index 0000000..17d887f --- /dev/null +++ b/Dialog/ITappable.cs @@ -0,0 +1,39 @@ +// +// ITapable.cs: +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + + public interface ITappable + { + ICommand Command { get; set; } + object CommandParameter { get; set;} + } +} \ No newline at end of file diff --git a/Dialog/RadioGroup.cs b/Dialog/RadioGroup.cs new file mode 100644 index 0000000..0516caf --- /dev/null +++ b/Dialog/RadioGroup.cs @@ -0,0 +1,53 @@ +// +// RadioGroup.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + /// + /// Captures the information about mutually exclusive elements in a IRoot + /// + public class RadioGroup : Group + { + public int Selected; + public Type EnumType; + + public RadioGroup(string key, int selected) : base(key) + { + Selected = selected; + } + + public RadioGroup(int selected) : base(null) + { + Selected = selected; + } + } +} + diff --git a/Dialog/Reflection API/AlignmentAttribute.cs b/Dialog/Reflection API/AlignmentAttribute.cs new file mode 100644 index 0000000..1858506 --- /dev/null +++ b/Dialog/Reflection API/AlignmentAttribute.cs @@ -0,0 +1,44 @@ +// +// AlignmentAttribute.cs: +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.UIKit; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method, Inherited = false)] + public class AlignmentAttribute : Attribute + { + public AlignmentAttribute(UITextAlignment alignment) + { + Alignment = alignment; + } + public UITextAlignment Alignment; + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/BindAttribute.cs b/Dialog/Reflection API/BindAttribute.cs new file mode 100644 index 0000000..2fed7c5 --- /dev/null +++ b/Dialog/Reflection API/BindAttribute.cs @@ -0,0 +1,108 @@ +// +// BindAttribute.cs: Bind Attribute for creating a data Binding to Elements +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Globalization; + using MonoTouch.MVVM; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = true)] + public class BindAttribute : Attribute + { + internal Binding Binding; + + public BindAttribute() + { + Binding = new Binding(); + } + + public BindAttribute(string targetPath) + { + Binding = new Binding(null, targetPath); + } + + public BindAttribute(string sourcePath, string targetPath) + { + Binding = new Binding(sourcePath, targetPath); + } + + public string TargetPath + { + get { return Binding.TargetPath; } + set { Binding.TargetPath = value; } + } + + public string SourcePath + { + get { return Binding.SourcePath; } + set { Binding.SourcePath = value; } + } + + public BindingMode BindingMode + { + get { return Binding.Mode; } + set { Binding.Mode = value; } + } + + public object TargetNullValue + { + get { return Binding.TargetNullValue; } + set { Binding.TargetNullValue = value; } + } + + public object FallbackValue + { + get { return Binding.FallbackValue; } + set { Binding.FallbackValue = value; } + } + + public Type ValueConverterType + { + get { return null; } + set { Binding.Converter = Activator.CreateInstance(value) as IValueConverter; } + } + + public object ConverterParameter + { + get { return Binding.ConverterParameter; } + set { Binding.ConverterParameter = value; } + } + public CultureInfo ConverterCulture + { + get { return Binding.ConverterCulture; } + set { Binding.ConverterCulture = value; } + } + + internal IValueConverter Converter + { + get { return Binding.Converter; } + } + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/BindingContext.cs b/Dialog/Reflection API/BindingContext.cs new file mode 100644 index 0000000..b7b03dc --- /dev/null +++ b/Dialog/Reflection API/BindingContext.cs @@ -0,0 +1,635 @@ +// +// BindingContext.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + using MonoTouch.CoreLocation; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + + + public class BindingContext : DisposableObject + { + private Dictionary> _ElementPropertyMap; + + public IRoot Root { get; set; } + + public BindingContext(object dataContext, string title) + { + if (dataContext == null) + throw new ArgumentNullException("dataContext"); + + var view = dataContext as IView; + if (view != null && view.Root != null && view.Root.Count > 0) + Root = view.Root; + else + { + BuildElementPropertyMap(); + Root = new RootElement(title); + Populate(dataContext, Root); + } + } + + private void Populate(object dataContext, IRoot root) + { + root.Add(CreateSectionList(dataContext, root)); + } + + private List CreateSectionList(object dataContext, IRoot root) + { + ISection lastSection = new Section() { Order = -1, Parent = root as Element }; + + var sectionList = new List() { lastSection }; + Element newElement = null; + + var members = GetMembers(dataContext); + + foreach (var member in members) + { + var skipAttribute = member.GetCustomAttribute(true); + if(skipAttribute != null) continue; + + var inline = member.GetCustomAttribute() != null; + var isList = member.GetCustomAttribute() != null; + var sectionAttribute = member.GetCustomAttribute(); + var toolbarButtonAttribute = member.GetCustomAttribute(); + var editButtonAttribute = member.GetCustomAttribute(); + + if (toolbarButtonAttribute != null) + { + var button = new UIBarButtonItem(toolbarButtonAttribute.ButtonType, delegate { (member as MethodInfo).Invoke(dataContext, new object[] {}); }); + if(root.ToolbarButtons == null) + root.ToolbarButtons = new List(); + + root.ToolbarButtons.Add(button); + } + + if (editButtonAttribute != null) + { + var button = new UIBarButtonItem(toolbarButtonAttribute.ButtonType, delegate { (member as MethodInfo).Invoke(dataContext, new object[] {}); }); + //root.EditButton = button; + } + + if (sectionAttribute != null) + { + lastSection = new Section(sectionAttribute.Caption, sectionAttribute.Footer) { Order = sectionAttribute.Order }; + lastSection.Parent = root as Element; + sectionList.Add(lastSection); + } + + newElement = GetElementForMember(dataContext, member); + + if ((inline || isList) && newElement is IRoot) + { + foreach(var element in ((IRoot)newElement).Sections[0].Elements) + lastSection.Add(element); + + root.Group = ((IRoot)newElement).Group; + } + else + { + lastSection.Add(newElement); + } + } + + foreach (var section in sectionList) + { + var orderedList = section.Elements.OrderBy(e=>e.Order).ToList(); + section.Elements = orderedList; + } + + var orderedSections = sectionList.Where(s=>s.Elements.Count > 0).OrderBy(section=>section.Order).ToList(); + return orderedSections; + } + + private Element GetElementForMember(object dataContext, MemberInfo member) + { + string caption = null; + Element element = null; + //MemberInfo last_radio_index = null; + var bindings = new List(); + + var captionAttribute = member.GetCustomAttribute(); + var orderAttribute = member.GetCustomAttribute(); + var bindAttributes = member.GetCustomAttributes(typeof(BindAttribute), false); + var popOnSelectionAttribute = member.GetCustomAttribute(); + + // var memberDataContext = GetDataContextForMember(dataContext, ref member); + var memberDataContext = dataContext; + + if(captionAttribute != null) + caption = captionAttribute.Caption; + else + caption = MakeCaption(member.Name); + + Type memberType = GetTypeForMember(member); + + if (!(member is MethodInfo)) + { + foreach (BindAttribute bindAttribute in bindAttributes) + { + bindings.Add(bindAttribute.Binding); + } + + var valueBinding = bindings.Where((b)=>b.TargetPath == "Value").FirstOrDefault() != null; + + if (!valueBinding) + { + bindings.Add(new Binding(member.Name, null)); + } + + foreach(var binding in bindings) + { + if (string.IsNullOrEmpty(binding.SourcePath)) + { + binding.SourcePath = member.Name; + } + // else + { + var sourceDataContext = memberDataContext; + var sourceProperty = sourceDataContext.GetType().GetNestedProperty(ref sourceDataContext, binding.SourcePath, true); + if (sourceProperty == null) + { + sourceDataContext = dataContext; + sourceProperty = sourceDataContext.GetType().GetNestedProperty(ref sourceDataContext, binding.SourcePath, true); + } + + binding.Source = sourceDataContext; + } + } + } + + if(_ElementPropertyMap.ContainsKey(memberType)) + element = _ElementPropertyMap[memberType](member, caption, dataContext); + else if (memberType.IsEnum) + { + SetDefaultConverter(member, "Value", new EnumConverter() { PropertyType = memberType }, bindings); + + var csection = new Section(); + var currentValue = GetValue(member, memberDataContext); + int index = 0; + int selected = 0; + + var pop = popOnSelectionAttribute != null; + + foreach(Enum value in Enum.GetValues(memberType)) + { + if (currentValue == value) + selected = index; + csection.Add(new RadioElement(value.GetDescription(), pop) { Index = index, Value = false}); + index++; + } + + element = new RootElement(caption, new RadioGroup(memberType.FullName, selected)) { csection }; + element.Caption = caption; + ((IRoot)element).Group = new RadioGroup(memberType.FullName, selected) { EnumType = memberType }; + ((IRoot)element).CellStyle = UITableViewCellStyle.Value1; + } + else if (typeof(EnumCollection).IsAssignableFrom(memberType)) + { + SetDefaultConverter(member, "Value", new EnumItemsConverter(), bindings); + + var csection = new Section() { IsMultiselect = true }; + var collection = GetValue(member, memberDataContext); + if (collection == null) + { + var collectionType = typeof(EnumCollection<>); + var enumType = memberType.GetGenericArguments()[0]; + Type[] generic = { enumType }; + + collection = Activator.CreateInstance(collectionType.MakeGenericType(generic)); + (member as PropertyInfo).SetValue(memberDataContext, collection, new object[] {}); + } + + var index = 0; + var items = (EnumCollection)collection; + foreach (var item in items.AllValues) + { + csection.Add(new CheckboxElement(item.Description, item.IsChecked, item.GroupName) { Index = index }); + index++; + } + + element = new RootElement(caption) { csection }; + ((IRoot)element).CellStyle = GetCellStyle(member, UITableViewCellStyle.Value1); + } + else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(memberType)) + { + SetDefaultConverter(member, "Value", new EnumerableConverter(), bindings); + var listBox = new ListBoxElement(); + int index = 0; + + Type viewType = memberType.GetGenericArguments()[0]; + + var rootAttribute = member.GetCustomAttribute(); + + var items = (IEnumerable)GetValue(member, memberDataContext); + foreach (var e in items) + { + Element newElement = null; + + newElement = CreateGenericRoot(viewType, null, e); + if (rootAttribute != null) + ((IRoot)newElement).ElementType = rootAttribute.DataTemplateType; + + //Populate(e, (IRoot)newElement); + + listBox.Add(newElement); + index++; + } + + element = CreateGenericRoot(memberType, listBox, null); + element.Caption = caption; + ((IRoot)element).CellStyle = GetCellStyle(member, UITableViewCellStyle.Default); + } + else + { + var nested = GetValue(member, memberDataContext); + if (nested != null) + { + // if(nested is IView) + // SetDefaultConverter(member, "Value", new ViewConverter(), bindings); + + var newRoot = CreateGenericRoot(memberType, null, nested); + newRoot.Caption = caption; + ((IRoot)newRoot).CellStyle = GetCellStyle(member, UITableViewCellStyle.Default); + + // Populate(nested, (IRoot)newRoot); + element = newRoot; + } + } + + if (orderAttribute != null) + element.Order = orderAttribute.Order; + + var bindable = element as IBindable; + if (bindable != null && bindings.Count != 0) //&& !(bindable is IRoot) + { + foreach (Binding binding in bindings) + { + if (binding.TargetPath == null) + { + binding.TargetPath = "Value"; + } + + BindingOperations.SetBinding(bindable, binding.TargetPath, binding); + } + } + + return element; + } + + private static object GetValue(MemberInfo mi, object o) + { + var fi = mi as FieldInfo; + if (fi != null) + return fi.GetValue(o); + var pi = mi as PropertyInfo; + if (pi != null) + { + var getMethod = pi.GetGetMethod(); + var value = getMethod.Invoke(o, new object[] {}); + return value; + } + + return null; + } + + private object GetDataContextForMember(object dataContext, ref MemberInfo member) + { + var explicitDataContext = dataContext; + + var view = dataContext as IView; + if (view != null && view.DataContext != null) + { + explicitDataContext = view.DataContext; + } + + var explicitMember = explicitDataContext.GetType().GetProperty(member.Name); + if (explicitMember != null) + { + member = explicitMember; + return explicitDataContext; + } + else + { + return dataContext; + } + } + + private static void SetValue(MemberInfo mi, object o, object val) + { + var fi = mi as FieldInfo; + if (fi != null) + { + fi.SetValue(o, val); + return; + } + var pi = mi as PropertyInfo; + var setMethod = pi.GetSetMethod(); + setMethod.Invoke(o, new object[] { val }); + } + + private static string MakeCaption(string name) + { + var sb = new StringBuilder(name.Length); + bool nextUp = true; + + foreach (char c in name) + { + if (nextUp) + { + sb.Append(Char.ToUpper(c)); + nextUp = false; + + } else + { + if (c == '_') + { + sb.Append(' '); + continue; + } + if (Char.IsUpper(c)) + sb.Append(' '); + sb.Append(c); + } + } + return sb.ToString(); + } + + // Returns the type for fields and properties and null for everything else + private static Type GetTypeForMember(MemberInfo mi) + { + if (mi is FieldInfo) + return ((FieldInfo)mi).FieldType; + + if (mi is PropertyInfo) + return ((PropertyInfo)mi).PropertyType; + + if (mi is MethodInfo) + return typeof(MethodInfo); + + return null; + } + + private void BuildElementPropertyMap() + { + _ElementPropertyMap = new Dictionary>(); + _ElementPropertyMap.Add(typeof(MethodInfo), (member, caption, dataContext)=> + { + Element element = null; + + var buttonAttribute = member.GetCustomAttribute(); + if (buttonAttribute != null) + { + element = new ButtonElement(caption) + { + BackgroundColor = buttonAttribute.BackgroundColor, + TextColor = buttonAttribute.TextColor, + Command = GetCommandForMember(dataContext, member) + }; + } + + return element; + }); + + _ElementPropertyMap.Add(typeof(CLLocationCoordinate2D), (member, caption, dataContext)=> + { + Element element = null; + + var mapAttribute = member.GetCustomAttribute(); + var location = (CLLocationCoordinate2D)GetValue(member, dataContext); + if (mapAttribute != null) + element = new MapElement(mapAttribute.Caption, mapAttribute.Value, location); + + return element; + }); + + _ElementPropertyMap.Add(typeof(string), (member, caption, dataContext)=> + { + Element element = null; + + var passwordAttribute = member.GetCustomAttribute(); + var entryAttribute = member.GetCustomAttribute(); + var multilineAttribute = member.GetCustomAttribute(); + var htmlAttribute = member.GetCustomAttribute(); + var alignmentAttribute = member.GetCustomAttribute(); + + if (passwordAttribute != null) + element = new EntryElement(caption, passwordAttribute.Placeholder, true); + else if (entryAttribute != null) + element = new EntryElement(caption, entryAttribute.Placeholder) { KeyboardType = entryAttribute.KeyboardType }; + else if (multilineAttribute != null) + element = new MultilineElement(caption); + else if (htmlAttribute != null) + element = new HtmlElement(caption); + else + { + var selement = new StringElement(caption, (string)GetValue(member, dataContext)); + + if (alignmentAttribute != null) + selement.Alignment = alignmentAttribute.Alignment; + + element = selement; + } + + var tappable = element as ITappable; + if (tappable != null) + ((ITappable)element).Command = GetCommandForMember(dataContext, member); + + return element; + }); + + _ElementPropertyMap.Add(typeof(float), (member, caption, dataContext)=> + { + Element element = null; + var rangeAttribute = member.GetCustomAttribute(); + if (rangeAttribute != null) + { + var floatElement = new FloatElement() { }; + floatElement.Caption = caption; + element = floatElement; + + floatElement.MinValue = rangeAttribute.Low; + floatElement.MaxValue = rangeAttribute.High; + floatElement.ShowCaption = rangeAttribute.ShowCaption; + } + else + { + var entryAttribute = member.GetCustomAttribute(); + var placeholder = ""; + var keyboardType = UIKeyboardType.NumberPad; + + if(entryAttribute != null) + { + placeholder = entryAttribute.Placeholder; + if(entryAttribute.KeyboardType != UIKeyboardType.Default) + keyboardType = entryAttribute.KeyboardType; + } + + element = new EntryElement(caption, placeholder, "") { KeyboardType = keyboardType }; + } + + return element; + }); + + _ElementPropertyMap.Add(typeof(bool), (member, caption, dataContext)=> + { + Element element = null; + + var checkboxAttribute = member.GetCustomAttribute(); + if (checkboxAttribute != null) + element = new CheckboxElement(caption) { }; + else + element = new BooleanElement(caption) { }; + + return element; + }); + + _ElementPropertyMap.Add(typeof(DateTime), (member, caption, dataContext)=> + { + Element element = null; + + var dateAttribute = member.GetCustomAttribute(); + var timeAttribute = member.GetCustomAttribute(); + + if(dateAttribute != null) + element = new DateElement(caption); + else if (timeAttribute != null) + element = new TimeElement(caption); + else + element = new DateTimeElement(caption); + + return element; + }); + + _ElementPropertyMap.Add(typeof(UIImage),(member, caption, dataContext)=> + { + return new ImageElement(); + }); + + _ElementPropertyMap.Add(typeof(int), (member, caption, dataContext)=> + { + return new StringElement(caption) { Value = GetValue(member, dataContext).ToString() }; + }); + } + + private UITableViewCellStyle GetCellStyle(MemberInfo member, UITableViewCellStyle defaultCellStyle) + { + var rootAttribute = member.GetCustomAttribute(); + var cellStyle = defaultCellStyle; + if (rootAttribute != null) + cellStyle = rootAttribute.CellStyle; + + return cellStyle; + } + + private Element CreateGenericRoot(Type type, Section section, object value) + { + var rootType = typeof(RootElement<>); + Type[] generic = { type }; + + var genericType = rootType.MakeGenericType(generic); + + var root = Activator.CreateInstance(genericType); + root.GetType().GetProperty("Value").SetValue(root, value, null); + ((IRoot)root).Add(section); + + var element = root as Element; + return element; + } + + private void SetDefaultConverter(MemberInfo member, string targetPath, IValueConverter converter, List bindings) + { + foreach (var binding in bindings) + { + if (binding.SourcePath == member.Name && binding.Converter == null) + { + binding.TargetPath = targetPath; + binding.Converter = converter; + } + } + } + private MemberInfo[] GetMembers(object dataContext) + { + return dataContext.GetType().GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance).Where(m => + { + var methodInfo = m as MethodBase; + //Bug 662867: var skip = m.GetCustomAttribute(true) != null; + var skip = m.Name == "ToString"; + return (methodInfo == null || !methodInfo.IsConstructor && !methodInfo.IsSpecialName) && m.MemberType != MemberTypes.Field && !skip; + }).ToArray(); + } + + private ICommand GetCommandForMember(object dataContext, MemberInfo member) + { + var buttonAttribute = member.GetCustomAttribute(); + if (buttonAttribute != null) + { + var context = dataContext; + var methodInfo = member as MethodInfo; + var methodName = buttonAttribute.MethodName; + + if (methodInfo == null) + { + if (string.IsNullOrEmpty(methodName)) + methodName = member.Name; + + methodInfo = context.GetType().GetMethod(methodName); + if (methodInfo == null) + { + var memberInfo = methodInfo as MemberInfo; + //context = GetDataContextForMember(dataContext, ref memberInfo); + context = dataContext; + methodInfo = memberInfo as MethodInfo; + } + } + + if (methodInfo == null) + throw new Exception(string.Format("Method not found : {0}", methodName)); + + return new ReflectiveCommand(context, methodInfo, null); + } + + return null; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + } + } +} diff --git a/Dialog/Reflection API/ButtonAttribute.cs b/Dialog/Reflection API/ButtonAttribute.cs new file mode 100644 index 0000000..4adf66d --- /dev/null +++ b/Dialog/Reflection API/ButtonAttribute.cs @@ -0,0 +1,58 @@ +// +// ButtonAttribute.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corportation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.UIKit; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method, Inherited = false)] + public class ButtonAttribute : Attribute + { + public ButtonAttribute(Color backgroundColor, Color textColor) + { + BackgroundColor = backgroundColor.ToUIColor(); + TextColor = textColor.ToUIColor(); + } + + public ButtonAttribute() + { + } + + public ButtonAttribute(string methodName) + { + MethodName = methodName; + } + + internal string MethodName; + public UIColor BackgroundColor; + public UIColor TextColor; + } +} + diff --git a/Dialog/Reflection API/CaptionAttribute.cs b/Dialog/Reflection API/CaptionAttribute.cs new file mode 100644 index 0000000..0a89994 --- /dev/null +++ b/Dialog/Reflection API/CaptionAttribute.cs @@ -0,0 +1,43 @@ +// +// CaptionAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method, Inherited = false)] + public class CaptionAttribute : Attribute + { + public CaptionAttribute(string caption) + { + Caption = caption; + } + public string Caption; + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/CheckboxAttribute.cs b/Dialog/Reflection API/CheckboxAttribute.cs new file mode 100644 index 0000000..4ef0eda --- /dev/null +++ b/Dialog/Reflection API/CheckboxAttribute.cs @@ -0,0 +1,38 @@ +// +// CheckboxAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + public class CheckboxAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/Color.cs b/Dialog/Reflection API/Color.cs new file mode 100644 index 0000000..1436ee0 --- /dev/null +++ b/Dialog/Reflection API/Color.cs @@ -0,0 +1,70 @@ +// +// Color.cs: Color Converter +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corportation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Linq; + using MonoTouch.UIKit; + + public enum Color + { + Clear, + Black , + DarkGray, + LightGray, + White, + Gray, + Red, + Green, + Blue, + Cyan, + Yellow, + Magenta, + Orange, + Purple, + Brown, + LightTextColor, + DarkTextColor, + GroupTableViewBackgroundColor, + ViewFlipsideBackgroundColor, + ScrollViewTexturedBackgroundColor, + } + + public static class ColorConverter + { + public static UIColor ToUIColor(this Color sourceColor) + { + UIColor uiColor = new UIColor(); + Type UIColorType = typeof(UIColor); + var color = UIColorType.GetProperties().SingleOrDefault((p)=>p.Name == sourceColor.ToString()); + return color.GetValue(uiColor, null) as UIColor; + } + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/DateAttribute.cs b/Dialog/Reflection API/DateAttribute.cs new file mode 100644 index 0000000..2e831ad --- /dev/null +++ b/Dialog/Reflection API/DateAttribute.cs @@ -0,0 +1,38 @@ +// +// DateAtribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + public class DateAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/EditButtonAttribute.cs b/Dialog/Reflection API/EditButtonAttribute.cs new file mode 100644 index 0000000..ee36078 --- /dev/null +++ b/Dialog/Reflection API/EditButtonAttribute.cs @@ -0,0 +1,17 @@ +namespace MonoTouch.MVVM +{ + using System; + using MonoTouch.UIKit; + + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public class EditButtonAttribute : Attribute + { + public EditButtonAttribute(UIBarButtonSystemItem buttonType) + { + ButtonType = buttonType; + } + + public UIBarButtonSystemItem ButtonType { get; set; } + } +} + diff --git a/Dialog/Reflection API/EntryAttribute.cs b/Dialog/Reflection API/EntryAttribute.cs new file mode 100644 index 0000000..6501122 --- /dev/null +++ b/Dialog/Reflection API/EntryAttribute.cs @@ -0,0 +1,50 @@ +// +// EntryAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.UIKit; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + public class EntryAttribute : Attribute + { + public EntryAttribute() : this(null) + { + } + + public EntryAttribute(string placeholder) + { + Placeholder = placeholder; + } + + public string Placeholder; + public UIKeyboardType KeyboardType; + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/HtmlAttribute.cs b/Dialog/Reflection API/HtmlAttribute.cs new file mode 100644 index 0000000..e81ea30 --- /dev/null +++ b/Dialog/Reflection API/HtmlAttribute.cs @@ -0,0 +1,38 @@ +// +// HtmlAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + public class HtmlAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/InlineAttribute.cs b/Dialog/Reflection API/InlineAttribute.cs new file mode 100644 index 0000000..0715a6e --- /dev/null +++ b/Dialog/Reflection API/InlineAttribute.cs @@ -0,0 +1,39 @@ +// +// InlineAttribute.cs +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + public class InlineAttribute : Attribute + { + } +} + diff --git a/Dialog/Reflection API/ListAttribute.cs b/Dialog/Reflection API/ListAttribute.cs new file mode 100644 index 0000000..a20b607 --- /dev/null +++ b/Dialog/Reflection API/ListAttribute.cs @@ -0,0 +1,39 @@ +// +// ListAttribute.cs +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.UIKit; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + public class ListAttribute : SectionAttribute + { + } +} diff --git a/Dialog/Reflection API/MapAttribute.cs b/Dialog/Reflection API/MapAttribute.cs new file mode 100644 index 0000000..3c241da --- /dev/null +++ b/Dialog/Reflection API/MapAttribute.cs @@ -0,0 +1,17 @@ +using System; +namespace MonoTouch.MVVM +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = true)] + public class MapAttribute : Attribute + { + public MapAttribute(string caption, string value) + { + Caption = caption; + Value = value; + } + + public string Caption {get;set;} + public string Value {get;set;} + } +} + diff --git a/Dialog/Reflection API/MulitlineAttribute.cs b/Dialog/Reflection API/MulitlineAttribute.cs new file mode 100644 index 0000000..28d718c --- /dev/null +++ b/Dialog/Reflection API/MulitlineAttribute.cs @@ -0,0 +1,38 @@ +// +// MultilineAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + public class MultilineAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/OrderAttribute.cs b/Dialog/Reflection API/OrderAttribute.cs new file mode 100644 index 0000000..2216c21 --- /dev/null +++ b/Dialog/Reflection API/OrderAttribute.cs @@ -0,0 +1,45 @@ +// +// OrderAttribute.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corportation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method, Inherited = false)] + public class OrderAttribute : Attribute + { + public OrderAttribute(int order) + { + Order = order; + } + + internal int Order; + } +} + diff --git a/Dialog/Reflection API/PasswordAttribute.cs b/Dialog/Reflection API/PasswordAttribute.cs new file mode 100644 index 0000000..fde7cf1 --- /dev/null +++ b/Dialog/Reflection API/PasswordAttribute.cs @@ -0,0 +1,41 @@ +// +// PasswordAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + public class PasswordAttribute : EntryAttribute + { + public PasswordAttribute(string placeholder) : base(placeholder) + { + } + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/PopOnSelectionAttribute.cs b/Dialog/Reflection API/PopOnSelectionAttribute.cs new file mode 100644 index 0000000..c243f05 --- /dev/null +++ b/Dialog/Reflection API/PopOnSelectionAttribute.cs @@ -0,0 +1,38 @@ +// +// PopOnSelectionAttribute.cs +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + public class PopOnSelectionAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/RangeAttribute.cs b/Dialog/Reflection API/RangeAttribute.cs new file mode 100644 index 0000000..4f109d1 --- /dev/null +++ b/Dialog/Reflection API/RangeAttribute.cs @@ -0,0 +1,44 @@ +// +// RangeAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + public class RangeAttribute : Attribute + { + public RangeAttribute(float low, float high) + { + Low = low; + High = high; + } + public float Low, High; + public bool ShowCaption; + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/RootAttribute.cs b/Dialog/Reflection API/RootAttribute.cs new file mode 100644 index 0000000..6b055c6 --- /dev/null +++ b/Dialog/Reflection API/RootAttribute.cs @@ -0,0 +1,47 @@ +// +// RootAttribute.cs +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.UIKit; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class RootAttribute: Attribute + { + public RootAttribute() + { + CellStyle = UITableViewCellStyle.Value1; + } + + public UITableViewCellStyle CellStyle { get; set; } + public Type DataTemplateType { get; set; } + } +} + diff --git a/Dialog/Reflection API/SectionAttribute.cs b/Dialog/Reflection API/SectionAttribute.cs new file mode 100644 index 0000000..7e5665a --- /dev/null +++ b/Dialog/Reflection API/SectionAttribute.cs @@ -0,0 +1,55 @@ +// +// SectionAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method, Inherited = false)] + public class SectionAttribute : Attribute + { + public SectionAttribute() + { + } + + public SectionAttribute(string caption) + { + Caption = caption; + } + + public SectionAttribute(string caption, string footer) + { + Caption = caption; + Footer = footer; + } + + public string Caption, Footer; + public int Order; + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/SkipAttribute.cs b/Dialog/Reflection API/SkipAttribute.cs new file mode 100644 index 0000000..83e35c2 --- /dev/null +++ b/Dialog/Reflection API/SkipAttribute.cs @@ -0,0 +1,38 @@ +// +// SkipAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method, Inherited = true)] + public class SkipAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/TimeAttribute.cs b/Dialog/Reflection API/TimeAttribute.cs new file mode 100644 index 0000000..8a372ad --- /dev/null +++ b/Dialog/Reflection API/TimeAttribute.cs @@ -0,0 +1,38 @@ +// +// TimeAttribute.cs +// +// Author: +// Miguel de Icaza (miguel@gnome.org) +// +// Copyright 2010, Novell, Inc. +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + public class TimeAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Dialog/Reflection API/ToolbarButton.cs b/Dialog/Reflection API/ToolbarButton.cs new file mode 100644 index 0000000..c4cd2ef --- /dev/null +++ b/Dialog/Reflection API/ToolbarButton.cs @@ -0,0 +1,17 @@ +namespace MonoTouch.MVVM +{ + using System; + using MonoTouch.UIKit; + + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public class ToolbarButtonAttribute: Attribute + { + public ToolbarButtonAttribute(UIBarButtonSystemItem buttonType) + { + ButtonType = buttonType; + } + + public UIBarButtonSystemItem ButtonType { get; set; } + } +} + diff --git a/Dialog/RefreshTableHeaderView.cs b/Dialog/RefreshTableHeaderView.cs new file mode 100644 index 0000000..57562e0 --- /dev/null +++ b/Dialog/RefreshTableHeaderView.cs @@ -0,0 +1,199 @@ +// +// RefreshTableHeaderView.cs: +// +// Author: +// Miguel de Icaza +// +// Code to support pull-to-refresh based on Martin Bowling's TweetTableView +// which is based in turn in EGOTableViewPullRefresh code which was created +// by Devin Doty and is Copyrighted 2009 enormego and released under the +// MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using System.Drawing; + using MonoTouch.CoreAnimation; + using MonoTouch.CoreGraphics; + using MonoTouch.UIKit; + + public enum RefreshViewStatus + { + ReleaseToReload, + PullToReload, + Loading + } + + public class RefreshTableHeaderView : UIView + { + static UIImage arrow = UIImage.FromResource(null, "arrow.png"); + UIActivityIndicatorView activity; + UILabel lastUpdateLabel, statusLabel; + UIImageView arrowView; + + public RefreshTableHeaderView(RectangleF rect) : base(rect) + { + AutoresizingMask = UIViewAutoresizing.FlexibleWidth; + + BackgroundColor = new UIColor (0.88f, 0.9f, 0.92f, 1); + lastUpdateLabel = new UILabel () + { + Font = UIFont.SystemFontOfSize (13f), + TextColor = new UIColor (0.47f, 0.50f, 0.57f, 1), + ShadowColor = UIColor.White, + ShadowOffset = new SizeF (0, 1), + BackgroundColor = this.BackgroundColor, + Opaque = true, + TextAlignment = UITextAlignment.Center, + AutoresizingMask = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleRightMargin + }; + AddSubview (lastUpdateLabel); + + statusLabel = new UILabel() + { + Font = UIFont.BoldSystemFontOfSize (14), + TextColor = new UIColor (0.47f, 0.50f, 0.57f, 1), + ShadowColor = lastUpdateLabel.ShadowColor, + ShadowOffset = new SizeF (0, 1), + BackgroundColor = this.BackgroundColor, + Opaque = true, + TextAlignment = UITextAlignment.Center, + AutoresizingMask = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleRightMargin + }; + AddSubview (statusLabel); + SetStatus (RefreshViewStatus.PullToReload); + + arrowView = new UIImageView() + { + ContentMode = UIViewContentMode.ScaleAspectFill, + Image = arrow, + AutoresizingMask = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleRightMargin + }; + arrowView.Layer.Transform = CATransform3D.MakeRotation ((float) Math.PI, 0, 0, 1); + AddSubview (arrowView); + + activity = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.Gray) + { + HidesWhenStopped = true, + AutoresizingMask = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleRightMargin + }; + AddSubview (activity); + } + + public override void LayoutSubviews() + { + base.LayoutSubviews (); + var bounds = Bounds; + + lastUpdateLabel.Frame = new RectangleF (0, bounds.Height - 30, bounds.Width, 20); + statusLabel.Frame = new RectangleF (0, bounds.Height-48, bounds.Width, 20); + arrowView.Frame = new RectangleF (20, bounds.Height - 65, 30, 55); + activity.Frame = new RectangleF (25, bounds.Height-38, 20, 20); + } + + private RefreshViewStatus status = (RefreshViewStatus)(-1); + + public virtual void SetStatus(RefreshViewStatus status) + { + if (this.status == status) + return; + + string s = "Release to refresh"; + + switch (status){ + case RefreshViewStatus.Loading: + s = "Loading..."; + break; + + case RefreshViewStatus.PullToReload: + s = "Pull down to refresh..."; + break; + } + statusLabel.Text = s; + } + + public override void Draw (RectangleF rect) + { + var context = UIGraphics.GetCurrentContext (); + context.DrawPath (CGPathDrawingMode.FillStroke); + statusLabel.TextColor.SetStroke (); + context.BeginPath (); + context.MoveTo (0, Bounds.Height-1); + context.AddLineToPoint (Bounds.Width, Bounds.Height-1); + context.StrokePath (); + } + + public bool IsFlipped; + + public void Flip (bool animate) + { + UIView.BeginAnimations (null); + UIView.SetAnimationDuration (animate ? .18f : 0); + arrowView.Layer.Transform = IsFlipped + ? CATransform3D.MakeRotation ((float)Math.PI, 0, 0, 1) + : CATransform3D.MakeRotation ((float)Math.PI * 2, 0, 0, 1); + + UIView.CommitAnimations (); + IsFlipped = !IsFlipped; + } + + private DateTime lastUpdateTime; + public DateTime LastUpdate + { + get { + return lastUpdateTime; + } + set { + if (value == lastUpdateTime) + return; + + lastUpdateTime = value; + if (value == DateTime.MinValue){ + lastUpdateLabel.Text = "Last Updated: never"; + } else + lastUpdateLabel.Text = String.Format ("Last Updated: {0:g}", value); + } + } + + public void SetActivity (bool active) + { + if (active){ + activity.StartAnimating (); + arrowView.Hidden = true; + SetStatus (RefreshViewStatus.Loading); + } else { + activity.StopAnimating (); + arrowView.Hidden = false; + } + } + } + + public class SearchChangedEventArgs : EventArgs + { + public string Text { get; set; } + + public SearchChangedEventArgs (string text) + { + Text = text; + } + } +} \ No newline at end of file diff --git a/Dialog/UITableViewElementCell.cs b/Dialog/UITableViewElementCell.cs new file mode 100644 index 0000000..f9b1b96 --- /dev/null +++ b/Dialog/UITableViewElementCell.cs @@ -0,0 +1,75 @@ +// +// UITableViewElementCell.cs: defines the base UITableViewCell ancestor +// +// Author: +// Robert Kozak (rkozak@nowcom.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class UITableViewElementCell : UITableViewCell + { + public virtual Element Element + { + get; + set; + } + + public UITableViewElementCell(UITableViewCellStyle style, string reuseIdentifier) : base(style, reuseIdentifier) + { + } + + public UITableViewElementCell(UITableViewCellStyle style, NSString reuseIdentifier) : base(style, reuseIdentifier) + { + } + + public UITableViewElementCell(NSCoder coder) : base(coder) + { + } + + public UITableViewElementCell(NSObjectFlag t) : base(t) + { + } + + public UITableViewElementCell(IntPtr handle) : base(handle) + { + } + + public UITableViewElementCell() : base() + { + } + + public override void PrepareForReuse() + { + base.PrepareForReuse(); + Element = null; + } + } +} + diff --git a/EnumValueConverter.cs b/EnumValueConverter.cs new file mode 100644 index 0000000..9feee72 --- /dev/null +++ b/EnumValueConverter.cs @@ -0,0 +1,20 @@ +namespace MonoTouch.Dialog +{ + using System; + using MonoTouch.MVVM; + using System.Globalization; + + public class EnumValueConverter: IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Enum.Parse(targetType, (string)value); + } + } +} + diff --git a/Extensions/EnumExtensions.cs b/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..43a9f4b --- /dev/null +++ b/Extensions/EnumExtensions.cs @@ -0,0 +1,66 @@ +// +// EnumExtensions.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace System +{ + using System.ComponentModel; + using System.Linq; + + public static class EnumExtensions + { + public static string GetDescription(this Enum value) + { + return GetDescriptionValue(value.ToString(), value.GetType()); + } + + public static string GetDescriptionValue(string name, Type enumType) + { + if (enumType == null || !enumType.IsEnum) + { + throw new ArgumentException("is not an enum", "enumType"); + } + + string value = String.Empty; + + var enumFields = from field in enumType.GetFields() + where field.IsLiteral && field.Name == name + select field; + + var enumField = enumFields.FirstOrDefault(); + if (enumField != null) + { + var attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(enumField, typeof(DescriptionAttribute)); + + value = attribute != null ? attribute.Description : enumField.Name; + } + + return value; + } + } +} diff --git a/Extensions/ExpressionExtensions.cs b/Extensions/ExpressionExtensions.cs new file mode 100644 index 0000000..da48d7c --- /dev/null +++ b/Extensions/ExpressionExtensions.cs @@ -0,0 +1,138 @@ +// +// ExpressionExtensions.cs: +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace System.Linq.Expressions +{ + using System.Collections.ObjectModel; + using System.Linq; + using System.Reflection; + + public static class ExpressionExtensions + { + public static string PropertyName(this Expression> property) + { + if (property != null) + { + MemberExpression memberExp; + if (!TryFindMemberExpression(property.Body, out memberExp)) + return string.Empty; + + var memberNames = new Collection(); + do + { + memberNames.Add(memberExp.Member.Name); + } + while (TryFindMemberExpression(memberExp.Expression, out memberExp)); + + var memberNamesArray = memberNames.Reverse().ToArray(); + + var result = string.Join(".", memberNamesArray); + return result; + } + + return null; + } + + private static bool TryFindMemberExpression(Expression expression, out MemberExpression memberExpression) + { + memberExpression = expression as MemberExpression; + if (memberExpression != null) + return true; + + // IsConversion checks for cases where the compiler created an automatic conversion, + // obj => Convert(obj.Property) [e.g., int -> object] + // OR: + // obj => ConvertChecked(obj.Property) [e.g., int -> long] + var unaryExpression = expression as UnaryExpression; + if (IsConversion(expression) && (unaryExpression != null)) + { + memberExpression = unaryExpression.Operand as MemberExpression; + if (memberExpression != null) + return true; + } + + return false; + } + + private static bool IsConversion(Expression expression) + { + return (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked); + } + } + + public static class AssignmentExpression + { + public static void SetValue(Expression> expr, T value) + { + object obj; + MemberInfo mInfo = expr.GetMemberInfo(out obj); + + FieldInfo fInfo = mInfo as FieldInfo; + PropertyInfo pInfo = mInfo as PropertyInfo; + + if (fInfo != null) + fInfo.SetValue(obj, value); + else if (pInfo != null && pInfo.CanWrite) + pInfo.SetValue(obj, value, null); + } + + public static MemberInfo GetMemberInfo(this Expression> expr, out object obj) + { + if (expr == null) + throw new ArgumentNullException("expr"); + + var body = expr.Body as MemberExpression; + + if (body == null) + { + var unaryExpr = expr.Body as UnaryExpression; + + if (unaryExpr == null) + throw new ArgumentException("'expr' should be either an unary expression or a member expression"); + + body = unaryExpr.Operand as MemberExpression; + } + + if (body == null) + throw new ArgumentException("'expr' should be a member expression"); + + LambdaExpression lambdaExpr = Expression.Lambda(body.Expression); + Delegate lambdaFunc = lambdaExpr.Compile(); + obj = lambdaFunc.DynamicInvoke(); + + MemberInfo mInfo = obj.GetType().GetMember(body.Member.Name).FirstOrDefault(); + + if (mInfo == null) + throw new InvalidOperationException("Member not found."); + + return mInfo; + } + + } +} diff --git a/Extensions/TypeExtensions.cs b/Extensions/TypeExtensions.cs new file mode 100644 index 0000000..3f902f2 --- /dev/null +++ b/Extensions/TypeExtensions.cs @@ -0,0 +1,63 @@ +namespace MonoTouch.MVVM +{ + using System; + using System.Reflection; + + public static class TypeExtensions + { + public static T GetCustomAttribute(this MemberInfo member) where T: Attribute + { + return GetCustomAttribute(member, false); + } + + public static T GetCustomAttribute(this MemberInfo member, bool inherited) where T: Attribute + { + var attributes = Attribute.GetCustomAttributes(member, typeof(T), inherited); + if (attributes.Length > 0) + return attributes[0] as T; + + return default(T); + } + + public static PropertyInfo GetNestedProperty(this Type sourceType, ref object obj, string path, bool allowPrivateProperties) + { + BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + if (allowPrivateProperties) + bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; + + Type type = sourceType; + + if (path.Contains(".")) + { + PropertyInfo info = null; + var properties = path.Split('.'); + for (int index = 0; index < properties.Length; index++) + { + var property = properties[index]; + info = type.GetProperty(property, bindingFlags); + if (info != null) + { + type = info.PropertyType; + + if (obj != null && index < properties.Length - 1) + { + try + { + obj = info.GetValue(obj, null); + } + catch (TargetInvocationException) + { + } + } + } + } + + return info; + } + else + return type.GetProperty (path, bindingFlags); + } + } +} + diff --git a/Images/arrow.png b/Images/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..aebf13b813c7288e59fb271b4fc10e6360a2f4bf GIT binary patch literal 1600 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

wYtz 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 x7jDs)E%!^|0aF;6QWz`?gJj`BDLf>FvW&@ + + + Debug + iPhoneSimulator + 9.0.21022 + 2.0 + {3FFBFFF8-5560-4EDE-82E5-3FFDFBBA8A50} + {E613F3A2-FE9C-494F-B74E-F63BCB86FEA6};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + MonoTouch.MVVM + MonoTouch.Dialog + 3.0 + v3.5 + 3.0 + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG + prompt + 4 + None + True + + + none + false + bin\iPhoneSimulator\Release + prompt + 4 + False + None + + + true + full + false + bin\iPhone\Debug + DEBUG + prompt + 4 + iPhone Developer + True + + + none + false + bin\iPhone\Release + prompt + 4 + False + iPhone Developer + + + none + false + bin + 4 + False + iPhone Developer + + + none + false + bin + 4 + False + iPhone Developer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + arrow.png + + + + + + diff --git a/MonoTouch.MVVM.pidb b/MonoTouch.MVVM.pidb new file mode 100644 index 0000000000000000000000000000000000000000..3363b3c0a3b1e40d0c9c7fde06236828762d6bab GIT binary patch literal 302263 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..1085dfe --- /dev/null +++ b/README.markdown @@ -0,0 +1,164 @@ +MonoTouch.MVVM +================ + +MonoTouch.MVVM started as adding WPF/Silverlight style binding +to MonoTouch.Dialog but in doing so it required more changes than +expected and so it became this project rather than just a branch +from MonoTouch.Dialog. + +Most of the concepts of MonoTouch.Dialog are still there (in fact, +at this point, the changes aren't that major but will be in the +future) such as Cells, Elements, Roots, Sections. Refer to +MonoTouch.Dialog for more information. + +MVVM, or the Model-View-ViewModel pattern is a pattern that promotes +the separation between Data, Business Logic and User Interface. The +Model describes the Data, the View describes the UI and the ViewModel +sits between the Model and View and contains all of the business logic. + +The key to the MVVM pattern is the communication between the layers. + +Data binding is one way to accomplish this communication in generic manner. + +Robert Kozak (rkozak@gmail.com) +@robertkozak - Twitter + +Project Status +============== + +MonoTouch.MVVM is being released as Alpha 0.1. I would like feedback and I have +plans to take this to MonoDroid and possibly WM7. If this works we can have a common +platform for all 3 major phone/tablet OSs + +Consider this to be highly experimental POC release. I have a lot more refactoring to do +and I have a lot of ideas I want to incorporate but it is a good start. + +Take a look at the sample I provided to give you an idea what can be done. + +If you want to help out on this project please send me a message. + + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Data Binding +=========== + +I've made a few changes to MonoTouch.Dialog to support data binding. + +First, there is a never Element type that is generic and a new property +"Value" to hold the value of the generic type. Element has a +property Value of type string and Element has a Value of type int. + +The other concrete Element types derive from Element and so they all +have a Value property. This is the default binding property: + + [Entry] + [Bind("Value")] + public string MovieName + { + get; + set; + } + +In the example above, the string value of the property MovieName will be +data bound to the Value property of EntryElement. + +Note: Since Value is available by default in each Element type if there is +no [Bind] statement for it it will be done for you. So this is the same as +above: + + [Entry] + public string MovieName + { + get; + set; + } + +Note: Any public property of Element or derived classes can be data bound, except for Cell. +There is no real programatic limitation of binding to Cell but the cell of an element is +virtualized by iOS and if you have more cells than what can be seen on the screen it will +be reused. And your binding may affect another unrelated cell. For this reason I suggest +you don't bind to any property of Cell and only to public properties of an Element. + +Value Converters +================ + +Sometimes, you want to bind an integer value to a Entry element which has a Value property +of type string or you want to bind a float and you want to display it as $10 rather than 10.0. + +You do this with an IValueConverter. IValueConverter has Convert and Convertback methods that +is part of the binding update process to automatically take care of conversions for you. + +Here is a simple example of converter that takes a float and returns a string formatted as +a Percentage: + + public class PercentConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return value; + + return string.Format("{0}%", value); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + var result = ((string)value).Replace("%", ""); + return result; + } + } + +and he is how it used: + + [Bind(ValueConverterType = typeof(PercentConverter)] + public float CriticsRating + { + get { return Get(()=>CriticsRating); } + set { Set(()=>CriticsRating, value); } + } + +INotifyPropertyChanged +====================== + +In order for data binding to work the framework needs to know then a property changes. +We do that using an interface called INotifiyPropertyChanged or INPC for short. It +is typically implemented like this: + + public event PropertyChangedEventHandler PropertyChanged; + + private void NotifyPropertyChanged(String info) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(info)); + } + } + +and in the setter of your properties you call NotifyPropertyChanged(). + +ViewModels and Views in MonoTouch.MVVM all derive from PropertyNotifier which +has two methods to help with this: Set() and Get(). + + public string FirstName + { + get { return Get(()=>FirstName); } + set { Set(()=>FirstName, value); } + } + +The Set() method knows how to call the PropertyChanged event and +since it is a function rather than a string it is compile time safe. The +standard way of calling PropertyChanged uses a string for the name of the +property that changed. This can lead to errors if you change the Property +declaration and forget to update the INPC call. Using Set() eliminates this +problem. + +It also uses a Dictionary to hold the property values so you don't need a backing field. + diff --git a/Samples/AppDelegateIPad.cs b/Samples/AppDelegateIPad.cs new file mode 100644 index 0000000..ee6bb21 --- /dev/null +++ b/Samples/AppDelegateIPad.cs @@ -0,0 +1,39 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using MonoTouch.Foundation; +using MonoTouch.UIKit; + +namespace Samples +{ + + // The name AppDelegateIPad is referenced in the MainWindowIPad.xib file. + public partial class AppDelegateIPad : UIApplicationDelegate + { + // This method is invoked when the application has loaded its UI and its ready to run + public override bool FinishedLaunching (UIApplication app, NSDictionary options) + { + window.AddSubview (navigation.View); + + // this method initializes the main menu Dialog + var startupThread = new Thread (Startup as ThreadStart); + startupThread.Start (); + + window.MakeKeyAndVisible (); + return true; + } + + [Export("Startup")] + private void Startup () + { + using (var pool = new NSAutoreleasePool ()) { + InvokeOnMainThread (delegate { + var binding = new BindingContext (new MovieListView (), "Movie List View"); + navigation.ViewControllers = new UIViewController[] { new DialogViewController (binding.Root, true) }; + }); + } + } + } +} + diff --git a/Samples/AppDelegateIPhone.cs b/Samples/AppDelegateIPhone.cs new file mode 100644 index 0000000..8d29467 --- /dev/null +++ b/Samples/AppDelegateIPhone.cs @@ -0,0 +1,38 @@ +namespace Samples +{ + using MonoTouch.Dialog; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + using System.Threading; + + // The name AppDelegateIPhone is referenced in the MainWindowIPhone.xib file. + public partial class AppDelegateIPhone : UIApplicationDelegate + { + // This method is invoked when the application has loaded its UI and its ready to run + public override bool FinishedLaunching(UIApplication app, NSDictionary options) + { + window.AddSubview(navigation.View); + + // this method initializes the main menu Dialog + var startupThread = new Thread(Startup as ThreadStart); + startupThread.Start(); + + window.MakeKeyAndVisible(); + return true; + } + + [Export("Startup")] + private void Startup () + { + using (var pool = new NSAutoreleasePool()) + { + InvokeOnMainThread(delegate + { + var binding = new BindingContext(new MovieListView(), "MVVM Samples"); + navigation.ViewControllers = new UIViewController[] { new DialogViewController(binding, true) }; + }); + } + } + } +} + diff --git a/Samples/Converters/CurrentMovieConverter.cs b/Samples/Converters/CurrentMovieConverter.cs new file mode 100644 index 0000000..53233bf --- /dev/null +++ b/Samples/Converters/CurrentMovieConverter.cs @@ -0,0 +1,27 @@ +namespace Samples +{ + using System; + using MonoTouch.Dialog; + using System.Globalization; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + using System.Collections.Generic; + + public class CurrentMovieConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var movieList = parameter as List; + + int index = System.Convert.ToInt32(value); + + return movieList[index]; + } + + public object ConvertBack(object value, Type targteType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} + diff --git a/Samples/Converters/FontConverter.cs b/Samples/Converters/FontConverter.cs new file mode 100644 index 0000000..3073d13 --- /dev/null +++ b/Samples/Converters/FontConverter.cs @@ -0,0 +1,27 @@ +namespace Samples +{ + using System; + using MonoTouch.Dialog; + using System.Globalization; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + + public class FontConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return null; + + var font = UIFont.BoldSystemFontOfSize((float)value); + + return font; + } + + public object ConvertBack(object value, Type targteType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} + diff --git a/Samples/Converters/MovieViewModelConverter.cs b/Samples/Converters/MovieViewModelConverter.cs new file mode 100644 index 0000000..da96e5d --- /dev/null +++ b/Samples/Converters/MovieViewModelConverter.cs @@ -0,0 +1,9 @@ +using System; +using MonoTouch.MVVM; +namespace Samples +{ +// public class MovieViewModelConverter :IValueConverter +// { +// } +} + diff --git a/Samples/Info.plist b/Samples/Info.plist new file mode 100644 index 0000000..c6e9d7a --- /dev/null +++ b/Samples/Info.plist @@ -0,0 +1,13 @@ + + + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + \ No newline at end of file diff --git a/Samples/Main.cs b/Samples/Main.cs new file mode 100644 index 0000000..bf868eb --- /dev/null +++ b/Samples/Main.cs @@ -0,0 +1,14 @@ +namespace Samples +{ + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class Application + { + static void Main(string[] args) + { + UIApplication.Main(args); + } + } +} + diff --git a/Samples/MainMenu.cs b/Samples/MainMenu.cs new file mode 100644 index 0000000..729b206 --- /dev/null +++ b/Samples/MainMenu.cs @@ -0,0 +1,37 @@ +namespace Samples +{ + using System; + using MonoTouch.MVVM; + using MonoTouch.Dialog; + using System.Collections.Generic; + using System.Linq; + using MonoTouch.Foundation; + using MonoTouch.UIKit; + + public class MenuDialog : DialogViewController + { + public MenuDialog(bool pushing) : base(pushing) + { + PrepareRoot(CreateRoot()); + Autorotate = true; + } + + private IRoot CreateRoot() + { + return new RootElement("Demos") + { + new Section() + { + new ButtonElement("Movie List Sample"){ Command = new UICommand((o)=>Push(new MovieListView()), null) } + }, + }; + } + + private void Push(View view) + { + var binding = new BindingContext(view, ""); + NavigationController.PushViewController(new DialogViewController(binding.Root, true), true); + } + } +} + diff --git a/Samples/MainWindowIPad.xib b/Samples/MainWindowIPad.xib new file mode 100644 index 0000000..05dc1b1 --- /dev/null +++ b/Samples/MainWindowIPad.xib @@ -0,0 +1,482 @@ + + + + 800 + 10C540 + 759 + 1038.25 + 458.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 79 + + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBIPadFramework + + + + 292 + {768, 1024} + + + 1 + MSAxIDEAA + + NO + NO + + 2 + + IBIPadFramework + + + + + YES + + + delegate + + + + 5 + + + + window + + + + 6 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + 4 + + + App Delegate + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 4.CustomClassName + 4.IBPluginDependency + + + YES + UIApplication + UIResponder + {{526, 33}, {783, 823}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + AppDelegateIPad + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 6 + + + + YES + + AppDelegateIPad + + window + id + + + IBUserSource + + + + + NSObject + + IBProjectSource + IBCocoaTouchTool/IBCocoaTouchToolIntegration.h + + + + NSObject + + IBProjectSource + IBInternalHeaders/IBAppKitAdditions.h + + + + NSObject + + IBProjectSource + IBInternalHeaders/IBConnection.h + + + + NSObject + + IBProjectSource + IBInternalHeaders/IBFieldEditor.h + + + + NSObject + + IBProjectSource + IBInternalHeaders/IBFoundationAdditions.h + + + + NSObject + + IBProjectSource + IBInternalHeaders/IBObjectContainer.h + + + + NSObject + + IBProjectSource + IBInternalHeaders/IBObjectIntegrationInternal.h + + + + NSObject + + IBProjectSource + IBInternalHeaders/IBWindowController.h + + + + NSObject + + IBProjectSource + IBPlugin/CustomViews/IBWindowRotationAnimation.h + + + + NSObject + + IBProjectSource + IBPlugin/Utilities/IBObjectMarshalling.h + + + + NSObject + + IBProjectSource + IBPlugin/Utilities/IBValueMarshallers.h + + + + NSObject + + IBProjectSource + IBPlugin/WidgetIntegration/Accessibility/IBUIAccessibilityIntegration.h + + + + NSObject + + IBProjectSource + IBPlugin/WidgetIntegration/IBUIObjectIntegration.h + + + + NSObject + + IBProjectSource + IBPlugin/WidgetIntegration/IBUIViewController/IBUIViewControllerEditorPlaceholderView.h + + + + NSObject + + IBProjectSource + IBPlugin/WidgetIntegration/IBUIViewController/IBUIViewControllerEditorView.h + + + + + YES + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSAccessibility.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSApplication.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSApplicationScripting.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSColorPanel.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSControl.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSDictionaryController.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSDragging.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSFontManager.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSFontPanel.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSKeyValueBinding.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSMenu.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSNibLoading.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSOutlineView.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSPasteboard.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSSavePanel.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSTableView.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSToolbarItem.h + + + + NSObject + + IBFrameworkSource + AppKit.framework/Headers/NSView.h + + + + NSObject + + IBFrameworkSource + DevToolsKit.framework/Headers/DTAssetLibrary.h + + + + NSObject + + IBFrameworkSource + DevToolsKit.framework/Headers/DTDragManager.h + + + + NSObject + + IBFrameworkSource + DevToolsKit.framework/Headers/DTTemplateChooserViewController.h + + + + NSObject + + IBFrameworkSource + DevToolsKit.framework/Headers/DTTypeCompletionHandler.h + + + + NSObject + + IBFrameworkSource + InterfaceBuilderKit.framework/Headers/IBObjectIntegration.h + + + + NSObject + + IBFrameworkSource + PrintCore.framework/Headers/PDEPluginInterface.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIResponder + NSObject + + + + + 0 + IBIPadFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + ../../../IBCocoaTouchPlugin.xcodeproj + 3 + 79 + + + diff --git a/Samples/MainWindowIPad.xib.designer.cs b/Samples/MainWindowIPad.xib.designer.cs new file mode 100644 index 0000000..9f0b403 --- /dev/null +++ b/Samples/MainWindowIPad.xib.designer.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Mono Runtime Version: 2.0.50727.1433 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +namespace Samples { + + + // Base type probably should be MonoTouch.Foundation.NSObject or subclass + [MonoTouch.Foundation.Register("AppDelegateIPad")] + public partial class AppDelegateIPad { + + private MonoTouch.UIKit.UIWindow __mt_window; + + #pragma warning disable 0169 + [MonoTouch.Foundation.Connect("window")] + private MonoTouch.UIKit.UIWindow window { + get { + this.__mt_window = ((MonoTouch.UIKit.UIWindow)(this.GetNativeField("window"))); + return this.__mt_window; + } + set { + this.__mt_window = value; + this.SetNativeField("window", value); + } + } + } +} diff --git a/Samples/MainWindowIPhone.xib b/Samples/MainWindowIPhone.xib new file mode 100644 index 0000000..4b954ec --- /dev/null +++ b/Samples/MainWindowIPhone.xib @@ -0,0 +1,303 @@ + + + + 768 + 10J567 + 823 + 1038.35 + 462.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 132 + + + YES + + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + + + 1316 + + {320, 480} + + + 1 + MSAxIDEAA + + NO + NO + + IBCocoaTouchFramework + + + + + 1 + + IBCocoaTouchFramework + NO + + + 256 + {0, 0} + NO + YES + YES + IBCocoaTouchFramework + + + YES + + + + IBCocoaTouchFramework + + + + 1 + + IBCocoaTouchFramework + NO + + + + + + + YES + + + delegate + + + + 5 + + + + window + + + + 7 + + + + navigation + + + + 13 + + + + + YES + + 0 + + + + + + 2 + + + YES + + + + + -1 + + + File's Owner + + + 4 + + + App Delegate + + + -2 + + + + + 8 + + + YES + + + + + + + 9 + + + YES + + + + + + 10 + + + + + 11 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 10.IBPluginDependency + 11.IBPluginDependency + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 2.UIWindow.visibleAtLaunch + 4.CustomClassName + 4.IBPluginDependency + 8.IBEditorWindowLastContentRect + 8.IBPluginDependency + 9.IBPluginDependency + + + YES + UIApplication + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + + YES + + + {{593, 276}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + AppDelegateIPhone + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{0, 515}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 13 + + + + YES + + AppDelegateIPhone + + YES + + YES + navigation + window + + + YES + id + id + + + + YES + + YES + navigation + window + + + YES + + navigation + id + + + window + id + + + + + IBUserSource + + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + + 3 + 132 + + diff --git a/Samples/MainWindowIPhone.xib.designer.cs b/Samples/MainWindowIPhone.xib.designer.cs new file mode 100644 index 0000000..03da713 --- /dev/null +++ b/Samples/MainWindowIPhone.xib.designer.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Mono Runtime Version: 2.0.50727.1433 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +namespace Samples { + + + // Base type probably should be MonoTouch.Foundation.NSObject or subclass + [MonoTouch.Foundation.Register("AppDelegateIPhone")] + public partial class AppDelegateIPhone { + + private MonoTouch.UIKit.UIWindow __mt_window; + + private MonoTouch.UIKit.UINavigationController __mt_navigation; + + #pragma warning disable 0169 + [MonoTouch.Foundation.Connect("window")] + private MonoTouch.UIKit.UIWindow window { + get { + this.__mt_window = ((MonoTouch.UIKit.UIWindow)(this.GetNativeField("window"))); + return this.__mt_window; + } + set { + this.__mt_window = value; + this.SetNativeField("window", value); + } + } + + [MonoTouch.Foundation.Connect("navigation")] + private MonoTouch.UIKit.UINavigationController navigation { + get { + this.__mt_navigation = ((MonoTouch.UIKit.UINavigationController)(this.GetNativeField("navigation"))); + return this.__mt_navigation; + } + set { + this.__mt_navigation = value; + this.SetNativeField("navigation", value); + } + } + } +} diff --git a/Samples/Models/MovieDataModel.cs b/Samples/Models/MovieDataModel.cs new file mode 100644 index 0000000..c7a38d9 --- /dev/null +++ b/Samples/Models/MovieDataModel.cs @@ -0,0 +1,30 @@ +namespace Samples +{ + using MonoTouch.MVVM; + using System; + using System.Collections.ObjectModel; + + public class MovieDataModel : Model + { + public ObservableCollection Movies { get; set; } + + + public override void Load() + { + Movies = new ObservableCollection(); + + Movies.Add(new MovieViewModel() { Name = "Forrest Gump", Genre = Genre.Drama, Rating = Rating.R, TicketPrice = 10f, CriticsRating = 89f}); + Movies.Add(new MovieViewModel() { Name = "Tangled", Genre = Genre.Animated, Rating = Rating.G, TicketPrice = 12f, CriticsRating = 91f }); + Movies.Add(new MovieViewModel() { Name = "The Matrix", Genre = Genre.Scifi, Rating = Rating.R, TicketPrice = 10f, CriticsRating = 82f }); + Movies.Add(new MovieViewModel() { Name = "Star Wars IV : A New Hope", Genre = Genre.ActionAdventure, Rating = Rating.PG, TicketPrice = 10f, CriticsRating = 78f }); + Movies.Add(new MovieViewModel() { Name = "Die Hard", Genre = Genre.ActionAdventure, Rating = Rating.R, TicketPrice = 10f, CriticsRating = 84f }); + Movies.Add(new MovieViewModel() { Name = "Toy Story 3", Genre = Genre.Animated, Rating = Rating.PG, TicketPrice = 16f, CriticsRating = 98f }); + Movies.Add(new MovieViewModel() { Name = "True Grit", Genre = Genre.Drama, Rating = Rating.R, TicketPrice = 12f, CriticsRating = 97f }); + Movies.Add(new MovieViewModel() { Name = "Black Swan", Genre = Genre.Drama, Rating = Rating.R, TicketPrice = 12f, CriticsRating = 88f }); + Movies.Add(new MovieViewModel() { Name = "Little Fockers", Genre = Genre.Comedy, Rating = Rating.PG13, TicketPrice = 12f, CriticsRating = 10f }); + Movies.Add(new MovieViewModel() { Name = "The Green Hornet 3D", Genre = Genre.ActionAdventure, Rating = Rating.PG13, TicketPrice = 12f, CriticsRating = 44f, ShownIn3D = true }); + Movies.Add(new MovieViewModel() { Name = "The Fighter", Genre = Genre.Drama, Rating = Rating.R, TicketPrice = 12f, CriticsRating = 89f }); + } + } +} + diff --git a/Samples/Samples.csproj b/Samples/Samples.csproj new file mode 100644 index 0000000..3ce7051 --- /dev/null +++ b/Samples/Samples.csproj @@ -0,0 +1,119 @@ + + + + Debug + iPhoneSimulator + 9.0.21022 + 2.0 + {2C326EA9-55E1-44FF-BDCC-38A84DD1E8F6} + {E613F3A2-FE9C-494F-B74E-F63BCB86FEA6};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Samples + IPhoneAndIPad + MainWindowIPad.xib + MainWindowIPhone.xib + Samples + 3.0 + v3.5 + DCMobile + 0.1 + MVVM Sample + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG + prompt + 4 + false + None + True + + + + none + false + bin\iPhoneSimulator\Release + prompt + 4 + False + false + None + + + true + full + false + bin\iPhone\Debug + DEBUG + prompt + 4 + false + iPhone Developer + True + None + + + + none + false + bin\iPhone\Release + prompt + 4 + False + false + iPhone Developer + + + + + + + + + + + + + MainWindowIPhone.xib + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3FFBFFF8-5560-4EDE-82E5-3FFDFBBA8A50} + MonoTouch.MVVM + + + + + + + + + \ No newline at end of file diff --git a/Samples/Samples.pidb b/Samples/Samples.pidb new file mode 100644 index 0000000000000000000000000000000000000000..90253f418f13d8f9199ccf033a770d884d8d7f82 GIT binary patch literal 28875 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 } + set { Set(()=>Number, value); } + } + + public string Street + { + get { return Get (() => Street); } + set { Set (() => Street, value); } + } + + public string City + { + get { return Get (() => City); } + set { Set (() => City, value); } + } + + public string State + { + get { return Get (() => State); } + set { Set (() => State, value); } + } + + public string Zip + { + get { return Get (() => Zip); } + set { Set (() => Zip, value); } + } + } +} + diff --git a/Samples/ViewModels/InterestingViewModel.cs b/Samples/ViewModels/InterestingViewModel.cs new file mode 100644 index 0000000..62e60a1 --- /dev/null +++ b/Samples/ViewModels/InterestingViewModel.cs @@ -0,0 +1,26 @@ +using System; +using MonoTouch.MVVM; + +namespace Samples +{ + public class InterestingViewModel : ViewModel + { + public string ResizingText + { + get { return Get(()=>ResizingText); } + set { Set(()=>ResizingText, value); } + } + + public string SampleText + { + get { return Get (() => SampleText, "Test"); } + set { Set (() => SampleText, value); } + } + + public InterestingViewModel() + { + ResizingText = "Resize me!"; + } + } +} + diff --git a/Samples/ViewModels/MovieViewModel.cs b/Samples/ViewModels/MovieViewModel.cs new file mode 100644 index 0000000..65490c5 --- /dev/null +++ b/Samples/ViewModels/MovieViewModel.cs @@ -0,0 +1,92 @@ +namespace Samples +{ + using System; + using MonoTouch.MVVM; + using System.ComponentModel; + + public enum Rating + { + NC17, + R, + [Description("PG-13")] + PG13, + PG, + G + } + + public enum Genre + { + [Description("Romantic Comedy")] + RomanticComedy, + Romance, + [Description("Action & Adventure")] + ActionAdventure, + Scifi, + Drama, + Horror, + Comedy, + Animated, + Childrens + } + + public enum Location + { + [Description("East Coast")] + EastCoast, + Midwest, + South, + [Description("West Coast")] + WestCoast + } + + public class MovieViewModel : ViewModel + { + public string Name + { + get { return Get(() => Name); } + set { Set(() => Name, value); } + } + + public Rating Rating + { + get { return Get(() => Rating, Rating.PG); } + set { Set(() => Rating, value); } + } + + public Genre Genre + { + get { return Get(() => Genre, Genre.Comedy); } + set { Set(() => Genre, value); } + } + + public float TicketPrice + { + get { return Get(()=>TicketPrice); } + set { Set(()=>TicketPrice, value); } + } + + public float CriticsRating + { + get { return Get (() => CriticsRating); } + set { Set (() => CriticsRating, value); } + } + + public bool ShownIn3D + { + get { return Get(()=>ShownIn3D); } + set { Set(()=>ShownIn3D, value); } + } + + public EnumCollection Location + { + get { return Get (() => Location); } + set { Set (() => Location, value); } + } + + public override string ToString() + { + return string.Format ("{0}", Name); + } + } +} + diff --git a/Samples/Views/AddressView.cs b/Samples/Views/AddressView.cs new file mode 100644 index 0000000..5210b91 --- /dev/null +++ b/Samples/Views/AddressView.cs @@ -0,0 +1,50 @@ +using System; +using MonoTouch.MVVM; +using MonoTouch.Dialog; + +namespace Samples +{ + public class AddressView: View + { + [Entry] + public string Number + { + get { return Get (() => Number); } + set { Set (() => Number, value); } + } + + [Entry] + public string Street + { + get { return Get (() => Street); } + set { Set (() => Street, value); } + } + + [Entry] + public string City + { + get { return Get (() => City); } + set { Set (() => City, value); } + } + + [Entry] + public string State + { + get { return Get (() => State); } + set { Set (() => State, value); } + } + + [Entry] + public string Zip + { + get { return Get (() => Zip); } + set { Set (() => Zip, value); } + } + + public override string ToString() + { + return string.Format ("{0} {1}, {2}, {3}, {4}", Number, Street, City, State, Zip); + } + } +} + diff --git a/Samples/Views/InterestingView.cs b/Samples/Views/InterestingView.cs new file mode 100644 index 0000000..60a2686 --- /dev/null +++ b/Samples/Views/InterestingView.cs @@ -0,0 +1,60 @@ +using System; +using MonoTouch.MVVM; +using MonoTouch.Dialog; +using MonoTouch.UIKit; + +namespace Samples +{ + public class InterestingView: View + { + [Bind("CaptionSize", "Entry.Font", ValueConverterType = typeof(FontConverter))] + [Bind("Alignment", "Entry.TextAlignment")] + [Entry] + [Caption(" ")] + public string ResizingText + { + get { return Get(()=>DataContext.ResizingText); } + set { Set(()=>DataContext.ResizingText, value); } + } + + [Range(12, 30)] + public float CaptionSize + { + get { return Get(()=>CaptionSize, 17); } + set { Set(()=>CaptionSize, value); } + } + + [Bind(SourcePath = "CaptionSize")] + [Entry] + public string Number + { + get { return Get (() => Number); } + set { Set (() => Number, value); } + } + + [PopOnSelection] + [Section] + public UITextAlignment Alignment + { + get { return Get (() => Alignment); } + set { Set (() => Alignment, value); } + } + + [Section("Entry with Custom Keyboard")] + [Bind("KeyboardType", "Entry.KeyboardType")] + [Entry] + public string Text + { + get { return Get (() => DataContext.SampleText); } + set { Set (() => DataContext.SampleText, value); } + } + + [PopOnSelection] + public UIKeyboardType KeyboardType + { + get { return Get (() => KeyboardType, UIKeyboardType.Default); } + set { Set (() => KeyboardType, value); } + } + } +} + diff --git a/Samples/Views/MovieElement.cs b/Samples/Views/MovieElement.cs new file mode 100644 index 0000000..ed78b81 --- /dev/null +++ b/Samples/Views/MovieElement.cs @@ -0,0 +1,63 @@ +namespace Samples +{ + using System; + using MonoTouch.UIKit; + using MonoTouch.CoreGraphics; + using MonoTouch.Dialog; + using System.Drawing; + using MonoTouch.Foundation; + + /// + /// This is an example of implementing the OwnerDrawnElement abstract class. + /// It makes it very simple to create an element that you draw using CoreGraphics + /// + public class MovieElement : OwnerDrawnElement + { + CGGradient gradient; + private UIFont _CaptionFont = UIFont.SystemFontOfSize(15.0f); + + public MovieElement(string caption) : base(UITableViewCellStyle.Default) + { + Caption = caption; + CGColorSpace colorSpace = CGColorSpace.CreateDeviceRGB(); + gradient = new CGGradient( + colorSpace, + new float[] { 0.95f, 0.95f, 0.95f, 1, + 0.85f, 0.85f, 0.85f, 1}, + new float[] { 0, 1 } ); + } + + public override void Draw(RectangleF bounds, CGContext context, UIView view) + { + UIColor.White.SetFill(); + context.FillRect(bounds); + + context.DrawLinearGradient(gradient, new PointF(bounds.Left, bounds.Top), new PointF(bounds.Left, bounds.Bottom), CGGradientDrawingOptions.DrawsAfterEndLocation); + + UIColor.DarkGray.SetColor(); + view.DrawString(Caption, new RectangleF(10, 10, bounds.Width - 20, TextHeight(bounds)), _CaptionFont, UILineBreakMode.WordWrap); + } + + public override float Height(RectangleF bounds) + { + var height = 40.0f + TextHeight(bounds); + return height; + } + + private float TextHeight(RectangleF bounds) + { + SizeF size; + using(NSString str = new NSString(this.Caption)) + { + size = str.StringSize(_CaptionFont, new SizeF(bounds.Width - 20, 1000), UILineBreakMode.WordWrap); + } + return size.Height; + } + + public override string ToString() + { + return string.Format(Caption); + } + } +} + diff --git a/Samples/Views/MovieListView.cs b/Samples/Views/MovieListView.cs new file mode 100644 index 0000000..44878c1 --- /dev/null +++ b/Samples/Views/MovieListView.cs @@ -0,0 +1,77 @@ +namespace Samples +{ + using System; + using System.Collections.ObjectModel; + using MonoTouch.MVVM; + using MonoTouch.Dialog; + using MonoTouch.UIKit; + using System.Linq; + + public class MovieListView: View + { + private View1 _View1 = new View1(); + private View2 _View2 = new View2(); + + [Section] + [Root(DataTemplateType = typeof(MovieElement))] + public ObservableCollection Movies + { + get; + set; + } + + [Section] + [Root(CellStyle = UITableViewCellStyle.Subtitle)] + [Caption("Root Address")] + public AddressView AddressView + { + get { return Get(()=>AddressView, new AddressView() {Number = "4751", Street ="Wilshire Blvd", City ="LA", State="CA", Zip ="90010" }); } + set { Set(()=>AddressView, value); } + } + + [Section(Order = 10)] + public IView DynamicView + { + get { return Get(()=>DynamicView, new View1()); } + set { Set(()=>DynamicView, value); } + } + + [Button] + [Section(Order = 11)] + public void ChangeDynamicView() + { + if(DynamicView.GetType() == typeof(View1)) + DynamicView = _View2; + else + DynamicView = _View1; + } + + [Inline] + [Section("", "This Address is the same as Root Address above")] + public AddressView InlineAddress + { + get { return Get(()=>AddressView); } + set { Set(()=>AddressView, value); } + } + + [Section] + public InterestingView InterestingStuff + { + get { return Get(()=>InterestingStuff, new InterestingView()); } + set { Set(()=>InterestingStuff, value); } + } + + public MovieListView() + { + Movies = new ObservableCollection(); + var dataModel = new MovieDataModel(); + dataModel.Load(); + + foreach(var movie in dataModel.Movies) + { + Movies.Add(new MovieView() { DataContext = movie }); + } + } + } +} + diff --git a/Samples/Views/MovieView.cs b/Samples/Views/MovieView.cs new file mode 100644 index 0000000..1a91b96 --- /dev/null +++ b/Samples/Views/MovieView.cs @@ -0,0 +1,84 @@ +namespace Samples +{ + using MonoTouch.Dialog; + using MonoTouch.MVVM; + using MonoTouch.UIKit; + + public class MovieView: View + { + [Section] + [Entry] + public string Name + { + get { return Get(()=>DataContext.Name); } + set { Set(()=>DataContext.Name, value); } + } + + [PopOnSelection] + public Genre Genre + { + get { return Get(()=>DataContext.Genre); } + set { Set(()=>DataContext.Genre, value); } + } + + [PopOnSelection] + public Rating Rating + { + get { return Get(()=>DataContext.Rating); } + set { Set(()=>DataContext.Rating, value); } + } + + [Bind(ValueConverterType = typeof(MoneyConverter))] + public float TicketPrice + { + get { return Get (() => DataContext.TicketPrice); } + set { Set (() => DataContext.TicketPrice, value); } + } + + [Bind(ValueConverterType = typeof(PercentConverter))] + public float CriticsRating + { + get { return Get (() => DataContext.CriticsRating); } + set { Set (() => DataContext.CriticsRating, value); } + } + + [Caption("Shown in 3D?")] + public bool ShownIn3D + { + get { return Get(()=>DataContext.ShownIn3D); } + set { Set(()=>DataContext.ShownIn3D, value); } + } + + public EnumCollection Location + { + get { return Get(() => DataContext.Location); } + set { Set(() => DataContext.Location, value); } + } + + [Section(Order = 1)] + [Button] + public void MakeMovieRestricted() + { + Rating = Rating.R; + } + + [Button] + [Caption("Toggle 3D")] + public void Toggle3D() + { + ShownIn3D = !ShownIn3D; + } + + [ToolbarButton(UIBarButtonSystemItem.Add)] + public void Add() + { + ShownIn3D = !ShownIn3D; + } + + public override string ToString() + { + return Name; + } + } +} + diff --git a/Samples/Views/View1.cs b/Samples/Views/View1.cs new file mode 100644 index 0000000..820a612 --- /dev/null +++ b/Samples/Views/View1.cs @@ -0,0 +1,33 @@ +using System; +using MonoTouch.MVVM; +using MonoTouch.Dialog; +namespace Samples +{ + public class View1: View + { + public string Person + { + get { return Get(()=>Person, "Robert"); } + set { Set(()=>Person, value); } + } + public int Age + { + get { return Get(()=>Age, 43); } + set { Set(()=>Age, value); } + } + } + + public class View2 : View + { + public string Company { get; set; } + [Inline] + public AddressView AddressView { get; set; } + + public View2() + { + Company = "Nowcom Corporation"; + AddressView = new AddressView() {Number = "4751", Street ="S Wilshire Blvd", City ="LA", State="CA", Zip ="90010" }; + } + } +} + diff --git a/View/IView.cs b/View/IView.cs new file mode 100644 index 0000000..c19ed35 --- /dev/null +++ b/View/IView.cs @@ -0,0 +1,45 @@ +// +// IView.cs: Interface for MVVM Views +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using MonoTouch.Dialog; + + public interface IView + { + object DataContext { get; set; } + IRoot Root { get; set; } + } + + public interface IView : IView where TDataContext : IViewModel, new() + { + new TDataContext DataContext { get; set; } + } +} + diff --git a/View/View.cs b/View/View.cs new file mode 100644 index 0000000..75e03f2 --- /dev/null +++ b/View/View.cs @@ -0,0 +1,152 @@ +// +// View.cs: Base class for MVVM Views +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using MonoTouch.Dialog; + using System.Collections.Generic; + using MonoTouch.UIKit; + + public class View : PropertyNotifier, IView + { + public object DataContext { get; set; } + public IRoot Root { get; set; } + + private IEnumerable _Sections; + + public View(): this("") + { + } + + public View(string caption) : base() + { + Root = CreateRoot(caption); + } + + public View(IRoot root) : base() + { + Root = root; + } + + protected virtual IRoot CreateRoot(string caption) + { + return new RootElement(caption); + } + + protected virtual IEnumerable Sections + { + get + { + if(_Sections == null) + { + _Sections = InitializeSections(); + } + + return _Sections; + } + set + { + _Sections = value; + ResetView(); + } + } + + protected virtual void ResetView() + { + Root.Clear(); + if (Sections != null) + Root.Add(Sections); + + if (DataContext != null) + Root.Caption = DataContext.ToString(); + } + + protected virtual IEnumerable InitializeSections() + { + return null; + } + + public override string ToString() + { + if (Root != null) + return Root.Caption; + + return string.Empty; + } + } + + public abstract class View : View, IView where TDataContext : IViewModel, new() + { + private TDataContext _DataContext; + public new TDataContext DataContext + { + get + { + return _DataContext; + } + set + { + if (!EqualityComparer.Default.Equals(_DataContext, value)) + { + ResetDataContext(value); + } + } + } + + public View(TDataContext dataContext, string caption) : base(caption) + { + ResetDataContext(dataContext); + } + + public View(string caption) : base(caption) + { + ResetDataContext(new TDataContext()); + } + + public View() : this("") + { + } + + protected void ResetDataContext(TDataContext dataContext) + { + if (DataContext != null) + { + DataContext.PropertyChanged -= (s, e) => { this.NotifyPropertyChanged (e.PropertyName); }; + } + + base.DataContext = dataContext; + _DataContext = dataContext; + + DataContext.PropertyChanged += (s, e)=> { this.NotifyPropertyChanged(e.PropertyName); }; + ResetView(); + } + } +} + diff --git a/ViewModel/IViewModel.cs b/ViewModel/IViewModel.cs new file mode 100644 index 0000000..a326fd7 --- /dev/null +++ b/ViewModel/IViewModel.cs @@ -0,0 +1,40 @@ +// +// IViewModel.cs: Interface for MVVM ViewModels +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System; + using System.ComponentModel; + + public interface IViewModel: INotifyPropertyChanged + { + //IView View { get; set; } + } +} + diff --git a/ViewModel/ViewModel.cs b/ViewModel/ViewModel.cs new file mode 100644 index 0000000..1b25ec1 --- /dev/null +++ b/ViewModel/ViewModel.cs @@ -0,0 +1,44 @@ +// +// ViewModel.cs: Base class for MVVM ViewModels +// +// Author: +// Robert Kozak (rkozak@gmail.com) +// +// Copyright 2011, Nowcom Corporation +// +// Code licensed under the MIT X11 license +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace MonoTouch.MVVM +{ + using System.ComponentModel; + using System; + + public abstract class ViewModel: PropertyNotifier, IViewModel + { + // public IView View { get; set; } + + public ViewModel() + { + } + } +} +