├── .gitattributes ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── Android ├── Assisticant.Android.csproj ├── BindingManagerExtensions.cs ├── ButtonBindingExtensions.cs ├── ListViewBindingExtensions.cs ├── NumberPickerBindingExtensions.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources │ └── Resource.Designer.cs └── TextBindingExtensions.cs ├── Assisticant.Netstandard └── Assisticant.Netstandard.csproj ├── Assisticant.UnitTest ├── Assisticant.UnitTest.Portable.csproj ├── Assisticant.UnitTest.WPF.csproj ├── CollectionContentTest.cs ├── CollectionData │ ├── SourceCollection.cs │ └── TargetCollection.cs ├── CollectionTest.cs ├── ComputedListTest.cs ├── ContactListData │ ├── Contact.cs │ ├── ContactList.cs │ ├── ContactListSortOrder.cs │ ├── ContactListViewModel.cs │ └── ContactViewModel.cs ├── DictionaryTests.cs ├── DirectComputed.cs ├── DirectComputedTest.cs ├── DirectConcurrencyTest.cs ├── DynamicSortOrderTest.cs ├── Images │ ├── UnitTestLogo.scale-100.png │ ├── UnitTestSmallLogo.scale-100.png │ ├── UnitTestSplashScreen.scale-100.png │ └── UnitTestStoreLogo.scale-100.png ├── IndirectComputed.cs ├── IndirectComputedTest.cs ├── IndirectConcurrencyTest.cs ├── LargeListTest.cs ├── MemoryLeakTest.cs ├── MultithreadedData │ ├── AbstractThread.cs │ ├── SourceThread.cs │ └── TargetThread.cs ├── MultithreadedTest.cs ├── NotificationTest.cs ├── NotifyDataErrorInfoTests.cs ├── Package.appxmanifest ├── Properties │ └── AssemblyInfo.cs ├── SortedCollectionTest.cs ├── SourceData.cs ├── SubscriptionTest.cs └── packages.config ├── Assisticant.sln ├── Assisticant ├── Assisticant.Framework.csproj ├── Assisticant.Universal.csproj ├── Assisticant.WPF.csproj ├── AssisticantExtensions.cs ├── AssisticantReflectionUtils.cs ├── Binding │ ├── BindingManager.cs │ ├── IDisplayDataConverter.cs │ └── IInputSubscription.cs ├── BindingInterceptor.cs ├── Collections │ ├── ComputedDictionary.cs │ ├── ComputedList.cs │ ├── Impl │ │ └── UpdateCollectionHelper.cs │ ├── ObservableDictionary.cs │ └── ObservableList.cs ├── Computed.cs ├── ComputedJob.cs ├── Descriptors │ ├── PlatformProxy.NotifyDataErrorInfo.cs │ ├── PlatformProxy.cs │ ├── ProxyDescriptionProvider.cs │ ├── ProxyEventDescriptor.cs │ ├── ProxyPropertyDescriptor.cs │ └── ProxyTypeDescriptor.cs ├── Fields │ ├── Computed.cs │ ├── ComputedSubscription.cs │ └── Observable.cs ├── ForView.cs ├── InertialProperty.cs ├── MakeCommand.cs ├── MemoizedTypeName.cs ├── Metas │ ├── AtomSlot.cs │ ├── BindingListSlot.cs │ ├── CollectionItem.cs │ ├── CollectionSlot.cs │ ├── CommandMeta.cs │ ├── ComputedMeta.cs │ ├── FieldMeta.cs │ ├── ListSlot.cs │ ├── MemberMeta.cs │ ├── MemberSlot.cs │ ├── MethodCommand.cs │ ├── ObservableMeta.cs │ ├── PassThroughSlot.cs │ ├── PropertyMeta.cs │ ├── TypeMeta.cs │ ├── ValuePropertyMeta.cs │ ├── ViewModelTypes.cs │ └── ViewProxy.cs ├── NamedPrecedents.cs ├── NotifyAfterAttribute.cs ├── Observable.cs ├── Precedent.cs ├── Properties │ └── AssemblyInfo.cs ├── PropertyTracker.cs ├── RecycleBin.cs ├── StaticExtension.cs ├── ThreadLocal.cs ├── Timers │ ├── DroppingTimeSpan.cs │ ├── FloatingDateTime.cs │ ├── FloatingTimeSpan.cs │ ├── FloatingTimeZone.cs │ ├── ObservableTimer.cs │ ├── RisingTimeSpan.cs │ └── UtcTimeZone.cs ├── UpdateScheduler.cs ├── Validation │ ├── ExpressionExtensions.cs │ ├── IValidation.cs │ ├── IValidationRules.cs │ ├── NumericPropertyValidationContextExtentions.cs │ ├── ObjectPropertyValidationContextExtensions.cs │ ├── OptionalMessagePropertyValidationContext.cs │ ├── PropertyPredicateContext.cs │ ├── PropertyRuleset.cs │ ├── PropertyValidationContext.cs │ ├── PropertyValidationContextBase.cs │ ├── PropertyValidator.cs │ ├── StringPropertyValidationContextExtensions.cs │ ├── ValidationRules.cs │ └── fluent.gv ├── ViewModelBase.cs ├── ViewModelLocatorBase.cs ├── ViewSelector.cs ├── ViewSelectorExtension.cs ├── WeakArray.cs ├── WeakHashSet.cs └── XamlTypes │ ├── PlatformProxy.cs │ ├── PrimitiveXamlType.cs │ ├── ProxyXamlMember.cs │ ├── ProxyXamlMetadataProvider.cs │ └── ProxyXamlType.cs ├── Common ├── CommonProperties.cs └── Mallardsoft.snk ├── License.txt ├── NuGet ├── App │ ├── Assisticant.App.nuspec │ └── content │ │ ├── Models │ │ ├── Document.cs.pp │ │ ├── Item.cs.pp │ │ └── Selection.cs.pp │ │ ├── Readme_Assisticant.txt.pp │ │ └── ViewModels │ │ ├── ItemHeader.cs.pp │ │ ├── ItemViewModel.cs.pp │ │ ├── MainViewModel.cs.pp │ │ └── ViewModelLocator.cs.pp ├── Core │ └── assisticant.nuspec └── Snippets │ ├── Assisticant.Snippets.1.1.0.nupkg │ ├── assisticant.snippets.nuspec │ ├── content │ └── Readme_Assisticant_Snippets.txt │ └── tools │ ├── comp.snippet │ ├── compList.snippet │ ├── install.ps1 │ ├── obs.snippet │ ├── obsList.snippet │ └── uninstall.ps1 ├── README.md └── iOS ├── Assisticant.iOS.csproj ├── BindingManagerExtensions.cs ├── ButtonBindingExtensions.cs ├── Properties └── AssemblyInfo.cs ├── StepperBindingExtensions.cs ├── TableViewBindingExtensions.cs └── TextBindingExtensions.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Run this workflow every time a commit gets pushed to main or a pull request gets opened against main 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | name: Call Azure Pipeline 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Azure Pipelines Action 18 | uses: Azure/pipelines@v1 19 | with: 20 | azure-devops-project-url: https://michaellperry.visualstudio.com/Assisticant 21 | azure-pipeline-name: 'Build from GitHub' 22 | azure-devops-token: ${{ secrets.AZURE_DEVOPS_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | packages/ 2 | NuGet/Core/lib/ 3 | NuGet/Forms/lib/ 4 | .vs/ 5 | 6 | #ignore thumbnails created by windows 7 | Thumbs.db 8 | #Ignore files build by Visual Studio 9 | *.obj 10 | *.pdb 11 | *.user 12 | *.aps 13 | *.pch 14 | *.vspscc 15 | *_i.c 16 | *_p.c 17 | *.ncb 18 | *.suo 19 | *.tlb 20 | *.tlh 21 | *.bak 22 | *.cache 23 | *.ilk 24 | *.log 25 | [Bb]in 26 | [Dd]ebug*/ 27 | *.lib 28 | *.sbr 29 | obj/ 30 | [Rr]elease*/ 31 | _ReSharper*/ 32 | [Tt]est[Rr]esult* 33 | .svn/ 34 | *.[Mm][Dd][Ff] 35 | *.[Ll][Dd][Ff] 36 | *.dbmdl 37 | *.eto 38 | *.ncb 39 | *.user 40 | *.stuff 41 | ~*.* 42 | *.suo 43 | *.xap 44 | *.vs10x 45 | Index.dat 46 | Storage.dat 47 | Generated_Code*/ 48 | *.Publish.xml 49 | *.docstates 50 | AppPackages/ 51 | *.ncrunchproject 52 | *.ncrunchsolution 53 | -------------------------------------------------------------------------------- /Android/Assisticant.Android.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {C4E189E5-7905-4678-A340-018F89274F49} 9 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | {9ef11e43-1701-4396-8835-8392d57abb70} 11 | Library 12 | Properties 13 | Assisticant.Binding 14 | Assisticant.Android 15 | 512 16 | True 17 | Resources\Resource.Designer.cs 18 | Off 19 | false 20 | v9.0 21 | true 22 | 23 | 24 | true 25 | portable 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | portable 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | bin\Release\Assisticant.Android.xml 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {2caefd06-13f2-47ea-b600-78c332d5bb2b} 61 | Assisticant.Netstandard 62 | 63 | 64 | 65 | 66 | 67 | 68 | 75 | -------------------------------------------------------------------------------- /Android/BindingManagerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using System; 3 | using System.Threading; 4 | 5 | namespace Assisticant.Binding 6 | { 7 | /// 8 | /// Binding manager extensions. 9 | /// 10 | public static class BindingManagerExtensions 11 | { 12 | /// 13 | /// Initialize the binding manager for an activity. 14 | /// 15 | /// The binding manager for the activity. 16 | /// The activity that owns the binding manager. 17 | public static void Initialize(this BindingManager bindings, Activity activity) 18 | { 19 | UpdateScheduler.Initialize(action => 20 | { 21 | ThreadPool.QueueUserWorkItem(delegate(Object obj) 22 | { 23 | activity.RunOnUiThread(action); 24 | }); 25 | }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Android/ButtonBindingExtensions.cs: -------------------------------------------------------------------------------- 1 | using Android.Widget; 2 | using System; 3 | 4 | namespace Assisticant.Binding 5 | { 6 | /// 7 | /// Button binding extensions. 8 | /// 9 | public static class ButtonBindingExtensions 10 | { 11 | class ButtonClickSubscription : IInputSubscription 12 | { 13 | private Button _control; 14 | private Action _action; 15 | 16 | public ButtonClickSubscription(Button control, Action action) 17 | { 18 | _control = control; 19 | _action = action; 20 | } 21 | 22 | public void Subscribe() 23 | { 24 | _control.Click += ButtonClick; 25 | } 26 | 27 | public void Unsubscribe() 28 | { 29 | _control.Click -= ButtonClick; 30 | } 31 | 32 | private void ButtonClick(object sender, EventArgs e) 33 | { 34 | _action(); 35 | } 36 | } 37 | 38 | /// 39 | /// Bind a button's command to an action. 40 | /// 41 | /// The binding manager. 42 | /// The button. 43 | /// The action to perform when the button is tapped. 44 | public static void BindCommand(this BindingManager bindings, Button control, Action action) 45 | { 46 | bindings.Bind(new ButtonClickSubscription(control, action)); 47 | } 48 | 49 | /// 50 | /// Bind a button's command and Enabled property to an action and a condition. 51 | /// 52 | /// The binding manager. 53 | /// The button. 54 | /// The ation to perform when the button is tapped. 55 | /// The condition that controls when the button is enabled. 56 | public static void BindCommand(this BindingManager bindings, Button control, Action action, Func condition) 57 | { 58 | bindings.Bind(condition, b => control.Enabled = b, new ButtonClickSubscription(control, action)); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Android/NumberPickerBindingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Android.Widget; 3 | 4 | namespace Assisticant.Binding 5 | { 6 | /// 7 | /// Number picker binding extensions. 8 | /// 9 | public static class NumberPickerBindingExtensions 10 | { 11 | class ValueBinding : IInputSubscription 12 | { 13 | private NumberPicker _control; 14 | private Action _input; 15 | private IDisplayDataConverter _converter; 16 | 17 | public ValueBinding(NumberPicker control, Action input, IDisplayDataConverter converter) 18 | { 19 | _control = control; 20 | _input = input; 21 | _converter = converter; 22 | } 23 | 24 | public void Subscribe() 25 | { 26 | _control.ValueChanged += NumberPickerValueChanged; 27 | } 28 | 29 | public void Unsubscribe() 30 | { 31 | _control.ValueChanged -= NumberPickerValueChanged; 32 | } 33 | 34 | private void NumberPickerValueChanged (object sender, EventArgs e) 35 | { 36 | _input(_converter.ConvertInput(_control.Value)); 37 | } 38 | } 39 | 40 | class Identity : IDisplayDataConverter 41 | { 42 | public static Identity Instance = new Identity(); 43 | 44 | public int ConvertOutput (int data) 45 | { 46 | return data; 47 | } 48 | 49 | public int ConvertInput (int display) 50 | { 51 | return display; 52 | } 53 | } 54 | 55 | /// 56 | /// Bind the value of a number picker to a property using a value converter. 57 | /// 58 | /// The binding manager. 59 | /// The number picker. 60 | /// A function that gets the property. 61 | /// An action sets the property. 62 | /// A custom value converter to type double. 63 | /// The type of property to which the value is bound. 64 | public static void BindValue(this BindingManager bindings, NumberPicker control, Func output, Action input, IDisplayDataConverter converter) 65 | { 66 | bindings.Bind (output, s => control.Value = converter.ConvertOutput(s), new ValueBinding(control, input, converter)); 67 | } 68 | 69 | /// 70 | /// Bind the value of a number picker to an integer property. 71 | /// 72 | /// The binding manager. 73 | /// The number picker. 74 | /// A function that gets the property. 75 | /// An action sets the property. 76 | public static void BindValue(this BindingManager bindings, NumberPicker control, Func output, Action input) 77 | { 78 | BindValue (bindings, control, output, input, Identity.Instance); 79 | } 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Android/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using Android.App; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Assisticant.Android")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("Assisticant.Android")] 14 | [assembly: AssemblyCopyright("Copyright © 2016")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: ComVisible(false)] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.0.0.0")] 30 | [assembly: AssemblyFileVersion("1.0.0.0")] 31 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/CollectionContentTest.cs: -------------------------------------------------------------------------------- 1 | #if NETFX_CORE 2 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 3 | #else 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | #endif 6 | using Assisticant.UnitTest.ContactListData; 7 | using System.Linq; 8 | 9 | namespace Assisticant.UnitTest 10 | { 11 | [TestClass] 12 | public class CollectionContentTest 13 | { 14 | private ContactList _contactList; 15 | private ContactListViewModel _viewModel; 16 | private int _collectionChangedCount; 17 | 18 | [TestInitialize] 19 | public void Initialize() 20 | { 21 | _contactList = new ContactList(); 22 | _viewModel = new ContactListViewModel(_contactList); 23 | _contactList.AddContact(new Contact() { FirstName = "Michael", LastName = "Perry" }); 24 | _contactList.AddContact(new Contact() { FirstName = "Ada", LastName = "Lovelace" }); 25 | _contactList.AddContact(new Contact() { FirstName = "Charles", LastName = "Babbage" }); 26 | 27 | _collectionChangedCount = 0; 28 | _viewModel.ContactsCollectionChanged += 29 | delegate 30 | { 31 | _collectionChangedCount++; 32 | }; 33 | } 34 | 35 | [TestMethod] 36 | public void WhenContactAddedShouldNotifyCollectionChanged() 37 | { 38 | ContactViewModel firstByDefaultOrder = _viewModel.Contacts.First(); 39 | _contactList.AddContact(new Contact() { FirstName = "Martin", LastName = "Fowler" }); 40 | 41 | Assert.AreEqual(1, _collectionChangedCount); 42 | } 43 | 44 | [TestMethod] 45 | public void WhenContactDeletedShouldNotifyCollectionChanged() 46 | { 47 | ContactViewModel firstByDefaultOrder = _viewModel.Contacts.First(); 48 | _contactList.DeleteContact(_contactList.Contacts.First()); 49 | 50 | Assert.AreEqual(1, _collectionChangedCount); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/CollectionData/SourceCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Assisticant.UnitTest.CollectionData 4 | { 5 | public class SourceCollection 6 | { 7 | private List _numbers = new List(); 8 | private Observable _indNumbers = new Observable(); 9 | 10 | public void Insert(int number) 11 | { 12 | _indNumbers.OnSet(); 13 | _numbers.Add(number); 14 | } 15 | 16 | public IEnumerable Numbers 17 | { 18 | get { _indNumbers.OnGet(); return _numbers; } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/CollectionData/TargetCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Assisticant.UnitTest.CollectionData 5 | { 6 | public class TargetCollection 7 | { 8 | private SourceCollection _source; 9 | private List _results = new List(); 10 | private Computed _depResults; 11 | 12 | public TargetCollection(SourceCollection source) 13 | { 14 | _source = source; 15 | _depResults = new Computed(UpdateResults); 16 | } 17 | 18 | public IEnumerable Results 19 | { 20 | get { _depResults.OnGet(); return _results; } 21 | } 22 | 23 | private void UpdateResults() 24 | { 25 | _results = _source.Numbers.Select(number => number + 1).ToList(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/CollectionTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | #if NETFX_CORE 3 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 4 | #else 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | #endif 7 | using Assisticant.UnitTest.CollectionData; 8 | 9 | namespace Assisticant.UnitTest 10 | { 11 | [TestClass] 12 | public class CollectionTest 13 | { 14 | private SourceCollection _source; 15 | 16 | [TestInitialize] 17 | public void Initialize() 18 | { 19 | _source = new SourceCollection(); 20 | } 21 | 22 | [TestMethod] 23 | public void InsertIntoSourceShouldCauseNewElementInTarget() 24 | { 25 | TargetCollection target = new TargetCollection(_source); 26 | 27 | _source.Insert(3); 28 | int firstNumber = target.Results.Single(); 29 | Assert.AreEqual(4, firstNumber); 30 | } 31 | 32 | [TestMethod] 33 | public void InsertIntoSourceBeforeTargetCreatedShouldCauseNewElementInTarget() 34 | { 35 | _source.Insert(3); 36 | 37 | TargetCollection target = new TargetCollection(_source); 38 | 39 | int firstNumber = target.Results.Single(); 40 | Assert.AreEqual(4, firstNumber); 41 | } 42 | 43 | [TestMethod] 44 | public void InsertASecondIntoSourceTargetShouldUpdate() 45 | { 46 | TargetCollection target = new TargetCollection(_source); 47 | _source.Insert(42); 48 | target.Results.Single(); 49 | 50 | _source.Insert(43); 51 | int[] results = target.Results.ToArray(); 52 | 53 | Assert.AreEqual(2, results.Length); 54 | Assert.AreEqual(43, results[0]); 55 | Assert.AreEqual(44, results[1]); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/ComputedListTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | #if NETFX_CORE 3 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 4 | #else 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | #endif 7 | using Assisticant.Collections; 8 | using Assisticant.UnitTest.ContactListData; 9 | 10 | namespace Assisticant.UnitTest 11 | { 12 | [TestClass] 13 | public class ComputedListTest 14 | { 15 | private ContactList _model; 16 | private ComputedList _contactViewModels; 17 | 18 | [TestInitialize] 19 | public void Initialize() 20 | { 21 | _model = new ContactList(); 22 | _model.AddContact(new Contact() { FirstName = "Charles", LastName = "Babbage" }); 23 | _model.AddContact(new Contact() { FirstName = "Alan", LastName = "Turing" }); 24 | _contactViewModels = new ComputedList(() => 25 | from c in _model.Contacts 26 | select new ContactViewModel(c) 27 | ); 28 | } 29 | 30 | [TestMethod] 31 | public void ComputedListMapsToSourceList() 32 | { 33 | Assert.AreEqual(2, _contactViewModels.Count); 34 | Assert.AreEqual("Charles Babbage", _contactViewModels[0].FullName); 35 | Assert.AreEqual("Alan Turing", _contactViewModels[1].FullName); 36 | } 37 | 38 | [TestMethod] 39 | public void WhenSourceListChanges_ComputedListChanges() 40 | { 41 | _model.AddContact(new Contact() { FirstName = "Bertrand", LastName = "Meyer" }); 42 | 43 | Assert.AreEqual(3, _contactViewModels.Count); 44 | Assert.AreEqual("Bertrand Meyer", _contactViewModels[2].FullName); 45 | } 46 | 47 | [TestMethod] 48 | public void ComputedsAreRecycled() 49 | { 50 | ContactViewModel oldObject = _contactViewModels[0]; 51 | _model.AddContact(new Contact() { FirstName = "Bertrand", LastName = "Meyer" }); 52 | 53 | Assert.AreSame(oldObject, _contactViewModels[0]); 54 | } 55 | 56 | [TestMethod] 57 | public void ListDependsUponSourceCollection() 58 | { 59 | int triggerUpdate = _contactViewModels.Count; 60 | bool wasInvalidated = false; 61 | _contactViewModels.ComputedSentry.Invalidated += delegate 62 | { 63 | wasInvalidated = true; 64 | }; 65 | 66 | _model.AddContact(new Contact() { FirstName = "Bertrand", LastName = "Meyer" }); 67 | 68 | Assert.IsTrue(wasInvalidated, "The computed list should be invalidated."); 69 | } 70 | 71 | [TestMethod] 72 | public void ListDoesNotDependUponChildProperties() 73 | { 74 | int triggerUpdate = _contactViewModels.Count; 75 | bool wasInvalidated = false; 76 | _contactViewModels.ComputedSentry.Invalidated += delegate 77 | { 78 | wasInvalidated = true; 79 | }; 80 | 81 | _model.Contacts.First().FirstName = "George"; 82 | 83 | Assert.IsFalse(wasInvalidated, "The computed list should not be invalidated."); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/ContactListData/Contact.cs: -------------------------------------------------------------------------------- 1 | using Assisticant; 2 | 3 | namespace Assisticant.UnitTest.ContactListData 4 | { 5 | public class Contact 6 | { 7 | private string _firstName; 8 | private string _lastName; 9 | 10 | #region Observable properties 11 | // Generated by Update Controls -------------------------------- 12 | private Observable _indFirstName = new Observable(); 13 | private Observable _indLastName = new Observable(); 14 | 15 | public string FirstName 16 | { 17 | get { _indFirstName.OnGet(); return _firstName; } 18 | set { _indFirstName.OnSet(); _firstName = value; } 19 | } 20 | 21 | public string LastName 22 | { 23 | get { _indLastName.OnGet(); return _lastName; } 24 | set { _indLastName.OnSet(); _lastName = value; } 25 | } 26 | // End generated code -------------------------------- 27 | #endregion 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/ContactListData/ContactList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Assisticant.Collections; 3 | 4 | namespace Assisticant.UnitTest.ContactListData 5 | { 6 | public class ContactList 7 | { 8 | private ObservableList _contacts = new ObservableList(); 9 | 10 | public void AddContact(Contact contact) 11 | { 12 | _contacts.Add(contact); 13 | } 14 | 15 | public void DeleteContact(Contact contact) 16 | { 17 | _contacts.Remove(contact); 18 | } 19 | 20 | public IEnumerable Contacts 21 | { 22 | get { return _contacts; } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/ContactListData/ContactListSortOrder.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Assisticant.UnitTest.ContactListData 3 | { 4 | public enum ContactListSortOrder 5 | { 6 | NoOrder, 7 | FirstName, 8 | LastName 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/ContactListData/ContactListViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Assisticant.UnitTest.ContactListData 6 | { 7 | public class ContactListViewModel 8 | { 9 | private ContactList _contactList; 10 | private ContactListSortOrder _sortOrder; 11 | private Observable _indSortOrder = new Observable(); 12 | 13 | private List _contactViewModels; 14 | private Computed _depContactViewModels; 15 | 16 | public delegate void NotifyCollectionChanged(); 17 | public event NotifyCollectionChanged ContactsCollectionChanged; 18 | 19 | public ContactListViewModel(ContactList contactList) 20 | { 21 | _contactList = contactList; 22 | _depContactViewModels = new Computed(UpdateContactViewModels); 23 | _depContactViewModels.Invalidated += new Action(_depContactViewModels_Invalidated); 24 | } 25 | 26 | void _depContactViewModels_Invalidated() 27 | { 28 | ContactsCollectionChanged(); 29 | } 30 | 31 | public ContactListSortOrder SortOrder 32 | { 33 | get 34 | { 35 | _indSortOrder.OnGet(); 36 | return _sortOrder; 37 | } 38 | set 39 | { 40 | if (_sortOrder != value) _indSortOrder.OnSet(); 41 | _sortOrder = value; 42 | } 43 | } 44 | 45 | public IEnumerable Contacts 46 | { 47 | get 48 | { 49 | _depContactViewModels.OnGet(); 50 | return _contactViewModels; 51 | } 52 | } 53 | 54 | private void UpdateContactViewModels() 55 | { 56 | IEnumerable contacts = _contactList.Contacts; 57 | if (SortOrder == ContactListSortOrder.FirstName) 58 | contacts = contacts.OrderBy(contact => contact.FirstName); 59 | else if (SortOrder == ContactListSortOrder.LastName) 60 | contacts = contacts.OrderBy(contact => contact.LastName); 61 | 62 | _contactViewModels = contacts 63 | .Select(contact => new ContactViewModel(contact)) 64 | .ToList(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/ContactListData/ContactViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Assisticant.UnitTest.ContactListData 4 | { 5 | public class ContactViewModel 6 | { 7 | private Contact _contact; 8 | 9 | public ContactViewModel(Contact contact) 10 | { 11 | _contact = contact; 12 | } 13 | 14 | public string FullName 15 | { 16 | get 17 | { 18 | return string.Format("{0} {1}", _contact.FirstName, _contact.LastName); 19 | } 20 | } 21 | 22 | public override bool Equals(object obj) 23 | { 24 | if (this == obj) 25 | return true; 26 | ContactViewModel that = obj as ContactViewModel; 27 | if (that == null) 28 | return false; 29 | return _contact == that._contact; 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | return _contact.GetHashCode(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/DirectComputed.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Fields; 2 | 3 | namespace Assisticant.UnitTest 4 | { 5 | public class DirectComputed 6 | { 7 | private SourceData _source; 8 | 9 | private Computed _property; 10 | 11 | public DirectComputed(SourceData source) 12 | { 13 | _source = source; 14 | _property = new Computed(() => _source.SourceProperty); 15 | } 16 | 17 | public int ComputedProperty 18 | { 19 | get { return _property; } 20 | } 21 | 22 | public bool IsUpToDate 23 | { 24 | get { return _property.IsUpToDate; } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/DirectComputedTest.cs: -------------------------------------------------------------------------------- 1 | #if NETFX_CORE 2 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 3 | #else 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | #endif 6 | 7 | namespace Assisticant.UnitTest 8 | { 9 | [TestClass] 10 | public class DirectComputedTest 11 | { 12 | public TestContext TestContext { get; set; } 13 | 14 | private SourceData _source; 15 | private DirectComputed _computed; 16 | 17 | [TestInitialize] 18 | public void Initialize() 19 | { 20 | _source = new SourceData(); 21 | _computed = new DirectComputed(_source); 22 | } 23 | 24 | [TestMethod] 25 | public void ComputedIsInitiallyOutOfDate() 26 | { 27 | Assert.IsFalse(_computed.IsUpToDate, "The dependent is initially up to date"); 28 | } 29 | 30 | [TestMethod] 31 | public void ComputedRemainsOutOfDateOnChange() 32 | { 33 | _source.SourceProperty = 3; 34 | Assert.IsFalse(_computed.IsUpToDate, "The dependent is up to date after change"); 35 | } 36 | 37 | [TestMethod] 38 | public void ComputedIsUpdatedOnGet() 39 | { 40 | int fetch = _computed.ComputedProperty; 41 | Assert.IsTrue(_computed.IsUpToDate, "The dependent has not been updated"); 42 | } 43 | 44 | [TestMethod] 45 | public void ComputedIsUpdatedAfterChangeOnGet() 46 | { 47 | _source.SourceProperty = 3; 48 | int fetch = _computed.ComputedProperty; 49 | Assert.IsTrue(_computed.IsUpToDate, "The dependent has not been updated"); 50 | } 51 | 52 | [TestMethod] 53 | public void ComputedGetsValueFromItsPrecedent() 54 | { 55 | _source.SourceProperty = 3; 56 | Assert.AreEqual(3, _computed.ComputedProperty); 57 | } 58 | 59 | [TestMethod] 60 | public void ComputedIsOutOfDateAgainAfterChange() 61 | { 62 | _source.SourceProperty = 3; 63 | int fetch = _computed.ComputedProperty; 64 | _source.SourceProperty = 4; 65 | Assert.IsFalse(_computed.IsUpToDate, "The dependent did not go out of date"); 66 | } 67 | 68 | [TestMethod] 69 | public void ComputedIsUpdatedAgainAfterChange() 70 | { 71 | _source.SourceProperty = 3; 72 | int fetch = _computed.ComputedProperty; 73 | _source.SourceProperty = 4; 74 | fetch = _computed.ComputedProperty; 75 | Assert.IsTrue(_computed.IsUpToDate, "The dependent did not get udpated"); 76 | } 77 | 78 | [TestMethod] 79 | public void ComputedGetsValueFromItsPrecedentAgainAfterChange() 80 | { 81 | _source.SourceProperty = 3; 82 | int fetch = _computed.ComputedProperty; 83 | _source.SourceProperty = 4; 84 | Assert.AreEqual(4, _computed.ComputedProperty); 85 | } 86 | 87 | [TestMethod] 88 | public void PrecedentIsOnlyAskedOnce() 89 | { 90 | int getCount = 0; 91 | _source.AfterGet += () => ++getCount; 92 | 93 | _source.SourceProperty = 3; 94 | int fetch = _computed.ComputedProperty; 95 | fetch = _computed.ComputedProperty; 96 | 97 | Assert.AreEqual(1, getCount); 98 | } 99 | 100 | [TestMethod] 101 | public void PrecedentIsAskedAgainAfterChange() 102 | { 103 | int getCount = 0; 104 | _source.AfterGet += () => ++getCount; 105 | 106 | _source.SourceProperty = 3; 107 | int fetch = _computed.ComputedProperty; 108 | fetch = _computed.ComputedProperty; 109 | _source.SourceProperty = 4; 110 | fetch = _computed.ComputedProperty; 111 | fetch = _computed.ComputedProperty; 112 | 113 | Assert.AreEqual(2, getCount); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/DirectConcurrencyTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if NETFX_CORE 3 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 4 | #else 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | #endif 7 | 8 | namespace Assisticant.UnitTest 9 | { 10 | [TestClass] 11 | public class DirectConcurrencyTest 12 | { 13 | public TestContext TestContext { get; set; } 14 | 15 | private SourceData _source; 16 | private DirectComputed _computed; 17 | 18 | [TestInitialize] 19 | public void Initialize() 20 | { 21 | _source = new SourceData(); 22 | _computed = new DirectComputed(_source); 23 | } 24 | 25 | [TestMethod] 26 | public void ComputedIsOutOfDateAfterConcurrentChange() 27 | { 28 | _source.AfterGet += () => _source.SourceProperty = 4; 29 | 30 | _source.SourceProperty = 3; 31 | int fetch = _computed.ComputedProperty; 32 | 33 | Assert.IsFalse(_computed.IsUpToDate, "The dependent is up to date after a concurrent change"); 34 | } 35 | 36 | [TestMethod] 37 | public void ComputedHasOriginalValueAfterConcurrentChange() 38 | { 39 | _source.AfterGet += () => _source.SourceProperty = 4; 40 | 41 | _source.SourceProperty = 3; 42 | Assert.AreEqual(3, _computed.ComputedProperty); 43 | } 44 | 45 | [TestMethod] 46 | public void ComputedIsUpToDateAfterSecondGet() 47 | { 48 | Action concurrentChange = () => _source.SourceProperty = 4; 49 | _source.AfterGet += concurrentChange; 50 | 51 | _source.SourceProperty = 3; 52 | int fetch = _computed.ComputedProperty; 53 | 54 | _source.AfterGet -= concurrentChange; 55 | 56 | fetch = _computed.ComputedProperty; 57 | 58 | Assert.IsTrue(_computed.IsUpToDate, "The dependent is not up to date after the second get"); 59 | } 60 | 61 | [TestMethod] 62 | public void ComputedHasModifiedValueAfterSecondGet() 63 | { 64 | Action concurrentChange = () => _source.SourceProperty = 4; 65 | _source.AfterGet += concurrentChange; 66 | 67 | _source.SourceProperty = 3; 68 | int fetch = _computed.ComputedProperty; 69 | 70 | _source.AfterGet -= concurrentChange; 71 | 72 | Assert.AreEqual(4, _computed.ComputedProperty); 73 | } 74 | 75 | [TestMethod] 76 | public void ComputedStillDependsUponPrecedent() 77 | { 78 | Action concurrentChange = () => _source.SourceProperty = 4; 79 | _source.AfterGet += concurrentChange; 80 | 81 | _source.SourceProperty = 3; 82 | int fetch = _computed.ComputedProperty; 83 | 84 | _source.AfterGet -= concurrentChange; 85 | 86 | fetch = _computed.ComputedProperty; 87 | _source.SourceProperty = 5; 88 | 89 | Assert.IsFalse(_computed.IsUpToDate, "The dependent no longer depends upon the precedent"); 90 | } 91 | 92 | [TestMethod] 93 | public void ComputedGetsTheUltimateValue() 94 | { 95 | Action concurrentChange = () => _source.SourceProperty = 4; 96 | _source.AfterGet += concurrentChange; 97 | 98 | _source.SourceProperty = 3; 99 | int fetch = _computed.ComputedProperty; 100 | 101 | _source.AfterGet -= concurrentChange; 102 | 103 | fetch = _computed.ComputedProperty; 104 | _source.SourceProperty = 5; 105 | 106 | Assert.AreEqual(5, _computed.ComputedProperty); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/DynamicSortOrderTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | #if NETFX_CORE 3 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 4 | #else 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | #endif 7 | using Assisticant.UnitTest.ContactListData; 8 | 9 | namespace Assisticant.UnitTest 10 | { 11 | [TestClass] 12 | public class DynamicSortOrderTest 13 | { 14 | private ContactList _contactList; 15 | private ContactListViewModel _viewModel; 16 | private int _collectionChangedCount; 17 | 18 | [TestInitialize] 19 | public void Initialize() 20 | { 21 | _contactList = new ContactList(); 22 | _viewModel = new ContactListViewModel(_contactList); 23 | _contactList.AddContact(new Contact() { FirstName = "Michael", LastName = "Perry" }); 24 | _contactList.AddContact(new Contact() { FirstName = "Ada", LastName = "Lovelace" }); 25 | _contactList.AddContact(new Contact() { FirstName = "Charles", LastName = "Babbage" }); 26 | 27 | _collectionChangedCount = 0; 28 | _viewModel.ContactsCollectionChanged += 29 | delegate 30 | { 31 | _collectionChangedCount++; 32 | }; 33 | } 34 | 35 | [TestMethod] 36 | public void InitiallyNoEventFired() 37 | { 38 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 39 | ContactViewModel firstByFirstName = _viewModel.Contacts.First(); 40 | Assert.AreEqual(0, _collectionChangedCount); 41 | } 42 | 43 | [TestMethod] 44 | public void WhenOrderByFirstNameAndFirstNameChangesShouldNotify() 45 | { 46 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 47 | ContactViewModel firstByFirstName = _viewModel.Contacts.First(); 48 | _contactList.Contacts.First().FirstName = "George"; 49 | 50 | Assert.AreEqual(1, _collectionChangedCount); 51 | } 52 | 53 | [TestMethod] 54 | public void WhenOrderByLastNameAndFirstNameChangesShouldNotNotify() 55 | { 56 | _viewModel.SortOrder = ContactListSortOrder.LastName; 57 | ContactViewModel firstByLastName = _viewModel.Contacts.First(); 58 | _contactList.Contacts.First().FirstName = "George"; 59 | 60 | Assert.AreEqual(0, _collectionChangedCount); 61 | } 62 | 63 | [TestMethod] 64 | public void WhenSortOrderChangedShouldNotify() 65 | { 66 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 67 | ContactViewModel firstByFirstName = _viewModel.Contacts.First(); 68 | _contactList.Contacts.First().FirstName = "George"; 69 | ContactViewModel firstByFirstNameAgain = _viewModel.Contacts.First(); 70 | 71 | _viewModel.SortOrder = ContactListSortOrder.LastName; 72 | 73 | Assert.AreEqual(2, _collectionChangedCount); 74 | } 75 | 76 | [TestMethod] 77 | public void WhenSortOrderChangesAndFirstNameChangesShouldNoLongerNotify() 78 | { 79 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 80 | ContactViewModel firstByFirstName = _viewModel.Contacts.First(); 81 | _contactList.Contacts.First().FirstName = "George"; 82 | ContactViewModel firstByFirstNameAgain = _viewModel.Contacts.First(); 83 | 84 | _viewModel.SortOrder = ContactListSortOrder.LastName; 85 | var firstByLastName = _viewModel.Contacts.First(); 86 | 87 | _contactList.Contacts.First().FirstName = "Charles"; 88 | 89 | Assert.AreEqual(2, _collectionChangedCount); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/Images/UnitTestLogo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellperry/Assisticant/1b8aae89f823ddc7fa4a3fa0e7df68a5bc0540bc/Assisticant.UnitTest/Images/UnitTestLogo.scale-100.png -------------------------------------------------------------------------------- /Assisticant.UnitTest/Images/UnitTestSmallLogo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellperry/Assisticant/1b8aae89f823ddc7fa4a3fa0e7df68a5bc0540bc/Assisticant.UnitTest/Images/UnitTestSmallLogo.scale-100.png -------------------------------------------------------------------------------- /Assisticant.UnitTest/Images/UnitTestSplashScreen.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellperry/Assisticant/1b8aae89f823ddc7fa4a3fa0e7df68a5bc0540bc/Assisticant.UnitTest/Images/UnitTestSplashScreen.scale-100.png -------------------------------------------------------------------------------- /Assisticant.UnitTest/Images/UnitTestStoreLogo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellperry/Assisticant/1b8aae89f823ddc7fa4a3fa0e7df68a5bc0540bc/Assisticant.UnitTest/Images/UnitTestStoreLogo.scale-100.png -------------------------------------------------------------------------------- /Assisticant.UnitTest/IndirectComputed.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Fields; 2 | 3 | namespace Assisticant.UnitTest 4 | { 5 | public class IndirectComputed 6 | { 7 | private DirectComputed _indermediateComputed; 8 | 9 | private Computed _property; 10 | 11 | public IndirectComputed(DirectComputed indermediateComputed) 12 | { 13 | _indermediateComputed = indermediateComputed; 14 | _property = new Computed(() => _indermediateComputed.ComputedProperty); 15 | } 16 | 17 | public int ComputedProperty 18 | { 19 | get { return _property; } 20 | } 21 | 22 | public bool IsUpToDate 23 | { 24 | get { return _property.IsUpToDate; } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/IndirectComputedTest.cs: -------------------------------------------------------------------------------- 1 | #if NETFX_CORE 2 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 3 | #else 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | #endif 6 | 7 | namespace Assisticant.UnitTest 8 | { 9 | [TestClass] 10 | public class IndirectComputedTest 11 | { 12 | public TestContext TestContext { get; set; } 13 | 14 | private SourceData _source; 15 | private DirectComputed _intermediateComputed; 16 | private IndirectComputed _computed; 17 | 18 | [TestInitialize] 19 | public void Initialize() 20 | { 21 | _source = new SourceData(); 22 | _intermediateComputed = new DirectComputed(_source); 23 | _computed = new IndirectComputed(_intermediateComputed); 24 | } 25 | 26 | [TestMethod] 27 | public void ComputedIsInitiallyOutOfDate() 28 | { 29 | Assert.IsFalse(_computed.IsUpToDate, "The dependent is initially up to date"); 30 | } 31 | 32 | [TestMethod] 33 | public void ComputedRemainsOutOfDateOnChange() 34 | { 35 | _source.SourceProperty = 3; 36 | Assert.IsFalse(_computed.IsUpToDate, "The dependent is up to date after change"); 37 | } 38 | 39 | [TestMethod] 40 | public void ComputedIsUpdatedOnGet() 41 | { 42 | int fetch = _computed.ComputedProperty; 43 | Assert.IsTrue(_computed.IsUpToDate, "The dependent has not been updated"); 44 | } 45 | 46 | [TestMethod] 47 | public void ComputedIsUpdatedAfterChangeOnGet() 48 | { 49 | _source.SourceProperty = 3; 50 | int fetch = _computed.ComputedProperty; 51 | Assert.IsTrue(_computed.IsUpToDate, "The dependent has not been updated"); 52 | } 53 | 54 | [TestMethod] 55 | public void ComputedGetsValueFromItsPrecedent() 56 | { 57 | _source.SourceProperty = 3; 58 | Assert.AreEqual(3, _computed.ComputedProperty); 59 | } 60 | 61 | [TestMethod] 62 | public void ComputedIsOutOfDateAgainAfterChange() 63 | { 64 | _source.SourceProperty = 3; 65 | int fetch = _computed.ComputedProperty; 66 | _source.SourceProperty = 4; 67 | Assert.IsFalse(_computed.IsUpToDate, "The dependent did not go out of date"); 68 | } 69 | 70 | [TestMethod] 71 | public void ComputedIsUpdatedAgainAfterChange() 72 | { 73 | _source.SourceProperty = 3; 74 | int fetch = _computed.ComputedProperty; 75 | _source.SourceProperty = 4; 76 | fetch = _computed.ComputedProperty; 77 | Assert.IsTrue(_computed.IsUpToDate, "The dependent did not get udpated"); 78 | } 79 | 80 | [TestMethod] 81 | public void ComputedGetsValueFromItsPrecedentAgainAfterChange() 82 | { 83 | _source.SourceProperty = 3; 84 | int fetch = _computed.ComputedProperty; 85 | _source.SourceProperty = 4; 86 | Assert.AreEqual(4, _computed.ComputedProperty); 87 | } 88 | 89 | [TestMethod] 90 | public void PrecedentIsOnlyAskedOnce() 91 | { 92 | int getCount = 0; 93 | _source.AfterGet += () => ++getCount; 94 | 95 | _source.SourceProperty = 3; 96 | int fetch = _computed.ComputedProperty; 97 | fetch = _computed.ComputedProperty; 98 | 99 | Assert.AreEqual(1, getCount); 100 | } 101 | 102 | [TestMethod] 103 | public void PrecedentIsAskedAgainAfterChange() 104 | { 105 | int getCount = 0; 106 | _source.AfterGet += () => ++getCount; 107 | 108 | _source.SourceProperty = 3; 109 | int fetch = _computed.ComputedProperty; 110 | fetch = _computed.ComputedProperty; 111 | _source.SourceProperty = 4; 112 | fetch = _computed.ComputedProperty; 113 | fetch = _computed.ComputedProperty; 114 | 115 | Assert.AreEqual(2, getCount); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/IndirectConcurrencyTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if NETFX_CORE 3 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 4 | #else 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | #endif 7 | 8 | namespace Assisticant.UnitTest 9 | { 10 | [TestClass] 11 | public class IndirectConcurrencyTest 12 | { 13 | public TestContext TestContext { get; set; } 14 | 15 | private SourceData _source; 16 | private DirectComputed _intermediateComputed; 17 | private IndirectComputed _computed; 18 | 19 | [TestInitialize] 20 | public void Initialize() 21 | { 22 | _source = new SourceData(); 23 | _intermediateComputed = new DirectComputed(_source); 24 | _computed = new IndirectComputed(_intermediateComputed); 25 | } 26 | 27 | [TestMethod] 28 | public void ComputedIsOutOfDateAfterConcurrentChange() 29 | { 30 | _source.AfterGet += () => _source.SourceProperty = 4; 31 | 32 | _source.SourceProperty = 3; 33 | int fetch = _computed.ComputedProperty; 34 | 35 | Assert.IsFalse(_computed.IsUpToDate, "The dependent is up to date after a concurrent change"); 36 | } 37 | 38 | [TestMethod] 39 | public void ComputedHasOriginalValueAfterConcurrentChange() 40 | { 41 | _source.AfterGet += () => _source.SourceProperty = 4; 42 | 43 | _source.SourceProperty = 3; 44 | Assert.AreEqual(3, _computed.ComputedProperty); 45 | } 46 | 47 | [TestMethod] 48 | public void ComputedIsUpToDateAfterSecondGet() 49 | { 50 | Action concurrentChange = () => _source.SourceProperty = 4; 51 | _source.AfterGet += concurrentChange; 52 | 53 | _source.SourceProperty = 3; 54 | int fetch = _computed.ComputedProperty; 55 | 56 | _source.AfterGet -= concurrentChange; 57 | 58 | fetch = _computed.ComputedProperty; 59 | 60 | Assert.IsTrue(_computed.IsUpToDate, "The dependent is not up to date after the second get"); 61 | } 62 | 63 | [TestMethod] 64 | public void ComputedHasModifiedValueAfterSecondGet() 65 | { 66 | Action concurrentChange = () => _source.SourceProperty = 4; 67 | _source.AfterGet += concurrentChange; 68 | 69 | _source.SourceProperty = 3; 70 | int fetch = _computed.ComputedProperty; 71 | 72 | _source.AfterGet -= concurrentChange; 73 | 74 | Assert.AreEqual(4, _computed.ComputedProperty); 75 | } 76 | 77 | [TestMethod] 78 | public void ComputedStillDependsUponPrecedent() 79 | { 80 | Action concurrentChange = () => _source.SourceProperty = 4; 81 | _source.AfterGet += concurrentChange; 82 | 83 | _source.SourceProperty = 3; 84 | int fetch = _computed.ComputedProperty; 85 | 86 | _source.AfterGet -= concurrentChange; 87 | 88 | fetch = _computed.ComputedProperty; 89 | _source.SourceProperty = 5; 90 | 91 | Assert.IsFalse(_computed.IsUpToDate, "The dependent no longer depends upon the precedent"); 92 | } 93 | 94 | [TestMethod] 95 | public void ComputedGetsTheUltimateValue() 96 | { 97 | Action concurrentChange = () => _source.SourceProperty = 4; 98 | _source.AfterGet += concurrentChange; 99 | 100 | _source.SourceProperty = 3; 101 | int fetch = _computed.ComputedProperty; 102 | 103 | _source.AfterGet -= concurrentChange; 104 | 105 | fetch = _computed.ComputedProperty; 106 | _source.SourceProperty = 5; 107 | 108 | Assert.AreEqual(5, _computed.ComputedProperty); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/LargeListTest.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Collections; 2 | using Assisticant.Fields; 3 | using Assisticant.UnitTest.ContactListData; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Assisticant.UnitTest 12 | { 13 | [TestClass] 14 | public class LargeListTest 15 | { 16 | [TestMethod] 17 | public void CanHaveManyPrecedents() 18 | { 19 | var contacts = new ObservableList( 20 | Enumerable.Range(0, 10000) 21 | .Select(i => new Contact() 22 | { 23 | FirstName = "FirstName" + i, 24 | LastName = "LastName" + i 25 | })); 26 | 27 | var sorted = new ComputedList(() => 28 | from c in contacts 29 | orderby c.FirstName, c.LastName 30 | select c); 31 | Assert.AreEqual("FirstName100", sorted.ElementAt(3).FirstName); 32 | 33 | sorted.ElementAt(3).FirstName = "George"; 34 | Assert.AreEqual("FirstName1000", sorted.ElementAt(3).FirstName); 35 | } 36 | 37 | class Projection 38 | { 39 | private readonly Observable _prefix; 40 | private readonly Contact _contact; 41 | private Computed _name; 42 | 43 | public Projection(Observable prefix, Contact contact) 44 | { 45 | _prefix = prefix; 46 | _contact = contact; 47 | 48 | _name = new Computed(() => 49 | prefix.Value + _contact.FirstName + _contact.LastName); 50 | } 51 | 52 | public string Name 53 | { 54 | get { return _name; } 55 | } 56 | } 57 | 58 | [TestMethod] 59 | public void CanHaveManyComputeds() 60 | { 61 | var prefix = new Observable("Before"); 62 | 63 | var contacts = new ObservableList( 64 | Enumerable.Range(0, 10000) 65 | .Select(i => new Contact() 66 | { 67 | FirstName = "FirstName" + i, 68 | LastName = "LastName" + i 69 | })); 70 | 71 | var projections = new ComputedList(() => 72 | from c in contacts 73 | select new Projection(prefix, c)); 74 | string dummy; 75 | foreach (var projection in projections) 76 | dummy = projection.Name; 77 | Assert.AreEqual("BeforeFirstName3LastName3", projections.ElementAt(3).Name); 78 | 79 | prefix.Value = "After"; 80 | foreach (var projection in projections) 81 | dummy = projection.Name; 82 | Assert.AreEqual("AfterFirstName3LastName3", projections.ElementAt(3).Name); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/MultithreadedData/AbstractThread.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Threading; 7 | #if NETFX_CORE 8 | using Windows.Foundation; 9 | using Windows.System.Threading; 10 | #endif 11 | 12 | namespace Assisticant.UnitTest.MultithreadedData 13 | { 14 | public abstract class AbstractThread 15 | { 16 | #if NETFX_CORE 17 | private IAsyncAction _asyncAction; 18 | #else 19 | private Thread _thread; 20 | #endif 21 | 22 | protected abstract void ThreadProc(); 23 | 24 | public AbstractThread() 25 | { 26 | #if !NETFX_CORE 27 | _thread = new Thread(() => ThreadProc()); 28 | #endif 29 | } 30 | 31 | public void Start() 32 | { 33 | #if NETFX_CORE 34 | _asyncAction = ThreadPool.RunAsync(wi => ThreadProc()); 35 | #else 36 | _thread.Start(); 37 | #endif 38 | } 39 | 40 | public void Join() 41 | { 42 | #if NETFX_CORE 43 | _asyncAction.AsTask().Wait(); 44 | #else 45 | _thread.Join(); 46 | #endif 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/MultithreadedData/SourceThread.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Assisticant.Fields; 3 | 4 | namespace Assisticant.UnitTest.MultithreadedData 5 | { 6 | public class SourceThread : AbstractThread 7 | { 8 | public const int MaxValue = 10000; 9 | 10 | private Observable _value = new Observable(); 11 | 12 | protected override void ThreadProc() 13 | { 14 | for (int i = 0; i <= MaxValue; i++) 15 | { 16 | Value = i; 17 | } 18 | } 19 | 20 | public int Value 21 | { 22 | get 23 | { 24 | lock (this) 25 | return _value; 26 | } 27 | set 28 | { 29 | lock (this) 30 | _value.Value = value; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/MultithreadedData/TargetThread.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using Assisticant.Fields; 5 | 6 | namespace Assisticant.UnitTest.MultithreadedData 7 | { 8 | public class TargetThread : AbstractThread 9 | { 10 | private SourceThread[] _sources; 11 | private Computed _total; 12 | 13 | public TargetThread(SourceThread[] sources) 14 | { 15 | _sources = sources; 16 | _total = new Computed(() => _sources.Sum(source => source.Value)); 17 | } 18 | 19 | public int Total 20 | { 21 | get 22 | { 23 | lock (this) 24 | return _total; 25 | } 26 | } 27 | 28 | protected override void ThreadProc() 29 | { 30 | for (int i = 0; i < SourceThread.MaxValue; i++) 31 | { 32 | int total = Total; 33 | if (total < 0) // This will never happen, but we need to ensure that the property get is not optimized out. 34 | throw new InvalidOperationException(); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/MultithreadedTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | #if NETFX_CORE 6 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 7 | #else 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | #endif 10 | using Assisticant.UnitTest.MultithreadedData; 11 | 12 | namespace Assisticant.UnitTest 13 | { 14 | [TestClass] 15 | public class MultithreadedTest 16 | { 17 | [TestMethod] 18 | public void IsThreadSafe() 19 | { 20 | const int ThreadCount = 10; 21 | 22 | // Start source threads. 23 | SourceThread[] sources = new SourceThread[ThreadCount]; 24 | for (int i = 0; i < ThreadCount; i++) 25 | { 26 | sources[i] = new SourceThread(); 27 | sources[i].Start(); 28 | } 29 | 30 | // Start target threads. 31 | TargetThread[] targets = new TargetThread[ThreadCount]; 32 | for (int i = 0; i < ThreadCount; i++) 33 | { 34 | targets[i] = new TargetThread(sources); 35 | targets[i].Start(); 36 | } 37 | 38 | // Wait for all threads to finish. 39 | for (int i = 0; i < ThreadCount; i++) 40 | { 41 | sources[i].Join(); 42 | targets[i].Join(); 43 | } 44 | 45 | // All targets are in the correct state. 46 | for (int i = 0; i < ThreadCount; i++) 47 | { 48 | Assert.AreEqual(ThreadCount * SourceThread.MaxValue, targets[i].Total); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/NotificationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if NETFX_CORE 3 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 4 | #else 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | #endif 7 | 8 | namespace Assisticant.UnitTest 9 | { 10 | public class NotifyingObservable : Observable 11 | { 12 | public event Action OnGainComputed; 13 | public event Action OnLoseComputed; 14 | 15 | protected override void GainDependent() 16 | { 17 | if (OnGainComputed != null) 18 | OnGainComputed(); 19 | } 20 | 21 | protected override void LoseDependent() 22 | { 23 | if (OnLoseComputed != null) 24 | OnLoseComputed(); 25 | } 26 | } 27 | 28 | [TestClass] 29 | public class NotificationTest 30 | { 31 | private bool _gained; 32 | private bool _lost; 33 | private NotifyingObservable _observable; 34 | private Computed _computed; 35 | private Computed _secondComputed; 36 | 37 | [TestInitialize] 38 | public void Initialize() 39 | { 40 | _gained = false; 41 | _observable = new NotifyingObservable(); 42 | _observable.OnGainComputed += () => { _gained = true; }; 43 | _observable.OnLoseComputed += () => { _lost = true; }; 44 | _computed = new Computed(() => { _observable.OnGet(); }); 45 | _secondComputed = new Computed(() => { _observable.OnGet(); }); 46 | } 47 | 48 | [TestMethod] 49 | public void DoesNotGainComputedOnCreation() 50 | { 51 | Assert.IsFalse(_gained, "The observable should not have gained a dependent."); 52 | } 53 | 54 | [TestMethod] 55 | public void GainsComputedOnFirstUse() 56 | { 57 | _computed.OnGet(); 58 | Assert.IsTrue(_gained, "The observable should have gained a dependent."); 59 | } 60 | 61 | [TestMethod] 62 | public void DoesNotGainComputedOnSecondUse() 63 | { 64 | _computed.OnGet(); 65 | _gained = false; 66 | _secondComputed.OnGet(); 67 | Assert.IsFalse(_gained, "The observable should not have gained a dependent."); 68 | } 69 | 70 | [TestMethod] 71 | public void DoesNotLoseComputedOnCreation() 72 | { 73 | Assert.IsFalse(_lost, "The observable should not have lost a dependent."); 74 | } 75 | 76 | [TestMethod] 77 | public void DoesNotLoseComputedOnFirstUse() 78 | { 79 | _computed.OnGet(); 80 | Assert.IsFalse(_lost, "The observable should not have lost a dependent."); 81 | } 82 | 83 | [TestMethod] 84 | public void LosesComputedWhenChanging() 85 | { 86 | _computed.OnGet(); 87 | _observable.OnSet(); 88 | Assert.IsTrue(_lost, "The observable should have lost a dependent."); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Assisticant.UnitTest.Universal 6 | rv 7 | Images\UnitTestStoreLogo.png 8 | Assisticant.UnitTest.Universal 9 | 10 | 11 | 6.3.0 12 | 6.3.0 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Assisticant.UnitTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Assisticant.UnitTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("93f186f5-6e37-40b1-8767-4c2305929548")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/SortedCollectionTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | #if NETFX_CORE 3 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 4 | #else 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | #endif 7 | using Assisticant.UnitTest.ContactListData; 8 | 9 | namespace Assisticant.UnitTest 10 | { 11 | [TestClass] 12 | public class SortedCollectionTest 13 | { 14 | private ContactList _contactList; 15 | private ContactListViewModel _viewModel; 16 | private int _collectionChangedCount; 17 | 18 | [TestInitialize] 19 | public void Initialize() 20 | { 21 | _contactList = new ContactList(); 22 | _viewModel = new ContactListViewModel(_contactList); 23 | _contactList.AddContact(new Contact() { FirstName = "Michael", LastName = "Perry" }); 24 | _contactList.AddContact(new Contact() { FirstName = "Ada", LastName = "Lovelace" }); 25 | _contactList.AddContact(new Contact() { FirstName = "Charles", LastName = "Babbage" }); 26 | 27 | _collectionChangedCount = 0; 28 | _viewModel.ContactsCollectionChanged += 29 | delegate 30 | { 31 | _collectionChangedCount++; 32 | }; 33 | } 34 | 35 | [TestMethod] 36 | public void WhenSortByFirstNameShouldSortViewModel() 37 | { 38 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 39 | 40 | ContactViewModel contactViewModel = _viewModel.Contacts.First(); 41 | Assert.AreEqual("Ada Lovelace", contactViewModel.FullName); 42 | } 43 | 44 | [TestMethod] 45 | public void WhenViewModelIsCreatedShouldNotNotifyCollectionChanged() 46 | { 47 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 48 | ContactViewModel firstByFirstName = _viewModel.Contacts.First(); 49 | Assert.AreEqual(0, _collectionChangedCount); 50 | } 51 | 52 | [TestMethod] 53 | public void WhenSortOrderIsChangedShouldNotifyViewModelCollectionChanged() 54 | { 55 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 56 | ContactViewModel firstByFirstName = _viewModel.Contacts.First(); 57 | 58 | _viewModel.SortOrder = ContactListSortOrder.LastName; 59 | Assert.AreEqual(1, _collectionChangedCount); 60 | Assert.AreEqual("Charles Babbage", _viewModel.Contacts.First().FullName); 61 | } 62 | 63 | [TestMethod] 64 | public void WhenSortOrderIsChangedButNotDifferentWeShouldNotGetACollectionChanged() 65 | { 66 | ContactViewModel defaultOrder = _viewModel.Contacts.First(); 67 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 68 | 69 | ContactViewModel firstByFirstName = _viewModel.Contacts.First(); 70 | _viewModel.SortOrder = ContactListSortOrder.FirstName; 71 | 72 | Assert.AreEqual(1, _collectionChangedCount); 73 | 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/SourceData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | #if NETFX_CORE 4 | using Windows.System.Threading; 5 | #endif 6 | using Assisticant.Fields; 7 | 8 | namespace Assisticant.UnitTest 9 | { 10 | public class SourceData 11 | { 12 | private Observable _sourceProperty = new Observable(); 13 | private AutoResetEvent _continue = new AutoResetEvent(false); 14 | 15 | public int SourceProperty 16 | { 17 | get 18 | { 19 | int result = _sourceProperty; 20 | #if NETFX_CORE 21 | var ignored = ThreadPool.RunAsync(wi => 22 | #else 23 | ThreadPool.QueueUserWorkItem(o => 24 | #endif 25 | { 26 | if (AfterGet != null) 27 | AfterGet(); 28 | _continue.Set(); 29 | }); 30 | _continue.WaitOne(); 31 | return result; 32 | } 33 | set { _sourceProperty.Value = value; } 34 | } 35 | 36 | public event Action AfterGet; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Assisticant.UnitTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Assisticant/AssisticantExtensions.cs: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * Update Controls .NET 4 | * Copyright 2010 Michael L Perry 5 | * MIT License 6 | * 7 | * http://updatecontrols.net 8 | * http://www.codeplex.com/updatecontrols/ 9 | * 10 | **********************************************************************/ 11 | 12 | using System; 13 | using System.Collections.Generic; 14 | 15 | namespace Assisticant 16 | { 17 | public static class AssisticantExtensions 18 | { 19 | /// 20 | /// Moves all objects into a new recycle bin, from which they can be extracted. 21 | /// 22 | /// A collection of objects to add to the bin. 23 | /// 24 | /// After the objects are added to the bin, the collection 25 | /// is cleared. Then it can be repopulated by extraction from 26 | /// the bin. 27 | /// 28 | public static RecycleBin Recycle(this ICollection collection) 29 | { 30 | RecycleBin bin = new RecycleBin(collection); 31 | 32 | if (collection != null) 33 | collection.Clear(); 34 | 35 | return bin; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Assisticant/Binding/BindingManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Assisticant.Fields; 4 | 5 | namespace Assisticant.Binding 6 | { 7 | /// 8 | /// Manages all data bindings for a view. Be sure to Initialize on load, Bind properties when 9 | /// the view is displayed, and Unbind when the view dissapears. 10 | /// 11 | public class BindingManager 12 | { 13 | struct SubscriptionPair 14 | { 15 | public ComputedSubscription Output; 16 | public IInputSubscription Input; 17 | } 18 | 19 | private List _subscriptions = new List(); 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | public BindingManager() 25 | { 26 | } 27 | 28 | /// 29 | /// Bind the results of a function to an action. 30 | /// 31 | /// The function that computes a value to output. 32 | /// The action to perform when new output is computed. 33 | /// The 1st type parameter. 34 | public void Bind(Func function, Action action) 35 | { 36 | _subscriptions.Add (new SubscriptionPair 37 | { 38 | Output = new Computed (function).Subscribe (action) 39 | }); 40 | } 41 | 42 | /// 43 | /// Bind a custom input subscription. 44 | /// 45 | /// The custom input subscription. 46 | public void Bind(IInputSubscription input) 47 | { 48 | input.Subscribe (); 49 | _subscriptions.Add (new SubscriptionPair 50 | { 51 | Input = input 52 | }); 53 | } 54 | 55 | /// 56 | /// Bind the results of a function to an action, and a custom input subscription. 57 | /// 58 | /// The function that computes a value to output. 59 | /// The action to perform when new output is computed. 60 | /// The custom input subscription. 61 | /// The 1st type parameter. 62 | public void Bind(Func function, Action action, IInputSubscription input) 63 | { 64 | input.Subscribe (); 65 | _subscriptions.Add (new SubscriptionPair 66 | { 67 | Output = new Computed (function).Subscribe (action), 68 | Input = input 69 | }); 70 | } 71 | 72 | /// 73 | /// Unbind all bindings. Call this method when the view disappers. 74 | /// 75 | public void Unbind() 76 | { 77 | foreach (var subscription in _subscriptions) { 78 | if (subscription.Output != null) 79 | subscription.Output.Unsubscribe (); 80 | if (subscription.Input != null) 81 | subscription.Input.Unsubscribe (); 82 | } 83 | _subscriptions.Clear(); 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /Assisticant/Binding/IDisplayDataConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Assisticant.Binding 4 | { 5 | public interface IDisplayDataConverter 6 | { 7 | TDisplay ConvertOutput(TData data); 8 | TData ConvertInput(TDisplay display); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Assisticant/Binding/IInputSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Assisticant.Binding 4 | { 5 | public interface IInputSubscription 6 | { 7 | void Subscribe(); 8 | void Unsubscribe(); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Assisticant/BindingInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Metas; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Assisticant 10 | { 11 | public class BindingInterceptor 12 | { 13 | public static BindingInterceptor Current = new BindingInterceptor(); 14 | 15 | public virtual object GetValue(MemberSlot member) { return member.GetValue(); } 16 | public virtual void SetValue(MemberSlot member, object value) { member.SetValue(value); } 17 | public virtual void UpdateValue(MemberSlot member) { member.UpdateValue(); } 18 | public virtual void Execute(MethodCommand command, object parameter) { command.ContinueExecute(parameter); } 19 | public virtual bool CanExecute(MethodCommand command, object parameter) { return command.ContinueCanExecute(parameter); } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Assisticant/Collections/ComputedList.cs: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * Update Controls .NET 4 | * Copyright 2011 Michael L Perry 5 | * MIT License 6 | * 7 | * http://updatecontrols.net 8 | * http://www.codeplex.com/updatecontrols/ 9 | * 10 | **********************************************************************/ 11 | 12 | using System; 13 | using System.Collections.Generic; 14 | 15 | namespace Assisticant.Collections 16 | { 17 | public class ComputedList : IEnumerable, IReadOnlyList 18 | { 19 | private readonly Func> _computeCollection; 20 | 21 | private List _list = new List(); 22 | private Computed _computedSentry; 23 | 24 | public ComputedList(Func> computeCollection) 25 | { 26 | _computeCollection = computeCollection; 27 | 28 | _computedSentry = new NamedComputed(MemoizedTypeName>.GenericName(), 29 | delegate { 30 | using (var bin = new RecycleBin(_list)) 31 | { 32 | _list.Clear(); 33 | 34 | var collection = computeCollection(); 35 | if (collection != null) 36 | foreach (T item in collection) 37 | _list.Add(bin.Extract(item)); 38 | } 39 | }); 40 | } 41 | 42 | public int IndexOf(T item) 43 | { 44 | _computedSentry.OnGet(); 45 | return _list.IndexOf(item); 46 | } 47 | 48 | public T this[int index] 49 | { 50 | get 51 | { 52 | _computedSentry.OnGet(); 53 | return _list[index]; 54 | } 55 | } 56 | 57 | public bool Contains(T item) 58 | { 59 | _computedSentry.OnGet(); 60 | return _list.Contains(item); 61 | } 62 | 63 | public void CopyTo(T[] array, int arrayIndex) 64 | { 65 | _computedSentry.OnGet(); 66 | _list.CopyTo(array, arrayIndex); 67 | } 68 | 69 | public int Count 70 | { 71 | get { _computedSentry.OnGet(); return _list.Count; } 72 | } 73 | 74 | public IEnumerator GetEnumerator() 75 | { 76 | _computedSentry.OnGet(); 77 | return _list.GetEnumerator(); 78 | } 79 | 80 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 81 | { 82 | _computedSentry.OnGet(); 83 | return ((System.Collections.IEnumerable)_list).GetEnumerator(); 84 | } 85 | 86 | public Computed ComputedSentry 87 | { 88 | get { return _computedSentry; } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Assisticant/Collections/Impl/UpdateCollectionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Assisticant.Collections.Impl 8 | { 9 | /// Helper structure used by ComputedDictionary and 10 | /// ObservableDictionary to represent the "Keys" and "Values" members. 11 | /// 12 | /// If you save a reference to the Keys or Values property of , 13 | /// the observable sentry should be informed when that collection is accessed. 14 | /// This helper class ensures that the sentry is notified. 15 | /// 16 | /// For , this class is even more 17 | /// important. Whenever ComputedDictionary is updated, a new dictionary is 18 | /// created to hold the updated content, so the Keys and Values collections 19 | /// change frequently. This wrapper ensure that you do not accidentally hold 20 | /// a reference to an out-of-date version of the Keys or Values collection. 21 | /// It also ensures that the dictionary is updated if necessary when it is 22 | /// accessed through the Keys or Values collection. 23 | /// 24 | public struct UpdateCollectionHelper : ICollection, ICollection 25 | { 26 | readonly Func> _get; 27 | 28 | public UpdateCollectionHelper(Func> getCollection) 29 | { 30 | _get = getCollection; 31 | } 32 | 33 | public void Add(T item) 34 | { 35 | throw new NotSupportedException(); 36 | } 37 | public void Clear() 38 | { 39 | throw new NotSupportedException(); 40 | } 41 | public bool Contains(T item) 42 | { 43 | return _get().Contains(item); 44 | } 45 | public void CopyTo(T[] array, int arrayIndex) 46 | { 47 | _get().CopyTo(array, arrayIndex); 48 | } 49 | 50 | public void CopyTo(Array array, int index) 51 | { 52 | _get().CopyTo((T[])array, index); 53 | } 54 | 55 | public int Count 56 | { 57 | get { return _get().Count; } 58 | } 59 | 60 | public bool IsSynchronized => false; 61 | public object SyncRoot => this; 62 | 63 | public bool IsReadOnly 64 | { 65 | get { return true; } 66 | } 67 | public bool Remove(T item) 68 | { 69 | throw new NotSupportedException(); 70 | } 71 | 72 | public IEnumerator GetEnumerator() 73 | { 74 | return _get().GetEnumerator(); 75 | } 76 | 77 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 78 | { 79 | return _get().GetEnumerator(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Assisticant/Collections/ObservableList.cs: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * Update Controls .NET 4 | * Copyright 2010 Michael L Perry 5 | * MIT License 6 | * 7 | * http://updatecontrols.net 8 | * http://www.codeplex.com/updatecontrols/ 9 | * 10 | **********************************************************************/ 11 | 12 | using System; 13 | using System.Collections; 14 | using System.Collections.Generic; 15 | 16 | namespace Assisticant.Collections 17 | { 18 | public class ObservableList : IList, IList, IReadOnlyList 19 | { 20 | private List _list; 21 | private Observable _indList = new NamedObservable(MemoizedTypeName>.GenericName()); 22 | 23 | public ObservableList() 24 | { 25 | _list = new List(); 26 | } 27 | 28 | public ObservableList(IEnumerable collection) 29 | { 30 | _list = new List(collection); 31 | } 32 | 33 | public int IndexOf(T item) 34 | { 35 | _indList.OnGet(); 36 | return _list.IndexOf(item); 37 | } 38 | 39 | public void Insert(int index, T item) 40 | { 41 | _indList.OnSet(); 42 | _list.Insert(index, item); 43 | } 44 | 45 | public void RemoveAt(int index) 46 | { 47 | _indList.OnSet(); 48 | _list.RemoveAt(index); 49 | } 50 | 51 | public T this[int index] 52 | { 53 | get 54 | { 55 | _indList.OnGet(); 56 | return _list[index]; 57 | } 58 | set 59 | { 60 | _indList.OnSet(); 61 | _list[index] = value; 62 | } 63 | } 64 | 65 | public void Add(T item) 66 | { 67 | _indList.OnSet(); 68 | _list.Add(item); 69 | } 70 | 71 | public void AddRange(IEnumerable items) 72 | { 73 | _indList.OnSet(); 74 | _list.AddRange(items); 75 | } 76 | 77 | public void Clear() 78 | { 79 | _indList.OnSet(); 80 | _list.Clear(); 81 | } 82 | 83 | public bool Contains(T item) 84 | { 85 | _indList.OnGet(); 86 | return _list.Contains(item); 87 | } 88 | 89 | public void CopyTo(T[] array, int arrayIndex) 90 | { 91 | _indList.OnGet(); 92 | _list.CopyTo(array, arrayIndex); 93 | } 94 | 95 | public int Count 96 | { 97 | get { _indList.OnGet(); return _list.Count; } 98 | } 99 | 100 | public bool IsReadOnly 101 | { 102 | get { return false; } 103 | } 104 | 105 | public bool Remove(T item) 106 | { 107 | _indList.OnSet(); 108 | return _list.Remove(item); 109 | } 110 | 111 | public IEnumerator GetEnumerator() 112 | { 113 | _indList.OnGet(); 114 | return _list.GetEnumerator(); 115 | } 116 | 117 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 118 | { 119 | _indList.OnGet(); 120 | return ((System.Collections.IEnumerable)_list).GetEnumerator(); 121 | } 122 | 123 | bool ICollection.IsSynchronized { get { return false; } } 124 | object ICollection.SyncRoot { get { return this; } } 125 | bool IList.IsFixedSize { get { return false; } } 126 | object IList.this[int index] { get { return this[index]; } set { this[index] = (T)value; } } 127 | 128 | int IList.Add(object value) { Add((T)value); return Count - 1; } 129 | bool IList.Contains(object value) { return Contains((T)value); } 130 | int IList.IndexOf(object value) { return IndexOf((T)value); } 131 | void IList.Insert(int index, object value) { Insert(index, (T)value); } 132 | void IList.Remove(object value) { Remove((T)value); } 133 | void ICollection.CopyTo(Array array, int index) { CopyTo((T[])array, index); } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Assisticant/ComputedJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Assisticant 7 | { 8 | public class ComputedJob : IDisposable 9 | { 10 | Computed _computed; 11 | bool _running; 12 | 13 | public ComputedJob(Action action) 14 | { 15 | _computed = new Computed(action); 16 | _computed.Invalidated += () => UpdateScheduler.ScheduleUpdate(UpdateNow); 17 | } 18 | 19 | public void Start() 20 | { 21 | if (_computed == null) 22 | throw new InvalidOperationException("Cannot restart ComputedJob"); 23 | _running = true; 24 | UpdateScheduler.ScheduleUpdate(UpdateNow); 25 | } 26 | 27 | public void Stop() 28 | { 29 | _running = false; 30 | _computed.Dispose(); 31 | _computed = null; 32 | } 33 | 34 | public void Dispose() { Stop(); } 35 | 36 | private void UpdateNow() 37 | { 38 | if (_running) 39 | _computed.OnGet(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Assisticant/Descriptors/PlatformProxy.NotifyDataErrorInfo.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Validation; 2 | using System; 3 | using System.Collections; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | 7 | namespace Assisticant.Descriptors 8 | { 9 | public abstract partial class PlatformProxy : INotifyDataErrorInfo 10 | { 11 | private IValidationRules _validator; 12 | 13 | public event EventHandler ErrorsChanged; 14 | 15 | partial void PlatformProxy_NotifyDataErrorInfo() 16 | { 17 | var validation = Instance as IValidation; 18 | _validator = validation?.Rules; 19 | if (_validator != null) 20 | { 21 | _validator.ErrorsChanged += Validator_ErrorsChanged; 22 | } 23 | } 24 | 25 | public bool HasErrors => _validator?.HasErrors ?? false; 26 | 27 | public IEnumerable GetErrors(string propertyName) 28 | { 29 | return _validator?.GetErrors(propertyName) ?? Enumerable.Empty(); 30 | } 31 | 32 | private void Validator_ErrorsChanged(string propertyName) 33 | { 34 | ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Assisticant/Descriptors/PlatformProxy.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Metas; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Assisticant.Descriptors 11 | { 12 | public abstract partial class PlatformProxy : ViewProxy, IDataErrorInfo 13 | { 14 | public string Error 15 | { 16 | get 17 | { 18 | var errorInfo = Instance as IDataErrorInfo; 19 | return errorInfo != null ? errorInfo.Error : null; 20 | } 21 | } 22 | 23 | public string this[string columnName] 24 | { 25 | get 26 | { 27 | var errorInfo = Instance as IDataErrorInfo; 28 | return errorInfo != null ? errorInfo[columnName] : null; 29 | } 30 | } 31 | 32 | protected PlatformProxy(object instance, ProxyTypeDescriptor descriptor) 33 | : base(instance, descriptor.Meta) 34 | { 35 | PlatformProxy_NotifyDataErrorInfo(); 36 | } 37 | 38 | public abstract ProxyTypeDescriptor GetTypeDescriptor(); 39 | 40 | public override ViewProxy WrapObject(object value) 41 | { 42 | if (value == null) 43 | return null; 44 | return (PlatformProxy)Activator.CreateInstance(typeof(PlatformProxy<>).MakeGenericType(value.GetType()), value); 45 | } 46 | 47 | partial void PlatformProxy_NotifyDataErrorInfo(); 48 | } 49 | 50 | [TypeDescriptionProvider(typeof(ProxyDescriptionProvider))] 51 | public sealed class PlatformProxy : PlatformProxy 52 | { 53 | public static readonly ProxyTypeDescriptor TypeDescriptor = new ProxyTypeDescriptor(typeof(TViewModel)); 54 | 55 | public PlatformProxy(object instance) 56 | : base(instance, TypeDescriptor) 57 | { 58 | } 59 | 60 | public override ProxyTypeDescriptor GetTypeDescriptor() 61 | { 62 | return TypeDescriptor; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Assisticant/Descriptors/ProxyDescriptionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Assisticant.Descriptors 10 | { 11 | public class ProxyDescriptionProvider : TypeDescriptionProvider 12 | { 13 | public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) 14 | { 15 | var objectInstance = instance as PlatformProxy; 16 | if (objectInstance != null) 17 | return objectInstance.GetTypeDescriptor(); 18 | 19 | if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PlatformProxy<>)) 20 | { 21 | var field = objectType.GetField("TypeDescriptor", BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); 22 | return (ICustomTypeDescriptor)field.GetValue(null); 23 | } 24 | 25 | return new ProxyTypeDescriptor(objectType); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Assisticant/Descriptors/ProxyEventDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Assisticant.Descriptors 10 | { 11 | public class ProxyEventDescriptor : EventDescriptor 12 | { 13 | readonly EventInfo _eventInfo; 14 | 15 | public ProxyEventDescriptor(EventInfo eventInfo) 16 | : base(eventInfo.Name, null) 17 | { 18 | _eventInfo = eventInfo; 19 | } 20 | 21 | public override void AddEventHandler(object proxy, Delegate value) 22 | { 23 | // Add the event handler to the wrapped object. 24 | _eventInfo.AddEventHandler(Unwrap(proxy), value); 25 | } 26 | 27 | public override void RemoveEventHandler(object proxy, Delegate value) 28 | { 29 | // Remove the event handler from the wrapped object. 30 | _eventInfo.RemoveEventHandler(Unwrap(proxy), value); 31 | } 32 | 33 | public override Type ComponentType 34 | { 35 | get { return _eventInfo.DeclaringType; } 36 | } 37 | 38 | public override Type EventType 39 | { 40 | get { return _eventInfo.EventHandlerType; } 41 | } 42 | 43 | public override bool IsMulticast 44 | { 45 | get { return _eventInfo.IsMulticast; } 46 | } 47 | 48 | private static object Unwrap(object proxy) 49 | { 50 | return ((PlatformProxy)proxy).Instance; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Assisticant/Descriptors/ProxyPropertyDescriptor.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Metas; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Assisticant.Descriptors 11 | { 12 | public class ProxyPropertyDescriptor : PropertyDescriptor 13 | { 14 | readonly ProxyTypeDescriptor _owner; 15 | readonly MemberMeta _meta; 16 | readonly Type _exposedType; 17 | 18 | public override Type ComponentType 19 | { 20 | get { return _owner.ProxyType; } 21 | } 22 | 23 | public override Type PropertyType 24 | { 25 | get { return _exposedType; } 26 | } 27 | 28 | public override bool IsReadOnly 29 | { 30 | get { return !_meta.CanWrite; } 31 | } 32 | 33 | public ProxyPropertyDescriptor(ProxyTypeDescriptor owner, MemberMeta meta) 34 | : base(meta.Name, null) 35 | { 36 | _owner = owner; 37 | _meta = meta; 38 | if (!meta.IsViewModel) 39 | _exposedType = meta.MemberType; 40 | else if (typeof(IEnumerable).IsAssignableFrom(meta.MemberType)) 41 | _exposedType = typeof(IEnumerable); 42 | else 43 | _exposedType = typeof(object); 44 | } 45 | 46 | public override object GetValue(object proxy) 47 | { 48 | return BindingInterceptor.Current.GetValue(GetSlot(proxy)); 49 | } 50 | 51 | public override void SetValue(object proxy, object value) 52 | { 53 | BindingInterceptor.Current.SetValue(GetSlot(proxy), value); 54 | } 55 | 56 | public override bool CanResetValue(object proxy) 57 | { 58 | return false; 59 | } 60 | 61 | public override void ResetValue(object proxy) 62 | { 63 | } 64 | 65 | public override bool ShouldSerializeValue(object proxy) 66 | { 67 | return false; 68 | } 69 | 70 | MemberSlot GetSlot(object proxy) 71 | { 72 | return ((ViewProxy)proxy).LookupSlot(_meta); 73 | } 74 | 75 | public override string ToString() 76 | { 77 | return _meta.ToString(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Assisticant/Descriptors/ProxyTypeDescriptor.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Metas; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Assisticant.Descriptors 10 | { 11 | public class ProxyTypeDescriptor : CustomTypeDescriptor 12 | { 13 | public readonly TypeMeta Meta; 14 | readonly ProxyPropertyDescriptor[] _properties; 15 | readonly PropertyDescriptorCollection _propertyCollection; 16 | readonly EventDescriptorCollection _events; 17 | public readonly Type ProxyType; 18 | 19 | public ProxyTypeDescriptor(Type type) 20 | { 21 | Meta = TypeMeta.Get(type); 22 | ProxyType = typeof(PlatformProxy<>).MakeGenericType(type); 23 | _properties = Meta.Members.Select(m => new ProxyPropertyDescriptor(this, m)).ToArray(); 24 | _propertyCollection = new PropertyDescriptorCollection(_properties); 25 | _events = new EventDescriptorCollection(type.GetEvents().Select(e => new ProxyEventDescriptor(e)).ToArray()); 26 | } 27 | 28 | public override PropertyDescriptorCollection GetProperties() 29 | { 30 | return _propertyCollection; 31 | } 32 | 33 | public override EventDescriptorCollection GetEvents() 34 | { 35 | return _events; 36 | } 37 | 38 | public override EventDescriptorCollection GetEvents(Attribute[] attributes) 39 | { 40 | return _events; 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return Meta.Type.Name; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Assisticant/Fields/Computed.cs: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * Update Controls .NET 4 | * Copyright 2011 Michael L Perry 5 | * MIT License 6 | * 7 | * This class based on a contribution by David Piepgrass. 8 | * 9 | * http://updatecontrols.net 10 | * http://www.codeplex.com/updatecontrols/ 11 | * 12 | **********************************************************************/ 13 | 14 | using System; 15 | using System.ComponentModel; 16 | 17 | namespace Assisticant.Fields 18 | { 19 | public class Computed : NamedComputed 20 | { 21 | protected internal T _value; 22 | protected Func _computeValue; 23 | 24 | public Computed(Func compute) : base((string)null, null) 25 | { 26 | base._update = Update; _computeValue = compute; 27 | } 28 | public Computed(string name, Func compute) : base(name, null) 29 | { 30 | base._update = Update; _computeValue = compute; 31 | } 32 | 33 | protected void Update() 34 | { 35 | _value = _computeValue(); 36 | // TODO: don't propagate updates when _value did not change. 37 | // T oldValue = _value; 38 | // _value = _computeValue(); 39 | // return _value == null ? oldValue != null : !_value.Equals(oldValue); 40 | } 41 | 42 | public T Value 43 | { 44 | get { base.OnGet(); return _value; } 45 | } 46 | public static implicit operator T(Computed computed) 47 | { 48 | return computed.Value; 49 | } 50 | 51 | public ComputedSubscription Subscribe(Action whenChanged) 52 | { 53 | return new ComputedSubscription(this, delegate (object prior) 54 | { 55 | T current = this.Value; 56 | whenChanged(current); 57 | return current; 58 | }, default(T)); 59 | } 60 | 61 | public ComputedSubscription Subscribe(Action whenChanged) 62 | { 63 | return new ComputedSubscription(this, delegate (object prior) 64 | { 65 | T current = this.Value; 66 | whenChanged(current, (T)prior); 67 | return current; 68 | }, default(T)); 69 | } 70 | 71 | public override string VisualizerName(bool withValue) 72 | { 73 | string s = VisualizerName(_name ?? "NamedComputed"); 74 | if (withValue) 75 | s += " = " + (_value == null ? "null" : _value.ToString()); 76 | return s; 77 | } 78 | internal static string VisualizerName(string name) 79 | { 80 | string typeName = MemoizedTypeName.GenericName(); 81 | if (!string.IsNullOrEmpty(name)) 82 | return string.Format("{0}: {1}", name, typeName); 83 | else 84 | return typeName; 85 | } 86 | 87 | [Obsolete, EditorBrowsable(EditorBrowsableState.Never)] 88 | public Computed ComputedSentry 89 | { 90 | get { return this; } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Assisticant/Fields/ComputedSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Assisticant.Fields 4 | { 5 | public class ComputedSubscription 6 | { 7 | private readonly Computed _computed; 8 | private readonly Func _update; 9 | 10 | private object _priorState; 11 | 12 | public ComputedSubscription(Computed computed, Func update, object initialState) 13 | { 14 | _computed = computed; 15 | _update = update; 16 | _priorState = initialState; 17 | 18 | _computed.Invalidated += Computed_Invalidated; 19 | Computed_Invalidated(); 20 | } 21 | 22 | public void Unsubscribe() 23 | { 24 | _computed.Invalidated -= Computed_Invalidated; 25 | } 26 | 27 | private void Computed_Invalidated() 28 | { 29 | UpdateScheduler.ScheduleUpdate(delegate () 30 | { 31 | _priorState = _update(_priorState); 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Assisticant/Fields/Observable.cs: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * Update Controls .NET 4 | * Copyright 2011 Michael L Perry 5 | * MIT License 6 | * 7 | * This class based on a contribution by David Piepgrass. 8 | * 9 | * http://updatecontrols.net 10 | * http://www.codeplex.com/updatecontrols/ 11 | * 12 | **********************************************************************/ 13 | using System; 14 | using System.ComponentModel; 15 | 16 | namespace Assisticant.Fields 17 | { 18 | public class Observable : NamedObservable 19 | { 20 | protected internal T _value; 21 | 22 | public Observable() { } 23 | public Observable(T value) : this((string)null, value) { } 24 | 25 | // Oops, this constructor causes ambiguity in case of Observable. 26 | // In that case, C# compilers will reinterpret existing code that previously 27 | // used the Observable(value) constructor to call Observable(name) instead. 28 | //public Observable(string name) : base(name) { } 29 | 30 | public Observable(string name, T value) : base(name) { _value = value; } 31 | public Observable(Type containerType, string name) : base(containerType, name) { } 32 | public Observable(Type containerType, string name, T value) : base(containerType, name) { _value = value; } 33 | 34 | public T Value 35 | { 36 | get { base.OnGet(); return _value; } 37 | set { 38 | if (_value == null ? value != null : !_value.Equals(value)) 39 | { 40 | base.OnSet(); 41 | _value = value; 42 | } 43 | } 44 | } 45 | public static implicit operator T(Observable observable) 46 | { 47 | return observable.Value; 48 | } 49 | 50 | public override string VisualizerName(bool withValue) 51 | { 52 | string s = "[I] " + Computed.VisualizerName(Name); 53 | if (withValue) 54 | s += " = " + (_value == null ? "null" : _value.ToString()); 55 | return s; 56 | } 57 | 58 | [Obsolete, EditorBrowsable(EditorBrowsableState.Never)] 59 | public Observable ObservableSentry 60 | { 61 | get { return this; } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Assisticant/MemoizedTypeName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Reflection; 6 | 7 | namespace Assisticant 8 | { 9 | /// .NET Framework reflection doesn't offer complete type names for 10 | /// generic types such as "List<int>" (the Type.Name value of that class is 11 | /// "List`1"). fills in the gap, and also saves the 12 | /// computed name for fast repeated lookups. 13 | public static class MemoizedTypeName 14 | { 15 | static Dictionary _shortNames = new Dictionary(); 16 | 17 | /// Computes a short language-agnostic name for a type, including 18 | /// generic parameters, e.g. GenericName(typeof(int)) is "Int32"; 19 | /// GenericName(typeof(Dictionary<int, string>)) is 20 | /// "Dictionary<Int32, String>". 21 | /// Type whose name you want 22 | /// Name with generic parameters, as explained in the summary. 23 | /// The result is memoized for generic types, so that the name is 24 | /// computed only once. 25 | public static string GenericName(Type type) 26 | { 27 | if (type == null) 28 | return null; 29 | string name; 30 | lock (_shortNames) 31 | { 32 | if (!_shortNames.TryGetValue(type, out name)) 33 | { 34 | if (type.IsGenericTypePortable()) 35 | _shortNames[type] = name = ComputeGenericName(type); 36 | else 37 | name = type.Name; 38 | } 39 | } 40 | return name; 41 | } 42 | 43 | /// Computes a type's name without memoization. 44 | internal static string ComputeGenericName(Type type) 45 | { 46 | string result = type.Name; 47 | if (type.IsGenericTypePortable()) 48 | { 49 | // remove genric indication (e.g. `1) 50 | result = result.Substring(0, result.LastIndexOf('`')); 51 | 52 | result = string.Format( 53 | "{0}<{1}>", 54 | result, 55 | string.Join(", ", type.GetGenericArgumentsPortable().Select(t => GenericName(t)).ToArray())); 56 | } 57 | return result; 58 | } 59 | 60 | /// Extension method on Type that is an alias for the method. 61 | public static string NameWithGenericParams(this Type t) 62 | { 63 | return GenericName(t); 64 | } 65 | } 66 | 67 | public static class MemoizedTypeName 68 | { 69 | static string _name; 70 | public static string GenericName() 71 | { 72 | if (_name == null) 73 | _name = MemoizedTypeName.GenericName(typeof(T)); 74 | return _name; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Assisticant/Metas/AtomSlot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Assisticant.Metas 4 | { 5 | public class AtomSlot : MemberSlot 6 | { 7 | object _sourceValue; 8 | object _value; 9 | bool _firePropertyChanged = false; 10 | 11 | internal AtomSlot(ViewProxy proxy, MemberMeta member) 12 | : base(proxy, member) 13 | { 14 | } 15 | 16 | public override void SetValue(object value) 17 | { 18 | var scheduler = UpdateScheduler.Begin(); 19 | 20 | try 21 | { 22 | value = UnwrapValue(value); 23 | Member.SetValue(Instance, value); 24 | } 25 | finally 26 | { 27 | if (scheduler != null) 28 | { 29 | foreach (Action updatable in scheduler.End()) 30 | updatable(); 31 | } 32 | } 33 | } 34 | 35 | public override object GetValue() 36 | { 37 | UpdateNow(); 38 | return _value; 39 | } 40 | 41 | internal override void UpdateValue() 42 | { 43 | _sourceValue = WrapValue(Member.GetValue(Instance)); 44 | } 45 | 46 | protected internal override void PublishChanges() 47 | { 48 | if (!Object.Equals(_value, _sourceValue)) 49 | { 50 | _value = _sourceValue; 51 | if (_firePropertyChanged) 52 | FirePropertyChanged(); 53 | } 54 | _firePropertyChanged = true; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Assisticant/Metas/CollectionItem.cs: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * Update Controls .NET 4 | * Copyright 2010 Michael L Perry 5 | * MIT License 6 | * 7 | * http://updatecontrols.net 8 | * http://updatecontrols.codeplex.com/ 9 | * 10 | **********************************************************************/ 11 | 12 | using System; 13 | using System.Collections.ObjectModel; 14 | 15 | namespace Assisticant.Metas 16 | { 17 | class CollectionItem : IDisposable 18 | { 19 | private ObservableCollection _collection; 20 | private object _item; 21 | private bool _inCollection; 22 | 23 | public CollectionItem(ObservableCollection collection, object item, bool inCollection) 24 | { 25 | _collection = collection; 26 | _item = item; 27 | _inCollection = inCollection; 28 | } 29 | 30 | public void Dispose() 31 | { 32 | if (_inCollection) 33 | _collection.Remove(_item); 34 | } 35 | 36 | public void EnsureInCollection(int index) 37 | { 38 | if (!_inCollection) 39 | { 40 | // Insert the item into the correct position. 41 | _collection.Insert(index, _item); 42 | } 43 | else if (!object.Equals(_collection[index], _item)) 44 | { 45 | // Remove the item from the old position. 46 | _collection.Remove(_item); 47 | 48 | // Insert the item in the correct position. 49 | _collection.Insert(index, _item); 50 | } 51 | } 52 | 53 | public override int GetHashCode() 54 | { 55 | return _item.GetHashCode(); 56 | } 57 | 58 | public override bool Equals(object obj) 59 | { 60 | if (obj == null) 61 | return false; 62 | if (obj == this) 63 | return true; 64 | if (!(obj is CollectionItem)) 65 | return false; 66 | CollectionItem that = (CollectionItem)obj; 67 | return Object.Equals( 68 | this._item, 69 | that._item); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Assisticant/Metas/CommandMeta.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Input; 8 | 9 | namespace Assisticant.Metas 10 | { 11 | public class CommandMeta : MemberMeta 12 | { 13 | public readonly MethodInfo Method; 14 | public readonly MemberMeta Condition; 15 | public readonly bool HasParameter; 16 | 17 | public override bool CanWrite { get { return false; } } 18 | 19 | CommandMeta(TypeMeta owner, MethodInfo method, MemberMeta condition) 20 | : base(owner, method.Name, typeof(ICommand)) 21 | { 22 | Method = method; 23 | Condition = condition; 24 | HasParameter = method.GetParameters().Any(); 25 | } 26 | 27 | public override object GetValue(object instance) 28 | { 29 | return new MethodCommand(instance, this); 30 | } 31 | 32 | public override void SetValue(object instance, object value) 33 | { 34 | throw new NotSupportedException(); 35 | } 36 | 37 | internal static IEnumerable GetAll(TypeMeta owner, IEnumerable properties) 38 | { 39 | var conditions = (from property in properties 40 | where property.MemberType == typeof(bool) && property.Name.StartsWith("Can") 41 | select property).ToList(); 42 | return from method in owner.Type.GetMethodsPortable() 43 | where method.IsPublic && !method.IsStatic && !method.IsSpecialName && method.ReturnType == typeof(void) && method.GetParameters().Length <= 1 44 | select new CommandMeta(owner, method, conditions.FirstOrDefault(c => c.Name == "Can" + method.Name)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Assisticant/Metas/ComputedMeta.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Fields; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace Assisticant.Metas 9 | { 10 | public class ComputedMeta : ValuePropertyMeta 11 | { 12 | ComputedMeta(MemberMeta observable, Type unwrappedType) 13 | : base(observable, unwrappedType) 14 | { 15 | } 16 | 17 | public override void SetValue(object instance, object value) 18 | { 19 | throw new MemberAccessException(); 20 | } 21 | 22 | internal static MemberMeta Intercept(MemberMeta member) 23 | { 24 | var unwrapped = UnwrapObservableType(member.MemberType, typeof(Computed<>)); 25 | if (unwrapped != null) 26 | return new ComputedMeta(member, unwrapped); 27 | else 28 | return member; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Assisticant/Metas/FieldMeta.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Assisticant.Metas 9 | { 10 | public class FieldMeta : MemberMeta 11 | { 12 | public readonly FieldInfo Field; 13 | 14 | public override bool CanWrite { get { return !Field.IsInitOnly; } } 15 | 16 | FieldMeta(TypeMeta owner, FieldInfo field) 17 | : base(owner, field.Name, field.FieldType) 18 | { 19 | Field = field; 20 | } 21 | 22 | public override object GetValue(object instance) 23 | { 24 | return Field.GetValue(instance); 25 | } 26 | 27 | public override void SetValue(object instance, object value) 28 | { 29 | if (!Field.IsInitOnly) 30 | Field.SetValue(instance, value); 31 | else 32 | throw new MemberAccessException(); 33 | } 34 | 35 | internal static IEnumerable GetAll(TypeMeta owner) 36 | { 37 | return from field in owner.Type.GetFieldsPortable() 38 | where field.IsPublic && !field.IsStatic 39 | select new FieldMeta(owner, field); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Assisticant/Metas/MemberMeta.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Specialized; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Assisticant.Metas 11 | { 12 | public abstract class MemberMeta 13 | { 14 | public readonly TypeMeta DeclaringType; 15 | public readonly string Name; 16 | public readonly Type MemberType; 17 | public readonly bool IsViewModel; 18 | public readonly bool IsCollection; 19 | public readonly bool IsList; 20 | public readonly bool IsObservableCollection; 21 | 22 | public virtual bool CanRead { get { return true; } } 23 | public virtual bool CanWrite { get { return true; } } 24 | 25 | public virtual IEnumerable EarlierMembers => Enumerable.Empty(); 26 | 27 | 28 | public MemberMeta(TypeMeta owner, string name, Type type) 29 | { 30 | DeclaringType = owner; 31 | Name = name; 32 | MemberType = type; 33 | IsViewModel = ViewModelTypes.IsViewModel(type); 34 | IsCollection = typeof(IEnumerable).IsAssignableFromPortable(MemberType) && MemberType != typeof(string); 35 | IsObservableCollection = typeof(INotifyCollectionChanged).IsAssignableFromPortable(type) || 36 | IsBindingList(type); 37 | IsList = typeof(IList).IsAssignableFromPortable(type) || 38 | IsIList(type); 39 | } 40 | 41 | public abstract void SetValue(object instance, object value); 42 | public abstract object GetValue(object instance); 43 | 44 | public override string ToString() 45 | { 46 | return String.Format("{0}.{1}", DeclaringType.Type.Name, Name); 47 | } 48 | 49 | private bool IsBindingList(Type type) 50 | { 51 | const string bindingList = "System.ComponentModel.IBindingList"; 52 | return type.FullName == bindingList || 53 | type.GetInterfacesPortable().Any(i => i.FullName == bindingList); 54 | } 55 | 56 | private bool IsIList(Type type) 57 | { 58 | const string iList = "System.Collections.Generic.IList"; 59 | return type.FullName.Contains(iList) || 60 | type.GetInterfacesPortable().Any(i => i.FullName.Contains(iList)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Assisticant/Metas/MethodCommand.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Fields; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Input; 9 | 10 | namespace Assisticant.Metas 11 | { 12 | public class MethodCommand : ICommand 13 | { 14 | public readonly object Instance; 15 | public readonly CommandMeta Meta; 16 | readonly Computed _computedCan; 17 | bool? _lastCan; 18 | 19 | public event EventHandler CanExecuteChanged; 20 | 21 | public MethodCommand(object instance, CommandMeta meta) 22 | { 23 | Instance = instance; 24 | Meta = meta; 25 | if (meta.Condition != null) 26 | { 27 | _computedCan = new Computed(() => (bool)meta.Condition.GetValue(Instance)); 28 | _computedCan.Invalidated += () => UpdateScheduler.ScheduleUpdate(UpdateNow); 29 | } 30 | } 31 | 32 | public bool CanExecute(object parameter) 33 | { 34 | if (_computedCan == null) 35 | return true; 36 | return BindingInterceptor.Current.CanExecute(this, parameter); 37 | } 38 | 39 | internal bool ContinueCanExecute(object parameter) 40 | { 41 | _lastCan = _computedCan.Value; 42 | return _lastCan.Value; 43 | } 44 | 45 | public void Execute(object parameter) 46 | { 47 | BindingInterceptor.Current.Execute(this, parameter); 48 | } 49 | 50 | internal void ContinueExecute(object parameter) 51 | { 52 | if (Meta.HasParameter) 53 | Meta.Method.Invoke(Instance, new object[] { parameter }); 54 | else 55 | Meta.Method.Invoke(Instance, new object[0]); 56 | } 57 | 58 | private void UpdateNow() 59 | { 60 | var can = _computedCan.Value; 61 | if (_lastCan != can && CanExecuteChanged != null) 62 | CanExecuteChanged(this, EventArgs.Empty); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Assisticant/Metas/ObservableMeta.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Fields; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Assisticant.Metas 10 | { 11 | public class ObservableMeta : ValuePropertyMeta 12 | { 13 | ObservableMeta(MemberMeta observable, Type unwrappedType) 14 | : base(observable, unwrappedType) 15 | { 16 | } 17 | 18 | public override void SetValue(object instance, object value) 19 | { 20 | _valueProperty.SetValue(UnderlyingMember.GetValue(instance), value, null); 21 | } 22 | 23 | internal static MemberMeta Intercept(MemberMeta member) 24 | { 25 | var unwrapped = UnwrapObservableType(member.MemberType, typeof(Observable<>)); 26 | if (unwrapped != null) 27 | return new ObservableMeta(member, unwrapped); 28 | else 29 | return member; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Assisticant/Metas/PassThroughSlot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Assisticant.Metas 4 | { 5 | public class PassThroughSlot : MemberSlot 6 | { 7 | public PassThroughSlot(ViewProxy proxy, MemberMeta member) : base(proxy, member) 8 | { 9 | } 10 | 11 | public override void SetValue(object value) 12 | { 13 | Member.SetValue(Instance, value); 14 | } 15 | 16 | public override object GetValue() 17 | { 18 | return Member.GetValue(Instance); 19 | } 20 | 21 | protected internal override void PublishChanges() 22 | { 23 | } 24 | 25 | internal override void UpdateValue() 26 | { 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Assisticant/Metas/PropertyMeta.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Assisticant.Metas 9 | { 10 | public class PropertyMeta : MemberMeta 11 | { 12 | public readonly PropertyInfo Property; 13 | private List _earlierPropertyNames; 14 | 15 | public override bool CanRead { get { return Property.CanRead; } } 16 | public override bool CanWrite { get { return Property.CanWrite; } } 17 | 18 | PropertyMeta(TypeMeta owner, PropertyInfo property) 19 | : base(owner, property.Name, property.PropertyType) 20 | { 21 | Property = property; 22 | 23 | _earlierPropertyNames = property 24 | .GetCustomAttributesPortable() 25 | .Select(a => a.OtherProperty) 26 | .ToList(); 27 | } 28 | 29 | public override object GetValue(object instance) 30 | { 31 | return Property.GetValue(instance, null); 32 | } 33 | 34 | public override void SetValue(object instance, object value) 35 | { 36 | Property.SetValue(instance, value, null); 37 | } 38 | 39 | public override IEnumerable EarlierMembers => 40 | _earlierPropertyNames.SelectMany(n => DeclaringType.Members.Where(x => x.Name == n)); 41 | 42 | internal static IEnumerable GetAll(TypeMeta owner) 43 | { 44 | return from property in owner.Type.GetPropertiesPortable() 45 | where property.CanRead && property.GetGetMethodPortable().IsPublic && !property.GetGetMethodPortable().IsStatic 46 | select new PropertyMeta(owner, property); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Assisticant/Metas/TypeMeta.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Assisticant.Metas 8 | { 9 | public class TypeMeta 10 | { 11 | public readonly Type Type; 12 | public readonly MemberMeta[] Members; 13 | static readonly Dictionary _cache = new Dictionary(); 14 | 15 | TypeMeta(Type type) 16 | { 17 | Type = type; 18 | var properties = PropertyMeta.GetAll(this).Concat(FieldMeta.GetAll(this)).Select(ValuePropertyMeta.InterceptAny).ToList(); 19 | Members = properties.Concat(CommandMeta.GetAll(this, properties)).ToArray(); 20 | } 21 | 22 | public static TypeMeta Get(Type type) 23 | { 24 | TypeMeta result; 25 | lock (_cache) 26 | { 27 | if (!_cache.TryGetValue(type, out result)) 28 | _cache[type] = result = new TypeMeta(type); 29 | } 30 | return result; 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return Type.ToString(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Assisticant/Metas/ValuePropertyMeta.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace Assisticant.Metas 8 | { 9 | public abstract class ValuePropertyMeta : MemberMeta 10 | { 11 | public readonly MemberMeta UnderlyingMember; 12 | protected readonly PropertyInfo _valueProperty; 13 | 14 | protected ValuePropertyMeta(MemberMeta observable, Type unwrappedType) 15 | : base(observable.DeclaringType, observable.Name, unwrappedType) 16 | { 17 | UnderlyingMember = observable; 18 | _valueProperty = observable.MemberType.GetPropertyPortable("Value"); 19 | } 20 | 21 | public override object GetValue(object instance) 22 | { 23 | return _valueProperty.GetValue(UnderlyingMember.GetValue(instance), null); 24 | } 25 | 26 | internal static MemberMeta InterceptAny(MemberMeta member) 27 | { 28 | return ComputedMeta.Intercept(ObservableMeta.Intercept(member)); 29 | } 30 | 31 | protected static Type UnwrapObservableType(Type observable, Type wrapper) 32 | { 33 | for (Type ancestor = observable; ancestor != typeof(object) && ancestor != null; ancestor = ancestor.BaseTypePortable()) 34 | if (ancestor.IsGenericTypePortable() && ancestor.GetGenericTypeDefinition() == wrapper) 35 | return ancestor.GetGenericArgumentsPortable().First(); 36 | return null; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Assisticant/Metas/ViewModelTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Specialized; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Input; 10 | using System.Reflection; 11 | 12 | namespace Assisticant.Metas 13 | { 14 | public static class ViewModelTypes 15 | { 16 | static readonly Dictionary _cache = new Dictionary(); 17 | static readonly Type[] _disabledByDefault = new[] 18 | { 19 | typeof(INotifyPropertyChanged), 20 | typeof(INotifyCollectionChanged), 21 | typeof(ICommand), 22 | typeof(IEnumerable) 23 | }; 24 | 25 | public static void Disable(Type type) 26 | { 27 | lock (_cache) 28 | _cache[type] = false; 29 | } 30 | 31 | public static bool IsViewModel(Type type) 32 | { 33 | bool result; 34 | lock (_cache) 35 | { 36 | if (!_cache.TryGetValue(type, out result)) 37 | _cache[type] = result = IsViewModelUncached(type); 38 | } 39 | return result; 40 | } 41 | 42 | static bool IsViewModelUncached(Type type) 43 | { 44 | if (type.IsValueTypePortable() || type.IsPrimitivePortable()) 45 | return false; 46 | if (type.IsGenericTypePortable() && type.GetGenericTypeDefinition() == typeof(Nullable<>)) 47 | return false; 48 | if (type.IsClassPortable() && type != typeof(object) && (type.FullName.StartsWith("System.") || type.FullName.StartsWith("Windows."))) 49 | return false; 50 | if (_disabledByDefault.Contains(type)) 51 | return false; 52 | var parent = type.BaseTypePortable(); 53 | if (parent != null && parent != typeof(object) && !IsViewModel(parent)) 54 | return false; 55 | foreach (var iface in type.GetInterfacesPortable()) 56 | if (!IsViewModel(iface)) 57 | return false; 58 | return true; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Assisticant/Metas/ViewProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | 7 | namespace Assisticant.Metas 8 | { 9 | [DebuggerDisplay("ForView.Wrap({Instance})")] 10 | public abstract class ViewProxy : INotifyPropertyChanged, IEditableObject 11 | { 12 | public readonly object Instance; 13 | readonly MemberSlot[] _slots; 14 | private int _notifyCount = 0; 15 | private HashSet _modifiedWhileNotifying = new HashSet(); 16 | 17 | public event PropertyChangedEventHandler PropertyChanged; 18 | 19 | public abstract ViewProxy WrapObject(object value); 20 | 21 | protected ViewProxy(object instance, TypeMeta type) 22 | { 23 | Instance = instance; 24 | _slots = (from member in type.Members 25 | select MemberSlot.Create(this, member)).ToArray(); 26 | } 27 | 28 | public void FirePropertyChanged(string name) 29 | { 30 | if (PropertyChanged != null) 31 | PropertyChanged(this, new PropertyChangedEventArgs(name)); 32 | } 33 | 34 | public MemberSlot LookupSlot(MemberMeta member) 35 | { 36 | return _slots.FirstOrDefault(s => s.Member == member); 37 | } 38 | 39 | public void BeginEdit() 40 | { 41 | var editable = Instance as IEditableObject; 42 | if (editable != null) 43 | editable.BeginEdit(); 44 | } 45 | 46 | public void CancelEdit() 47 | { 48 | var editable = Instance as IEditableObject; 49 | if (editable != null) 50 | editable.CancelEdit(); 51 | } 52 | 53 | public void EndEdit() 54 | { 55 | var editable = Instance as IEditableObject; 56 | if (editable != null) 57 | editable.EndEdit(); 58 | } 59 | 60 | public override bool Equals(object obj) 61 | { 62 | if (obj == null) 63 | return false; 64 | if (obj == this) 65 | return true; 66 | if (obj.GetType() != this.GetType()) 67 | return false; 68 | return Equals(Instance, ((ViewProxy)obj).Instance); 69 | } 70 | 71 | public override int GetHashCode() 72 | { 73 | return Instance.GetHashCode(); 74 | } 75 | 76 | public override string ToString() 77 | { 78 | return Instance.ToString(); 79 | } 80 | 81 | internal void Notify(Action publishChanges) 82 | { 83 | _notifyCount++; 84 | try 85 | { 86 | publishChanges(); 87 | } 88 | finally 89 | { 90 | _notifyCount--; 91 | if (_notifyCount == 0) 92 | { 93 | var modifiedMembers = _modifiedWhileNotifying.ToList(); 94 | _modifiedWhileNotifying.Clear(); 95 | foreach (var member in modifiedMembers) 96 | { 97 | member.PublishChanges(); 98 | } 99 | } 100 | } 101 | } 102 | 103 | internal bool IsNotifying => _notifyCount > 0; 104 | 105 | internal void WasModified(MemberSlot member) 106 | { 107 | _modifiedWhileNotifying.Add(member); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Assisticant/NamedPrecedents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics; 6 | using System.Reflection; 7 | 8 | namespace Assisticant 9 | { 10 | public class NamedComputed : Computed 11 | { 12 | public NamedComputed(Action update) : this(null, update) { } 13 | public NamedComputed(string name, Action update) : base(update) { _name = name; } 14 | 15 | protected string _name; 16 | public string Name 17 | { 18 | get { 19 | if (_name == null) 20 | _name = ComputeName(); 21 | return _name; 22 | } 23 | } 24 | 25 | public override string VisualizerName(bool withValue) 26 | { 27 | return VisNameWithOptionalHash(Name, withValue); 28 | } 29 | public static string GetClassAndMethodName(Delegate d) 30 | { 31 | var method = GetMethodInfo(d); 32 | return MemoizedTypeName.GenericName(method.DeclaringType) + "." + method.Name; 33 | } 34 | static MethodInfo GetMethodInfo(Delegate d) 35 | { 36 | #if NETSTANDARD1_4 37 | return d.GetMethodInfo(); 38 | #else 39 | return d.Method; 40 | #endif 41 | } 42 | protected virtual string ComputeName() 43 | { 44 | return GetClassAndMethodName(_update) + "()"; 45 | } 46 | } 47 | 48 | public class NamedObservable : Observable 49 | { 50 | public NamedObservable() : base() { } 51 | public NamedObservable(string name) : base() { _name = name; } 52 | public NamedObservable(Type valueType) : this(valueType.NameWithGenericParams()) { } 53 | public NamedObservable(Type containerType, string name) : 54 | this(string.Format("{0}.{1}", containerType.NameWithGenericParams(), name)) { } 55 | 56 | public override void OnGet() 57 | { 58 | // TODO: Figure out _name 59 | base.OnGet(); 60 | } 61 | 62 | protected string _name; 63 | public string Name 64 | { 65 | get { return _name ?? "NamedObservable"; } 66 | set { _name = value; } 67 | } 68 | 69 | public override string VisualizerName(bool withValue) 70 | { 71 | return VisNameWithOptionalHash("[I] " + Name, withValue); 72 | } 73 | } 74 | 75 | [Obsolete] 76 | public class NamedObservable : Assisticant.Fields.Observable 77 | { 78 | public NamedObservable() : base() { } 79 | public NamedObservable(T value) : base(value) { } 80 | public NamedObservable(string name, T value) : base(name, value) { } 81 | public NamedObservable(Type containerType, string name) : base(containerType, name) { } 82 | public NamedObservable(Type containerType, string name, T value) : base(containerType, name, value) { } 83 | } 84 | 85 | [Obsolete] 86 | public class NamedComputed : Assisticant.Fields.Computed 87 | { 88 | public NamedComputed(Func compute) : base(compute) { } 89 | public NamedComputed(string name, Func compute) : base(name, compute) { } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Assisticant/NotifyAfterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Assisticant 4 | { 5 | [AttributeUsage(AttributeTargets.Property)] 6 | public class NotifyAfterAttribute : Attribute 7 | { 8 | public string OtherProperty { get; } 9 | 10 | public NotifyAfterAttribute(string otherProperty) 11 | { 12 | OtherProperty = otherProperty; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Assisticant/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | [assembly: AssemblyTitle("Assisticant")] 7 | [assembly: AssemblyProduct("Assisticant")] 8 | -------------------------------------------------------------------------------- /Assisticant/StaticExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Markup; 8 | using SystemStaticExtension = System.Windows.Markup.StaticExtension; 9 | 10 | namespace Assisticant 11 | { 12 | [MarkupExtensionReturnType(typeof(object))] 13 | public class StaticExtension : MarkupExtension 14 | { 15 | readonly SystemStaticExtension _inner = new SystemStaticExtension(); 16 | 17 | [ConstructorArgument("member")] 18 | public string Member { get { return _inner.Member; } set { _inner.Member = value; } } 19 | 20 | public Type MemberType { get { return _inner.MemberType; } set { _inner.MemberType = value; } } 21 | 22 | public StaticExtension() 23 | { 24 | } 25 | 26 | public StaticExtension(string member) 27 | { 28 | Member = member; 29 | } 30 | 31 | public override object ProvideValue(IServiceProvider serviceProvider) 32 | { 33 | return ForView.Wrap(_inner.ProvideValue(serviceProvider)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Assisticant/ThreadLocal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | 4 | namespace Assisticant 5 | { 6 | public class ThreadLocal 7 | { 8 | private Dictionary _valueByThread = new Dictionary(); 9 | 10 | public T Value 11 | { 12 | get 13 | { 14 | lock (this) 15 | { 16 | T value; 17 | if (!_valueByThread.TryGetValue(Thread.CurrentThread.ManagedThreadId, out value)) 18 | return default(T); 19 | return value; 20 | } 21 | } 22 | set 23 | { 24 | lock (this) 25 | { 26 | _valueByThread[Thread.CurrentThread.ManagedThreadId] = value; 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Assisticant/Timers/FloatingTimeSpan.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Assisticant.Timers 8 | { 9 | public abstract class FloatingTimeSpan 10 | { 11 | protected readonly FloatingTimeZone _zone; 12 | protected readonly DateTime _zero; 13 | 14 | public FloatingTimeZone Zone { get { return _zone; } } 15 | public DateTime ZeroMoment { get { return _zero; } } 16 | public abstract TimeSpan Snapshot { get; } 17 | public int Days { get { return GetComponent(Snapshot.Days, new TimeSpan(Snapshot.Days, 0, 0, 0), TimeSpan.FromDays(1)); } } 18 | public int Hours { get { return GetComponent(Snapshot.Hours, new TimeSpan(Snapshot.Days, Snapshot.Hours, 0, 0), TimeSpan.FromHours(1)); } } 19 | public int Minutes { get { return GetComponent(Snapshot.Minutes, new TimeSpan(Snapshot.Days, Snapshot.Hours, Snapshot.Minutes, 0), TimeSpan.FromMinutes(1)); } } 20 | public int Seconds { get { return GetComponent(Snapshot.Seconds, new TimeSpan(Snapshot.Days, Snapshot.Hours, Snapshot.Minutes, Snapshot.Seconds), TimeSpan.FromSeconds(1)); } } 21 | 22 | protected abstract int GetComponent(int component, TimeSpan cut, TimeSpan increment); 23 | 24 | protected FloatingTimeSpan(FloatingTimeZone zone, DateTime zero) 25 | { 26 | _zone = zone; 27 | _zero = zero; 28 | } 29 | 30 | public override string ToString() { return Snapshot.ToString(); } 31 | 32 | protected void CheckZone(FloatingTimeSpan other) 33 | { 34 | if (_zone != other._zone) 35 | throw new ArgumentException("Cannot relate FloatingTimeSpan instances from two different time zones"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Assisticant/Timers/ObservableTimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Assisticant.Timers 7 | { 8 | public class ObservableTimer : Observable 9 | { 10 | FloatingTimeZone _zone; 11 | DateTime _time; 12 | bool _expired; 13 | 14 | public DateTime ExpirationTime { get { return _time; } } 15 | 16 | public bool IsExpired 17 | { 18 | get 19 | { 20 | OnGet(); 21 | return _expired; 22 | } 23 | } 24 | 25 | internal ObservableTimer(FloatingTimeZone zone, DateTime time) 26 | { 27 | _zone = zone; 28 | _time = time; 29 | _expired = _zone.GetStableTime() >= time; 30 | } 31 | 32 | public static ObservableTimer Get(FloatingTimeZone zone, DateTime time) 33 | { 34 | return zone.GetTimer(time); 35 | } 36 | 37 | public static ObservableTimer GetUtc(DateTime utc) 38 | { 39 | return FloatingTimeZone.Utc.GetTimer(utc); 40 | } 41 | 42 | protected override void GainDependent() 43 | { 44 | _zone.Enqueue(this, _expired); 45 | } 46 | 47 | protected override void LoseDependent() 48 | { 49 | _zone.Dequeue(this); 50 | } 51 | 52 | internal void Expire(bool expire) 53 | { 54 | if (_expired != expire) 55 | { 56 | _expired = expire; 57 | OnSet(); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Assisticant/Timers/UtcTimeZone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace Assisticant.Timers 8 | { 9 | class UtcTimeZone : FloatingTimeZone 10 | { 11 | Timer _timer; 12 | 13 | public override DateTime GetRawTime() { return DateTime.UtcNow; } 14 | 15 | public UtcTimeZone() 16 | { 17 | _timer = new Timer(Expire, null, Timeout.Infinite, -1); 18 | } 19 | 20 | protected override void ScheduleTimer(TimeSpan delay) 21 | { 22 | _timer.Change(Convert.ToInt32(Math.Max(20, delay.TotalMilliseconds)), -1); 23 | } 24 | 25 | protected override void CancelTimer() 26 | { 27 | _timer.Change(Timeout.Infinite, -1); 28 | } 29 | 30 | void Expire(object state) 31 | { 32 | _timer.Change(Timeout.Infinite, -1); 33 | NotifyTimerExpired(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Assisticant/UpdateScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace Assisticant 6 | { 7 | public class UpdateScheduler 8 | { 9 | private static Action _runOnUIThread; 10 | private static ThreadLocal _currentSet = new ThreadLocal(); 11 | private static List _futureUpdates = new List(); 12 | 13 | public static void Initialize(Action runOnUIThread) 14 | { 15 | if (_runOnUIThread == null) 16 | { 17 | _runOnUIThread = runOnUIThread; 18 | foreach (var update in _futureUpdates) 19 | _runOnUIThread(update); 20 | } 21 | } 22 | 23 | public static UpdateScheduler Begin() 24 | { 25 | // If someone is already capturing the affected set, 26 | // let them keep that responsibility. 27 | if (_currentSet.Value != null) 28 | return null; 29 | 30 | UpdateScheduler currentSet = new UpdateScheduler(); 31 | _currentSet.Value = currentSet; 32 | return currentSet; 33 | } 34 | 35 | public static void ScheduleUpdate(Action update) 36 | { 37 | UpdateScheduler currentSet = _currentSet.Value; 38 | if (currentSet != null) 39 | currentSet._updatables.Add(update); 40 | else if (_runOnUIThread != null) 41 | _runOnUIThread(update); 42 | else 43 | _futureUpdates.Add(update); 44 | } 45 | 46 | private List _updatables = new List(); 47 | 48 | public IEnumerable End() 49 | { 50 | System.Diagnostics.Debug.Assert(_currentSet.Value == this); 51 | _currentSet.Value = null; 52 | return _updatables; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Assisticant/Validation/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace Assisticant.Validation 5 | { 6 | internal static class ExpressionExtensions 7 | { 8 | public static string GetPropertyName(this Expression> expr) 9 | { 10 | var body = (MemberExpression)expr.Body; 11 | var propertyName = body.Member.Name; 12 | 13 | return propertyName; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Assisticant/Validation/IValidation.cs: -------------------------------------------------------------------------------- 1 | namespace Assisticant.Validation 2 | { 3 | public interface IValidation 4 | { 5 | IValidationRules Rules { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Assisticant/Validation/IValidationRules.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Assisticant.Validation 4 | { 5 | public interface IValidationRules 6 | { 7 | event ValidationRules.ErrorsChangedDelegate ErrorsChanged; 8 | bool HasErrors { get; } 9 | IEnumerable GetErrors(string propertyName); 10 | } 11 | } -------------------------------------------------------------------------------- /Assisticant/Validation/NumericPropertyValidationContextExtentions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Assisticant.Validation 5 | { 6 | public static class NumericPropertyValidationContextExtentions 7 | { 8 | public static OptionalMessagePropertyValidationContext GreaterThan( 9 | this PropertyValidationContextBase context, 10 | T lowerBound, 11 | Comparer comparer = null) 12 | where T : IComparable, IComparable 13 | { 14 | comparer = DefaultIfNull(comparer); 15 | var name = context.PropertyName; 16 | 17 | return context.BeginPredicate( 18 | x => comparer.Compare(x, lowerBound) > 0, 19 | () => $"{name} must be greater than {lowerBound}"); 20 | } 21 | 22 | public static OptionalMessagePropertyValidationContext GreaterThanOrEqualTo( 23 | this PropertyValidationContextBase context, 24 | T lowerBound, 25 | Comparer comparer = null) 26 | where T : IComparable, IComparable 27 | { 28 | comparer = DefaultIfNull(comparer); 29 | var name = context.PropertyName; 30 | 31 | return context.BeginPredicate( 32 | x => comparer.Compare(x, lowerBound) >= 0, 33 | () => $"{name} must be at least {lowerBound}"); 34 | } 35 | 36 | public static OptionalMessagePropertyValidationContext LessThan( 37 | this PropertyValidationContextBase context, 38 | T upperBound, 39 | Comparer comparer = null) 40 | where T : IComparable, IComparable 41 | { 42 | comparer = DefaultIfNull(comparer); 43 | var name = context.PropertyName; 44 | 45 | return context.BeginPredicate( 46 | x => comparer.Compare(x, upperBound) < 0, 47 | () => $"{name} must be less than {upperBound}"); 48 | } 49 | 50 | public static OptionalMessagePropertyValidationContext LessThanOrEqualTo( 51 | this PropertyValidationContextBase context, 52 | T upperBound, 53 | Comparer comparer = null) 54 | where T : IComparable, IComparable 55 | { 56 | comparer = DefaultIfNull(comparer); 57 | var name = context.PropertyName; 58 | 59 | return context.BeginPredicate( 60 | x => comparer.Compare(x, upperBound) > 0, 61 | () => $"{name} must be no more than {upperBound}"); 62 | } 63 | 64 | private static Comparer DefaultIfNull(Comparer comparer) 65 | where T : IComparable, IComparable 66 | { 67 | return comparer != null ? comparer : Comparer.Default; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Assisticant/Validation/ObjectPropertyValidationContextExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Assisticant.Validation 2 | { 3 | public static class ObjectPropertyValidationContextExtensions 4 | { 5 | public static OptionalMessagePropertyValidationContext NotNull( 6 | this PropertyValidationContext context) 7 | where T : class 8 | { 9 | return context.BeginPredicate( 10 | v => v != null, 11 | () => $"{context._currentRuleset.PropExpr.GetPropertyName()} must not be null." 12 | ); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Assisticant/Validation/OptionalMessagePropertyValidationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Assisticant.Validation 6 | { 7 | public sealed class OptionalMessagePropertyValidationContext : PropertyValidationContextBase 8 | { 9 | private readonly Func _predicate; 10 | private readonly Func _messageFactory; 11 | 12 | internal OptionalMessagePropertyValidationContext( 13 | IEnumerable rulesets, 14 | PropertyRuleset ruleset, 15 | Func predicate, 16 | Func messageFactory 17 | ) : base(rulesets, ruleset) 18 | { 19 | _predicate = predicate; 20 | _messageFactory = messageFactory; 21 | } 22 | 23 | public PropertyValidationContext WithMessage(Func messageFactory) 24 | { 25 | return new PropertyValidationContext( 26 | _rulesets, 27 | FinalizeRuleset(messageFactory) 28 | ); 29 | } 30 | 31 | internal override IEnumerable FinalizeRulesets() 32 | { 33 | return FinalizeRulesets(_messageFactory); 34 | } 35 | 36 | private IEnumerable FinalizeRulesets(Func messageFactory) 37 | { 38 | var ruleset = FinalizeRuleset(messageFactory); 39 | 40 | return _rulesets.Concat(new[] {ruleset}); 41 | } 42 | 43 | internal override PropertyRuleset FinalizeRuleset() 44 | { 45 | return _currentRuleset.AddRule(_predicate, _messageFactory); 46 | } 47 | 48 | internal PropertyRuleset FinalizeRuleset(Func messageFactory) 49 | { 50 | return _currentRuleset.AddRule(_predicate, messageFactory); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Assisticant/Validation/PropertyPredicateContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Assisticant.Validation 5 | { 6 | public sealed class PropertyPredicateContext 7 | { 8 | private readonly IEnumerable _rulesets; 9 | private readonly PropertyRuleset _currentRuleset; 10 | private readonly Func _predicate; 11 | 12 | internal PropertyPredicateContext(IEnumerable rulesets, PropertyRuleset currentRuleset, Func predicate) 13 | { 14 | _rulesets = rulesets; 15 | _predicate = predicate; 16 | _currentRuleset = currentRuleset; 17 | } 18 | 19 | public PropertyValidationContext WithMessage(Func messageFactory) 20 | { 21 | var ruleset = _currentRuleset.AddRule(_predicate, messageFactory); 22 | 23 | return new PropertyValidationContext(_rulesets, ruleset); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Assisticant/Validation/PropertyRuleset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace Assisticant.Validation 6 | { 7 | internal abstract class PropertyRuleset 8 | { 9 | public abstract void AddTo(ValidationRules rules); 10 | } 11 | 12 | internal sealed class PropertyRuleset : PropertyRuleset 13 | { 14 | public Expression> PropExpr { get; } 15 | private readonly Tuple, Func>[] _rules; 16 | 17 | public PropertyRuleset(Expression> propExpr) 18 | { 19 | PropExpr = propExpr; 20 | 21 | _rules = new Tuple, Func>[0]; 22 | } 23 | 24 | private PropertyRuleset(PropertyRuleset from, Func predicate, Func messageFactory) 25 | { 26 | PropExpr = from.PropExpr; 27 | 28 | _rules = from._rules 29 | .Concat(new[] {Tuple.Create(predicate, messageFactory)}) 30 | .ToArray(); 31 | } 32 | 33 | public PropertyRuleset AddRule(Func predicate, Func messageFactory) 34 | { 35 | return new PropertyRuleset(this, predicate, messageFactory); 36 | } 37 | 38 | public override void AddTo(ValidationRules rules) 39 | { 40 | var validator = rules.ValidatorForProperty(PropExpr); 41 | 42 | foreach (var rule in _rules) 43 | { 44 | validator.AddRule(x => rule.Item1((T)x), rule.Item2); 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Assisticant/Validation/PropertyValidationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace Assisticant.Validation 6 | { 7 | public sealed class PropertyValidationContext : PropertyValidationContextBase 8 | { 9 | internal PropertyValidationContext(IEnumerable rulesets, Expression> propExpression) 10 | : base(rulesets, propExpression) 11 | { 12 | } 13 | 14 | internal PropertyValidationContext(IEnumerable rulesets, PropertyRuleset currentRuleset) 15 | : base(rulesets, currentRuleset) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Assisticant/Validation/PropertyValidationContextBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace Assisticant.Validation 7 | { 8 | public abstract class PropertyValidationContextBase 9 | { 10 | internal readonly IEnumerable _rulesets; 11 | internal readonly PropertyRuleset _currentRuleset; 12 | 13 | internal PropertyValidationContextBase(IEnumerable rulesets, Expression> propExpression) 14 | { 15 | _rulesets = rulesets; 16 | _currentRuleset = new PropertyRuleset(propExpression); 17 | } 18 | 19 | internal PropertyValidationContextBase(IEnumerable rulesets, PropertyRuleset currentRuleset) 20 | { 21 | _rulesets = rulesets; 22 | _currentRuleset = currentRuleset; 23 | } 24 | 25 | public string PropertyName => _currentRuleset.PropExpr.GetPropertyName(); 26 | 27 | public PropertyPredicateContext Where(Func predicate) 28 | { 29 | return new PropertyPredicateContext(_rulesets, _currentRuleset, predicate); 30 | } 31 | 32 | public PropertyValidationContext For(Expression> propExpression) 33 | { 34 | return new PropertyValidationContext( 35 | FinalizeRulesets(), 36 | propExpression 37 | ); 38 | } 39 | 40 | internal OptionalMessagePropertyValidationContext BeginPredicate( 41 | Func predicate, 42 | Func messageFactory) 43 | { 44 | return new OptionalMessagePropertyValidationContext( 45 | _rulesets, 46 | FinalizeRuleset(), 47 | predicate, 48 | messageFactory); 49 | } 50 | 51 | internal virtual IEnumerable FinalizeRulesets() 52 | { 53 | return _rulesets.Concat(new [] {FinalizeRuleset()}); 54 | } 55 | 56 | internal virtual PropertyRuleset FinalizeRuleset() 57 | { 58 | return _currentRuleset; 59 | } 60 | 61 | public ValidationRules Build() 62 | { 63 | var rules = new ValidationRules(); 64 | 65 | var allRulesets = FinalizeRulesets(); 66 | 67 | foreach (var ruleset in allRulesets) 68 | { 69 | ruleset.AddTo(rules); 70 | } 71 | 72 | return rules; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Assisticant/Validation/PropertyValidator.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Collections; 2 | using Assisticant.Fields; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Assisticant.Validation 8 | { 9 | public class PropertyValidator : IDisposable 10 | { 11 | private readonly ObservableList> _rules = new ObservableList>(); 12 | private readonly Computed> _validationErrors; 13 | private readonly ComputedSubscription _subscription; 14 | 15 | public string PropertyName { get; } 16 | 17 | public PropertyValidator(string propertyName, Func function, Action notify) 18 | { 19 | PropertyName = propertyName; 20 | _validationErrors = new Computed>(() => 21 | { 22 | var value = function(); 23 | return _rules.Select(r => r(value)).Where(e => e != null).ToList(); 24 | }); 25 | _subscription = _validationErrors.Subscribe((errors, priorErrors) => 26 | { 27 | if (priorErrors == null) 28 | { 29 | if (errors != null && errors.Any()) 30 | { 31 | notify(propertyName); 32 | } 33 | } 34 | else 35 | { 36 | if (errors == null || !priorErrors.SequenceEqual(errors)) 37 | { 38 | notify(propertyName); 39 | } 40 | } 41 | }); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | _subscription.Unsubscribe(); 47 | } 48 | 49 | public void AddRule(Func rule) 50 | { 51 | _rules.Add(rule); 52 | } 53 | 54 | public void AddRule(Func predicate, Func errGenerator) 55 | { 56 | _rules.Add(v => predicate(v) ? null : errGenerator()); 57 | } 58 | 59 | public IEnumerable ValidationErrors => _validationErrors.Value; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Assisticant/Validation/StringPropertyValidationContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Assisticant.Validation 4 | { 5 | public static class StringPropertyValidationContextExtensions 6 | { 7 | public static OptionalMessagePropertyValidationContext NotNullOrEmpty( 8 | this PropertyValidationContext context) 9 | { 10 | return context.BeginPredicate( 11 | v => !string.IsNullOrEmpty(v), 12 | () => $"{context._currentRuleset.PropExpr.GetPropertyName()} must not be empty." 13 | ); 14 | } 15 | 16 | public static OptionalMessagePropertyValidationContext NotNullOrWhitespace( 17 | this PropertyValidationContext context) 18 | { 19 | return context.BeginPredicate( 20 | v => !string.IsNullOrWhiteSpace(v), 21 | () => $"{context._currentRuleset.PropExpr.GetPropertyName()} must not be empty or whitespace." 22 | ); 23 | } 24 | 25 | public static OptionalMessagePropertyValidationContext MaxLength( 26 | this PropertyValidationContext context, 27 | int bound) 28 | { 29 | return context.BeginPredicate( 30 | v => v == null || v.Length <= bound, 31 | () => $"{context._currentRuleset.PropExpr.GetPropertyName()} may be at most {bound} characters long." 32 | ); 33 | } 34 | 35 | public static OptionalMessagePropertyValidationContext Matches( 36 | this PropertyValidationContextBase context, 37 | string pattern) 38 | { 39 | var regex = new Regex(pattern); 40 | 41 | return context.BeginPredicate( 42 | v => v == null || regex.IsMatch(v), 43 | () => $"{context._currentRuleset.PropExpr.GetPropertyName()} is not valid." 44 | ); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Assisticant/Validation/ValidationRules.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace Assisticant.Validation 7 | { 8 | public static class Validator 9 | { 10 | public static PropertyValidationContext For(Expression> propExpr) 11 | { 12 | return new PropertyValidationContext(new PropertyRuleset[0], propExpr); 13 | } 14 | } 15 | 16 | public sealed class ValidationRules : IDisposable, IValidationRules 17 | { 18 | public delegate void ErrorsChangedDelegate(string propertyName); 19 | 20 | public event ErrorsChangedDelegate ErrorsChanged; 21 | 22 | private readonly Dictionary _validatorByPropertyName = 23 | new Dictionary(); 24 | 25 | internal ValidationRules() 26 | { 27 | 28 | } 29 | 30 | internal PropertyValidator ValidatorForProperty(Expression> property) 31 | { 32 | var propertyName = property.GetPropertyName(); 33 | PropertyValidator propertyValidator; 34 | if (!_validatorByPropertyName.TryGetValue(propertyName, out propertyValidator)) 35 | { 36 | var function = property.Compile(); 37 | propertyValidator = new PropertyValidator(propertyName, () => function(), Notify); 38 | _validatorByPropertyName.Add(propertyName, propertyValidator); 39 | } 40 | return propertyValidator; 41 | } 42 | 43 | private void Notify(string propertyName) 44 | { 45 | ErrorsChanged?.Invoke(propertyName); 46 | } 47 | 48 | public void Dispose() 49 | { 50 | foreach (var propertyValidator in _validatorByPropertyName.Values) 51 | propertyValidator.Dispose(); 52 | } 53 | 54 | public bool HasErrors => _validatorByPropertyName.Values 55 | .Any(p => p.ValidationErrors.Any()); 56 | 57 | public IEnumerable GetErrors(string propertyName) 58 | { 59 | PropertyValidator propertyValidator; 60 | if (_validatorByPropertyName.TryGetValue(propertyName, out propertyValidator)) 61 | return propertyValidator.ValidationErrors; 62 | else 63 | return Enumerable.Empty(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Assisticant/Validation/fluent.gv: -------------------------------------------------------------------------------- 1 | digraph "fluent" 2 | { 3 | 4 | Validator -> PropertyValidationContext [label="For T"] 5 | Validator -> StringPropertyValidationContext [label="For string"] 6 | Validator -> NumericPropValidationContext [label="For int"] 7 | 8 | PropertyValidationContext -> PropertyPredicateContext [label="Where"] 9 | PropertyValidationContext -> PropertyValidationContext [label="For T"] 10 | PropertyValidationContext -> StringPropertyValidationContext [label="For string"] 11 | PropertyValidationContext -> NumericPropValidationContext [label="For int"] 12 | PropertyValidationContext -> ValidationRules [label="Build"] 13 | 14 | StringPropertyValidationContext -> PropertyValidationContext [style=dashed] 15 | StringPropertyValidationContext -> StringPropertyValidationContext [label="Matches"] 16 | 17 | NumericPropValidationContext -> PropertyValidationContext [style=dashed] 18 | NumericPropValidationContext -> NumericPropValidationContext [label="Rule"] 19 | 20 | PropertyPredicateContext -> PropertyValidationContext [label="WithMessage"] 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Assisticant/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Assisticant 7 | { 8 | public class ViewModelBase : INotifyPropertyChanged 9 | { 10 | private PropertyTracker _propertyTracker; 11 | 12 | public event PropertyChangedEventHandler PropertyChanged; 13 | 14 | public ViewModelBase() 15 | { 16 | _propertyTracker = new PropertyTracker(FirePropertyChanged); 17 | } 18 | 19 | protected T Get(Func getMethod, [CallerMemberName] string propertyName = "") 20 | { 21 | ForView.Initialize(); 22 | return _propertyTracker.Get(getMethod, propertyName); 23 | } 24 | 25 | protected IEnumerable GetCollection(Func> getMethod, [CallerMemberName] string propertyName = "") 26 | { 27 | ForView.Initialize(); 28 | return _propertyTracker.GetCollection(getMethod, propertyName); 29 | } 30 | 31 | private void FirePropertyChanged(string propertyName) 32 | { 33 | if (PropertyChanged != null) 34 | PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Assisticant/ViewModelLocatorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | using System.Diagnostics; 6 | using System.Windows; 7 | 8 | namespace Assisticant 9 | { 10 | public class ViewModelLocatorBase : INotifyPropertyChanged 11 | { 12 | private class ViewModelContainer 13 | { 14 | private Computed _computed; 15 | private object _viewModel; 16 | 17 | public ViewModelContainer(Action firePropertyChanged, Func constructor) 18 | { 19 | _computed = new Computed(() => _viewModel = ForView.Wrap(constructor())); 20 | _computed.Invalidated += () => UpdateScheduler.ScheduleUpdate(firePropertyChanged); 21 | } 22 | 23 | public object ViewModel 24 | { 25 | get { _computed.OnGet(); return _viewModel; } 26 | } 27 | } 28 | 29 | public event PropertyChangedEventHandler PropertyChanged; 30 | 31 | private IDictionary _containerByName = new Dictionary(); 32 | 33 | private readonly bool _designMode; 34 | 35 | public ViewModelLocatorBase() 36 | { 37 | #if WPF 38 | _designMode = DesignerProperties.GetIsInDesignMode(new DependencyObject()); 39 | #endif 40 | } 41 | 42 | public bool DesignMode 43 | { 44 | get { return _designMode; } 45 | } 46 | 47 | public object ViewModel(Func constructor, [CallerMemberName] string propertyName = "") 48 | { 49 | if (DesignMode) 50 | return constructor(); 51 | 52 | ForView.Initialize(); 53 | ViewModelContainer container; 54 | if (!_containerByName.TryGetValue(propertyName, out container)) 55 | { 56 | container = new ViewModelContainer(() => FirePropertyChanged(propertyName), constructor); 57 | _containerByName.Add(propertyName, container); 58 | } 59 | return container.ViewModel; 60 | } 61 | 62 | private void FirePropertyChanged(string propertyName) 63 | { 64 | if (PropertyChanged != null) 65 | PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Assisticant/ViewSelector.cs: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * Assisticant 4 | * Copyright 2014 Michael L Perry 5 | * MIT License 6 | * 7 | * http://assisticant.net 8 | * 9 | **********************************************************************/ 10 | 11 | #if WPF 12 | 13 | #endif 14 | using System; 15 | 16 | #if WPF 17 | using Assisticant.Descriptors; 18 | using System.Windows.Controls; 19 | using System.Windows; 20 | #endif 21 | 22 | namespace Assisticant 23 | { 24 | public class ViewSelector : DataTemplateSelector 25 | { 26 | #if WPF 27 | public override DataTemplate SelectTemplate(object item, DependencyObject container) 28 | { 29 | var wrapper = item as PlatformProxy; 30 | var element = container as FrameworkElement; 31 | if (wrapper != null && element != null) 32 | { 33 | for (var type = wrapper.Instance.GetType(); type != null && type != typeof(object); type = type.BaseType) 34 | { 35 | var template = element.TryFindResource(new DataTemplateKey(type)) as DataTemplate; 36 | if (template != null) 37 | return template; 38 | } 39 | } 40 | 41 | return base.SelectTemplate(item, container); 42 | } 43 | #endif 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Assisticant/ViewSelectorExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Markup; 7 | 8 | namespace Assisticant 9 | { 10 | [MarkupExtensionReturnType(typeof(ViewSelector))] 11 | public class ViewSelectorExtension : MarkupExtension 12 | { 13 | public override object ProvideValue(IServiceProvider serviceProvider) 14 | { 15 | return new ViewSelector(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Assisticant/WeakHashSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Assisticant 8 | { 9 | class WeakHashSet : IEnumerable 10 | where T : class 11 | { 12 | readonly HashSet _set = new HashSet(); 13 | 14 | public int Count { get { return _set.Count; } } 15 | 16 | public void Add(T item) 17 | { 18 | _set.Add(new HashedWeakReference(item)); 19 | } 20 | 21 | public void Remove(T item) 22 | { 23 | _set.Remove(new HashedWeakReference(item)); 24 | } 25 | 26 | public bool Contains(T item) 27 | { 28 | return _set.Contains(new HashedWeakReference(item)); 29 | } 30 | 31 | IEnumerator IEnumerable.GetEnumerator() { return GetItems().GetEnumerator(); } 32 | IEnumerator IEnumerable.GetEnumerator() { return GetItems().GetEnumerator(); } 33 | 34 | IEnumerable GetItems() 35 | { 36 | foreach (var reference in _set) 37 | { 38 | var target = reference.Target as T; 39 | if (target != null) 40 | yield return target; 41 | } 42 | } 43 | 44 | class HashedWeakReference 45 | { 46 | private readonly WeakReference _inner; 47 | 48 | int _hashCode; 49 | 50 | public HashedWeakReference(object target) 51 | { 52 | _inner = new WeakReference(target); 53 | _hashCode = target.GetHashCode(); 54 | } 55 | 56 | public object Target 57 | { 58 | get { return _inner.Target; } 59 | } 60 | 61 | public override bool Equals(object obj) 62 | { 63 | var reference = obj as HashedWeakReference; 64 | if (reference == null) 65 | return false; 66 | var target1 = Target; 67 | var target2 = reference.Target; 68 | if (target1 == null || target2 == null) 69 | return false; 70 | return Equals(target1, target2); 71 | } 72 | 73 | public override int GetHashCode() { return _hashCode; } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Assisticant/XamlTypes/PlatformProxy.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Metas; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Assisticant.XamlTypes 11 | { 12 | public class PlatformProxy : ViewProxy, INotifyDataErrorInfo 13 | { 14 | public bool HasErrors 15 | { 16 | get 17 | { 18 | var errors = Instance as INotifyDataErrorInfo; 19 | if (errors != null) 20 | return errors.HasErrors; 21 | else 22 | return false; 23 | } 24 | } 25 | 26 | public event EventHandler ErrorsChanged { add { } remove { } } 27 | 28 | protected PlatformProxy(object instance, TypeMeta type) 29 | : base(instance, type) 30 | { 31 | } 32 | 33 | public IEnumerable GetErrors(string propertyName) 34 | { 35 | var errors = Instance as INotifyDataErrorInfo; 36 | if (errors != null) 37 | return errors.GetErrors(propertyName); 38 | else 39 | return Enumerable.Empty(); 40 | } 41 | 42 | public override ViewProxy WrapObject(object value) 43 | { 44 | if (value == null) 45 | return null; 46 | return (PlatformProxy)Activator.CreateInstance(typeof(PlatformProxy<>).MakeGenericType(value.GetType()), value); 47 | } 48 | } 49 | 50 | public class PlatformProxy : PlatformProxy 51 | { 52 | public PlatformProxy(object instance) 53 | : base(instance, TypeMeta.Get(typeof(TViewModel))) 54 | { 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Assisticant/XamlTypes/PrimitiveXamlType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Windows.UI.Xaml.Markup; 7 | 8 | namespace Assisticant.XamlTypes 9 | { 10 | public class PrimitiveXamlType : IXamlType 11 | { 12 | readonly Type _type; 13 | static readonly Dictionary _cache = new Dictionary(); 14 | 15 | public IXamlType BaseType 16 | { 17 | get { throw new NotImplementedException(); } 18 | } 19 | 20 | public IXamlMember ContentProperty 21 | { 22 | get { throw new NotImplementedException(); } 23 | } 24 | 25 | public string FullName 26 | { 27 | get { throw new NotImplementedException(); } 28 | } 29 | 30 | public bool IsArray 31 | { 32 | get { throw new NotImplementedException(); } 33 | } 34 | 35 | public bool IsBindable 36 | { 37 | get { throw new NotImplementedException(); } 38 | } 39 | 40 | public bool IsCollection 41 | { 42 | get { throw new NotImplementedException(); } 43 | } 44 | 45 | public bool IsConstructible 46 | { 47 | get { throw new NotImplementedException(); } 48 | } 49 | 50 | public bool IsDictionary 51 | { 52 | get { throw new NotImplementedException(); } 53 | } 54 | 55 | public bool IsMarkupExtension 56 | { 57 | get { throw new NotImplementedException(); } 58 | } 59 | 60 | public IXamlType ItemType 61 | { 62 | get { throw new NotImplementedException(); } 63 | } 64 | 65 | public IXamlType KeyType 66 | { 67 | get { throw new NotImplementedException(); } 68 | } 69 | 70 | public Type UnderlyingType 71 | { 72 | get { return _type; } 73 | } 74 | 75 | PrimitiveXamlType(Type type) 76 | { 77 | _type = type; 78 | } 79 | 80 | public static PrimitiveXamlType Get(Type type) 81 | { 82 | lock (_cache) 83 | { 84 | PrimitiveXamlType result; 85 | if (!_cache.TryGetValue(type, out result)) 86 | _cache[type] = result = new PrimitiveXamlType(type); 87 | return result; 88 | } 89 | } 90 | 91 | public object ActivateInstance() 92 | { 93 | throw new NotImplementedException(); 94 | } 95 | 96 | public void AddToMap(object instance, object key, object value) 97 | { 98 | throw new NotImplementedException(); 99 | } 100 | 101 | public void AddToVector(object instance, object value) 102 | { 103 | throw new NotImplementedException(); 104 | } 105 | 106 | public object CreateFromString(string value) 107 | { 108 | throw new NotImplementedException(); 109 | } 110 | 111 | public IXamlMember GetMember(string name) 112 | { 113 | throw new NotImplementedException(); 114 | } 115 | 116 | public void RunInitializer() 117 | { 118 | throw new NotImplementedException(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Assisticant/XamlTypes/ProxyXamlMember.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Metas; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Windows.UI.Xaml.Markup; 8 | 9 | namespace Assisticant.XamlTypes 10 | { 11 | public class ProxyXamlMember : IXamlMember 12 | { 13 | readonly IXamlType _owner; 14 | readonly MemberMeta _meta; 15 | 16 | public bool IsAttachable 17 | { 18 | get { return false; } 19 | } 20 | 21 | public bool IsDependencyProperty 22 | { 23 | get { throw new NotImplementedException(); } 24 | } 25 | 26 | public bool IsReadOnly 27 | { 28 | get { return !_meta.CanWrite; } 29 | } 30 | 31 | public string Name 32 | { 33 | get { return _meta.Name; } 34 | } 35 | 36 | public IXamlType TargetType 37 | { 38 | get { return _owner; } 39 | } 40 | 41 | public IXamlType Type 42 | { 43 | get 44 | { 45 | if (!_meta.IsViewModel) 46 | return PrimitiveXamlType.Get(_meta.MemberType); 47 | return ProxyXamlType.Get(_meta.MemberType); 48 | } 49 | } 50 | 51 | public ProxyXamlMember(IXamlType owner, MemberMeta meta) 52 | { 53 | _owner = owner; 54 | _meta = meta; 55 | } 56 | 57 | public object GetValue(object proxy) 58 | { 59 | return BindingInterceptor.Current.GetValue(GetSlot(proxy)); 60 | } 61 | 62 | public void SetValue(object proxy, object value) 63 | { 64 | BindingInterceptor.Current.SetValue(GetSlot(proxy), value); 65 | } 66 | 67 | MemberSlot GetSlot(object proxy) 68 | { 69 | return ((ViewProxy)proxy).LookupSlot(_meta); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Assisticant/XamlTypes/ProxyXamlMetadataProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Windows.UI.Xaml.Markup; 7 | 8 | namespace Assisticant.XamlTypes 9 | { 10 | public class ProxyXamlMetadataProvider : IXamlMetadataProvider 11 | { 12 | public IXamlType GetXamlType(string fullName) 13 | { 14 | return null; 15 | } 16 | 17 | public IXamlType GetXamlType(Type type) 18 | { 19 | if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(PlatformProxy<>)) 20 | return ProxyXamlType.Get(type.GenericTypeArguments[0]); 21 | return null; 22 | } 23 | 24 | public XmlnsDefinition[] GetXmlnsDefinitions() 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Assisticant/XamlTypes/ProxyXamlType.cs: -------------------------------------------------------------------------------- 1 | using Assisticant.Metas; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Windows.UI.Xaml.Markup; 8 | 9 | namespace Assisticant.XamlTypes 10 | { 11 | public class ProxyXamlType : IXamlType 12 | { 13 | readonly TypeMeta _meta; 14 | readonly Type _underlyingType; 15 | readonly Dictionary _members; 16 | static readonly Dictionary _cache = new Dictionary(); 17 | 18 | public IXamlType BaseType 19 | { 20 | get { return null; } 21 | } 22 | 23 | public IXamlMember ContentProperty 24 | { 25 | get { return null; } 26 | } 27 | 28 | public string FullName 29 | { 30 | get { return _meta.Type.FullName; } 31 | } 32 | 33 | public bool IsArray 34 | { 35 | get { throw new NotImplementedException(); } 36 | } 37 | 38 | public bool IsBindable 39 | { 40 | get { return true; } 41 | } 42 | 43 | public bool IsCollection 44 | { 45 | get { return false; } 46 | } 47 | 48 | public bool IsConstructible 49 | { 50 | get { return false; } 51 | } 52 | 53 | public bool IsDictionary 54 | { 55 | get { return false; } 56 | } 57 | 58 | public bool IsMarkupExtension 59 | { 60 | get { return false; } 61 | } 62 | 63 | public IXamlType ItemType 64 | { 65 | get { throw new NotImplementedException(); } 66 | } 67 | 68 | public IXamlType KeyType 69 | { 70 | get { throw new NotImplementedException(); } 71 | } 72 | 73 | public Type UnderlyingType 74 | { 75 | get { return _underlyingType; } 76 | } 77 | 78 | ProxyXamlType(TypeMeta meta) 79 | { 80 | _meta = meta; 81 | _underlyingType = typeof(PlatformProxy<>).MakeGenericType(meta.Type); 82 | _members = meta.Members.Select(m => new ProxyXamlMember(this, m)).ToDictionary(m => m.Name); 83 | } 84 | 85 | public static ProxyXamlType Get(Type type) 86 | { 87 | var meta = TypeMeta.Get(type); 88 | lock (_cache) 89 | { 90 | ProxyXamlType result; 91 | if (!_cache.TryGetValue(type, out result)) 92 | _cache[type] = result = new ProxyXamlType(meta); 93 | return result; 94 | } 95 | } 96 | 97 | public object ActivateInstance() 98 | { 99 | throw new NotImplementedException(); 100 | } 101 | 102 | public void AddToMap(object instance, object key, object value) 103 | { 104 | throw new NotImplementedException(); 105 | } 106 | 107 | public void AddToVector(object instance, object value) 108 | { 109 | throw new NotImplementedException(); 110 | } 111 | 112 | public object CreateFromString(string value) 113 | { 114 | throw new NotImplementedException(); 115 | } 116 | 117 | public IXamlMember GetMember(string name) 118 | { 119 | ProxyXamlMember member; 120 | _members.TryGetValue(name, out member); 121 | return member; 122 | } 123 | 124 | public void RunInitializer() 125 | { 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Common/CommonProperties.cs: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * Update Controls .NET 4 | * Copyright 2010 Michael L Perry 5 | * MIT License 6 | * 7 | * http://updatecontrols.net 8 | * http://www.codeplex.com/updatecontrols/ 9 | * 10 | **********************************************************************/ 11 | 12 | using System.Reflection; 13 | using System; 14 | 15 | [assembly: AssemblyDescription("")] 16 | [assembly: AssemblyConfiguration("")] 17 | [assembly: AssemblyCompany("Michael L Perry")] 18 | [assembly: AssemblyCopyright("Copyright 2014 Michael L Perry, MIT License")] 19 | [assembly: AssemblyTrademark("")] 20 | [assembly: AssemblyCulture("")] 21 | [assembly: AssemblyVersion("2.3.0.0")] 22 | [assembly: AssemblyFileVersion("1.1.6.0")] 23 | -------------------------------------------------------------------------------- /Common/Mallardsoft.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellperry/Assisticant/1b8aae89f823ddc7fa4a3fa0e7df68a5bc0540bc/Common/Mallardsoft.snk -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Michael L Perry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet/App/Assisticant.App.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Assisticant.App 5 | 1.1.0 6 | Michael L Perry 7 | Michael L Perry 8 | http://assisticant.net/license 9 | http://assisticant.net/ 10 | http://assisticant.net/images/logosmall.png 11 | false 12 | Add a view model locator for use in an Assisticant application. 13 | Copyright 2014, Michael L Perry 14 | mvvm viewmodel databinding inotifypropertychanged inpc 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /NuGet/App/content/Models/Document.cs.pp: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Assisticant.Collections; 7 | using Assisticant.Fields; 8 | 9 | namespace $rootnamespace$.Models 10 | { 11 | public class Document 12 | { 13 | private Observable _name = new Observable(); 14 | private ObservableList _items = new ObservableList(); 15 | 16 | public string Name 17 | { 18 | get { return _name; } 19 | set { _name.Value = value; } 20 | } 21 | 22 | public IEnumerable Items 23 | { 24 | get { return _items; } 25 | } 26 | 27 | public Item NewItem() 28 | { 29 | Item item = new Item(); 30 | _items.Add(item); 31 | return item; 32 | } 33 | 34 | public void DeleteItem(Item item) 35 | { 36 | _items.Remove(item); 37 | } 38 | 39 | public bool CanMoveDown(Item item) 40 | { 41 | return _items.IndexOf(item) < _items.Count - 1; 42 | } 43 | 44 | public bool CanMoveUp(Item item) 45 | { 46 | return _items.IndexOf(item) > 0; 47 | } 48 | 49 | public void MoveDown(Item item) 50 | { 51 | int index = _items.IndexOf(item); 52 | _items.RemoveAt(index); 53 | _items.Insert(index + 1, item); 54 | } 55 | 56 | public void MoveUp(Item item) 57 | { 58 | int index = _items.IndexOf(item); 59 | _items.RemoveAt(index); 60 | _items.Insert(index - 1, item); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /NuGet/App/content/Models/Item.cs.pp: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Assisticant.Collections; 6 | using Assisticant.Fields; 7 | 8 | namespace $rootnamespace$.Models 9 | { 10 | public class Item 11 | { 12 | private Observable _name = new Observable(); 13 | 14 | public string Name 15 | { 16 | get { return _name; } 17 | set { _name.Value = value; } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /NuGet/App/content/Models/Selection.cs.pp: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Assisticant.Fields; 3 | 4 | namespace $rootnamespace$.Models 5 | { 6 | public class Selection 7 | { 8 | private Observable _selectedItem = new Observable(); 9 | 10 | public Item SelectedItem 11 | { 12 | get { return _selectedItem; } 13 | set { _selectedItem.Value = value; } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /NuGet/App/content/Readme_Assisticant.txt.pp: -------------------------------------------------------------------------------- 1 | Add the ViewModels namespace to App.xaml: 2 | 3 | -- In Windows Store or Windows Phone applications: 4 | 7 | 8 | -- In WPF or Silverlight applications: 9 | 12 | 13 | 14 | Add the view model locator to the resource dictionary: 15 | 16 | 17 | 18 | ... 19 | 20 | 21 | 22 | 23 | 24 | Reference the view model locator in each view: 25 | 26 | 29 | -------------------------------------------------------------------------------- /NuGet/App/content/ViewModels/ItemHeader.cs.pp: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using $rootnamespace$.Models; 4 | 5 | namespace $rootnamespace$.ViewModels 6 | { 7 | public class ItemHeader 8 | { 9 | private readonly Item _item; 10 | 11 | public ItemHeader(Item item) 12 | { 13 | _item = item; 14 | } 15 | 16 | public Item Item 17 | { 18 | get { return _item; } 19 | } 20 | 21 | public string Name 22 | { 23 | get { return _item.Name ?? ""; } 24 | } 25 | 26 | public override bool Equals(object obj) 27 | { 28 | if (obj == this) 29 | return true; 30 | ItemHeader that = obj as ItemHeader; 31 | if (that == null) 32 | return false; 33 | return Object.Equals(this._item, that._item); 34 | } 35 | 36 | public override int GetHashCode() 37 | { 38 | return _item.GetHashCode(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /NuGet/App/content/ViewModels/ItemViewModel.cs.pp: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using $rootnamespace$.Models; 5 | 6 | namespace $rootnamespace$.ViewModels 7 | { 8 | public class ItemViewModel 9 | { 10 | private readonly Item _item; 11 | 12 | public ItemViewModel(Item item) 13 | { 14 | _item = item; 15 | } 16 | 17 | public string Name 18 | { 19 | get { return _item.Name; } 20 | set { _item.Name = value; } 21 | } 22 | 23 | public override bool Equals(object obj) 24 | { 25 | if (obj == this) 26 | return true; 27 | ItemViewModel that = obj as ItemViewModel; 28 | if (that == null) 29 | return false; 30 | return Object.Equals(this._item, that._item); 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | return _item.GetHashCode(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /NuGet/App/content/ViewModels/MainViewModel.cs.pp: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Input; 7 | using $rootnamespace$.Models; 8 | using Assisticant; 9 | 10 | namespace $rootnamespace$.ViewModels 11 | { 12 | public class MainViewModel 13 | { 14 | private readonly Document _document; 15 | private readonly Selection _selection; 16 | 17 | public MainViewModel(Document document, Selection selection) 18 | { 19 | _document = document; 20 | _selection = selection; 21 | } 22 | 23 | public IEnumerable Items 24 | { 25 | get 26 | { 27 | return 28 | from item in _document.Items 29 | select new ItemHeader(item); 30 | } 31 | } 32 | 33 | public ItemHeader SelectedItem 34 | { 35 | get 36 | { 37 | return _selection.SelectedItem == null 38 | ? null 39 | : new ItemHeader(_selection.SelectedItem); 40 | } 41 | set 42 | { 43 | if (value != null) 44 | _selection.SelectedItem = value.Item; 45 | } 46 | } 47 | 48 | public ItemViewModel ItemDetail 49 | { 50 | get 51 | { 52 | return _selection.SelectedItem == null 53 | ? null 54 | : new ItemViewModel(_selection.SelectedItem); 55 | } 56 | } 57 | 58 | public ICommand AddItem 59 | { 60 | get 61 | { 62 | return MakeCommand 63 | .Do(delegate 64 | { 65 | _selection.SelectedItem = _document.NewItem(); 66 | }); 67 | } 68 | } 69 | 70 | public ICommand DeleteItem 71 | { 72 | get 73 | { 74 | return MakeCommand 75 | .When(() => _selection.SelectedItem != null) 76 | .Do(delegate 77 | { 78 | _document.DeleteItem(_selection.SelectedItem); 79 | _selection.SelectedItem = null; 80 | }); 81 | } 82 | } 83 | 84 | public ICommand MoveItemDown 85 | { 86 | get 87 | { 88 | return MakeCommand 89 | .When(() => 90 | _selection.SelectedItem != null && 91 | _document.CanMoveDown(_selection.SelectedItem)) 92 | .Do(delegate 93 | { 94 | _document.MoveDown(_selection.SelectedItem); 95 | }); 96 | } 97 | } 98 | 99 | public ICommand MoveItemUp 100 | { 101 | get 102 | { 103 | return MakeCommand 104 | .When(() => 105 | _selection.SelectedItem != null && 106 | _document.CanMoveUp(_selection.SelectedItem)) 107 | .Do(delegate 108 | { 109 | _document.MoveUp(_selection.SelectedItem); 110 | }); 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /NuGet/App/content/ViewModels/ViewModelLocator.cs.pp: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Assisticant; 7 | using $rootnamespace$.Models; 8 | 9 | namespace $rootnamespace$.ViewModels 10 | { 11 | public class ViewModelLocator : ViewModelLocatorBase 12 | { 13 | private Document _document; 14 | private Selection _selection; 15 | 16 | public ViewModelLocator() 17 | { 18 | if (DesignMode) 19 | _document = LoadDesignModeDocument(); 20 | else 21 | _document = LoadDocument(); 22 | _selection = new Selection(); 23 | } 24 | 25 | public object Main 26 | { 27 | get { return ViewModel(() => new MainViewModel(_document, _selection)); } 28 | } 29 | 30 | public object Item 31 | { 32 | get 33 | { 34 | return ViewModel(() => _selection.SelectedItem == null 35 | ? null 36 | : new ItemViewModel(_selection.SelectedItem)); 37 | } 38 | } 39 | 40 | private Document LoadDocument() 41 | { 42 | // TODO: Load your document here. 43 | Document document = new Document(); 44 | var one = document.NewItem(); 45 | one.Name = "One"; 46 | var two = document.NewItem(); 47 | two.Name = "Two"; 48 | var three = document.NewItem(); 49 | three.Name = "Three"; 50 | return document; 51 | } 52 | 53 | private Document LoadDesignModeDocument() 54 | { 55 | Document document = new Document(); 56 | var one = document.NewItem(); 57 | one.Name = "Design"; 58 | var two = document.NewItem(); 59 | two.Name = "Mode"; 60 | var three = document.NewItem(); 61 | three.Name = "Data"; 62 | return document; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /NuGet/Core/assisticant.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Assisticant 5 | 1.5.8 6 | Michael L Perry 7 | Michael L Perry 8 | Automatically discover view model dependencies and databind without INotifyPropertyChanged. UWP, WPF, iOS, or Android. 9 | http://assisticant.net/license 10 | http://assisticant.net/ 11 | http://assisticant.net/images/logosmall.png 12 | false 13 | en-US 14 | mvvm viewmodel databinding inotifypropertychanged inpc wpf xamarin ios android netstandard 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /NuGet/Snippets/Assisticant.Snippets.1.1.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellperry/Assisticant/1b8aae89f823ddc7fa4a3fa0e7df68a5bc0540bc/NuGet/Snippets/Assisticant.Snippets.1.1.0.nupkg -------------------------------------------------------------------------------- /NuGet/Snippets/assisticant.snippets.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Assisticant.Snippets 5 | 1.1.0 6 | David Piepgrass 7 | Michael L Perry 8 | Code snippets for Assisticant. 9 | http://assisticant.net/license 10 | http://assisticant.net/ 11 | http://assisticant.net/images/logosmall.png 12 | false 13 | en-US 14 | mvvm viewmodel databinding inotifypropertychanged inpc wpf silverlight wp7 15 | 16 | -------------------------------------------------------------------------------- /NuGet/Snippets/content/Readme_Assisticant_Snippets.txt: -------------------------------------------------------------------------------- 1 | Use the following snippets to create observable and computed properties: 2 | 3 | obs - Observable property 4 | An observable property tracks changes. These will be the values of your data 5 | model. 6 | 7 | obslist - Observable list 8 | The user can add and remove items in an observable list. These will be 9 | child collections in your data model. 10 | 11 | comp - Computed property 12 | A computed property calculates its value from code. The user cannot change 13 | its value directly. They can only change the observable properties that the 14 | computed one depends upon. 15 | 16 | All view model properties are computed by default. Only use this template 17 | to cache the computed value when it is expensive to calculate. 18 | 19 | complist - Computed list 20 | A computed list calculates its contents from a linq query. The user cannot 21 | add or remove items in this list. They can only change the observable lists 22 | and properties that the linq query depends upon. 23 | 24 | All view model lists are observable by default. Only use this template to 25 | cache lists that are expensive to calculate, or are used by other properties 26 | of the view model. 27 | 28 | If these snippets aren't working for you, please remove and reinstall the 29 | Assisticant.Snippets package. -------------------------------------------------------------------------------- /NuGet/Snippets/tools/comp.snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | comp 6 | comp 7 | Code snippet to help you make properties based on 'Computed<T>', an Assisticant class that represents a value computed based on one or more Observables. Note: in most cases you should just write a normal property instead of using Computed<T>. Computed<T> is useful to cache the result of a computation when the value of the property is expensive to compute. 8 | David Piepgrass 9 | 10 | Expansion 11 | 12 |
13 | 14 | 15 | 16 | type 17 | string 18 | Data type 19 | 20 | 21 | name 22 | ComputedProperty 23 | A name for this computed property 24 | 25 | 26 | expr 27 | TODO 28 | Write an expression here to compute the value of the Computed 29 | 30 | 31 | _$name$; 32 | public $type$ $name$ 33 | { 34 | get { 35 | if (_$name$ == null) 36 | _$name$ = new Computed<$type$>(() => $expr$); 37 | return _$name$.Value; 38 | } 39 | }]]> 40 | 41 | 42 |
43 |
-------------------------------------------------------------------------------- /NuGet/Snippets/tools/compList.snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | complist 6 | complist 7 | Code snippet for 'ComputedList<T>', which is used to compute a list of values based on a set of other values that utilize Observable, Computed or other classes in Assisticant. 8 | David Piepgrass 9 | 10 | Expansion 11 | 12 |
13 | 14 | 15 | 16 | type 17 | string 18 | Data type of each element 19 | 20 | 21 | name 22 | List 23 | Property Name 24 | 25 | 26 | expr 27 | from i in items select new Projection(i) 28 | Write a LINQ query or other code to compute the list of items as IEnumerable<T>. 29 | 30 | 31 | _$name$; 32 | public IEnumerable<$type$> $name$ 33 | { 34 | get { 35 | if (_$name$ == null) 36 | _$name$ = new ComputedList<$type$>(() => 37 | $expr$ 38 | ); 39 | return _$name$; 40 | } 41 | }]]> 42 | 43 | 44 |
45 |
-------------------------------------------------------------------------------- /NuGet/Snippets/tools/install.ps1: -------------------------------------------------------------------------------- 1 | # Install script for code snippets with NuGet 2 | # ============================================= 3 | # Written by Geert van Horrik (see http://blog.catenalogic.com) 4 | # 5 | # Version 1.0 / 2011-02-18 6 | # 7 | # Required call to get environment variables 8 | 9 | param($installPath, $toolsPath, $package, $project) 10 | 11 | # You only have to customize the $snippetFolder name below, 12 | # don't forget to rename the $snippetFolder of the other file 13 | # ("uninstall.ps1") as well 14 | 15 | $snippetFolder = "Assisticant" 16 | 17 | # Actual script start 18 | $source = "$toolsPath\*.snippet" 19 | $vsVersions = @("2005", "2008", "2010", "2012", "2013", "2015") 20 | 21 | Foreach ($vsVersion in $vsVersions) 22 | { 23 | $myCodeSnippetsFolder = "$HOME\My Documents\Visual Studio $vsVersion\Code Snippets\Visual C#\My Code Snippets\" 24 | 25 | if (Test-Path $myCodeSnippetsFolder) 26 | { 27 | $destination = "$myCodeSnippetsFolder$snippetFolder" 28 | if (!($myCodeSnippetsFolder -eq $destination)) 29 | { 30 | if (!(Test-Path $destination)) 31 | { 32 | New-Item $destination -itemType "directory" 33 | } 34 | 35 | "Installing code snippets for Visual Studio $vsVersion" 36 | Copy-Item $source $destination 37 | } 38 | else 39 | { 40 | "Define a value for snippetFolder variable, cannot be empty" 41 | } 42 | } 43 | else 44 | { 45 | "Path not found $myCodeSnippetsFolder" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /NuGet/Snippets/tools/obs.snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | obs 6 | obs 7 | Code snippet for 'Observable<T>' 8 | David Piepgrass 9 | 10 | Expansion 11 | 12 |
13 | 14 | 15 | 16 | type 17 | string 18 | Data type 19 | 20 | 21 | name 22 | Name 23 | Property Name 24 | 25 | 26 | _$name$ = new Observable<$type$>(default($type$)); 27 | public $type$ $name$ 28 | { 29 | get { return _$name$; } 30 | set { _$name$.Value = value; } 31 | }]]> 32 | 33 | 34 |
35 |
-------------------------------------------------------------------------------- /NuGet/Snippets/tools/obsList.snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | obslist 6 | obslist 7 | Code snippet for 'ObservableList<T>' 8 | David Piepgrass 9 | 10 | Expansion 11 | 12 |
13 | 14 | 15 | 16 | type 17 | string 18 | Data type of each element 19 | 20 | 21 | name 22 | List 23 | Property Name 24 | 25 | 26 | _$name$ = new ObservableList<$type$>(); 27 | public IEnumerable<$type$> $name$ 28 | { 29 | get { return _$name$; } 30 | }]]> 31 | 32 | 33 |
34 |
-------------------------------------------------------------------------------- /NuGet/Snippets/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | # Uninstall script for code snippets with NuGet 2 | # ============================================= 3 | # Written by Geert van Horrik (see http://blog.catenalogic.com) 4 | # 5 | # Version 1.0 / 2011-02-18 6 | # 7 | # Required call to get environment variables 8 | 9 | param($installPath, $toolsPath, $package, $project) 10 | 11 | # You only have to customize the $snippetFolder name below, 12 | # don't forget to rename the $snippetFolder of the other file 13 | # ("install.ps1") as well 14 | 15 | $snippetFolder = "Assisticant" 16 | 17 | # Actual script start 18 | $vsVersions = @("2005", "2008", "2010", "2012", "2013", "2015") 19 | 20 | Foreach ($vsVersion in $vsVersions) 21 | { 22 | $myCodeSnippetsFolder = "$HOME\My Documents\Visual Studio $vsVersion\Code Snippets\Visual C#\My Code Snippets\" 23 | if (Test-Path $myCodeSnippetsFolder) 24 | { 25 | $destination = "$myCodeSnippetsFolder$snippetFolder" 26 | if (!($myCodeSnippetsFolder -eq $destination)) 27 | { 28 | if (Test-Path $destination) 29 | { 30 | "Uninstalling code snippets for Visual Studio $vsVersion" 31 | Remove-Item $destination -recurse -force 32 | } 33 | } 34 | else 35 | { 36 | "Define a value for snippetFolder variable, cannot be empty" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /iOS/Assisticant.iOS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | 8.0.30703 7 | 2.0 8 | {89B554EB-2886-475F-9672-F90FAE10C4F2} 9 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | Library 11 | Assisticant.Binding 12 | Resources 13 | Assisticant.iOS 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG 21 | prompt 22 | 4 23 | false 24 | true 25 | iPhone Developer 26 | 27 | 28 | 29 | 30 | none 31 | true 32 | bin\Release\ 33 | prompt 34 | 4 35 | false 36 | iPhone Developer 37 | bin\Release\Assisticant.iOS.XML 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {2caefd06-13f2-47ea-b600-78c332d5bb2b} 56 | Assisticant.Netstandard 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /iOS/BindingManagerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UIKit; 3 | using Foundation; 4 | 5 | namespace Assisticant.Binding 6 | { 7 | /// 8 | /// Binding manager extensions. 9 | /// 10 | public static class BindingManagerExtensions 11 | { 12 | /// 13 | /// Initialize the binding manager for a view controller. 14 | /// 15 | /// The binding manager for this view. 16 | /// The view controller for this view. 17 | public static void Initialize (this BindingManager bindings, UIViewController controller) 18 | { 19 | UpdateScheduler.Initialize (a => 20 | controller.BeginInvokeOnMainThread (new Action(a))); 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /iOS/ButtonBindingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UIKit; 3 | 4 | namespace Assisticant.Binding 5 | { 6 | /// 7 | /// Button binding extensions. 8 | /// 9 | public static class ButtonBindingExtensions 10 | { 11 | class ButtonClickSubscription : IInputSubscription 12 | { 13 | private UIButton _control; 14 | private Action _action; 15 | 16 | public ButtonClickSubscription(UIButton control, Action action) 17 | { 18 | _control = control; 19 | _action = action; 20 | } 21 | 22 | public void Subscribe() 23 | { 24 | _control.TouchUpInside += ButtonTouchUpInside; 25 | } 26 | 27 | public void Unsubscribe() 28 | { 29 | _control.TouchUpInside -= ButtonTouchUpInside; 30 | } 31 | 32 | private void ButtonTouchUpInside(object sender, EventArgs e) 33 | { 34 | _action (); 35 | } 36 | } 37 | 38 | /// 39 | /// Bind a button's command to an action. 40 | /// 41 | /// The binding manager. 42 | /// The button. 43 | /// The action to perform when the button is tapped. 44 | public static void BindCommand(this BindingManager bindings, UIButton control, Action action) 45 | { 46 | bindings.Bind (new ButtonClickSubscription (control, action)); 47 | } 48 | 49 | /// 50 | /// Bind a button's command and Enabled property to an action and a condition. 51 | /// 52 | /// The binding manager. 53 | /// The button. 54 | /// The ation to perform when the button is tapped. 55 | /// The condition that controls when the button is enabled. 56 | public static void BindCommand(this BindingManager bindings, UIButton control, Action action, Func condition) 57 | { 58 | bindings.Bind (condition, b => control.Enabled = b, new ButtonClickSubscription (control, action)); 59 | } 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /iOS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Assisticant.iOS")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Assisticant.iOS")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("89b554eb-2886-475f-9672-f90fae10c4f2")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /iOS/StepperBindingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Foundation; 3 | using UIKit; 4 | 5 | namespace Assisticant.Binding 6 | { 7 | /// 8 | /// Stepper binding extensions. 9 | /// 10 | public static class StepperBindingExtensions 11 | { 12 | class ValueBinding : IInputSubscription 13 | { 14 | private UIStepper _control; 15 | private Action _input; 16 | private IDisplayDataConverter _converter; 17 | 18 | public ValueBinding(UIStepper control, Action input, IDisplayDataConverter converter) 19 | { 20 | _control = control; 21 | _input = input; 22 | _converter = converter; 23 | } 24 | 25 | public void Subscribe() 26 | { 27 | _control.ValueChanged += StepperValueChanged; 28 | } 29 | 30 | public void Unsubscribe() 31 | { 32 | _control.ValueChanged -= StepperValueChanged; 33 | } 34 | 35 | private void StepperValueChanged (object sender, EventArgs e) 36 | { 37 | _input(_converter.ConvertInput(_control.Value)); 38 | } 39 | } 40 | 41 | class Identity : IDisplayDataConverter 42 | { 43 | public static Identity Instance = new Identity(); 44 | 45 | public double ConvertOutput (double data) 46 | { 47 | return data; 48 | } 49 | 50 | public double ConvertInput (double display) 51 | { 52 | return display; 53 | } 54 | } 55 | 56 | class ConvertInt : IDisplayDataConverter 57 | { 58 | public static ConvertInt Instance = new ConvertInt(); 59 | 60 | public double ConvertOutput (int data) 61 | { 62 | return data; 63 | } 64 | 65 | public int ConvertInput (double display) 66 | { 67 | return (int)display; 68 | } 69 | } 70 | 71 | /// 72 | /// Bind the Value property of a UIStepper to a property using a value converter. 73 | /// 74 | /// The binding manager. 75 | /// The stepper. 76 | /// A function that gets the property. 77 | /// An action sets the property. 78 | /// A custom value converter to type double. 79 | /// The type of property to which the value is bound. 80 | public static void BindValue(this BindingManager bindings, UIStepper control, Func output, Action input, IDisplayDataConverter converter) 81 | { 82 | bindings.Bind (output, s => control.Value = converter.ConvertOutput(s), new ValueBinding(control, input, converter)); 83 | } 84 | 85 | /// 86 | /// Bind the Value property of a UIStepper to a double property. 87 | /// 88 | /// The binding manager. 89 | /// The stepper. 90 | /// A function that gets the property. 91 | /// An action sets the property. 92 | public static void BindValue(this BindingManager bindings, UIStepper control, Func output, Action input) 93 | { 94 | BindValue (bindings, control, output, input, Identity.Instance); 95 | } 96 | 97 | /// 98 | /// Bind the Value property of a UIStepper to an integer property. 99 | /// 100 | /// The binding manager. 101 | /// The stepper. 102 | /// A function that gets the property. 103 | /// An action sets the property. 104 | public static void BindValue(this BindingManager bindings, UIStepper control, Func output, Action input) 105 | { 106 | BindValue (bindings, control, output, input, ConvertInt.Instance); 107 | } 108 | } 109 | } 110 | 111 | --------------------------------------------------------------------------------