- ベストアンサー
C#のGraphicsクラスを使用して画像をフェードインで表示する方法
- C#のGraphicsクラスを使用して画像をフェードインで表示する方法について説明します。
- フォームの操作ができなくなる問題を解決するために、マルチスレッドを使用します。
- フェードインのアニメーションを行うために、ColorMatrixとImageAttributesを使用し、画像の透明度を変化させます。
- みんなの回答 (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呼び出して直接描画しても良いですが,これだと別ウィンドウに隠れた後に再度表示された場合などの再描画がなされません。
その他の回答 (2)
ソースが難読でしたので想像で。 Paint でInvalidate を呼んだら、再帰無限ループとはいかなくても再描画(WM_PAINT) の嵐になりそうですね。 それに・・・マルチスレッドになってないような。デリゲート無しにバックグランドスレッドから this.BackgroundImage = this.mapObj; を行えば、普通は例外になるはず(たぶん)。 ちゃんとバックグラウンドスレッドでフェードアウト処理を行い、デリゲートでもってメインスレッドで this.BackgroundImage = this.mapObj; を呼べばいいと思います。そこでInvalidate も呼ぶと。 非常にトリッキーな操作でデッドロックしちゃってるみたいですね。
お礼
/********************************************* 画像をフェードインさせるための処理 *********************************************/ 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; というペイントイベント引数から生成するのと違いがわからないんですよね:・・・・ ・・・なぞです。
- Yune-Kichi
- ベストアンサー率74% (465/626)
UIスレッドでJoinしてはいけません。 UIスレッドは,Workerスレッドに「何かやれ」とだけ伝えて,そのまま処理を終えるのが基本的な作法です。 さらに,UIスレッド以外からUIを操作してはいけません。 今回の場合,単にタイマーを使って100msごとに再描画させるだけで十分でしょう。 System.Windows.Forms.TimerはUIスレッドで動作しますから,クロススレッド呼び出しの問題も出てきません。 なお,スレッドを使うならばせめてasync/awaitを使うか, TaskやBackgroundWorker,Delegate.BeginInvokeといったワーカースレッドを利用する物を使った方がよいでしょう。 Threadクラス自体を使ってスレッドを作成する必要は,.NET Framework 1.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"); } } とりあえず上記のようなコードでフェードイン画像がうごきました。 ただ今回はタイマーで定期的に描画させましたが これはやはり別スレッドよりタイマーのほうがいいのでしょうかね? さしあたり、これをある程度クラス化してちょっとしたゲーム・・・・つまりはかまいたちの夜的なサウンドノベルゲーム用の 背景として流用したいのですが。
お礼
いろいろとありがとうございます。 いただいた回答によるとまだかなり、要改善のようなので もうすこしブラッシュアップしてみます。