最终我使用了抽象工厂模式。事实是:
您无法从 PCL ViewModel 层引用视图层,因为 VM 层不应该与视图层相关。这样做的好处之一是您可以创建 ViewModel 层的另一个使用者,而不依赖于目标平台。例如在一个 ViewModel 库 PCL 项目的基础上创建 Windows 8 和 Windows Phone 8 应用程序。
The GridView
是一个 WinRT 组件,可以绑定到ObservableCollection<T>
. ObservableCollection<T>
里面可用both视图层和视图模型层。如果您想支持应用程序内部的增量加载(这对于大型数据集来说是必须的),那么您需要创建一个特殊的子类ObservableCollection<T>
实现ISupportIncrementalLoading
。我们什么want只需在 ViewModel 项目中创建该子类即可。但我们不能这样做因为ISupportIncrementalLoading
仅在 WinRT 项目中可用。
这个问题可以通过使用抽象工厂模式来解决。 ViewModel 真正想要的是ObservableCollection<T>
,但是视图层需要一个 ObservableCollection 来实现ISupportIncrementalLoading
。所以答案是在 ViewModel 层中定义一个接口,为 ViewModel 提供它想要的确切内容;我们就这样称呼它吧IPortabilityFactory
。然后在View层定义具体实现IPortabilityFactory
called PortabilityFactory
。在View层使用IoC来映射IPortabilityFactory
(ViewModel 接口)到PortabilityFactory
(查看层具体实现)。
在 ViewModel 类的构造函数中,有一个IPortabilityFactory
实例注入。现在 ViewModel 有一个工厂,可以给它一个ObservableCollection<T>
实例。
现在而不是打电话new ObservableCollection<Thing>()
在您调用的 ViewModel 中factory.GetIncrementalCollection<Thing>(...)
.
好的,我们已经完成了 ViewModel 层;现在我们需要自定义实现ObservableCollection<T>
。它被称为IncrementalLoadingCollection
它是在视图层中定义的。它实现了ISupportIncrementalLoading
.
以下是代码和说明,以及 ISupportIncrementalLoading 的实现。
在 ViewModel 层(PCL)中,我有一个抽象工厂接口。
public interface IPortabilityFactory
{
ObservableCollection<T> GetIncrementalCollection<T>(int take, Func<int, Task<List<T>>> loadMoreItems, Action onBatchStart, Action<List<T>> onBatchComplete);
}
在视图层(本例中为 Windows 8 应用程序)中,我实现了一个如下所示的具体工厂:
public class PortabilityFactory : IPortabilityFactory
{
public ObservableCollection<T> GetIncrementalCollection<T>(int take, Func<int, Task<List<T>>> loadMoreItems, Action onBatchStart, Action<List<T>> onBatchComplete)
{
return new IncrementalLoadingCollection<T>(take, loadMoreItems, onBatchStart, onBatchComplete);
}
}
同样,在视图层内,我碰巧使用 Unity 作为我的 IoC。创建 IoC 时,我将 IPortabilityFactory(在 PCL 中)映射到 PortabilityFactory(在视图层;应用程序项目)。
Container.RegisterType<IPortabilityFactory, PortabilityFactory>(new ContainerControlledLifetimeManager());
我们现在需要创建 ObservableCollection 的子类,代码如下:
public class IncrementalLoadingCollection<T>
: ObservableCollection<T>, ISupportIncrementalLoading
{
private Func<int, Task<List<T>>> _loadMoreItems = null;
private Action<List<T>> _onBatchComplete = null;
private Action _onBatchStart = null;
/// <summary>
/// How many records to currently skip
/// </summary>
private int Skip { get; set; }
/// <summary>
/// The max number of items to get per batch
/// </summary>
private int Take { get; set; }
/// <summary>
/// The number of items in the last batch retrieved
/// </summary>
private int VirtualCount { get; set; }
/// <summary>
/// .ctor
/// </summary>
/// <param name="take">How many items to take per batch</param>
/// <param name="loadMoreItems">The load more items function</param>
public IncrementalLoadingCollection(int take, Func<int, Task<List<T>>> loadMoreItems, Action onBatchStart, Action<List<T>> onBatchComplete)
{
Take = take;
_loadMoreItems = loadMoreItems;
_onBatchStart = onBatchStart;
_onBatchComplete = onBatchComplete;
VirtualCount = take;
}
/// <summary>
/// Returns whether there are more items (if the current batch size is equal to the amount retrieved then YES)
/// </summary>
public bool HasMoreItems
{
get { return this.VirtualCount >= Take; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
CoreDispatcher dispatcher = Window.Current.Dispatcher;
_onBatchStart(); // This is the UI thread
return Task.Run<LoadMoreItemsResult>(
async () =>
{
var result = await _loadMoreItems(Skip);
this.VirtualCount = result.Count;
Skip += Take;
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
() =>
{
foreach (T item in result) this.Add(item);
_onBatchComplete(result); // This is the UI thread
});
return new LoadMoreItemsResult() { Count = (uint)result.Count };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
IncrementalLoadingCollection 的构造函数需要四个参数,这些参数将由 ViewModel 通过工厂提供:
take - 这是页面大小
loadMoreItems - 这是对 ViewModel 内部函数的委托引用,该函数将检索下一批项目(重要的是,该函数不会在 UI 线程内运行)
onBatchStart - 这将在调用 loadMoreItems 方法之前调用。这允许我对 ViewModel 上可能影响视图的属性进行更改。例如,有一个可观察的 IsProcessing 属性,该属性绑定到进度条的 Visibility 属性。
onBatchComplete - 这将在检索最新批次并传入项目后立即调用。最重要的是,此函数将在 UI 线程上调用。
在 ViewModel 层中,我的 ViewModel 有一个构造函数,它接受 IPortabilityFactory 对象:
public const string IsProcessingPropertyName = "IsProcessing";
private bool _isProcessing = false;
public bool IsProcessing
{
get
{
return _isProcessing;
}
set
{
if (_isProcessing == value)
{
return;
}
RaisePropertyChanging(IsProcessingPropertyName);
_isProcessing = value;
RaisePropertyChanged(IsProcessingPropertyName);
}
}
private IPortabilityFactory _factory = null;
public ViewModel(IPortabilityFactory factory)
{
_factory = factory;
Initialize();
}
private async void Initialize()
{
Things = _factory.GetIncrementalCollection<Thing>(10, LoadThings,
() => IsProcessing = true, BatchLoaded);
}
private void BatchLoaded(List<Thing> batch)
{
IsProcessing = false;
}
private async Task<List<Thing>> LoadThings(int skip)
{
var items = await _service.GetThings(skip, 10 /*page size*/);
return items;
}
我希望这可以帮助别人。