C#フォーム pictureBoxに表示させた画像が描画後すぐに消える

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
nasuten
記事: 2
登録日時: 10年前

C#フォーム pictureBoxに表示させた画像が描画後すぐに消える

#1

投稿記事 by nasuten » 10年前

はじめまして、nasutenと申します。
現在C#を利用してゲームのマップエディターを作成しています。
初心者なので勉強しつつと言った感じで、まだわからないことだらけです。

[1] 質問文
 [1.1] 自分が今行いたい事は何か
pictureBoxの画像を任意のタイミングで切り替えたい

 [1.2] どのように取り組んだか(プログラムコードがある場合記載)
actChipという構造体型の変数に入っている、Bitmap型の画像をピクチャーボックスに表示させています。

コード:

//一番最初の描画はツリービューのノードをクリックしたとき
        //----------------------------
        //  ツリービューのノードをクリックしたら画像を描画
        private void TreeNodeClick(Object sender, TreeNodeMouseClickEventArgs e)
        {
            //最上位ノード以外をクリックした場合
            if (e.Node != topTreeNode)
            {
                //指定したマップをアクティブにする
                mapData.SetActMap(e.Node.Text);//ノード名=マップ名。
                //ピクチャーボックスの画像を選択されたマップの画像に切り替え
                ChangeMapChipBox();
            }
        }
 //チップセット画像の変更関数
        private void ChangeMapChipBox()
        {
           
            //選択されているマップのタイルイメージを取得
            if (mapData != null)
            {
                
                //ピクチャーボックスのグラフィクスを取得
                Graphics g = chipBox.CreateGraphics();
                //ピクチャーボックスに描画
                g.DrawImage(mapData.ActChip.tileImage, 0, 0);
                g.Dispose();
            }
        }
        //----------------------------
        //  ピクチャーボックスの再描画
        private void PictBoxRepaint(Object sender, PaintEventArgs e)
        {

            if (sender == chipBox)
            {
                //chipBox(pictureBox)にチップセット画像を描画
                try
                {
                    ChangeMapChipBox();
                }
                catch
                {
                    MessageBox.Show("error");
                }
                finally
                {
                   
                }
            }
        
        }

 [1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)
該当ピクチャーボックスをスクロールした際は、期待通りの結果(画像が消えない)がでるんですが、フォームを最小化したり
タブを切り替えた後、ピクチャーボックスを再表示すると画像が表示されない。(一瞬表示されてすぐ消える)

 [1.4] 今何がわからないのか、知りたいのか
何が間違ってるのかがわからないです。再描画時に処理を行うようにしていますが、
スクロール時の再描画は問題ないのに、タブ変更やフォームの最小化を行ったときに
このような症状がでるのはなぜでしょうか。
画像
画像左:タブ切り替え前
画像右:infoタブに切り替えた後、mapタブに戻ってきた状況
[2] 環境  
 [2.1] OS : Windows7
 [2.2] コンパイラ名 : VisualStudioExpress2012 for windows Desktop

[3] その他
 ・どの程度C言語を理解しているか
入門書を一回読んだ程度の初心者です
 ・ライブラリを使っている場合は何を使っているか
標準で備わってるもののみ。

お手数おかけしますが、何卒よろしくお願いします。

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: C#フォーム pictureBoxに表示させた画像が描画後すぐに消える

#2

投稿記事 by YuO » 10年前

WinFormsにおいて,コントロールへの描画は基本的にPaintイベントで行います。
CreateGraphicsメソッドの解説にも書いてはあるのですが,Control.CreateGraphicsで作られたGraphicsオブジェクトに対して行った描画は,再描画が必要になっても描画されません (描画できません)。

コントロールへの描画は,コントロールのPaintイベントで行い,描画内容を変更したい場合は対象となるコントロールのRefreshメソッドを呼び出すことでPaintイベントを発生させて行う必要があります。

sleep

Re: C#フォーム pictureBoxに表示させた画像が描画後すぐに消える

#3

投稿記事 by sleep » 10年前

nasuten さんが書きました:  [1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)
該当ピクチャーボックスをスクロールした際は、期待通りの結果(画像が消えない)がでるんですが、フォームを最小化したり
タブを切り替えた後、ピクチャーボックスを再表示すると画像が表示されない。(一瞬表示されてすぐ消える)
事実だけ述べると、DrawImageメソッドで描画後 Formのダイアログ背景色で描画した画像が上書きされてます。
nasuten さんが書きました:  [1.4] 今何がわからないのか、知りたいのか
何が間違ってるのかがわからないです。再描画時に処理を行うようにしていますが、
スクロール時の再描画は問題ないのに、タブ変更やフォームの最小化を行ったときに
このような症状がでるのはなぜでしょうか。
FormのPaintイベント内で、コントロールのPaintイベントで実施すべき描画処理を行っていることが原因です。
起動時、最小化時、タブ遷移時等に描画順序の問題が発生する模様です。

起動時、最小化時、タブ遷移時等の描画順序は以下です。
①BackgroundImageの描画(コントロールの領域を含まないクライアント領域を描画)(※設定されている場合に限る)
②コントロール内からの描画(PictureBoxのPaintイベントからは何も描画していないため、恐らく無効領域のまま)
③DrawImageメソッドの描画(無効領域かどうかに関係なく上書き。①④とは異なるhDCによる描画)
④ダイアログ背景色の描画(残った無効領域を描画。無効領域の取得タイミングは不明。③は無効領域として認識の模様)

