Alla fine, ho utilizzato il modello di fabbrica astratto. I fatti sono:
Non è possibile fare riferimento al livello Vista dallo strato PCL ViewModel, come lo strato di VM non dovrebbe occuparsi di livello della Vista. Uno dei vantaggi di questo è che è possibile creare un altro utente del livello ViewModel senza alcuna dipendenza dalla piattaforma di destinazione. per esempio. creare un'applicazione per Windows 8 e Windows Phone 8 dal retro di un progetto PCL della libreria ViewModel.
Il GridView
è un componente WinRT che può essere associato a un ObservableCollection<T>
. è disponibile all'interno di sia il livello Visualizza e il livello ViewModel. Se si desidera supportare il caricamento incrementale all'interno della vostra app (che è un must per i grandi insiemi di dati), allora avete bisogno di creare una sottoclasse speciale di ObservableCollection<T>
che implementa ISupportIncrementalLoading
. Quello che noi vogliamo fare è semplicemente creare quella sottoclasse all'interno del progetto ViewModel e il gioco è fatto. Ma non possiamo fare questo perché ISupportIncrementalLoading
è disponibile solo all'interno di un progetto di WinRT.
Questo problema può essere risolto utilizzando il modello di fabbrica astratto. Tutto ciò che il ViewModel desidera veramente è uno , ma il livello Visualizza richiede una ObservableCollection che implementa ISupportIncrementalLoading
. Quindi la risposta è definire un'interfaccia nel livello ViewModel che dia al ViewModel quello che vuole; chiamiamolo IPortabilityFactory
. Quindi nel livello Visualizza definire un'implementazione concreta di IPortabilityFactory
denominata PortabilityFactory
. Utilizzare un IoC nel livello View per mappare IPortabilityFactory
(interfaccia ViewModel) su PortabilityFactory
(Visualizza livello concreto impl.).
Sulla costruttore della classe ViewModel, hanno un'istanza IPortabilityFactory
iniettato. Ora ViewModel ha una fabbrica che gli darà un'istanza ObservableCollection<T>
.
Ora invece di chiamare new ObservableCollection<Thing>()
nel ViewModel chiamate factory.GetIncrementalCollection<Thing>(...)
.
OK, quindi abbiamo finito con il livello ViewModel; ora abbiamo bisogno di quella implementazione personalizzata di ObservableCollection<T>
. Si chiama IncrementalLoadingCollection
ed è definito nel livello View. Implementa ISupportIncrementalLoading
.
Ecco il codice e le spiegazioni, insieme a un'implementazione di ISupportIncrementalLoading.
Nel livello ViewModel (PCL) ho un'interfaccia di fabbrica astratta.
public interface IPortabilityFactory
{
ObservableCollection<T> GetIncrementalCollection<T>(int take, Func<int, Task<List<T>>> loadMoreItems, Action onBatchStart, Action<List<T>> onBatchComplete);
}
Nello strato Vista (Windows 8 app, in questo caso) a implementare una fabbrica di cemento come questo:
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);
}
}
Anche in questo caso, all'interno dello strato vista, mi capita di usare l'unità per il mio CIO . Quando viene creato il IoC, mappo IPortabilityFactory (in PCL) a PortabilityFactory (nel livello View, il progetto dell'app).
Container.RegisterType<IPortabilityFactory, PortabilityFactory>(new ContainerControlledLifetimeManager());
Ora abbiamo bisogno di creare una sottoclasse di ObservableCollection ed ecco il codice:
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>();
}
}
costruttore di IncrementalLoadingCollection chiede quattro parametri che saranno forniti dal ViewModel tramite la fabbrica:
prendere - questa è la dimensione della pagina
loadMoreItems - questo è un riferimento delegato a una funzione all'interno del ViewModel che recupererà il prossimo gruppo di oggetti (soprattutto, questa funzione non verrà eseguito all'interno del thread UI)
onBatchStart - questo verrà richiamato appena prima che i loadMoreItems il metodo è chiamato. Questo mi consente di apportare modifiche alle proprietà sul ViewModel che potrebbero influire sulla vista. ad esempio, disporre di una proprietà IsProcessing osservabile associata alla proprietà Visibility di una barra di avanzamento.
onBatchComplete - questo verrà richiamato subito dopo il recupero dell'ultimo batch e inoltrato gli articoli. In modo cruciale, questa funzione verrà richiamata sul thread dell'interfaccia utente.
Nello strato ViewModel, il mio ViewModel ha un costruttore su di esso che accetta un oggetto 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;
}
Spero che questo aiuta qualcuno.