27176


## 概要

  * WebBrowserクラスを使用したスクレイピング中、特定の要素を見つけたときユーザ向けの画面を切り替えてパスワードを入力させます。
  * 入力後画面切り替え直後の処理を実行したいです。
  * 現状処理を続行させることができず、ユーザが情報入力後画面切り替え直後の処理と全く同じ処理を変えた別メソッドを呼んでいます。
  * これをユーザの入力後、処理を再開できるように修正したいのですが方法が見つかりません。

## ソースコード

main.cs

[code]

    public void Click_DownloadFiles(object sender, RoutedEventArgs e)
    {
        // ユーザ向かいには「処理中」のような画面が出ている状態です。
        A();
    
        // WebBrowserクラスを使用したスクレイピング処理中で、
        // 特定の要素（パスワード入力画面入力フォームなどを見つけるとユーザに画面を返します。）
        if (elementExist())
            {
                url = string.Format("views/Pages/PasswordInput.xaml")
                Frame.MainFrame.Navigate(new URI(url, UriKind.Relative));
                return;
            }
        // 入力後ここから処理を再開したい…
        B();
    }
    
    
    public void Click_DownloadFilesAfterInput(object sender, RoutedEventArgs) e
    {
        // 現状は入力が終わったら押すボタンを分けて別メソッドに元の処理の続きをコピーしている
        B();
    }
    
[/code]

PasswordInput.xaml.cs

[code]

    private void PasswordBoxChanged(object sender, RoutedEventArgs e)
    {
        if (this.DataContext != null)
        {
            // ユーザの入力を保持
            ((dynamic)this.DataContext).Password = ((PasswordBox)sender).SecurePassword;
        }
    }
    
    private void Click_DownloadFilesAfterInput(object sender, RoutedEventArgs e)
    {
        if (this.DataContext != null)
        {
            // メイン処理のクラスに戻る
            ((dynamic)this.DataContext).Click_DownloadFilesAfterInput(sender, e);
            Button_DownloadFiles.IsEnabled = false;
        }
    }
    
[/code]

## 試したこと

  * returnを使ったらメインの処理から抜けてしまうので使ったら無理そうです。
  * goto文を使う

main.cs

[code]     public void Click_DownloadFiles(object sender, RoutedEventArgs e)

    {
        if (State == "PW" ) goto pw;
        A();
    
        if (elementExist())
            {
                url = string.Format("views/Pages/PasswordInput.xaml")
                Frame.MainFrame.Navigate(new URI(url, UriKind.Relative));
                return;
            }
        // 入力後ここから処理を再開したい…
        pw:
        B();
    }
    
[/code]

PasswordInput.xaml.cs

[code]     private void PasswordBoxChanged(object sender, RoutedEventArgs e)

    {
        if (this.DataContext != null)
        {
            ((dynamic)this.DataContext).Password = ((PasswordBox)sender).SecurePassword;
        }
    }
    
    private void Click_DownloadFilesAfterInput(object sender, RoutedEventArgs e)
    {
        if (this.DataContext != null)
        {
            ((dynamic)this.DataContext).State = "PW";
            ((dynamic)this.DataContext).Click_DownloadFilesAfterInput(sender, e);
            Button_DownloadFiles.IsEnabled = false;
        }
    }
    
[/code]

※ 処理は続行できるようになるものの、メインの処理でもともと定義していた変数参照できなくなる…

という状態です。よろしくお願いします。


CC BY-SA 3.0
2016-06-27T13:29:44.220
27115
2016-06-29T06:41:45.803
2016-06-28T08:14:07.310
8000
12459
question
c# .net wpf
C#でメイン処理途中でユーザからの入力画面に切り替えてまたメインの処理に戻る方法はありますか？


処理を途中で中断して再開する、というと[コルーチン](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3)ですかね。

.NETはネイティブにはサポートしていませんが、質問者さんが試されていたような

  * どこまで進んだかという状態や、残しておきたい変数をフィールドに格納しておき
  * それを見て自動的に続きの地点までジャンプする

といった形に自動変換する構文が最近の C# には用意されています。

で、今回の場合、[Teratailに投稿された同質問](https://teratail.com/questions/39347)の回答にもありますが、非同期メソッドを使うとスムーズに書けるかと思います。概要については下記サイトなどが参考になります。

[非同期メソッド - C# によるプログラミング入門 | ++C++; // 未確認飛行
C](http://ufcpp.net/study/csharp/sp5_async.html)

ただ「何らかのボタンが押されるまで待つ」といった非同期メソッドは用意されていませんから、代わりに
[`TaskCompletionSource`](https://msdn.microsoft.com/ja-
jp/library/dd449174\(v=vs.100\).aspx) を使います。

例えばClick_DownloadFiles側ではこのように書いておき・・・

[code]

    // ここに async を付ける
    public async void Click_DownloadFiles(object sender, RoutedEventArgs e)
    {
        // 略
    
        if (elementExist())
        {
            var tcs = new TaskCompletionSource<SecurePassword>();
            // PasswordInput.xaml.csからアクセスできるように
            this.PasswordInputCompletionSource = tcs;
    
            url = string.Format("views/Pages/PasswordInput.xaml");
            Frame.MainFrame.Navigate(new URI(url, UriKind.Relative));
    
            // tcs.SetResult()が呼び出されるまで**非同期に**待つ
            // 実際には一旦このメソッドは終了し、後で再び呼び出されることになる
            // SetResultに渡された引数はここで戻り値として得られる
            var password = await tcs.Task;
    
            // paswordを使って何かする
            // （Bで使いたいなら先に変数を定義しておく等）
        }
    
        B();
    }
    
[/code]

パスワード入力画面では次のようにして処理を戻します。

[code]

    private void Click_DownloadFilesAfterInput(object sender, RoutedEventArgs e)
    {
        // 用意しておいたTaskCompletionSourceを取り出し、
        var cts = ((dynamic)this.DataContext).PasswordInputCompletionSource;
        // 結果を設定する（とClick_DownloadFilesが再開する）
        cts.SetResult(password);
    }
    
[/code]

ここではパスワードの受け渡しも `TaskCompletionSource` を通して行っていますので、`Password`
プロパティ？を公開する必要はありません。  
※値の受け渡しをしない `TaskCompletionSource` はないので、もしパスワードの受け渡し方法は変えたくない場合は、object なり
bool なり適当な値を代わりに渡すとよいでしょう。


CC BY-SA 3.0
2016-06-29T06:41:45.803
27176
2016-06-29T06:41:45.803
8000
27115
answer