none
System.Windows.Forms.GroupBoxのOnPaint + ErrorProviderでごみが描写されてしまう。 RRS feed

  • 質問

  • お世話になります。
    GroupBoxのOnPaintをオーバーライドして描画したところ、ErrorProviderの!アイコンの下にゴミが出てしまうようになりました。
    ErrorProviderの動作がよくわからないため、問題の切り分けができないでいます。

    ○実現したいこと:

    • GroupBoxの枠線を指定色にしたい。

    ○実行したこと:

    • GroupBoxを継承し、OnPaintで下記のコードを記述。

    ○問題点:

    • ErrorProviderのSetErrorメソッドを実行すると!マークが点滅している背後にゴミが映り込んでしまう。(描画のミス?)

    ○詳細:

    Formには以下のデザインをしています。

    • GroupBoxExを貼り付け
    • GroupBoxEx内にtextBox1を貼り付け
    • FormにErrorProviderを貼り付け

    ○開発環境:

    VS2010SP1 & C# 4.0 .NET 4.0, Windows.Forms

    以上、どなたか分かる方がいらっしゃいましたらご教授願います。

        class GroupBoxEx : GroupBox
        {
            protected override void OnPaint(PaintEventArgs e)
            {
                var size = TextRenderer.MeasureText(Text, Font);
                var br = e.ClipRectangle;
                br.Y += size.Height / 2;
                br.Height -= size.Height / 2;
                ControlPaint.DrawBorder(e.Graphics, br, Color.Gray, ButtonBorderStyle.Solid);
                var tr = e.ClipRectangle;
                tr.X += 6;
                tr.Width = size.Width;
                tr.Height = size.Height;
                e.Graphics.FillRectangle(new SolidBrush(BackColor), tr);
                e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), tr);
            }
        }
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
            private void Form1_Load(object sender, EventArgs e)
            {
                errorProvider1.SetError(textBox1, "エラー");
            }
        }
    

    2014年1月6日 6:17

回答

  • e.ClipRectangleは「再描画しなければならない領域」であり、それ以外の領域は描画されないように除外処理(Clip)されます。
    GroupBoxの右下の一部だけを他のウィンドウで覆い隠してから、そのウィンドウを消すと間違った処理をしていることがよくわかるはずです。

    GroupBox内でアイコンが点滅する場合、アイコン部分だけが再描画の対象になるのに、OnPaintでの計算で原点をClipRectangleから算出すると、アイコンの左上を(0,0)としてアイコンの幅と高さの領域だけが描画されてしまいます。
    そのため、アイコンが消えているときには本来GroupBoxの左上に描画されるはずの枠線とTextが描画されてしまうことになり、ゴミがあるように見えることになります。

    正しくは以下のようにするとよいでしょう。

    class GroupBoxEx : GroupBox
    {
        protected override void OnPaint(PaintEventArgs e)
        {
            var textSize = TextRenderer.MeasureText(Text, Font);
    
            //描画するときはこのGroupBoxの左上の点が(0,0)である
            int borderTop = textSize.Height / 2;
            int borderHeight = Math.Max(0, this.Height - textSize.Height / 2);
    
            //幅を超えるようなTextの場合に、はみ出ないようにする
            textSize.Width = Math.Max(0, Math.Min(textSize.Width, this.Width - 12));
    
            if ( textSize.Height>this.Height-1)
            {//高さが低くなったときに下線が消えないように
                textSize.Height = this.Height - 1;
            }
    
            Rectangle borderRectangle = new Rectangle(0, borderTop, this.Width, borderHeight);
            Rectangle textRectangle = new Rectangle(6, 0, textSize.Width, textSize.Height);
    
            ControlPaint.DrawBorder(e.Graphics, borderRectangle, Color.Gray, ButtonBorderStyle.Solid);
            e.Graphics.FillRectangle(new SolidBrush(BackColor), textRectangle);
            e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), textRectangle);
        }
    }


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク taritari 2014年1月6日 23:54
    2014年1月6日 9:39

すべての返信

  • e.ClipRectangleは「再描画しなければならない領域」であり、それ以外の領域は描画されないように除外処理(Clip)されます。
    GroupBoxの右下の一部だけを他のウィンドウで覆い隠してから、そのウィンドウを消すと間違った処理をしていることがよくわかるはずです。

    GroupBox内でアイコンが点滅する場合、アイコン部分だけが再描画の対象になるのに、OnPaintでの計算で原点をClipRectangleから算出すると、アイコンの左上を(0,0)としてアイコンの幅と高さの領域だけが描画されてしまいます。
    そのため、アイコンが消えているときには本来GroupBoxの左上に描画されるはずの枠線とTextが描画されてしまうことになり、ゴミがあるように見えることになります。

    正しくは以下のようにするとよいでしょう。

    class GroupBoxEx : GroupBox
    {
        protected override void OnPaint(PaintEventArgs e)
        {
            var textSize = TextRenderer.MeasureText(Text, Font);
    
            //描画するときはこのGroupBoxの左上の点が(0,0)である
            int borderTop = textSize.Height / 2;
            int borderHeight = Math.Max(0, this.Height - textSize.Height / 2);
    
            //幅を超えるようなTextの場合に、はみ出ないようにする
            textSize.Width = Math.Max(0, Math.Min(textSize.Width, this.Width - 12));
    
            if ( textSize.Height>this.Height-1)
            {//高さが低くなったときに下線が消えないように
                textSize.Height = this.Height - 1;
            }
    
            Rectangle borderRectangle = new Rectangle(0, borderTop, this.Width, borderHeight);
            Rectangle textRectangle = new Rectangle(6, 0, textSize.Width, textSize.Height);
    
            ControlPaint.DrawBorder(e.Graphics, borderRectangle, Color.Gray, ButtonBorderStyle.Solid);
            e.Graphics.FillRectangle(new SolidBrush(BackColor), textRectangle);
            e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), textRectangle);
        }
    }


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク taritari 2014年1月6日 23:54
    2014年1月6日 9:39
  • gekka 様、返信ありがとうございます。わかりやすいサンプルと解説ありがとうございました。
    おかげさまでとても助かりました。
    e.ClipRectangleを誤解していました。
    問題のプログラムでは、GroupBox左上の文字らしきものが描画されているのも道理というわけですね。

    ありがとうございました。

    2014年1月6日 23:54