none
本体フォームのforループで子フォーム上にグラフィックを描くには(gの扱い方、イベントのやり方) RRS feed

  • 質問

  • 一つには、本体のフォームの必要な変数を全てpublic にして渡せば出来る
    とは思いますが、変数が多く、ローカルな変数をpublic にもしたくありません。
    (スケールも上手く行っていません)


    イベントを使って、子のフォームのグラフィックを描画関数を動かしたら楽に出来そう
    と考えましたが、Graphics g = e.Graphics; の gの扱い方が良く分かりません。

    // 子のフォームのペイントイベント
    private void cPicBox_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        GraphInit(g);                   // グラフの初期化。あるスケール値で設定されている。
        
        //ehOneSceenDraw(g, abc, ... , xyz);// イベントハンドラーなので、ここで利用するものではない と思います。
                                   // また、gが本体で指定できない。
    }

    本体のイベントに対応したイベントハンドラー①
    void ehOneSceenDraw( Graphics g, int abc,  .....    , int xyz )
    本体のフォームで、引数、Graphics g が渡せない。

    本体のイベントに対応したイベントハンドラー②
    void ehOneSceenDraw( int abc,  .....    , int xyz )
    {
        Graphics g = cPicBox.CreateGraphics(); 
        // ここでのgとPaintイベントでのgは違うようで、スケールなどが違い、描画が上手くできない。
    }

    gのスマートな扱い方、またはこのような場合の上手いやり方を教えてください。

    2010年9月29日 2:06

