プロが教える店舗&オフィスのセキュリティ対策術

C#のGraphicsクラスを用いて画像をフェードインで表示させよとしています。
まず以下のコードをごらんください。
ただ、フェードインで画像は表示されるものの、フォームの操作が一切できなくなってしまいます。
そのためマルチスレッドにしようとしたのですが
using System;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Web;
using System.Net;
using System.Text;
using System.Threading;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Diagnostics;


public class MainClass{

public static void Main(string [] args){
NewForm formObj = new NewForm();
Application .Run(formObj);
}
}

public class NewForm : Form {

//インスタンス変数の宣言
public Graphics g ;
public Bitmap mapObj ;
public Image imageObj;
public ImageAttributes ia;
public ColorMatrix cm;
public Thread th ;
public ParameterizedThreadStart ts;
public PaintEventArgs e;
public Rectangle rec;
public int flag = 0;

public delegate void TestDelegate();
public TestDelegate deleObj;

public NewForm(){
Button buttonObj = new Button();
buttonObj.Width=100;
buttonObj.Height = 30;

//フェードさせるためのイベント発行用ボタンの設置
buttonObj.Click += new EventHandler(this.SetMethod);
this.Controls.Add(buttonObj);
}
public void SetMethod(object sender , EventArgs e){

this.Paint += new PaintEventHandler(this.ThreadMethod);
//フォームコントロールの再描画を促す
this.Invalidate();

}
public void ThreadMethod(object sender ,PaintEventArgs eventObj){
this.ts = new ParameterizedThreadStart(this.ThreadRenderMethod);
this.th = new Thread(this.ts);
this.th.Start(eventObj);

MessageBox.Show("ThreadMethod実行後");
MessageBox.Show(InvokeRequired.ToString());
this.th.Join();
}
public void ThreadRenderMethod(object paintObj){
MessageBox.Show(InvokeRequired.ToString());
this.deleObj =delegate(){
//無現ループしてしまうので、再描画イベント後イベントハンドラーを削除
this.Paint -= new PaintEventHandler(this.ThreadMethod);


PaintEventArgs e = (PaintEventArgs)paintObj;
try{
Console.WriteLine("paint メソッド発生");
this. g = e.Graphics;
this.mapObj = new Bitmap(this.Width,this.Height);
this.imageObj = Image.FromFile("C:\\c#\\test.jpg");
//this.g = Graphics.FromImage(this.mapObj);
this.cm = new ColorMatrix();
this.cm.Matrix00 = 1;
this.cm.Matrix11 = 1;
this.cm.Matrix22 = 1;
this.cm.Matrix33 = 0.0F;
this.cm.Matrix44 = 1;
this.ia = new ImageAttributes();
this.ia.SetColorMatrix(this.cm);
this.rec = new Rectangle(0, 0, this.Width, this.Height);
this.g.DrawImage(this.imageObj,rec,0,0,this.imageObj.Width,imageObj.Height,GraphicsUnit.Pixel,this.ia);
this.BackgroundImage = mapObj;

for(double i = 0.0; i <= 1.0; i = i + 0.001){
this.cm.Matrix33 = (float) i;
this.ia.SetColorMatrix(this.cm);
this.g.DrawImage(this.imageObj,rec,0,0,this.imageObj.Width,imageObj.Height,GraphicsUnit.Pixel,this.ia);
this.BackgroundImage = this.mapObj;
Thread.Sleep(100);
}
//this.imageObj.Dispose();
//this.g.Dispose();

}catch(Exception ex){
Console.WriteLine(ex.ToString());
}
Console.WriteLine("paint end");
};
this.deleObj();
//this.Invoke(this.deleObj);
this.BackgroundImage = Image.FromFile("C:\\c#\\test.jpg");
}
}

上記コードでも、フェードは動作するものの、やはりフォームの操作ができなくなります。
どう対処したらよいでしょうか?
識者の方、よろしくご教授ください
お願いいたします

A 回答 (3件)

> ただ今回はタイマーで定期的に描画させましたが


> これはやはり別スレッドよりタイマーのほうがいいのでしょうかね?