DrawImageメソッドの前にMessageBoxを設置すると、上記の描画順序が以下に変わります。
①BackgroundImageの描画(コントロールの領域を含まないクライアント領域を描画)(※設定されている場合に限る)
②コントロール内からの描画(PictureBoxのPaintイベントからは何も描画していないため、恐らく無効領域のまま)
③ダイアログ背景色の描画(残った無効領域を描画。無効領域の取得タイミングは不明)
④DrawImageメソッドの描画(無効領域かどうかに関係なく上書き。①③とは異なるhDCによる描画)

BackgroundImageとダイアログ背景色の描画は、FormのPaintイベント(OnPaint含む)とは別に実施されてます。


以下は上記の描画順序の問題を確認するコードです。(③と④の順序)
今回の問題は、FormのPaintイベントから描画することをやめ、PictureBoxのPaintイベントから描画する形式に変更することで解決できます。

コード:

//「空のプロジェクト」作成後
//プロジェクトの「参照の追加(R)」から以下を参照設定に追加
//System
//System.Drawing
//System.Windows.Forms

using System;
using System.Net;
using System.Windows.Forms;
using System.Drawing;

class MyForm : Form
{
    private PictureBox pbox = new PictureBox() { Width = 250, Height = 200 };
    private Bitmap image = new Bitmap(new WebClient().OpenRead(@"http://kabegami1.up.seesaa.net/image/wallpaper_345.jpg"));

    public MyForm()
    {
        this.BackgroundImage = image;  //背景
        this.Paint += MyForm_Paint;
        this.Controls.Add(pbox);

        pbox.Paint += pbox_Paint;
    }

    private void MyForm_Paint(object sender, PaintEventArgs e)
    {
        //MessageBox.Show("MyForm_Paint");  //DrawImageより前で止めると、①ダイアログ背景色、②画像の順で描画される。
        Graphics g = pbox.CreateGraphics();
        g.DrawImage(image, 0, 0);
        g.Dispose();
        //MessageBox.Show("MyForm_Paint");  //DrawImageより後で止めると、①画像、②ダイアログ背景色の順で描画される。
    }

    private void pbox_Paint(object sender, PaintEventArgs e)
    {
        //e.Graphics.DrawImage(image, 0, 0);  //FormのPaintイベントでの描画をやめ、PictureBoxのPaintイベントで描画する。(※Form背景および上記2つのMessageBoxとDrawImageはコメントアウトすること)
    }
}

class Class1
{
    static void Main()
    {
        new MyForm().ShowDialog();
    }
}
しかし、これらを自分で実装する必要はなく、
実はPictureBoxのImageプロパティに画像を設定するだけで同様の動作となります。
(Imageプロパティに設定された画像を基に、PictureBoxのOnPaint(のデフォルト実装)で同等のことが行われています)

コード:

using System;
using System.Net;
using System.Windows.Forms;
using System.Drawing;

class MyForm : Form
{
    private PictureBox pbox = new PictureBox() { Width = 250, Height = 200 };
    private Bitmap image = new Bitmap(new WebClient().OpenRead(@"http://kabegami1.up.seesaa.net/image/wallpaper_345.jpg"));

    public MyForm()
    {
        this.Paint += MyForm_Paint;
        this.Controls.Add(pbox);
    }

    private void MyForm_Paint(object sender, PaintEventArgs e)
    {
        pbox.Image = image;
    }
}

class Class1
{
    static void Main()
    {
        new MyForm().ShowDialog();
    }
}

nasuten
記事: 2
登録日時: 10年前

Re: C#フォーム pictureBoxに表示させた画像が描画後すぐに消える

#4

投稿記事 by nasuten » 10年前

YuOさん、sleepさん、回答ありがとうございました。
お二人方がおっしゃるとおり、処理をpictureBoxのペイントイベントで行ったところ、期待通りの結果がでました。
以下のようにコード変更しました

コード:

        //----------------------------
        //  ツリーノードクリック
        private void TreeNodeClick(Object sender, TreeNodeMouseClickEventArgs e)
        {
            //最初の描画は最上位ノード以外をクリックした場合
            if (e.Node != topTreeNode)
            {
                //指定したマップをアクティブにする
                mapData.SetActMap(e.Node.Text);//ノード名=マップ名
                ////ピクチャーボックスを再描画
                chipBox.Refresh();
            }
        }
        //----------------------------
        //  ピクチャーボックスのペイントイベント
        private void PictBoxRepaint(Object sender, PaintEventArgs e)
        {
            if (sender == chipBox)
            {
                    //アクティブなマップのタイルイメージを取得
                    if (mapData != null)//マップデータがあるかどうか
                    {
                        if (mapData.ActChip.tileImage != null)//マップの画像があるかどうか
                        {
                            //画像の描画
                            e.Graphics.DrawImage(mapData.ActChip.tileImage, 0, 0);
                            e.Dispose();
                        }
                    }
            }
        }
お忙しい中詳しく回答していただき、ありがとうございました!

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: C#フォーム pictureBoxに表示させた画像が描画後すぐに消える

#5

投稿記事 by YuO » 10年前

Paintイベントの引数であるeやe.GraphicsをDisposeしてはいけません。

Paintイベントは複数登録することが可能です。
しかし,最初に実行されたPaintのイベントハンドラでeがDisposeされた場合,その後に呼び出されるイベントハンドラ中でeを使おうとすると,ObjectDisposedExceptionが発生するかもしれません。
eやe.Graphicsなどは呼び出し側が所有権を持っているので,破棄する作業は所有権を持っている呼び出し側が行う必要があり,呼び出された側が勝手に破棄してはいけないものです。

閉鎖

“C言語何でも質問掲示板” へ戻る