2013-01-20 1 views
8

J'ai une application MVVM Cross exécutée sur Windows Phone 8 que j'ai récemment utilisée pour utiliser des bibliothèques de classes portables. Les modèles d'affichage sont dans la bibliothèque de classes portable et l'un d'eux expose une propriété qui active et désactive un PerformanceProgressBar à partir de la boîte à outils Silverlight for WP via la liaison de données. Lorsque l'utilisateur appuie sur un bouton, RelayCommand déclenche un processus d'arrière-plan qui définit la propriété sur true, ce qui doit activer la barre de progression et effectuer le traitement en arrière-plan. Avant de le porter sur un PCL, j'ai pu invoquer le changement du thread d'interface utilisateur pour m'assurer que la barre de progression était activée, mais l'objet Dispatcher n'est pas disponible dans un PCL. Comment puis-je contourner cela?Mettre à jour l'unité d'exécution de l'interface utilisateur de la bibliothèque de classes portable

Merci

Dan

+1

Je ne suis pas entièrement sûr dans win-phone, mais pouvez-vous utiliser 'Application.Current.Dispatcher' pour invoquer la mise à jour? Ou serait-ce 'Deployment.Current.Dispatcher'? –

Répondre

12

Si vous n'avez pas accès au répartiteur, vous pouvez simplement passer un délégué de la méthode BeginInvoke à votre classe:

public class YourViewModel 
{ 
    public YourViewModel(Action<Action> beginInvoke) 
    { 
     this.BeginInvoke = beginInvoke; 
    } 

    protected Action<Action> BeginInvoke { get; private set; } 

    private void SomeMethod() 
    { 
     this.BeginInvoke(() => DoSomething()); 
    } 
} 