長時間処理には,タイマーが向く用途とスレッドが向く用途があります。
・UIの変化に「時間」というファクターがあるものは,タイマーが向く (非UIスレッドはUIを変化させることが出来ないから)
・UIとは関係なく処理に時間がかかるのであれば,スレッドが向く (タイマーは無駄に処理を細切れにするだけ。さらに,非CPU原因で止まる可能性がある)

今回のフェードインやフェードアウトは,まさしく「UIの変化に時間というファクターがあるもの」なので,タイマーが向きます。
逆に,例えばファイルの圧縮などの処理はUIとは関係ないのでスレッドが向きます (無理をすればタイマーでもできなくないですが……)。
ネットワーク処理などは,
・非同期I/O (HttpWebRequest.BeginGetResponse/EndGetResponseなど) を使う
・スレッドを用意して同期処理する
などができますが,タイマーを使うことはできません (システムメソッドの呼び出し自体に時間がかかっているから)。

もちろん,両方を混ぜることもできます。
アニメーションなどは,UI自体はタイマーで描画させて,裏のスレッドで次々とフレームを生成しておく,などの方法がとれるでしょう。


> ただ、謎なのが、Grahpicsオブジェクトを this.CreateGraphics();
> という形で生成しているんですよね・・・・。

作りの問題ですね。
CreateGraphicsはあまり使うことがないと思いますが……。

・ボタンを押したとき (=初期設定)
1. ボタンのEnabledをfalseにする
2. RenderingMethodの
> this.imageObj = Image.FromFile("C:\\c#\\test.jpg");
から
> this.ia.SetColorMatrix(this.cm);
を実行する。ただし,
> this.cm.Matrix33 = this.splitTime;
は不要。
# this.Width/this.Heightよりもthis.ClientSize.Widthとthis.ClientSize.Heightの方がやりたいことのような気がしますが……。
3. タイマーを起動する (Enabled = true)
4. this.Refreshで再描画

・Timer.Tickイベント (=時間経過に伴う処理)
1. this.splitTimeの再設定
2. this.splitTimeを確認して,必要時間が経過していたらタイマーのEnabledをfalseにしてタイマーを止めて,ボタンのEnabledをtrueにして,イベントハンドラから出る
3. this.cm.Matrix33の再設定
4. this.Refreshで再描画

・Paintイベント (=描画のみを行う)
1. 画像が用意されていれば,描画する

と,役割分担をちゃんとすれば,おかしなことにはなりません。
# タイマーイベントでCreateGraphics呼び出して直接描画しても良いですが,これだと別ウィンドウに隠れた後に再度表示された場合などの再描画がなされません。
    • good
    • 0
この回答へのお礼

いろいろとありがとうございます。
いただいた回答によるとまだかなり、要改善のようなので
もうすこしブラッシュアップしてみます。

お礼日時:2012/10/28 22:12

ソースが難読でしたので想像で。



Paint でInvalidate を呼んだら、再帰無限ループとはいかなくても再描画(WM_PAINT) の嵐になりそうですね。

それに・・・マルチスレッドになってないような。デリゲート無しにバックグランドスレッドから
this.BackgroundImage = this.mapObj;
を行えば、普通は例外になるはず(たぶん)。

ちゃんとバックグラウンドスレッドでフェードアウト処理を行い、デリゲートでもってメインスレッドで
this.BackgroundImage = this.mapObj;
を呼べばいいと思います。そこでInvalidate も呼ぶと。

非常にトリッキーな操作でデッドロックしちゃってるみたいですね。
    • good
    • 0
この回答へのお礼

/*********************************************

画像をフェードインさせるための処理

*********************************************/
using System;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Web;
using System.Net;
using System.Text;
using System.Threading;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Diagnostics;


public class MainClass{

public static void Main(string [] args){
NewForm formObj = new NewForm();
Application .Run(formObj);
}
}



