Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

MvxObservableCollection with sample (see Tutorial project) #212

Closed
wants to merge 1 commit into from

3 participants

@SeeD-Seifer

Added MvxObservableCollection with sample (see Sample/Tutorial/CollectionViewModel);
Added extension for Dialog.Section that uses INotifyCollectionChanged as item source (see Tutorial/Dialog);

NOTE: Sample implemented only for MonoTouch.

@SeeD-Seifer SeeD-Seifer Added MvxObservableCollection with sample (see Sample/Tutorial/Collec…
…tionViewModel);

Added extension for Dialog.Section that uses INotifyCollectionChanged as item source (see Tutorial/Dialog);
81ad51f
@Cheesebaron

Any reason as to why you are using your own Sorting algorithm which looks a lot like BubbleSort?

@SeeD-Seifer

Eh... nope.
Just used the simplest and forgot about it.

@SeeD-Seifer

Actually I'm not sure about importance of the Sort method (but it was needed for me, so I decided to keep it).

@slodge
Owner

Location currently http://binged.it/10sEtx5 - so don't expect quick review :)

Quick questions:

  1. Do you definitely think these should both be included in core and not in an add-on? (I think you're probably right but just checking...)

  2. For including this in v3, is there any test harness we could add?

  3. Is this all original work contributed under Ms-PL? Or do I have to add any acknowledgments to Mvx readme/license?

Thanks for the PR - sorry that skiing is getting in the way .... well, not that sorry... ;)

Stuart

@SeeD-Seifer
  1. I think mvx-collection does the same job as MvxNotifyPropertyChanged (raise callback from main thread) plus few extension methods (AddRange/RemoveRange). So yes, think the right place is Core.

  2. (I suppose you are talking about UnitTests) Yes, I made few unit-tests for this collection, so I just will add it to v3.

  3. Not sure about the original license but the note added to the header of the source file.
    The original method took from Nathan Nesbit's Blog http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx
    For other code you can apply whatever license you like.

(Seems I need to take few lessons about licenses)

@slodge
Owner

Have emailed Nathan to check license - it's polite if nothing else :)

@slodge
Owner

Received:

Thanks for checking.

Consider all the code on my blog to be MS-PL, unless it is stated otherwise.

Thanks

I really want to integrate this into v3, not into vNext... moving forwards :)

Small unit tests only needed.

Stuart

@SeeD-Seifer

Have some difficulties with UnitTest run (on Mac).
Also there are conflict of usage ObservableCollection as it present in both System.dll (.NET 4.0 library) and System.Windows.dll (Portable library).
Seems UnitTest project should not reference .NET 4.0 libraries at all. Portable libraries should be referenced instead, as UnitTest should test Mvx Portable libraries.
Need more time for integration.

@slodge
Owner

Can you gist the unit tests? I'm only running those test on win right now.

Only minimal tests needed

I think I will be dropping the sort method - but the rest will get pulled into tuna

Thanks

Sorry this one has taken so long - I'm either quick or slow... Sorry!

@SeeD-Seifer

See #219 .
Testing from my VM is too slow.

@slodge
Owner

Closing this - #219

@slodge slodge closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 1, 2013
  1. @SeeD-Seifer

    Added MvxObservableCollection with sample (see Sample/Tutorial/Collec…

    SeeD-Seifer authored
    …tionViewModel);
    
    Added extension for Dialog.Section that uses INotifyCollectionChanged as item source (see Tutorial/Dialog);
