Я использую реактивное программирование для создания 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;
}
Этот подход основан на том, что я считал Призм. См. «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
В случае переименования мой ViewModel может выставить 'IObservable <(string originalText, Action onCommit)>'. Код в представлении довольно минимален. Он подписывается, и когда он получает значение, он может создать ContentDialog, установить DataContext на что-то, связанное со значением, которое он получил в подписке, и подключить кнопку OK, чтобы вызвать делегат onCommit, чтобы ViewModel получал результат. –
JustinM
Где вы пользуетесь командами? Когда пользователи сообщают (например, нажимают кнопку «переименование»), пользовательский интерфейс должен вызывать команду. Если вы хотите отреагировать на что-то, что является слоем ниже вас, значит, вы не знаете о вас, поэтому можете зарегистрировать обратный вызов (подписаться на IObservable) –