public class NewForm : Form {

//インスタンス変数の宣言
public Graphics g ;
public Bitmap mapObj ;
public Image imageObj;
public ImageAttributes ia;
public ColorMatrix cm;
public Thread th ;
public ParameterizedThreadStart ts;
public PaintEventArgs eventE;
public Rectangle rec;
public int flag = 0;
public float splitTime = 0.0F;

//delegate
public delegate void TestDelegate();
public TestDelegate deleObj;

//指定秒間ごとにフェードさせるためのタイマーオブジェクト
public System.Windows.Forms.Timer timerObj ;

public NewForm(){
Button buttonObj = new Button();
buttonObj.Width=100;
buttonObj.Height = 30;

//フェードさせるためのイベント発行用ボタンの設置
buttonObj.Click += new EventHandler(this.SetMethod);
this.Controls.Add(buttonObj);

//タイマーオブジェクトの作成
this.timerObj = new System.Windows.Forms.Timer();
}
public void SetMethod(object sender , EventArgs e){
this.g = null;
this.splitTime = 0.0F;
this.Paint += new PaintEventHandler(this.SetTimerEventMthod);
//フォームコントロールの再描画を促す
this.Invalidate();
Console.WriteLine("SetMethod終了");

}

//描画が開始されたらセットされるタイマーイベント
public void SetTimerEventMthod(Object sender,PaintEventArgs e){
Console.WriteLine("SetTimerEventMthod開始");
this.Paint -= new PaintEventHandler(this.SetTimerEventMthod);
this.eventE =e;

//タイマーイベントの作成

this.timerObj.Tick += new EventHandler(this.RenderingMethod);
this.timerObj.Interval =100;
this.timerObj.Start();
Console.WriteLine("SetTimerEventMthod終了");
}

public void RenderingMethod(Object sender,EventArgs e){
Console.WriteLine("RenderingMethod開始");
try{
Console.WriteLine("paint メソッド発生");
this. g = this.CreateGraphics();
Console.WriteLine("gオブジェクト取得");
this.mapObj = new Bitmap(this.Width,this.Height);
this.imageObj = Image.FromFile("C:\\c#\\test.jpg");
this.cm = new ColorMatrix();
this.cm.Matrix00 = 1;
this.cm.Matrix11 = 1;
this.cm.Matrix22 = 1;
this.cm.Matrix33 = 0.0F;
this.cm.Matrix44 = 1;
this.ia = new ImageAttributes();

this.cm.Matrix33 = this.splitTime;
this.ia.SetColorMatrix(this.cm);
this.rec = new Rectangle(0, 0, this.Width, this.Height);
this.g.DrawImage(this.imageObj,rec,0,0,this.imageObj.Width,imageObj.Height,GraphicsUnit.Pixel,this.ia);
Console.WriteLine("最初描画終了");
this.splitTime += 0.001F;

Console.WriteLine(this.splitTime);
if(this.splitTime >1.0F){
this.timerObj.Tick -= new EventHandler(this.RenderingMethod);
this.timerObj.Stop();
}


}catch(Exception ex){
Console.WriteLine(ex.ToString());
Console.WriteLine(ex.StackTrace);
}
Console.WriteLine("paint end");

}
}

ただ、謎なのが、Grahpicsオブジェクトを this.CreateGraphics();
という形で生成しているんですよね・・・・。
PaintEventArgs e の
Graphics g = e.Graphics;
というペイントイベント引数から生成するのと違いがわからないんですよね:・・・・




・・・なぞです。

お礼日時:2012/10/28 15:29

UIスレッドでJoinしてはいけません。



UIスレッドは,Workerスレッドに「何かやれ」とだけ伝えて,そのまま処理を終えるのが基本的な作法です。
さらに,UIスレッド以外からUIを操作してはいけません。

今回の場合,単にタイマーを使って100msごとに再描画させるだけで十分でしょう。
System.Windows.Forms.TimerはUIスレッドで動作しますから,クロススレッド呼び出しの問題も出てきません。


なお,スレッドを使うならばせめてasync/awaitを使うか,
TaskやBackgroundWorker,Delegate.BeginInvokeといったワーカースレッドを利用する物を使った方がよいでしょう。
Threadクラス自体を使ってスレッドを作成する必要は,.NET Framework 1.0の時代からほとんど存在しません。
    • good
    • 0
