いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Control.Invalidateについて (System.Windows.Forms.Control, Invalidate, Control.Update)


よく、カスタムコントロールの作成などにて、コントロールの再描画を頻繁に行ったりすることが
あります。その場合、Control.Invalidateメソッドをコールする事になるのですが、このメソッドを
利用する場合は一点注意が必要です。

短い時間間隔で連続して発行されたInvalidateの要求は一つに纏められる。


これは、javaのswingの場合と同じです。

以下に、サンプルを記述します。
このサンプルは、ボタンが押下されると一定間隔で少しずつ四角形を大きく描画していく
想定で作成されているとします。

// vim:set ts=4 sw=4 et ws is nowrap ft=cs:
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace Gsf.Demo{

    public class DemoForm : Form{

        const int MAX_LOOP_COUNT = 500;

        protected Button btnA;
        protected Button btnB;

        public DemoForm(){

            InitializeComponent();

            MethodInvoker enableChange = () => {
                btnA.Enabled = !btnA.Enabled;
                btnB.Enabled = !btnB.Enabled;
            };

            int size = 0;
            btnA.Click += (s, e) => {

                for(int i = 0; i < MAX_LOOP_COUNT; i++){
                    size = i;
                    Invalidate();
                }

                enableChange();
            };

            btnB.Click += (s, e) => {

                for(int i = 0; i < MAX_LOOP_COUNT; i++){
                    size -= 1;
                    Invalidate();
                }

                enableChange();
            };

            Paint += (s, e) => {

                using(Pen rectPen = new Pen(Color.Blue, 15)){
                    e.Graphics.DrawRectangle(rectPen, new Rectangle(new Point(0, 50), new Size(size, size)));
                }

                Thread.Sleep(10);
            };
        }

        protected void InitializeComponent(){

            SuspendLayout();

            Width      = 700;
            Height     = 600;

            btnA       = new Button();
            btnA.Text  = "paint rect";
            btnA.Width = 100;
            btnA.Location = new Point(0, 0);

            btnB          = new Button();
            btnB.Text     = "rewind";
            btnB.Width    = 100;
            btnB.Enabled  = false;
            btnB.Location = new Point(110, 0);

            Controls.Add(btnA);
            Controls.Add(btnB);

            ResumeLayout();
        }


        [STAThread]
        static void Main(){
            Application.EnableVisualStyles();
            Application.Run(new DemoForm());
        }
    }
}


で、実際このプログラムを動作させてみると、一気に最終地点の大きさの
四角形が表示され、一気にその四角形が消えたりします。

これは、ループ内で呼んでいるInvalidateが一つに纏められて最後の描画のみ
が行われているからです。


これを、一回ずつ描画してくようにするには、Invalidateの呼び出し部分を
以下のようにします。

Invalidate();
Update();

Control.Update()は、無効化された領域をリフレッシュし
描画が完了するまで待ちますのでこれを仕込むと
上記のサンプルの場合は想定通りの動きをするようにします。

// vim:set ts=4 sw=4 et ws is nowrap ft=cs:
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace Gsf.Demo{

    public class DemoForm : Form{

        const int MAX_LOOP_COUNT = 500;

        protected Button btnA;
        protected Button btnB;

        public DemoForm(){

            InitializeComponent();

            MethodInvoker enableChange = () => {
                btnA.Enabled = !btnA.Enabled;
                btnB.Enabled = !btnB.Enabled;
            };

            int size = 0;
            btnA.Click += (s, e) => {

                for(int i = 0; i < MAX_LOOP_COUNT; i++){
                    size = i;
                    Invalidate();
                    Update();
                }

                enableChange();
            };

            btnB.Click += (s, e) => {

                for(int i = 0; i < MAX_LOOP_COUNT; i++){
                    size -= 1;
                    Invalidate();
                    Update();
                }

                enableChange();
            };

            Paint += (s, e) => {

                using(Pen rectPen = new Pen(Color.Blue, 15)){
                    e.Graphics.DrawRectangle(rectPen, new Rectangle(new Point(0, 50), new Size(size, size)));
                }

                Thread.Sleep(10);
            };
        }

        protected void InitializeComponent(){

            SuspendLayout();

            Width      = 700;
            Height     = 600;

            btnA       = new Button();
            btnA.Text  = "paint rect";
            btnA.Width = 100;
            btnA.Location = new Point(0, 0);

            btnB          = new Button();
            btnB.Text     = "rewind";
            btnB.Width    = 100;
            btnB.Enabled  = false;
            btnB.Location = new Point(110, 0);

            Controls.Add(btnA);
            Controls.Add(btnB);

            ResumeLayout();
        }


        [STAThread]
        static void Main(){
            Application.EnableVisualStyles();
            Application.Run(new DemoForm());
        }
    }
}


上のプログラムを動作させると今度は四角形がじょじょに大きくなり
また、じょじょに小さくなっていきます。


尚、Control.Refresh()というメソッドもあり、これは内部で

Invalidate(true);
Update();

としているのと同じ事です。
Invalidateメソッドに渡しているboolの引数はtrueを渡すと
そのコントロールの配下のコントロール全てを無効化すると
いう意味になります。デフォルトはfalseです。