2017-01-23 9 views
0

Я использую реактивное программирование для создания MVVM-приложения и пытаюсь выяснить, как моя модель просмотра может поднять вопрос и дождаться диалога, чтобы пригласить пользователя на ответ.Запросы диалогового взаимодействия с использованием IObservable

Например, когда пользователь нажимает кнопку «Переименовать», я хочу, чтобы появилось диалоговое окно, которое позволяет пользователю изменять текст. Мой подход заключается в том, чтобы модель представления отображала свойство IObservable<string>. Код в представлении View прослушивает испускаемые значения и может отображать UWP ContentDialog. Если пользователь меняет текст и нажимает OK, код в этом диалоговом окне вызывает ReportResult(string newText) на модели просмотра. У меня есть код ниже, чтобы показать, как он работает. Два вопроса:

Это разумный подход для сбора информации от пользователя?

Кроме того, у меня есть два тонких подхода к построению этого и не знаю, что лучше.

interface IServiceRequest<TSource, TResult> : ISubject<TResult, TSource> { } 

// Requests for information are just 'passed through' to listeners, if any. 
class ServiceRequestA<TSource, TResult> : IServiceRequest<TSource, TResult> 
{ 
    IObservable<TSource> _requests; 
    Subject<TResult> _results = new Subject<TResult>(); 

    public ServiceRequestA(IObservable<TSource> requests) 
    { 
     _requests = requests; 
    } 

    public IObservable<TResult> Results => _results; 
    public void OnCompleted() => _results.OnCompleted(); 
    public void OnError(Exception error) => _results.OnError(error); 
    public void OnNext(TResult value) => _results.OnNext(value); 
    public IDisposable Subscribe(IObserver<TSource> observer) => _requests.Subscribe(observer); 
} 

// Requests for information are 'parked' inside the class even if there are no listeners 
// This happens when InitiateRequest is called. Alternately, this class could implement 
// IObserver<TSource>. 
class ServiceRequestB<TSource, TResult> : IServiceRequest<TSource, TResult> 
{ 
    Subject<TSource> _requests = new Subject<TSource>(); 
    Subject<TResult> _results = new Subject<TResult>(); 

    public void InitiateRequest(TSource request) => _requests.OnNext(request); 
    public IObservable<TResult> Results => _results; 
    public void OnCompleted() => _results.OnCompleted(); 
    public void OnError(Exception error) => _results.OnError(error); 
    public void OnNext(TResult value) => _results.OnNext(value); 
    public IDisposable Subscribe(IObserver<TSource> observer) => _requests.Subscribe(observer); 
} 

class MyViewModel 
{ 
    ServiceRequestA<string, int> _serviceA; 
    ServiceRequestB<string, int> _serviceB; 

    public MyViewModel() 
    { 
     IObservable<string> _words = new string[] { "apple", "banana" }.ToObservable(); 

     _serviceA = new ServiceRequestA<string, int>(_words); 
     _serviceA 
      .Results 
      .Subscribe(i => Console.WriteLine($"The word is {i} characters long.")); 
     WordSizeServiceRequest = _serviceA; 

     // Alternate approach using the other service implementation 
     _serviceB = new ServiceRequestB<string, int>(); 
     IDisposable sub = _words.Subscribe(i => _serviceB.InitiateRequest(i)); // should dispose later 
     _serviceB 
      .Results 
      .Subscribe(i => Console.WriteLine($"The word is {i} characters long.")); 
     WordSizeServiceRequest = _serviceB; 
    } 

    public IServiceRequest<string, int> WordSizeServiceRequest { get; set; } 
    // Code outside the view model, probably in the View code-behind, would do this: 
    // WordSizeServiceRequest.Select(w => w.Length).Subscribe(WordSizeServiceRequest); 
} 

Основываясь на комментариях Ли Кэмпбелла, здесь есть другой подход. Может быть, ему это понравится? Я действительно не знаю, как создать IRenameDialog. Раньше это было всего лишь немного кода в представлении.

public interface IRenameDialog 
{ 
    void StartRenameProcess(string original); 
    IObservable<string> CommitResult { get; } 
} 

public class SomeViewModel 
{ 
    ObservableCommand _rename = new ObservableCommand(); 
    BehaviorSubject<string> _name = new BehaviorSubject<string>(""); 