この回答へのお礼

ありがとうございます。
/*********************************************

画像をフェードインさせるための処理

*********************************************/
using System;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Web;
using System.Net;
using System.Text;
using System.Threading;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Diagnostics;


public class MainClass{

public static void Main(string [] args){
NewForm formObj = new NewForm();
Application .Run(formObj);
}
}



public class NewForm : Form {

//インスタンス変数の宣言
public Graphics g ;
public Bitmap mapObj ;
public Image imageObj;
public ImageAttributes ia;
public ColorMatrix cm;
public Thread th ;
public ParameterizedThreadStart ts;
public PaintEventArgs eventE;
public Rectangle rec;
public int flag = 0;
public float splitTime = 0.0F;

//delegate
public delegate void TestDelegate();
public TestDelegate deleObj;

//指定秒間ごとにフェードさせるためのタイマーオブジェクト
public System.Windows.Forms.Timer timerObj ;

public NewForm(){
Button buttonObj = new Button();
buttonObj.Width=100;
buttonObj.Height = 30;

//フェードさせるためのイベント発行用ボタンの設置
buttonObj.Click += new EventHandler(this.SetMethod);
this.Controls.Add(buttonObj);

//タイマーオブジェクトの作成
this.timerObj = new System.Windows.Forms.Timer();
}
public void SetMethod(object sender , EventArgs e){
this.g = null;
this.splitTime = 0.0F;
this.Paint += new PaintEventHandler(this.SetTimerEventMthod);
//フォームコントロールの再描画を促す
this.Invalidate();
Console.WriteLine("SetMethod終了");

}

//描画が開始されたらセットされるタイマーイベント
public void SetTimerEventMthod(Object sender,PaintEventArgs e){
Console.WriteLine("SetTimerEventMthod開始");
this.Paint -= new PaintEventHandler(this.SetTimerEventMthod);
this.eventE =e;

//タイマーイベントの作成

this.timerObj.Tick += new EventHandler(this.RenderingMethod);
this.timerObj.Interval =100;
this.timerObj.Start();
Console.WriteLine("SetTimerEventMthod終了");
}

public void RenderingMethod(Object sender,EventArgs e){
Console.WriteLine("RenderingMethod開始");
try{
Console.WriteLine("paint メソッド発生");
this. g = this.CreateGraphics();
Console.WriteLine("gオブジェクト取得");
this.mapObj = new Bitmap(this.Width,this.Height);
this.imageObj = Image.FromFile("C:\\c#\\test.jpg");
this.cm = new ColorMatrix();
this.cm.Matrix00 = 1;
this.cm.Matrix11 = 1;
this.cm.Matrix22 = 1;
this.cm.Matrix33 = 0.0F;
this.cm.Matrix44 = 1;
this.ia = new ImageAttributes();

this.cm.Matrix33 = this.splitTime;
this.ia.SetColorMatrix(this.cm);
this.rec = new Rectangle(0, 0, this.Width, this.Height);
this.g.DrawImage(this.imageObj,rec,0,0,this.imageObj.Width,imageObj.Height,GraphicsUnit.Pixel,this.ia);
Console.WriteLine("最初描画終了");
this.splitTime += 0.001F;

Console.WriteLine(this.splitTime);
if(this.splitTime >1.0F){
this.timerObj.Tick -= new EventHandler(this.RenderingMethod);
this.timerObj.Stop();
}


}catch(Exception ex){
Console.WriteLine(ex.ToString());
Console.WriteLine(ex.StackTrace);
}
Console.WriteLine("paint end");

}
}
とりあえず上記のようなコードでフェードイン画像がうごきました。
ただ今回はタイマーで定期的に描画させましたが

これはやはり別スレッドよりタイマーのほうがいいのでしょうかね?
さしあたり、これをある程度クラス化してちょっとしたゲーム・・・・つまりはかまいたちの夜的なサウンドノベルゲーム用の
背景として流用したいのですが。

お礼日時:2012/10/28 15:36

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!