Puis instancier (à partir d'une classe qui a accès au répartiteur):

var dispatcherDelegate = action => Dispatcher.BeginInvoke(action); 

var viewModel = new YourViewModel(dispatcherDelegate); 

Ou vous pouvez également créer une enveloppe autour de votre répartiteur.

D'abord, définir une interface IDispatcher dans votre bibliothèque de classe portable:

public interface IDispatcher 
{ 
    void BeginInvoke(Action action); 
} 

Puis, dans le projet qui a accès au répartiteur, mettre en œuvre l'interface:

public class DispatcherWrapper : IDispatcher 
{ 
    public DispatcherWrapper(Dispatcher dispatcher) 
    { 
     this.Dispatcher = dispatcher; 
    } 

    protected Dispatcher Dispatcher { get; private set; } 

    public void BeginInvoke(Action action) 
    { 
     this.Dispatcher.BeginInvoke(action); 
    } 
} 

Ensuite, vous pouvez simplement passez cet objet en tant qu'instance IDispatcher à votre bibliothèque de classes portable.

+0

Ceci est une approche réalisable - et c'est exactement ce que fait MvvmCross - seulement il le fait en utilisant IoC au niveau de la configuration de la plate-forme – Stuart

+0

Merci - cela a fonctionné pour moi! –

+0

Votre appel ... Mais, si vous utilisez MvvmCross, vous trouverez probablement dev plus facile en utilisant 'InvokeOnMainThread (action)' - car cela vous fournira des implémentations pré-construites sur chaque plate-forme (et cela inclut des choses comme ' CheckAccess() 'appels avant chaque appel) – Stuart

15

Toutes les plates-formes MvvmCross exigent que les actions de l'interface utilisateur-get rassemblèrent retour à l'interface utilisateur Discussion/Appartement - mais chaque plate-forme ne différemment ....

Pour contourner ce problème, MvvmCross fournit un moyen multi-plateforme pour ce faire - en utilisant un objet injecté IMvxViewDispatcherProvider.

Par exemple, sur Windows Phone IMvxViewDispatcherProvider est fourni en fin de compte par MvxMainThreadDispatcher dans https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.WindowsPhone/Views/MvxMainThreadDispatcher.cs

Ceci implémente l'InvokeOnMainThread en utilisant:

private bool InvokeOrBeginInvoke(Action action) 
    { 
     if (_uiDispatcher.CheckAccess()) 
      action(); 
     else 
      _uiDispatcher.BeginInvoke(action); 

     return true; 
    } 

Pour le code dans ViewModels:

  • votre ViewModel hérite de MvxViewModel
  • les MvxViewModel hérite d'un MvxApplicationObject
  • les MvxApplicationObject hérite d'un MvxNotifyPropertyChanged
  • l'objet MvxNotifyPropertyChanged hérite d'un MvxMainThreadDispatchingObject

MvxMainThreadDispatchingObject est https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross/ViewModels/MvxMainThreadDispatchingObject.cs

public abstract class MvxMainThreadDispatchingObject 
    : IMvxServiceConsumer<IMvxViewDispatcherProvider> 
{ 
    protected IMvxViewDispatcher ViewDispatcher 
    { 
     get { return this.GetService().Dispatcher; } 
    } 

    protected void InvokeOnMainThread(Action action) 
    { 
     if (ViewDispatcher != null) 
      ViewDispatcher.RequestMainThreadAction(action); 
    } 
} 

Alors ... ta vue Modèle peut simplement appeler le InvokeOnMainThread(() => DoStuff());


Un autre point à noter est que MvvmCross effectue automatiquement les conversions de thread d'interface utilisateur pour les mises à jour de propriété qui sont signalés dans un MvxViewModel (ou bien dans un objet MvxNotifyPropertyChanged) à travers les RaisePropertyChanged() méthodes - voir:

protected void RaisePropertyChanged(string whichProperty) 
    { 
     // check for subscription before going multithreaded 
     if (PropertyChanged == null) 
      return; 

     InvokeOnMainThread(
      () => 
       { 
        var handler = PropertyChanged; 

        if (handler != null) 
         handler(this, new PropertyChangedEventArgs(whichProperty)); 
       }); 
    } 

dans https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross/ViewModels/MvxNotifyPropertyChanged.cs


Cette Le regroupement automatique des appels RaisePropertyChanged() fonctionne bien dans la plupart des cas, mais peut s'avérer un peu inefficace si vous augmentez un grand nombre de propriétés modifiées à partir d'un thread d'arrière-plan - cela peut entraîner un grand nombre de changements de contexte de threads. Ce n'est pas quelque chose que vous devez être au courant dans la plupart de votre code - mais si vous ne trouvez jamais il y a un problème, il peut aider à modifier le code comme:

MyProperty1 = newValue1; 
MyProperty2 = newValue2; 
// ... 
MyProperty10 = newValue10; 

à:

InvokeOnMainThread(() => { 
     MyProperty1 = newValue1; 
     MyProperty2 = newValue2; 
     // ... 
     MyProperty10 = newValue10; 
}); 

Si vous utilisez jamais ObservableCollection, alors s'il vous plaît noter que MvvmCross ne pas faire un thread pour les marshalling tirés par ces classes INotifyPropertyChanged ou INotifyCollectionChanged événements - il est donc à vous en tant que développeur pour marshall ces changements .

La raison: ObservableCollection existe dans les bases de code MS et Mono - il n'y a donc pas de manière simple que MvvmCross puisse changer ces implémentations existantes.

1

Une autre option qui pourrait être plus facile est de stocker une référence à SynchronizationContext.Current dans le constructeur de votre classe. Ensuite, plus tard, vous pouvez utiliser _context.Post (() => ...) pour invoquer le contexte - qui est le thread de l'interface utilisateur dans WPF/WinRT/SL.

class MyViewModel 
{ 
    private readonly SynchronizationContext _context; 
    public MyViewModel() 
    { 
     _context = SynchronizationContext.Current. 
    } 

    private void MyCallbackOnAnotherThread() 
    { 
     _context.Post(() => UpdateTheUi()); 
    } 
} 
Questions connexes