2013-01-20 9 views
8

Mam aplikację MVVM Cross działającą w systemie Windows Phone 8, którą ostatnio przeportowałem, aby używać bibliotek klas przenośnych.Zaktualizuj wątek interfejsu użytkownika z przenośnej biblioteki klas.

Modele widoku znajdują się w przenośnej bibliotece klas, a jedna z nich udostępnia właściwość, która włącza i wyłącza PerformanceProgressBar z zestawu narzędzi Silverlight for WP poprzez powiązanie danych.

Gdy użytkownik naciśnie przycisk RelayCommand rozpoczyna proces w tle, który ustawia właściwość na wartość true, co powinno umożliwić pasek postępu i przetwarzanie w tle.

Przed przeniesieniem do PCL mogłem wywołać zmianę z wątku UI, aby upewnić się, że pasek postępu został włączony, ale obiekt Dispatchera nie jest dostępny w PCL. Jak mogę obejść ten problem?

Dzięki

Dan

+1

Nie jestem całkowicie pewny w telefonie z wygraną, ale czy można użyć 'Application.Current.Dispatcher' do wywołania aktualizacji? Czy może to być 'Deployment.Current.Dispatcher'? –

Odpowiedz

12

Jeśli nie masz dostępu do dyspozytora, można po prostu przejść delegata metody BeginInvoke do swojej klasie:

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

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

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

Następnie, aby go instanciate (z klasy, która ma dostęp do dyspozytora):

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

var viewModel = new YourViewModel(dispatcherDelegate); 

Możesz także utworzyć opakowanie wokół swojego dyspozytora.

Najpierw zdefiniować interfejs IDispatcher w swojej przenośnej biblioteki klas:

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

Następnie, w ramach projektu, który ma dostęp do dyspozytora, implementować interfejs:

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); 
    } 
} 

Następnie można po prostu przekazać ten obiekt jako instancję IDispatchera do przenośnej biblioteki klas.

+0

Jest to wykonalne podejście - i to w zasadzie dokładnie to, co robi MvvmCross - tylko robi to za pomocą IoC na poziomie konfiguracji platformy – Stuart

+0

Dzięki - to zadziałało dla mnie! –

+0

Twoja rozmowa ... Jeśli jednak korzystasz z MvvmCross, to prawdopodobnie łatwiej znajdziesz dev za pomocą 'InvokeOnMainThread (action)' - co zapewni Ci gotowe implementacje na każdej platformie (i zawiera takie rzeczy jak '' CheckAccess() 'wywołania przed każdym wywołaniem) – Stuart

15

wszystkich platformach MvvmCross wymagają UI-actions się marshalled powrotem na UI Temat/Apartament - ale każda platforma robi to inaczej ....

Aby obejść ten problem, MvvmCross zapewnia sposób wieloplatformowe w tym celu - przy użyciu obiektu, któremu wstrzyknięto IMvxViewDispatcherProvider.

Na przykład, na WindowsPhone IMvxViewDispatcherProvider jest ostatecznie przez MvxMainThreadDispatcher w https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.WindowsPhone/Views/MvxMainThreadDispatcher.cs

ten realizuje InvokeOnMainThread używając:

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

     return true; 
    } 

Dla kodu w ViewModels:

  • Twój ViewModel dziedziczy z MvxViewModel
  • z MvxViewModel dziedziczy an MvxApplicationObject
  • z MvxApplicationObject dziedziczy z MvxNotifyPropertyChanged
  • przedmiotem MvxNotifyPropertyChanged dziedziczy z MvxMainThreadDispatchingObject

MvxMainThreadDispatchingObject jest 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); 
    } 
} 

Więc ... Twój widok Model może po prostu zadzwoń InvokeOnMainThread(() => DoStuff());


jeden dodatkowy punkt, aby pamiętać, że MvvmCross automatycznie wykonuje konwersje wątku UI aktualizacje własności, które sygnalizowane są w MvxViewModel (czy rzeczywiście w każdym MvxNotifyPropertyChanged obiektu) za pomocą metod RaisePropertyChanged() - patrz:

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)); 
       }); 
    } 

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


This automatyczne rozkazy połączeń RaisePropertyChanged() działają dobrze w większości sytuacji, ale mogą być nieco nieefektywne, jeśli podnosisz wiele zmienionych właściwości z wątku tła - może to prowadzić do znacznego przełączania kontekstu wątków. To nie jest coś, co trzeba mieć świadomość w większości kodzie - ale jeśli kiedykolwiek zrobić uważają, że jest to problem, to może pomóc zmienić kod tak:

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

do:

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

Jeśli kiedykolwiek użyć ObservableCollection, to należy pamiętać, że MvvmCross robi nie robić żadnych wątek punkt etapowy dla INotifyPropertyChanged lub INotifyCollectionChanged wydarzeń opalanych tych klas - tak to do Ciebie jako programista Marshall te zmiany .

Powód: ObservableCollection istnieje w bazach kodów MS i Mono - więc nie ma łatwego sposobu, aby MvvmCross mógł zmienić te istniejące implementacje.

1

Inną opcją, która może być łatwiejsza, jest zapisanie odniesienia do SynchronizationContext.Current w swoim konstruktorze klasy. Następnie, możesz użyć _context.Post (() => ...) do wywołania w kontekście - który jest wątkiem UI w WPF/WinRT/SL.

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

    private void MyCallbackOnAnotherThread() 
    { 
     _context.Post(() => UpdateTheUi()); 
    } 
} 
Powiązane problemy