    public SomeViewModel(IRenameDialog renameDialog,string originalName) 
    { 
     _name.OnNext(originalName); 
     _rename = new ObservableCommand(); 
     var whenClickRenameDisplayDialog = 
      _rename 
      .WithLatestFrom(_name, (_, n) => n) 
      .Subscribe(n => renameDialog.StartRenameProcess(n)); 
     var whenRenameCompletesPrintIt = 
      renameDialog 
      .CommitResult 
      .Subscribe(n => 
      { 
       _name.OnNext(n); 
       Console.WriteLine($"The new name is {n}"); 
      }; 
     var behaviors = new CompositeDisposable(whenClickRenameDisplayDialog, whenRenameCompletesPrintIt); 
    } 

    public ICommand RenameCommand => _rename; 
} 

ответ

1

Ли пристыдил меня придумывают с лучшим решением. Первое и лучшее оказалось очень простым. Я передаю в конструктор одного из них:

public interface IConfirmationDialog 
{ 
    Task<bool> Show(string message); 
} 

Внутри моей точки зрения модели, я могу сделать что-то вроде этого ...

IConfirmationDialog dialog = null; // provided by constructor 
_deleteCommand.Subscribe(async _ => 
{ 
    var result = await dialog.Show("Want to delete?"); 
    if (result==true) 
    { 
     // delete the file 
    } 
}); 

Строительство ConfirmationDialog не было трудно. Я просто создаю один из них в части моего кода, который создает модели представления и присваивает их представлениям.

public class ConfirmationDialogHandler : IConfirmationDialog 
{ 
    public async Task<bool> Show(string message) 
    { 
     var dialog = new ConfirmationDialog(); // Is subclass of ContentDialog 
     dialog.Message = message; 
     var result = await dialog.ShowAsync(); 
     return (result == ContentDialogResult.Primary); 
    } 
} 

Таким образом, решение выше довольно чистое; зависимости, мои требования к модели представления представлены в конструкторе. Другой подход, похожий на то, что Prism и ReactiveUI делают, - это тот, где ViewModel построен без зависимости, в которой он нуждается. Вместо этого в представлении есть немного кода, чтобы заполнить эту зависимость.Мне не нужно иметь несколько обработчиков, так что я просто так:

public interface IInteractionHandler<TInput, TOutput> 
{ 
    void SetHandler(Func<TInput, TOutput> handler); 
    void RemoveHandler(); 
} 

public class InteractionBroker<TInput, TOutput> : IInteractionHandler<TInput, TOutput> 
{ 
    Func<TInput, TOutput> _handler; 

    public TOutput GetResponse(TInput input) 
    { 
     if (_handler == null) throw new InvalidOperationException("No handler has been defined."); 
     return _handler(input); 
    } 
    public void RemoveHandler() => _handler = null; 

    public void SetHandler(Func<TInput, TOutput> handler) => _handler = handler ?? throw new ArgumentNullException(); 
} 

И тогда мой ViewModel предоставляет свойство как это:

public IInteractionHandler<string,Task<bool>> Delete { get; } 

и обрабатывает команды удаления, как это:

_deleteCommand.Subscribe(async _ => 
{ 
    bool shouldDelete = await _deleteInteractionBroker.GetResponse("some file name"); 
    if (shouldDelete) 
    { 
     // delete the file 
    } 
}); 
1

Хм. Первый блок кода выглядит как повторная реализация IObservable<T>, на самом деле я думаю, что событие хуже ISubject<T>, так что вызывает тревожные звонки.

Затем класс MyViewModel выполняет другие функции, такие как передать IObservable<string> в качестве параметра (зачем?), Создавать подписки (побочные эффекты) в конструкторе и выставлять службу как общедоступное свойство. Вы также имеете код с кодом в своем коде зрения, который часто является кодовым запахом в MVVM.

Я бы предложил читать на MVVM (решена проблема для 10yrs) и havnig посмотрим на то, как другие клиентские приложения используют Rx/Реактивное программирование с помощью MVVM (решается задача для ~ 6yrs)

+0

Этот подход основан на том, что я считал Призм. См. «InteractionRequest » в [link] (https://msdn.microsoft.com/en-us/library/gg431432 (v = pandp.50) .aspx) и [link] (https://msdn.microsoft. ком/EN-US/библиотека/gg405494 (v = pandp.40) .aspx). Вместо того, чтобы использовать событие, чтобы заставить View что-то сделать, я использую 'Observable '. Я подумал, что было бы полезно иметь механизм для представления, чтобы отправить какой-то ответ, хотя это может произойти, если в объекте уведомления был задействован механизм обратного вызова. – JustinM

+0

В случае переименования мой ViewModel может выставить 'IObservable <(string originalText, Action onCommit)>'. Код в представлении довольно минимален. Он подписывается, и когда он получает значение, он может создать ContentDialog, установить DataContext на что-то, связанное со значением, которое он получил в подписке, и подключить кнопку OK, чтобы вызвать делегат onCommit, чтобы ViewModel получал результат. – JustinM

+0

Где вы пользуетесь командами? Когда пользователи сообщают (например, нажимают кнопку «переименование»), пользовательский интерфейс должен вызывать команду. Если вы хотите отреагировать на что-то, что является слоем ниже вас, значит, вы не знаете о вас, поэтому можете зарегистрировать обратный вызов (подписаться на IObservable) –