使用MvvmCross如何将注释列表绑定到MapView?

时间:2013-07-23 17:10:42

标签: xamarin mvvmcross

假设我有一个MapView,它包含一个属性“Annotations”。要在MapView上获取注释,您必须使用AddAnotation或AddAnotations。

public class SiteItems
{
    public string Title { get; set; }
    public string SubTitle { get; set; }

    public string Phone { get; set; }
    public string Address { get; set; }
    public string Url { get; set; }

    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

然后我有一个ViewModel:

public class SiteViewModel : MvxViewModel
{

    private IObservableCollection<Models.SiteItems> _siteItems;
    public IObservableCollection<Models.SiteItems> SiteItems {
        get{ return _siteItems; }
        set{ _siteItems = value; 
            RaisePropertyChanged (() => SiteItems);
        }
    }
}

我还有一个将SiteItem转换为MKAnnotation的转换器

所以我想我的问题是如何绑定这样的东西,因为我们无法直接绑定到“Annotations”属性?我绑定命令吗?

谢谢,任何帮助表示赞赏!

1 个答案:

答案 0 :(得分:5)

订阅更改集合是Data-Binding的基石之一,并且依赖于对INotifyCollectionChanged接口的一点了解。

在MvvmCross源代码中,有一些示例类显示如何订阅集合及其更改通知 - 例如Droid中的MvxViewGroupExtensions.cs和触摸中的MvxTableViewSource.cs

该技术的核心是创建一个AdapterSource对象,该对象在整个列表或列表的某些部分中侦听更改,并相应地采取相应的操作。

同样类型的方法适用于具有多个 - 但是标记的地图 - 尽管我们还没有任何帮助类。


如果没有真正拥有Mac或iOS设备,这里大致是我创建包装器的步骤......

假设我有一个Model对象:

public class House
{
    public double Lat { get; set; }
    public double Lng { get; set; }
    public string Name { get; set; }
}

在ViewModel中,如:

public class FirstViewModel : MvxViewModel
{
    public ObservableCollection<House> HouseList { get; set; }
}

完成此操作后,在View中我们可以为每个House创建一个注释类 - 例如类似的东西:

public class HouseAnnotation : MKAnnotation
{
    public HouseAnnotation(House house)
    {
        // Todo - the details of actually using the house here.
        // in theory you could also data-bind to the house too (e.g. if it's location were to move...)
    }

    public override CLLocationCoordinate2D Coordinate { get; set; }
}

然后我们可以创建一个HouseAnnotationManager,负责管理映射到地图上显示的注释中的更改的HouseList中的更改转换。

为此,我们会将经理方法提供给:

  1. 创建单个注释:

    private MKAnnotation CreateAnnotation(House house)
    {
        return new HouseAnnotation(house);
    }
    
  2. 向地图(以及本地查找表)添加注释

    private void AddAnnotationFor(House house)
    {
        var annotation = CreateAnnotation(house);
        _annotations[house] = annotation;
        _mapView.AddAnnotation(annotation);
    }
    
  3. 从地图(以及本地查找表)中删除注释

    private void RemoveAnnotationFor(House house)
    {
        var annotation = _annotations[house];
        _mapView.RemoveAnnotation(annotation);
        _annotations.Remove(house);
    }
    
  4. 对列表执行相同的操作:

    private void AddAnnotations(IList newItems)
    {
        foreach (House house in newItems)
        {
            AddAnnotationFor(house);
        }
    }
    
    private void RemoveAnnotations(IList oldItems)
    {
        foreach (House house in oldItems)
        {
            RemoveAnnotationFor(house);
        }
    }
    
  5. 回复INotifyCollection更改:

    private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                AddAnnotations(e.NewItems);
                break;
            case NotifyCollectionChangedAction.Remove:
                RemoveAnnotations(e.OldItems);
                break;
            case NotifyCollectionChangedAction.Replace:
                RemoveAnnotations(e.OldItems);
                AddAnnotations(e.NewItems);
                break;
            case NotifyCollectionChangedAction.Move:
                // not interested in this
                break;
            case NotifyCollectionChangedAction.Reset:
                ReloadAllAnnotations();
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
    
  6. 回应整个列表更改:

    // MvxSetToNullAfterBinding isn't strictly needed any more 
    // - but it's nice to have for when binding is torn down
    [MvxSetToNullAfterBinding]
    public virtual IEnumerable<House> ItemsSource
    {
        get { return _itemsSource; }
        set { SetItemsSource(value); }
    }
    
    protected virtual void SetItemsSource(IEnumerable<House> value)
    {
        if (_itemsSource == value)
            return;
    
        if (_subscription != null)
        {
            _subscription.Dispose();
            _subscription = null;
        }
        _itemsSource = value;
        if (_itemsSource != null && !(_itemsSource is IList))
            MvxBindingTrace.Trace(MvxTraceLevel.Warning,
                                  "Binding to IEnumerable rather than IList - this can be inefficient, especially for large lists");
    
        ReloadAllAnnotations();
    
        var newObservable = _itemsSource as INotifyCollectionChanged;
        if (newObservable != null)
        {
            _subscription = newObservable.WeakSubscribe(OnItemsSourceCollectionChanged);
        }
    }
    
  7. 完全编写后,您的ViewModel可以拥有一个私有的_manager字段,并可以将其创建并数据绑定为:

            _manager = new HouseAnnotationManager(myMapView);
    
            var set = this.CreateBindingSet<FirstView, FirstViewModel>();
            set.Bind(_manager).To(vm => vm.HouseList);
            set.Apply();
    

    总的来说,这可能类似于:https://gist.github.com/slodge/6070386

    免责声明:此代码尚未编译,更不用说运行了,但这种方法基本上是正确的(我认为)

    注意:如果这样做/不适用于某些修复,我非常希望将其作为样本提交回Mvx社区;)


    相同的基本方法也适用于Android - 尽管在Android中你还必须与设置 - Ant,Google Play v2和所有爵士乐进行斗争。


    如果您想进行进一步的地图操作 - 例如更改地图中心并在添加房屋时进行缩放,这显然可以在管理器中的方法(例如AddAnnotation)的覆盖范围内完成。