回答

  • delegate を利用した描画コールバックとして扱えばよいだけではないかな?と思いました。(イベントでよいでしょう)

    設計としては、「描画処理」と「描画に必要なデータ」の2つを、親フォームからも子フォームからも取り除いて独立させ、親フォームは描画データの管理、子フォームは描画処理の管理として、親から子へデータだけを受け渡せばよいと思います。(そうすれば、受け渡すものはメンバ1つなり引数1つなりにまとまりますし、親で描画する必要もなくなります)

    とりあえず delegate でコールバックする例。もちろん、子フォームのイベントとして作成しても問題ありません。

    // 子
    public partial class SubForm : Form
    {
     // PaintGraphCallback を作成していますが、引数が何もないなら
     // PaintEventHandler を使えば十分かもしれませんね
     public delegate PaintGraphCallback(Graphics g, ...子から親へ渡したい引数...);
    
     // private へ変更
     private SubForm()
     {
     this.InitializeComponents();
     }
    
     // コールバックを受け取るコンストラクタを追加
     public SubForm(PaintGraphCallback paintGraph) : this()
     {
     this.PaintGraph = paintGraph;
     }
    
     private PaintGraphCallback PaintGraph;
    
     private void cPicBox_Paint(object sender, PaintEventArgs e)
     {
     GraphInit(e.Graphics);
     this.PaintGraphCallback(e.Graphics, ...);
     // PaintEventHandler を使うなら
     this.PaintGraphCallback(this, e)
     }
    }
    
    // 親
    public class MainForm : Form
    {
     private void Button1_Click(object sender, EventArgs e)
     {
     var form = new SubForm(this.DrawGraph);
     }
    
     private void DrawGraph(Graphics g, ...)
     {
     for (int x = 0; x < data.count; x++)
      g.Draw....
     }
    }
    
    • 回答としてマーク クサキ 2010年10月4日 9:28
    2010年9月29日 3:45
  • > 少なくとも、私がその方法を知っているので、こちらで行いたいと思います。

    先の私のコードでは、クサキさんのを真似してイベントにしています。

    > ここの説明で、gを使っての具体的にグラフを描画するところの説明はないですよね。

    いえ、私の文章や私のコードで、具体的に書いたつもりです。。。(;_;)
    (追記:私を強調しすぎで、ちょっと印象悪い文章ですね(--;)

    ◇グラフの描画処理を記述する場所について。
    通常の描画は必ず Paint イベントを契機にして行う必要があります。
    つまり、グラフの描画を行う場所は、Paint イベントハンドラになります。
    先の私のコードでは、Paint 内で線を描画しています。

    ◇親からのグラフ描画指示方法について。
    描画を強制的に行わせる方法として Refresh メソッドが用意されています(Invalidate+Update)。
    Refresh さえできれば、Paint の中のコードを実行すること(つまりグラフの描画)ができます。
    先の私のコードでは、その Refresh を、親から描画用データをもらった際に合わせて行っています。

    ◇別の方法について。
    ただおそらく、クサキさんは前述しました「Paint イベントを利用しない方法」がお望みなのかなと思いました。
    この場合、g は Paint イベントでなくても取得できますし、描画処理も任意の場所に記述できます。
    その方法(他の方が書かれている Bitmap を使った一例)の具体例は、先の私のリンク先にありますので、一度ご確認いただければと思います。

    • 編集済み TH01 2010年10月1日 1:59 1行追記
    • 回答としてマーク クサキ 2010年10月4日 9:30
    2010年9月30日 8:07
  • 方法2b) 親の変数だけをイベントハンドラーに渡し、gは子の中で何とかする
    ということで、しばらく話題を集中してください。

    了解しました。何が問題になっているのかようやく見えてきました。(と思います)

    本体のイベントに対応したイベントハンドラー②
    void ehOneSceenDraw( int abc,  .....    , int xyz )
    {
        Graphics g = cPicBox.CreateGraphics(); 
        // ここでのgとPaintイベントでのgは違うようで、スケールなどが違い、描画が上手くできない。
    }

    ここでCreateGraphcs()していますから、gは全く新しい別のGraphicsオブジェクトになります。cPicBox_PaintイベントハンドラでもehOneSceenDrawイベントハンドラでも、取得したPictureBoxのGraphicsオブジェクトに対して毎回1から描画を行なわなければなりません。その度にGraphicsオブジェクトを初期化するのは効率が悪そうなので、一度初期化されたGraphicsオブジェクトを、子クラス内で使いまわすようにすれば良いと思います。

    > あとはK.Takaokaさんが書かれているようにデリゲート経由で子に親のメソッドを渡しても良いと思います。
    最初のコールバックと書かれているコメントのことですか。

    そうです。

    (親側にデリゲート、イベント、イベントの発火を書くやり方は分かるのですが、
     ここの説明が理解できていません)
    ここの説明は、”方法2b) 親の変数だけをイベントハンドラーに渡し、gは子の中で何とかする”
    のことで、gが何とかなっているということですか。

    gが何とかなるとは直接関係ありません。gを何とかするのは上に書いたことで実現できるのではないかと思います。
    デリゲートというのはメソッドを入れるための変数だと思って下さい。例えばint型やstring型などの変数がありますよね。それと同じようにデリゲート型の変数があり、そこにメソッドを入れるのです。メソッドは参照型のような形で入ります。ここでは親のメソッドを入れていますから、子はデリゲートを通して親のそのメソッドにアクセスできるようになります。つまり、子が親のメソッドをコールバックしていることになります。これを頭に入れてK.Takaokaさんのコードをもう一度読んでみて下さい。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク クサキ 2010年10月1日 1:09
    2010年9月30日 16:10
    モデレータ
  • > 1.私は親が子の描画メソッドをコールすると逆向きに考えてしまいますが、
    >   何が原因で反対になっているのでしょう?

    今回の場合、次の2つの方法がとれるからです。
    どちらでも同じことが実現できます。

    a. 親から子にデータを渡さない場合
    描画コードは親に記述し、子からはそれを呼び出す。
    → K.Takaoka さんの delegate を使用したサンプル。
    → (追記:annningo さんのも現状はこちら。)

    b. 親から子にデータを渡す場合
    描画コードは子に記述する。
    → 私のサンプル。

    いずれの場合も、描画タイミングは親が指定することになりますが、その方法としてクサキさんはイベントを用いられています。
    そのイベントのハンドラでクサキさんは描画も行おうとされていますので、g をどう解決するかという問題が生じていますが、それについては Refresh を呼び出すだけにするか、Paint イベントを利用しない方法(Bitmap への描画)をとるかのどちらかというご提案の話につながります。

    > 2.デリゲート・イベントとコールバックは同じものと考えて良いですか?

    デリゲートとコールバックは同じものと考えていいです(C#では同じです)。
    イベントは、デリゲートを利用した仕組みです。

    > 3.デリゲート・イベントは親の側でも子の側でも書けるということですか?

    親・子というのはアプリケーション仕様上の位置づけにすぎませんが、デリゲートやイベントは言語仕様です。
    言語仕様上は親側・子側の区別なしに記述できますが、どちらに記述が必要かはそのアプリケーション仕様次第です。
    今回の場合、子側にイベントやデリゲートを記述する必要性はそれほどなさそうに思います。

    • 編集済み TH01 2010年10月1日 2:08 追記
    • 回答としてマーク クサキ 2010年10月4日 9:31
    2010年10月1日 1:49

すべての返信

  • 子フォーム上にグラフィックを描く処理は子フォーム上で行うべきですから、その処理を本体フォームからどうやって呼ぶかですよね。本体フォームから子フォームを開くと思いますので、本体フォームで子フォームのインスタンスはわかっているわけですから、あとはそこから子フォームの描画メソッドを呼ぶだけで済みそうな気がします。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年9月29日 3:13
    モデレータ
  • delegate を利用した描画コールバックとして扱えばよいだけではないかな?と思いました。(イベントでよいでしょう)

    設計としては、「描画処理」と「描画に必要なデータ」の2つを、親フォームからも子フォームからも取り除いて独立させ、親フォームは描画データの管理、子フォームは描画処理の管理として、親から子へデータだけを受け渡せばよいと思います。(そうすれば、受け渡すものはメンバ1つなり引数1つなりにまとまりますし、親で描画する必要もなくなります)

    とりあえず delegate でコールバックする例。もちろん、子フォームのイベントとして作成しても問題ありません。

    // 子
    public partial class SubForm : Form
    {
     // PaintGraphCallback を作成していますが、引数が何もないなら
     // PaintEventHandler を使えば十分かもしれませんね
     public delegate PaintGraphCallback(Graphics g, ...子から親へ渡したい引数...);
    
     // private へ変更
     private SubForm()
     {
     this.InitializeComponents();
     }
    
     // コールバックを受け取るコンストラクタを追加
     public SubForm(PaintGraphCallback paintGraph) : this()
     {
     this.PaintGraph = paintGraph;
     }
    
     private PaintGraphCallback PaintGraph;
    
     private void cPicBox_Paint(object sender, PaintEventArgs e)
     {
     GraphInit(e.Graphics);
     this.PaintGraphCallback(e.Graphics, ...);
     // PaintEventHandler を使うなら
     this.PaintGraphCallback(this, e)
     }
    }
    
    // 親
    public class MainForm : Form
    {
     private void Button1_Click(object sender, EventArgs e)
     {
     var form = new SubForm(this.DrawGraph);
     }
    
     private void DrawGraph(Graphics g, ...)
     {
     for (int x = 0; x < data.count; x++)
      g.Draw....
     }
    }
    
    • 回答としてマーク クサキ 2010年10月4日 9:28
    2010年9月29日 3:45
  • >またはこのような場合の上手いやり方を教えてください。

    今までに出た方法以外には、親から子にメッセージを送ってWndProcで受け取って処理する等も。

    しかしながら、今出ている情報であればTrapeMiyaさんの方法が一番シンプルで分かりやすいと思います。

     

    >gのスマートな扱い方

    こちらに関しては、具体的にどのように利用しようと思っているのか不明なので情報が足りません。

    なお、単に渡し方などの問題であって解決している場合は無視してください。

    2010年9月29日 6:35
  • > // 親
    > private void DrawGraph(Graphics g, ...)
    > {
    > for (int x = 0; x < data.count; x++)
    >  g.Draw....
    > }

    Graphics g は子のもので、親でどうして使えるのですか?
    g.Draw....は子でやるものだと思うのですが?

    // 子
    > private void cPicBox_Paint(object sender, PaintEventArgs e)
    > {
    > GraphInit(e.Graphics);
    > this.PaintGraphCallback(e.Graphics, ...);
    > // PaintEventHandler を使うなら
    > this.PaintGraphCallback(this, e)
    > }

    ここのコーディング理解出来ていませんが、
    ここで、描画のルーチンを書くと、for ループでの発火のタイミングではなく
    子のフォームのPaintのタイミングで描画されるような気がするのですが?


    親のフォームのfor loopの中で、イベントを発火させるものだと思っています。
    そこで、親のフォームで、
    public delegate void GraphOneSceneDrawEvent(int abc,  .....    , int xyz);
    public static event GraphOneSceneDrawEvent eGraphOneSceneDraw;
    デリゲート、イベントを作り
    forループの中で
    eGraphOneSceneDrawEvent(abc,  .....    , xyz);のようにイベントを発火させています。

    子のフォームので
    リッスンするために
    Oya.eGraphOneSceneDraw += new RHEED.GraphOneSceneDrawEvent(ehGraphOneSceneDraw);とし、
    イベントハンドラーを定義
    ehGraphOneSceneDrawEvent(int abc,  .....    , int xyz);
    {
       // 描画処理 (ただ、これでは、引数にgが無いので描画ができない。 
    }

    ただ、これですと、親のフォームで、どのように子のgを指定するのか分かりません。
    私のデリゲート、イベントの理解は上のようなパターンだけで深くは理解できていないようで、
    少し変わってくるとついて行けないようです。コールバックのところをもう少し細かく教えて頂けたらと思います。

    子のフォームでデリゲートなど定義されていますが、このパターンだと上記の問題を解決できるということですか?
    (元々、デリゲートやイベントはどちらのフォームに書いても良いものなのですか?)

    2010年9月29日 8:48
  • K.Takaoka さんが返信されるかもしれないけど先に書かせてもらいます。

    > Graphics g は子のもので、親でどうして使えるのですか?

    どのメソッドであっても普通に使えます。
    親で処理したとしても、g への操作が反映される先は、もちろん子側です。
    たとえば渡されるオブジェクトが子の TextBox tb であったとしても、親で tb.Text に代入したりできますよね。
    (Thread が異なると少し事情は違ってきますけど、今回は同じだと想像します。)

    > g.Draw....は子でやるものだと思うのですが?

    今回は、描画に使用する値は親が持っていて、それを子に渡さずに描画することが命題ですので、親でするしかないのですよね?
    g を親に渡して描画してもらうことになるので、親のメソッドを直接呼び出すか、デリゲートするかのどちらかがいいと思いました。

    それから、子の PictureBox に描画を行わせるタイミングを親が制御しないといけないことについては、親から何らかの形で cPicBox.Refresh() が実行されるようにするといいと思います。
    親でイベントを発生してそれを子でハンドルするか、親に通知先 PictureBox のコレクションを持つかなどになると思います。
    たとえば具体的には、
    ehGraphOneSceneDrawEvent(int abc,  .....    , int xyz);
    {
       // 描画処理 (ただ、これでは、引数にgが無いので描画ができない。
       // ↑は↓で解決するはず。
       cPicBox.Refresh();
    }

    描画の仕方ですが、Paint イベントを利用しない方法も有効かもしれません。
    その場合は、以下のスレッド(の私の返信とか)が参考になるかもしれません。

    pictureBoxへの描画する際のちらつきを抑えるには
    http://social.msdn.microsoft.com/Forums/ja-JP/csharpexpressja/thread/9825e388-63b2-4a8b-a74f-d272b329545d/#8c1adeca-7e48-4cbf-8597-b02d63492703

    2010年9月29日 11:36
  • PictureBox の Graphics を取り出して描画するのではなく、別途 Bitmap を作って貼り付ければ、もっと考え方が広がるのではないかと思いました。
    Jitta@わんくま同盟
    2010年9月29日 13:40
  • ただ、これですと、親のフォームで、どのように子のgを指定するのか分かりません。

    私の理解だと親のgを子供にイベントを通して渡したいということだと思いますが、合っていますでしょうか? であれば、イベントの引数は自由に作成できますので、親のgをイベントハンドラに渡すことは簡単です。以下が参考になると思います。

    第17章 言語に内蔵されたイベント機能
    http://www.atmarkit.co.jp/fdotnet/csharp_abc2/csabc2_017/cs2_017_02.html


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年9月29日 14:43
    モデレータ
  • 【TH01さん】
    > どのメソッドであっても普通に使えます。
    > 親で処理したとしても、g への操作が反映される先は、もちろん子側です。
    > たとえば渡されるオブジェクトが子の TextBox tb であったとしても、
    > 親で tb.Text に代入したりできますよね。

    TextBoxは分かりますが、Paintイベントの引数である PaintEventArgs e
    から作られる、g をどう渡せば良いか分かりません。


    > 今回は、描画に使用する値は親が持っていて、それを子に渡さずに描画することが
    > 命題ですので、親でするしかないのですよね?

    そうなんですか?

    <trapemiyaさんの最初のコメント>
    > 子フォーム上にグラフィックを描く処理は子フォーム上で行うべきですから、そ

    <K.Takaokaさんの最初のコメント>
    > 親フォームは描画データの管理、子フォームは描画処理の管理として、
    > 親から子へデータだけを受け渡せばよいと思います。

    私も、子で描画ルーチンを作成するものと思っていました。


    > ehGraphOneSceneDrawEvent(int abc,  .....    , int xyz);
    > {
    >   // 描画処理 (ただ、これでは、引数にgが無いので描画ができない。
    >   // ↑は↓で解決するはず。
    >   cPicBox.Refresh();
    > }

     gが無いとg.DrawLineなどの命令が使えず、Refreshしても意味が無いと思いますが。


    【trapemiyaさん】
    > 私の理解だと親のgを子供にイベントを通して渡したいということだと思いますが、
    > 合っていますでしょうか? 
    私は、子のgを親に渡すのだと思うのですが。
    (親の変数を渡すデリゲートを書くことは出来ています。)


    全体的に少し混乱しています。
    この間の話、私なり以下のように思っています。如何ですか。

    ・方法1: 子のgを親に渡して描画する方法
    ・方法2: デリゲート、イベントを使って親のデータを子に渡し描画する方法

    方法1の場合、子のPaintイベントの引数である PaintEventArgs eから
              作られる、g を親に渡す方法を教えて頂ければ解決すると思います。
    方法2の場合、a)方法1と同じように、子のgを一旦もらい、
                 他の引数と同じようにイベントハンドラーに渡す。
              b)親の変数だけをイベントハンドラーに渡し、gは子の中で何とかする。

     

    2010年9月30日 3:43
  • > ここで、描画のルーチンを書くと、for ループでの発火のタイミングではなく
    >子のフォームのPaintのタイミングで描画されるような気がするのですが?

    > 親のフォームのfor loopの中で、イベントを発火させるものだと思っています。

    うーん? 前回の内容からは、ループに関する話がまったくないので、どこでだれがなにのためにループしているか、まったくわかりませんでした。

    この話から色々察すると、

    ・親フォームが長時間にわたってデータの生成作業を行っているループの中から、作業の現在状態を子フォームでリアルタイムに描画したい

    という話でしょうか? こういう話になると、使用されているグラブ描画のライブラリが状態を追加しながら描画ができるか、描画のために親フォームのループが停止していいのか、そもそも親フォームのループはバックグラウンドで行われているのか?といったところが実際のポイントになりそうですね。

    根本的な解決は、最初に書いたようにきちんとロジックを再分割して設計をなおせばすぐなきもしますが、現状のままでいく前提で1つづつ解決していきましょう。

    ・子フォームの描画処理を親フォームで実行するにはどうしたらいいか

    前回の投稿の通りです。

    ・親フォームのループから、子フォームの描画処理を呼ぶにはどうすればよいか

    子フォームか、cPicBox の Invalidate() を呼び出せば OK ですが、親フォームがボタンのイベントでループしていたりすると、描画が止まっていますので更新されません。BackgroundWorker を利用するなどの工夫が必要になります。

    利用されているグラフ描画のライブラリが、データの生成に応じてどんどん追記していけるようなものであれば、Jitta さんが書かれているように Bitmap や GraphicsPath に描画して、子フォームは Bitmap を Graphics に DrawImage するだけにするとよいかと思います。しかし、GraphInit がどのような処理かわかりませんが、子フォーム側で描画に関するスケーリングなどを変更できるような場合には親が管理する Bitmap に対して GraphInit などの処理を行わないといけないかもしれないですね。

    2010年9月30日 3:44
  • 私は、子のgを親に渡すのだと思うのですが。
    (親の変数を渡すデリゲートを書くことは出来ています。)


    全体的に少し混乱しています。
    この間の話、私なり以下のように思っています。如何ですか。

    ・方法1: 子のgを親に渡して描画する方法
    ・方法2: デリゲート、イベントを使って親のデータを子に渡し描画する方法

    方法1の場合、子のPaintイベントの引数である PaintEventArgs eから
              作られる、g を親に渡す方法を教えて頂ければ解決すると思います。
    方法2の場合、a)方法1と同じように、子のgを一旦もらい、
                 他の引数と同じようにイベントハンドラーに渡す。
              b)親の変数だけをイベントハンドラーに渡し、gは子の中で何とかする。 

    確認させて下さい。実現されたいのは以下のことでしょうか? 子のgを利用して子に描画を行うが、描画を行うための情報が親にたくさんある。よって、方法1では子のgを親に渡し、親側で子に描画しようとしている。方法2はたくさんある親の情報を子に渡し、子側で子に描画をしようとしている。

    上記がその通りだとして先走ると、方法1は親で子のPaintイベントをサブスクライブすれば済みそうです。ただやっぱり子の描写は子に任せるのがオブジェクト指向だと思いますので、この方法はあまり適切ではないと思います。方法2a)は子のgを一旦親に渡す意味がないので、現実的なのはb)です。でもこの親から渡す変数が多いのがそもそも問題の始まりなんですよね?しかし、親から渡す変数が多くても必要なものは子に渡すというのがオブジェクト指向的には良いと思います。子に渡すというのは子から必要な親の情報にアクセスできるということも含みます。
    あとはK.Takaokaさんが書かれているようにデリゲート経由で子に親のメソッドを渡しても良いと思います。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年9月30日 5:03
    モデレータ
  • (trapemiya さんが確認されているのに返信してすみません。以下のコードは多分クサキさんが実現されたいことそのままかなと思ってます。)

    クサキさん
    > 私も、子で描画ルーチンを作成するものと思っていました。

    私に変な思いこみがありました。
    一番初めに「全てpublic にして渡せば出来るとは思いますが」と書かれていたのでデータは渡せないと思い込んでいたのですが、public にせずに渡す方法があればそれで良かったんですね。

    すると話としてはよくある「別のフォームにデータを渡す方法」に関することになりますね。
    一般的には、
    ・受渡し用のクラスを作成してそこにセットして渡す。
    ・DataSet のようなものに入れて渡す。
    などがあると思います。

    クサキさん
    > TextBoxは分かりますが、Paintイベントの引数である PaintEventArgs e
    > から作られる、g をどう渡せば良いか分かりません。
    と、
    > gが無いとg.DrawLineなどの命令が使えず、Refreshしても意味が無いと思いますが。

    Refresh や Invalidate を実行することで描画が必要になるため、Paint イベントが発生します。
    その Paint の中で何らかの形で入手した描画用データを使用して描画すると良いと思いました。

    以下はその方法(書かれた方法2)でのサンプルコードです。
    ただし実際には、K.Takaoka さんが書かれているように BackgroundWorker 等の利用も必要になりそうに思います。

    // 描画用データの入れ物
    public class DrawData
    {
        public Point Point1 { get; set; }
        public Point Point2 { get; set; }
    }

    // イベント引数用
    public class DrawDataChangedEventArgs : EventArgs
    {
        public DrawDataChangedEventArgs(DrawData data)
        {
            this.Data = data;
        }
        public DrawData Data { get; private set; }
    }

    // 親フォーム
    public partial class Form親 : Form
    {
        ・・・
        private void button1_Click(object sender, EventArgs e)
        {
            for (var i = 10; i <= 100; i += 10)
            {
                var data =
                    new DrawData()
                    {
                        Point1 = new Point(0, 0),
                        Point2 = new Point(i, i),
                    };
                OnDrawDataChanged(data);
                Application.DoEvents();
            }
        }

        private void OnDrawDataChanged(DrawData data)
        {
            if (DrawDataChanged != null)
                DrawDataChanged(this, new DrawDataChangedEventArgs(data));
        }

        public event EventHandler<DrawDataChangedEventArgs> DrawDataChanged;
    }

    // 子フォーム
    public partial class Form子 : Form
    {
        ・・・
        private void cPicBox_Paint(object sender, PaintEventArgs e)
        {
            if (_drawData == null) return;

            // 親からもらったデータを使用して描画
            e.Graphics.DrawLine(Pens.Red, _drawData.Point1, _drawData.Point2);
        }

        private DrawData _drawData;

        private void form親_DrawDataChanged(object sender, DrawDataChangedEventArgs e)
        {
            // どこかに次のようなイベントの割り当てコードがある。
            // form親.DrawDataChanged += form親_DrawDataChanged;

            _drawData = e.Data;
            cPicBox.Refresh();
        }
    }

    • 編集済み TH01 2010年9月30日 5:34 全体的に少しだけ修正
    2010年9月30日 5:12
  • まずは当初の質問についての返信です。
    実際に試しながら考えてみました。

    最初、g を子が受け取って描画させようと思いましたが
    その方法がわかりません。

    例えば g を Graphics 型から Image 型に変換または抽出できればよいのですが、出来ませんでした。
    クサキさんが遭遇された問題のひとつがたぶんこれかなと。

    そこで、Jittaさんの
    「PictureBox の Graphics を取り出して描画するのではなく、別途 Bitmap を作って貼り付ければ、もっと考え方が広がるのではないかと思いました。」
    を参考に、親 → 子の変数を Bitmap にするとうまく出来ます。

    Graphics という言葉から「絵」そのものを連想してしまいますが
    「ペン」とか「筆」や「イーゼル」といった描画手段として捉えるべきでしょう。

    つまり、親のなにかに関連付けられた g を渡されても、
    親にある「絵」そのものは利用できないと。

    ちなみに MSDN ライブラリでは
    「GDI+ 描画サーフェイスをカプセル化します。
    Graphics クラスには、オブジェクトをディスプレイ デバイスに描画するためのメソッドが用意されています。Graphics は、特定のデバイス コンテキストに関連付けられます。」
    といった解説です。
    http://msdn.microsoft.com/ja-jp/library/system.drawing.graphics(VS.80).aspx

    以下、実際に試したコードです。
    for文にするのは面倒だったので
    1秒間隔で3回描画しています。

    ↓親のコード

    
      private void button1_Click(object sender, EventArgs e)
      {
       //Form2 表示
       Form2 form2 = new Form2();
       form2.Show();
    
       Bitmap b = new Bitmap(30, 30); //描画対象のビットマップを作成。
       Graphics g = Graphics.FromImage(b); //描画の手段として g を準備。
    
       g.DrawLine(new Pen(Color.Black), new Point(0, 0), new Point(30, 30)); //対角線描画。
       form2.myPaint(b);
       //form2.myPaint(g); //←うまくいかなかったパターン
    
       System.Threading.Thread.Sleep(1000);
    
       g.DrawLine(new Pen(Color.Black), new Point(0, 30), new Point(30, 0)); //もうひとつ対角線描画。
       form2.myPaint(b);
    
       System.Threading.Thread.Sleep(1000);
    
       g.DrawLine(new Pen(Color.Black), new Point(0, 15), new Point(30, 15)); //中線描画。
       form2.myPaint(b);
    
       g.Dispose();
       b.Dispose();
      }
    

    ↓子のコード
    
      public void myPaint(Bitmap b)
      //public void myPaint(Graphics g) //Graphics で受け取っても描画方法がわからない。
      {
       pictureBox1.Image = b;
    
       pictureBox1.Refresh(); //これが無いと、ディスプレイ上で都度更新されません。
      }
    

    試したのはここまでです。
    あとは仕様なり目的なりによって
    「・方法1: 子のgを親に渡して描画する方法」
    「・方法2: デリゲート、イベントを使って親のデータを子に渡し描画する方法」
    に変えていくのだと思いますが、
    「方法2」のように子画面のことは子画面のコードとして書くのを第一候補に考えます。

    後々、子画面に表示されるものを変えたいとき、
    なるべく子画面のコード変更で済むようにしたいのが理由です。
    2010年9月30日 5:35
  • 【K.Takaokaさん】
    > ・親フォームが長時間にわたってデータの生成作業を行っているループの中から、
    > 作業の現在状態を子フォームでリアルタイムに描画したいという話でしょうか?
    そうです。

    > こういう話になると、使用されているグラブ描画のライブラリが状態を追加しながら描画ができるか、
    > 描画のために親フォームのループが停止していいのか、そもそも親フォームのループは
    > バックグラウンドで行われているのか?といったところが実際のポイントになりそうですね。
    よく分からない部分がありますが、バックグラウンドではありません。

    【trapemiyaさん】
    > 確認させて下さい。実現されたいのは以下のことでしょうか? 子のgを利用して子に
    > 描画を行うが、描画を行うための情報が親にたくさんある。よって、方法1では子のgを
    > 親に渡し、親側で子に描画しようとしている。方法2はたくさんある親の情報を子に渡し、
    > 子側で子に描画をしようとしている
    そうです。

    > 上記がその通りだとして先走ると、方法1は親で子のPaintイベントをサブスクライブすれば済みそうです。
    > ただやっぱり子の描写は子に任せるのがオブジェクト指向だと思いますので、
    > この方法はあまり適切ではないと思います。方法2a)は子のgを一旦親に渡す意味がないので、
    > 現実的なのはb)です。でもこの親から渡す変数が多いのがそもそも問題の始まりなんですよね?

    引数が多いのはデリゲート・イベントを使えますので、
    親の変数を子に渡し、子の関数を動かすこともできます。

    > しかし、親から渡す変数が多くても必要なものは子に渡すというのがオブジェクト指向的には良いと思います。

    私もそう思っています。
    方法2b) 親の変数だけをイベントハンドラーに渡し、gは子の中で何とかする
    ということで、しばらく話題を集中してください。

    ”gは子の中で何とかする”としましたが、何とか出来ないでいます。

    ---で、最初の質問になります。以下、繰り返します。
    イベントを使って、子のフォームのグラフィックを描画関数を動かしたら楽に出来そう
    と考えましたが、Graphics g = e.Graphics; の gの扱い方が良く分かりません。

    // 子のフォームのペイントイベント
    private void cPicBox_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        GraphInit(g);                   // グラフの初期化。あるスケール値で設定されている。
       
        //ehOneSceenDraw(g, abc, ... , xyz);// イベントハンドラーなので、ここで利用するものではない と思います。
                                   // また、gが本体で指定できない。
    }

    本体のイベントに対応したイベントハンドラー①
    void ehOneSceenDraw( Graphics g, int abc,  .....    , int xyz )
    本体のフォームで、引数、Graphics g が渡せない。

    本体のイベントに対応したイベントハンドラー②
    void ehOneSceenDraw( int abc,  .....    , int xyz )
    {
        Graphics g = cPicBox.CreateGraphics(); 
        // ここでのgとPaintイベントでのgは違うようで、スケールなどが違い、描画が上手くできない。
    }

    gのスマートな扱い方、またはこのような場合の上手いやり方を教えてください。
    ---


    > あとはK.Takaokaさんが書かれているようにデリゲート経由で子に親のメソッドを渡しても良いと思います。
    最初のコールバックと書かれているコメントのことですか。
    良く分かりませんで、色々まわりから聞いていました。
    (親側にデリゲート、イベント、イベントの発火を書くやり方は分かるのですが、
     ここの説明が理解できていません)
    ここの説明は、”方法2b) 親の変数だけをイベントハンドラーに渡し、gは子の中で何とかする”
    のことで、gが何とかなっているということですか。
    また、見直してみます。

     

     

    2010年9月30日 6:24
  • > すると話としてはよくある「別のフォームにデータを渡す方法」に関することになりますね。
    > 一般的には EventArgs のような考え方と同様に、
    > ・受渡し用のクラスを作成してそこにセットして渡す。
    > ・DataSet のようなものに入れて渡す。
    > の2つがあると思います

    「別のフォームにデータを渡す方法」はデリゲート、イベントで渡す方が楽なような気がします。
    少なくとも、私がその方法を知っているので、こちらで行いたいと思います。

    ここの説明で、gを使っての具体的にグラフを描画するところの説明はないですよね。

    2010年9月30日 7:17
  • > 少なくとも、私がその方法を知っているので、こちらで行いたいと思います。

    先の私のコードでは、クサキさんのを真似してイベントにしています。

    > ここの説明で、gを使っての具体的にグラフを描画するところの説明はないですよね。

    いえ、私の文章や私のコードで、具体的に書いたつもりです。。。(;_;)
    (追記:私を強調しすぎで、ちょっと印象悪い文章ですね(--;)

    ◇グラフの描画処理を記述する場所について。
    通常の描画は必ず Paint イベントを契機にして行う必要があります。
    つまり、グラフの描画を行う場所は、Paint イベントハンドラになります。
    先の私のコードでは、Paint 内で線を描画しています。

    ◇親からのグラフ描画指示方法について。
    描画を強制的に行わせる方法として Refresh メソッドが用意されています(Invalidate+Update)。
    Refresh さえできれば、Paint の中のコードを実行すること(つまりグラフの描画)ができます。
    先の私のコードでは、その Refresh を、親から描画用データをもらった際に合わせて行っています。

    ◇別の方法について。
    ただおそらく、クサキさんは前述しました「Paint イベントを利用しない方法」がお望みなのかなと思いました。
    この場合、g は Paint イベントでなくても取得できますし、描画処理も任意の場所に記述できます。
    その方法(他の方が書かれている Bitmap を使った一例)の具体例は、先の私のリンク先にありますので、一度ご確認いただければと思います。

    • 編集済み TH01 2010年10月1日 1:59 1行追記
    • 回答としてマーク クサキ 2010年10月4日 9:30
    2010年9月30日 8:07
  • 方法2b) 親の変数だけをイベントハンドラーに渡し、gは子の中で何とかする
    ということで、しばらく話題を集中してください。

    了解しました。何が問題になっているのかようやく見えてきました。(と思います)

    本体のイベントに対応したイベントハンドラー②
    void ehOneSceenDraw( int abc,  .....    , int xyz )
    {
        Graphics g = cPicBox.CreateGraphics(); 
        // ここでのgとPaintイベントでのgは違うようで、スケールなどが違い、描画が上手くできない。
    }

    ここでCreateGraphcs()していますから、gは全く新しい別のGraphicsオブジェクトになります。cPicBox_PaintイベントハンドラでもehOneSceenDrawイベントハンドラでも、取得したPictureBoxのGraphicsオブジェクトに対して毎回1から描画を行なわなければなりません。その度にGraphicsオブジェクトを初期化するのは効率が悪そうなので、一度初期化されたGraphicsオブジェクトを、子クラス内で使いまわすようにすれば良いと思います。

    > あとはK.Takaokaさんが書かれているようにデリゲート経由で子に親のメソッドを渡しても良いと思います。
    最初のコールバックと書かれているコメントのことですか。

    そうです。

    (親側にデリゲート、イベント、イベントの発火を書くやり方は分かるのですが、
     ここの説明が理解できていません)
    ここの説明は、”方法2b) 親の変数だけをイベントハンドラーに渡し、gは子の中で何とかする”
    のことで、gが何とかなっているということですか。

    gが何とかなるとは直接関係ありません。gを何とかするのは上に書いたことで実現できるのではないかと思います。
    デリゲートというのはメソッドを入れるための変数だと思って下さい。例えばint型やstring型などの変数がありますよね。それと同じようにデリゲート型の変数があり、そこにメソッドを入れるのです。メソッドは参照型のような形で入ります。ここでは親のメソッドを入れていますから、子はデリゲートを通して親のそのメソッドにアクセスできるようになります。つまり、子が親のメソッドをコールバックしていることになります。これを頭に入れてK.Takaokaさんのコードをもう一度読んでみて下さい。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク クサキ 2010年10月1日 1:09
    2010年9月30日 16:10
    モデレータ
  • >> 本体のイベントに対応したイベントハンドラー②
    >> void ehOneSceenDraw( int abc,  .....    , int xyz )
    >> {
    >>    Graphics g = cPicBox.CreateGraphics(); 
    >>    // ここでのgとPaintイベントでのgは違うようで、スケールなどが違い、描画が上手くできない。
    >> }

    > ここでCreateGraphcs()していますから、gは全く新しい別のGraphicsオブジェクトになります。
    > cPicBox_PaintイベントハンドラでもehOneSceenDrawイベントハンドラでも、
    > 取得したPictureBoxのGraphicsオブジェクトに対して毎回1から描画を行なわなければ
    > なりません。

    gは全く新しい別のGraphicsオブジェクトのようですので、
    Graphics g = cPicBox.CreateGraphics();の後で 
    Paintイベントのところでやっている座標変換とスケール変換の
    2行を入れるだけで、上手く行くようになりました。

    > その度にGraphicsオブジェクトを初期化するのは効率が悪そうなので、
    > 一度初期化されたGraphicsオブジェクトを、子クラス内で使いまわすようにすれば良いと思います。

    Paintのところのgをここでも使える変数に代入して使うと、例外が発生して止まってしまいます。

    > ここでは親のメソッドを入れていますから、子はデリゲートを通して親のそのメソッドに
    > アクセスできるようになります。つまり、子が親のメソッドをコールバックしていること
    > になります。これを頭に入れてK.Takaokaさんのコードをもう一度読んでみて下さい。

    私には、今回の話においては、親が子の描画メソッドをコールするように思えます。
    それで、親にデリゲートとイベントを書き、for loop の中でイベントを発火させる
    という風にやっていますが、K.Takaokaもさんのコードもtrapemiyaさんも
    子が親のメソッドをコールバックしていると考えられるのですね。
    この辺で私がK.Takaokaさんのコードを理解しずらい原因になっているような気がします。

    K.Takaokaさんのコードをもう一度読むまえに、3つほど質問を。
    1.私は親が子の描画メソッドをコールすると逆向きに考えてしまいますが、
      何が原因で反対になっているのでしょう?
    2.デリゲート・イベントとコールバックは同じものと考えて良いですか?
    3.デリゲート・イベントは親の側でも子の側でも書けるということですか?


    最初の問題は解決しました。
    沢山の方に色々助言を頂きましたが、全部フォーロー出来ず申し訳ありません。
    今後、ご指摘の点少しづつ勉強して行こうと思っています。ありがとうございました。

    2010年10月1日 1:08
  • > 1.私は親が子の描画メソッドをコールすると逆向きに考えてしまいますが、
    >   何が原因で反対になっているのでしょう?

    今回の場合、次の2つの方法がとれるからです。
    どちらでも同じことが実現できます。

    a. 親から子にデータを渡さない場合
    描画コードは親に記述し、子からはそれを呼び出す。
    → K.Takaoka さんの delegate を使用したサンプル。
    → (追記:annningo さんのも現状はこちら。)

    b. 親から子にデータを渡す場合
    描画コードは子に記述する。
    → 私のサンプル。

    いずれの場合も、描画タイミングは親が指定することになりますが、その方法としてクサキさんはイベントを用いられています。
    そのイベントのハンドラでクサキさんは描画も行おうとされていますので、g をどう解決するかという問題が生じていますが、それについては Refresh を呼び出すだけにするか、Paint イベントを利用しない方法(Bitmap への描画)をとるかのどちらかというご提案の話につながります。

    > 2.デリゲート・イベントとコールバックは同じものと考えて良いですか?

    デリゲートとコールバックは同じものと考えていいです(C#では同じです)。
    イベントは、デリゲートを利用した仕組みです。

    > 3.デリゲート・イベントは親の側でも子の側でも書けるということですか?

    親・子というのはアプリケーション仕様上の位置づけにすぎませんが、デリゲートやイベントは言語仕様です。
    言語仕様上は親側・子側の区別なしに記述できますが、どちらに記述が必要かはそのアプリケーション仕様次第です。
    今回の場合、子側にイベントやデリゲートを記述する必要性はそれほどなさそうに思います。

    • 編集済み TH01 2010年10月1日 2:08 追記
    • 回答としてマーク クサキ 2010年10月4日 9:31
    2010年10月1日 1:49
  • 私には、今回の話においては、親が子の描画メソッドをコールするように思えます。
    それで、親にデリゲートとイベントを書き、for loop の中でイベントを発火させる
    という風にやっていますが、K.Takaokaもさんのコードもtrapemiyaさんも
    子が親のメソッドをコールバックしていると考えられるのですね。
    この辺で私がK.Takaokaさんのコードを理解しずらい原因になっているような気がします。

    おそらくクサキさんが書かれているコードとK.Takaokaさんが書かれているコードとは考え方が違っているからだと思います。クサキさんが書かれているコードは、親でイベントを発生させて子のイベントハンドラに必要な情報を渡して描画させているのだと思います。これは親で子のイベントハンドラを実行していることになります。イベントが飛んできて子でイベントハンドラが実行されているように見えるかもしれませんが、親に渡された子のイベントハンドラを親がイベントを発生させるタイミングで実行しています。
    一方、K.Takaokaさんのコードはその逆です。子に渡された親のメソッドを、子が実行しています。クラス的には親から子を呼んでいますが(Callしていますが)、逆に子が親のメソッドを実行するのでCallのBackでCallBackです。

    K.Takaokaさんのコードをもう一度読むまえに、3つほど質問を。
    1.私は親が子の描画メソッドをコールすると逆向きに考えてしまいますが、
      何が原因で反対になっているのでしょう?
    2.デリゲート・イベントとコールバックは同じものと考えて良いですか?
    3.デリゲート・イベントは親の側でも子の側でも書けるということですか?

    1.に関しては上で説明した通りです。

    2.デリゲート・イベントは.NET Frameworkに用意されている仕組みです。明確な定義は無いと思いますが、コールバックはデリゲートを1.のような形態で使用された場合にそのように呼びます。ですから、デリゲートをコールバックとして使わない場合もあります。例えばPredicateデリゲートは、比較してtrueかfalseを判断するメソッドを渡すために使われます。
    イベントをコールバックと表現する人はほとんどいないでしょう。

    3.もちろん書けます。イベントは実行してもらうイベントハンドラを渡すためにデリゲートを利用しています。ご質問されている内容は、親から子に変数を渡せるか?その逆もできるか?と本質的に同じです。親から子にデリゲートを通してメソッドを渡せますし、子から親へもデリゲートを通してメソッドを渡せます。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年10月1日 4:32
    モデレータ