2017-01-23 5 views
0

私はMVVMアプリケーションを構築するためにリアクティブプログラミングを使用しています。私のビューモデルがどのように質問を出し、ユーザーに回答を促すダイアログが出るのを待っていますか?IObservableを使用したダイアログのやりとり要求

たとえば、ユーザーが名前の変更ボタンをクリックすると、ユーザーがテキストを変更できるダイアログが表示されます。私のアプローチは、ビューモデルがIObservable<string>プロパティを公開することです。ビュー内のコードビハインドは放出された値をリッスンし、UWP ContentDialogを表示することがあります。ユーザーがテキストを変更してOKをクリックすると、そのダイアログのコードはビューモデルでReportResult(string newText)を呼び出します。どのように動作するかを示すために、いくつかのコードがあります。 2つの質問:

これはユーザーから情報を収集するための妥当なアプローチですか?

また、私はこれを構築するための2つの微妙に異なるアプローチがあり、どれが良いか分かりません。

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

Leeが来るに私を辱めよりよい解決法で。最初と最高は非常に簡単であることが分かった。私の見解モデル内部

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

、私はこのような何かを行うことができます... ConfirmationDialogを構築

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

は難しくありませんでした:私は、コンストラクタこれらのいずれかに渡します。私は、ビューモデルを作成してビューに割り当てるコードの部分にこれらのいずれかを作成します。

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

Hmm。 コードの最初のブロックはIObservable<T>の再実装のように見えますが、実際はイベントが悪いと思われますISubject<T>、アラーム音が鳴ります。

MyViewModelクラスでは、IObservable<string>をパラメータとして渡し(理由は?)、コンストラクタでサブスクリプション(副作用)を作成し、サービスを公開プロパティとして公開します。あなたはまた、ビューコードの背後にあるコードを持っていることもあります。これはMVVMのコード臭です。

私はMVVM(10yrsのための問題を解決した)上に読んで示唆して他のクライアントアプリケーションが(〜6yrsための解決の問題)MVVMでのRx /リプログラミングを使用する方法を見てhavnigう

+0

このアプローチは、私がプリズムがしたと考えたものに基づいています。 [link](https://msdn.microsoft.com/en-us/library/gg431432(v = pandp.50).aspx)と[link](https://msdn.microsoft。)の「InteractionRequest 」を参照してください。 com/ja-us/library/gg405494(v = pandp.40).aspx)。ビューに何かをさせるためにイベントを使用するのではなく、Observable を使用しています。私は、通知オブジェクトがその中にコールバックメカニズムを持っていた場合に起こる可能性がありますが、Viewが何らかの応答を返すためのメカニズムを持つことが有用であると考えました。 – JustinM

+0

名前変更の場合、私のViewModelは 'IObservable <(string originalText、Action onCommit)>'を公開する可能性があります。ビュー内のコードはかなり小さいです。サブスクライブし、値を受け取るとContentDialogを作成し、DataContextをサブスクリプションで受け取った値に関連付けるように設定し、OKボタンを押してonCommitデリゲートを呼び出し、ViewModelが結果を取得するようにします。 – JustinM

+0

コマンドはどこにありますか?ユーザーが(「名前を変更する」などのボタンをクリックするなど)通知すると、UIはコマンドを呼び出す必要があります。あなたの下にある層に反応したいのであなたについて知りませんので、コールバックを登録することができます(IObservableに登録する) –

関連する問題