This page is out of date. Refresh to see the latest.
View
1  Cirrious/Cirrious.MvvmCross/Cirrious.MvvmCross.csproj
@@ -113,6 +113,7 @@
<Compile Include="Plugins\MvxFileBasedPluginManager.cs" />
<Compile Include="Plugins\MvxLoaderPluginRegistry.cs" />
<Compile Include="Plugins\MvxLoaderBasedPluginManager.cs" />
+ <Compile Include="ViewModels\MvxObservableCollection.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
View
172 Cirrious/Cirrious.MvvmCross/ViewModels/MvxObservableCollection.cs
@@ -0,0 +1,172 @@
+/**
+ * The original AddRange method code took from Nathan Nesbit's Blog
+ * http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx
+ *
+ * Few optimizations applied to the AddRange/RemoveRange logic.
+ */
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using Cirrious.MvvmCross.Interfaces.ServiceProvider;
+using Cirrious.MvvmCross.Interfaces.Views;
+using Cirrious.MvvmCross.ExtensionMethods;
+
+namespace Cirrious.MvvmCross.ViewModels
+{
+ public class MvxObservableCollection<T>
+ : ObservableCollection<T>
+ , IList<T>
+ //, IObservableCollection<T>
+ , IMvxServiceConsumer<IMvxViewDispatcherProvider>
+ {
+ private IMvxViewDispatcher _dispatcher;
+ protected IMvxViewDispatcher ViewDispatcher
+ {
+ get { return _dispatcher ?? (_dispatcher = this.GetService<IMvxViewDispatcherProvider>().Dispatcher); }
+ }
+
+ public MvxObservableCollection()
+ {
+ }
+
+ public MvxObservableCollection(IEnumerable<T> source)
+ : base(source)
+ {
+ }
+
+ public void AddRange(IEnumerable<T> items)
+ {
+ if (items == null)
+ return;
+
+ this.CheckReentrancy();
+
+ //
+ // We need the starting index later
+ //
+ int startingIndex = this.Count;
+
+ bool isList = items is IList<T>;
+ IList<T> itemsList = items as IList<T>;
+
+ //
+ // Add the items directly to the inner collection
+ //
+ foreach (var item in items)
+ {
+ this.Items.Add(item);
+ if (!isList)
+ {
+ if (itemsList == null)
+ itemsList = new List<T>();
+ itemsList.Add(item);
+ }
+ }
+
+ if (startingIndex == this.Count)
+ return;
+
+ //
+ // Now raise the changed events
+ //
+ this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
+ this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
+
+ //
+ // We have to change our input of new items into an IList since that is what the
+ // event args require.
+ //
+ var changedItems = new ReadOnlyCollection<T>(itemsList);
+ this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems, startingIndex));
+ }
+
+ public void RemoveRange(IEnumerable<T> items)
+ {
+ this.CheckReentrancy();
+
+ //
+ // We need the starting index later
+ //
+ int startingIndex = this.Count;
+
+ bool isList = items is IList<T>;
+ IList<T> itemsList = items as IList<T>;
+
+ //
+ // Add the items directly to the inner collection
+ //
+ foreach (var item in items)
+ {
+ this.Items.Remove(item);
+ if (!isList)
+ {
+ if (itemsList == null)
+ itemsList = new List<T>();
+ itemsList.Add(item);
+ }
+ }
+
+ if (startingIndex == this.Count)
+ return;
+
+ //
+ // Now raise the changed events
+ //
+ this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
+ this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
+
+ //
+ // We have to change our input of new items into an IList since that is what the
+ // event args require.
+ //
+ var changedItems = new ReadOnlyCollection<T>(itemsList);
+ this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, changedItems, startingIndex));
+ }
+
+ public void Sort(IComparer<T> comparer)
+ {
+ this.CheckReentrancy();
+
+ var movedItems = new List<T>();
+
+ T item;
+ for (int i = 1; i < Count; i++)
+ {
+ item = this[i];
+ bool isReplaced = false;
+
+ var j = i;
+ while ((j > 0) && (comparer.Compare(this[j - 1], item) == 1))
+ {
+ this[j] = this[j - 1];
+ j = j - 1;
+ isReplaced = true;
+ }
+ this[j] = item;
+ if (isReplaced)
+ movedItems.Add(item);
+ }
+
+ this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, movedItems, -1, -1));
+ }
+
+ protected void InvokeOnMainThread(Action action)
+ {
+ if (ViewDispatcher != null)
+ ViewDispatcher.RequestMainThreadAction(action);
+ }
+
+ protected override void OnCollectionChanged (NotifyCollectionChangedEventArgs e)
+ {
+ InvokeOnMainThread(() => base.OnCollectionChanged(e));
+ }
+
+ protected override void OnPropertyChanged (PropertyChangedEventArgs e)
+ {
+ InvokeOnMainThread(() => base.OnPropertyChanged(e));
+ }
+ }
+}
+
View
1  Sample - Tutorial/Tutorial/Tutorial.Core/Tutorial.Core.csproj
@@ -58,6 +58,7 @@
<Compile Include="ViewModels\Lessons\SimpleTextPropertyViewModel.cs" />
<Compile Include="ViewModels\Lessons\TipViewModel.cs" />
<Compile Include="ViewModels\MainMenuViewModel.cs" />
+ <Compile Include="ViewModels\Lessons\CollectionViewModel.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Cirrious\Cirrious.MvvmCross\Cirrious.MvvmCross.csproj">
View
59 Sample - Tutorial/Tutorial/Tutorial.Core/ViewModels/Lessons/CollectionViewModel.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Windows.Input;
+using System.Threading;
+using System.Threading.Tasks;
+using Cirrious.MvvmCross.ViewModels;
+using Cirrious.MvvmCross.Commands;
+
+namespace Tutorial.Core.ViewModels.Lessons
+{
+ public class CollectionViewModel
+ : MvxViewModel
+ {
+ public MvxObservableCollection<string> Items { get; private set; }
+
+ public CollectionViewModel()
+ {
+ Items = new MvxObservableCollection<string>(new string[] { "one", "two", "three" });
+ }
+
+ public ICommand AddCommand
+ {
+ get { return new MvxRelayCommand(OnAddCommand); }
+ }
+
+ public ICommand RemoveRandom
+ {
+ get { return new MvxRelayCommand(OnRemoveRandomCommand); }
+ }
+
+ private void OnAddCommand()
+ {
+ // simulate async task
+ Task.Factory.StartNew(() => {
+
+ // simulate heavy job
+ Thread.Sleep(500);
+
+ // create few items to add
+ var count = Items.Count;
+ var items = new string[] { count.ToString(), (count + 1).ToString() };
+
+ // fill ObservableCollection (do not use InvokeOnMainThread)
+ Items.AddRange(items);
+ });
+ }
+
+ private void OnRemoveRandomCommand()
+ {
+ Task.Factory.StartNew(() => {
+
+ Thread.Sleep(500);
+
+ var index = new System.Random().Next(Items.Count);
+ Items.RemoveAt(index);
+ });
+ }
+ }
+}
+
View
3  Sample - Tutorial/Tutorial/Tutorial.Core/ViewModels/MainMenuViewModel.cs
@@ -35,7 +35,8 @@ public MainMenuViewModel()
typeof(Lessons.PullToRefreshViewModel),
typeof(Lessons.TipViewModel),
typeof(Lessons.CompositeViewModel),
- typeof(Lessons.LocationViewModel)
+ typeof(Lessons.LocationViewModel),
+ typeof(Lessons.CollectionViewModel)
};
}
}
View
153 Sample - Tutorial/Tutorial/Tutorial.UI.Touch/Dialog/SectionEx.cs
@@ -0,0 +1,153 @@
+using System;
+using CrossUI.Touch.Dialog.Elements;
+using System.Collections.Generic;
+using MonoTouch.UIKit;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Tutorial.UI.Touch.Dialog
+{
+ public delegate Element ElementBuilderDelegate<T>(T item);
+
+ public class SectionEx<T>
+ : Section
+ {
+ #region constructors
+ public SectionEx (IList<T> itemsSource, ElementBuilderDelegate<T> builder) : base ()
+ {
+ ElementBuilder = builder;
+ ItemsSource = itemsSource;
+ }
+
+ /// <summary>
+ /// Constructs a Section with the specified header
+ /// </summary>
+ /// <param name="caption">
+ /// The header to display
+ /// </param>
+ public SectionEx (string caption, IList<T> itemsSource, ElementBuilderDelegate<T> builder) : base (caption)
+ {
+ ElementBuilder = builder;
+ ItemsSource = itemsSource;
+ }
+
+ /// <summary>
+ /// Constructs a Section with a header and a footer
+ /// </summary>
+ /// <param name="caption">
+ /// The caption to display (or null to not display a caption)
+ /// </param>
+ /// <param name="footer">
+ /// The footer to display.
+ /// </param>
+ public SectionEx (string caption, string footer, IList<T> itemsSource, ElementBuilderDelegate<T> builder) : base (caption, footer)
+ {
+ ElementBuilder = builder;
+ ItemsSource = itemsSource;
+ }
+
+ public SectionEx (UIView header, IList<T> itemsSource, ElementBuilderDelegate<T> builder) : base (header)
+ {
+ ElementBuilder = builder;
+ ItemsSource = itemsSource;
+ }
+
+ public SectionEx (UIView header, UIView footer, IList<T> itemsSource, ElementBuilderDelegate<T> builder) : base (header, footer)
+ {
+ ElementBuilder = builder;
+ ItemsSource = itemsSource;
+ }
+ #endregion
+
+ public ElementBuilderDelegate<T> ElementBuilder { get; private set; }
+
+ private readonly Dictionary<T, Element> _map = new Dictionary<T, Element>();
+
+ private IList<T> _itemsSource;
+ public IList<T> ItemsSource
+ {
+ get { return _itemsSource; }
+ set
+ {
+ if (_itemsSource == value)
+ return;
+
+ var collectionChanged = _itemsSource as INotifyCollectionChanged;
+ if (collectionChanged != null)
+ collectionChanged.CollectionChanged -= CollectionChangedOnCollectionChanged;
+ _itemsSource = value;
+ collectionChanged = _itemsSource as INotifyCollectionChanged;
+ if (collectionChanged != null)
+ collectionChanged.CollectionChanged += CollectionChangedOnCollectionChanged;
+
+ Rebuild();
+ }
+ }
+
+ protected virtual void CollectionChangedOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Reset:
+ this.Clear();
+ break;
+ case NotifyCollectionChangedAction.Add:
+ var startingPos = args.NewStartingIndex;
+ var elements = new Element[args.NewItems.Count];
+ int idx = 0;
+ foreach (var item in args.NewItems.Cast<T>())
+ {
+ var element = ElementBuilder(item);
+ _map[item] = element;
+
+ elements[idx] = element;
+ idx++;
+ }
+
+ var pos = startingPos >= 0 ? startingPos : Count;
+ this.Insert(pos, UITableViewRowAnimation.Automatic, elements);
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ foreach (var item in args.OldItems.Cast<T>())
+ {
+ Element element;
+ if (_map.TryGetValue(item, out element))
+ {
+ this.Remove(element);
+ _map.Remove(item);
+ }
+ }
+ break;
+ case NotifyCollectionChangedAction.Move:
+ if (args.NewItems != null && args.NewItems.Count > 0)
+ this.Rebuild();
+ break;
+ }
+ }
+
+ public void Rebuild()
+ {
+ RemoveRange(0, Count);
+ _map.Clear();
+ foreach (var item in _itemsSource)
+ {
+ _map[item] = ElementBuilder(item);
+ }
+
+ AddAll(_map.Values);
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ base.Dispose (disposing);
+
+ var collectionChanged = _itemsSource as INotifyCollectionChanged;
+ if (collectionChanged != null)
+ collectionChanged.CollectionChanged -= CollectionChangedOnCollectionChanged;
+ _itemsSource = null;
+
+ _map.Clear();
+ }
+ }
+}
+
View
8 Sample - Tutorial/Tutorial/Tutorial.UI.Touch/Tutorial.UI.Touch.csproj
@@ -67,9 +67,6 @@
<Reference Include="mscorlib" />
<Reference Include="monotouch">
</Reference>
- <Reference Include="System.Net.Touch">
- <HintPath>..\..\..\bin\Touch\Debug\System.Net.dll</HintPath>
- </Reference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@@ -96,6 +93,8 @@
<Compile Include="Views\Lessons\TipView.designer.cs">
<DependentUpon>TipView.cs</DependentUpon>
</Compile>
+ <Compile Include="Views\Lessons\CollectionView.cs" />
+ <Compile Include="Dialog\SectionEx.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
@@ -166,4 +165,7 @@
<InterfaceDefinition Include="Views\Lessons\PullToRefreshTableCellView.xib" />
<InterfaceDefinition Include="Views\Lessons\TipView.xib" />
</ItemGroup>
+ <ItemGroup>
+ <Folder Include="Dialog\" />
+ </ItemGroup>
</Project>
View
44 Sample - Tutorial/Tutorial/Tutorial.UI.Touch/Views/Lessons/CollectionView.cs
@@ -0,0 +1,44 @@
+using System;
+using Cirrious.MvvmCross.Dialog.Touch;
+using Tutorial.Core.ViewModels.Lessons;
+using Cirrious.MvvmCross.Views;
+using MonoTouch.UIKit;
+using CrossUI.Touch.Dialog.Elements;
+using Tutorial.UI.Touch.Dialog;
+
+namespace Tutorial.UI.Touch.Views.Lessons
+{
+ public class CollectionView
+ : MvxTouchDialogViewController<CollectionViewModel>
+ {
+ public CollectionView(MvxShowViewModelRequest request)
+ : base(request, UITableViewStyle.Grouped, null, true)
+ {
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ this.NavigationItem.SetLeftBarButtonItem(new UIBarButtonItem("Cancel", UIBarButtonItemStyle.Bordered, null), false);
+ this.NavigationItem.LeftBarButtonItem.Clicked += delegate
+ {
+ ViewModel.DoClose();
+ };
+
+ var addButton = new UIBarButtonItem(UIBarButtonSystemItem.Add, (s, e) => {
+ ViewModel.AddCommand.Execute(null);
+ });
+ var removeButton = new UIBarButtonItem("-", UIBarButtonItemStyle.Plain, (s, e) => {
+ ViewModel.RemoveRandom.Execute(null);
+ });
+ this.NavigationItem.SetRightBarButtonItems(new UIBarButtonItem[]{ addButton, removeButton}, false);
+
+ this.Root = new RootElement("Observable Collection")
+ {
+ new SectionEx<string>(ViewModel.Items, (item) => new StringElement(item))
+ };
+ }
+ }
+}
+
Something went wrong with that request. Please try again.