Web検索はbingがおすすめ!

Paintイベントで描画が必要な領域を取得する - C#

Paintイベントで描画が必要な領域を取得するコードを紹介します。

概要

こちらの記事では、Invalidateメソッドを利用して、コントロールの領域の一部を再描画するコードを紹介しました。 Invalidateメソッドで領域を指定した場合、Paintイベントでは領域のクリップがされるため、全面を描画しても、 再描画が必要な部分のみが描画されます。
描画処理が軽い処理の場合は、全面を描画する方法で問題ないですが、描画処理が重い場合は、再描画が必要な部分のみを Paintイベントで描画したい動作にする必要があります。この記事では、Paintイベント内で、再描画が必要な部分の領域を取得する コードを紹介します。

対応方法

描画を必要とする領域は、Paintイベントの PaintEventArgs パラメーターのClipRectangleプロパティで取得できます。

プログラム

UI

下図のフォームを作成します。Panel, Button, TextBox を配置します。

コード

以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace InvalidateDemo
{
  public partial class FormInvalidateGetArea : Form
  {
    private int[,] DrawCount;

    public FormInvalidateGetArea()
    {
      InitializeComponent();
    }

    private void panel1_Paint(object sender, PaintEventArgs e)
    {
      int rWidth = 32;
      int rHeiht = 32;

      Pen p = new Pen(Color.FromArgb(0x5d, 0x86, 0xba), 1);

      Font f = new Font("MS UI Gothic", 10);
      SolidBrush bs = new SolidBrush(Color.FromArgb(0x00, 0x44, 0x99));

      for (int x = 0; x < 16; x++) {
        for (int y = 0; y < 8; y++) {
          Rectangle drawRect = new Rectangle(x * rWidth, y * rHeiht, rWidth, rHeiht);

          if (drawRect.IntersectsWith(e.ClipRectangle) == true) {
            DrawCount[x, y]++;
            int red = Math.Max(0xcd - DrawCount[x, y] * 16, 0); // - DrawCount[x, y] * 4;
            int green = Math.Max(0xdc - DrawCount[x, y] * 16, 0);
            int blue = 0xef;
            SolidBrush b = new SolidBrush(Color.FromArgb(red, green, blue));

            e.Graphics.FillRectangle(b, drawRect);
            e.Graphics.DrawRectangle(p, drawRect);

            e.Graphics.DrawString(Convert.ToString(DrawCount[x, y]), f, bs, new Point(x * rWidth + 2, y * rHeiht + 2));
          }
        }
      }

      textBox1.Text += string.Format("left:{0:d}, Top:{1:d}, width:{2:d}, height:{3:d}\r\n", 
        e.ClipRectangle.Left, e.ClipRectangle.Top, e.ClipRectangle.Width, e.ClipRectangle.Height);
    }

    private void FormInvalidateGetArea_Load(object sender, EventArgs e)
    {
      DrawCount = new int[16, 8];
      for (int i = 0; i < 16; i++) {
        for (int j = 0; j < 8; j++) {
          DrawCount[i, j] = 0;
        }
      }
    }

    private void button1_Click(object sender, EventArgs e)
    {
      Rectangle r = new Rectangle(160, 80, 240, 140);
      panel1.Invalidate(r);
    }
  }
}

解説

基本的な動作はこちらの記事と同様です。
if (drawRect.IntersectsWith(e.ClipRectangle) == true) { ... } のコードを追加しており、 描画する領域が描画が必要な領域に含まれるか判定し、描画が必要な領域に含まれる場合のみに FillRectangle,DrawRectangle メソッドを呼び出して画面描画を実行しています。

IntersectsWithメソッドの動作の詳細はこちらの記事を参照してください。
  for (int x = 0; x < 16; x++) {
    for (int y = 0; y < 8; y++) {
      Rectangle drawRect = new Rectangle(x * rWidth, y * rHeiht, rWidth, rHeiht);

      if (drawRect.IntersectsWith(e.ClipRectangle) == true) {
        DrawCount[x, y]++;
        int red = Math.Max(0xcd - DrawCount[x, y] * 16, 0); // - DrawCount[x, y] * 4;
        int green = Math.Max(0xdc - DrawCount[x, y] * 16, 0);
        int blue = 0xef;
        SolidBrush b = new SolidBrush(Color.FromArgb(red, green, blue));

        e.Graphics.FillRectangle(b, drawRect);
        e.Graphics.DrawRectangle(p, drawRect);

        e.Graphics.DrawString(Convert.ToString(DrawCount[x, y]), f, bs, new Point(x * rWidth + 2, y * rHeiht + 2));
      }
    }
  }

Paintイベントが実行された際に、ClipRectangleプロパティの値をテキストボックスに表示します。
  textBox1.Text += string.Format("left:{0:d}, Top:{1:d}, width:{2:d}, height:{3:d}\r\n", 
    e.ClipRectangle.Left, e.ClipRectangle.Top, e.ClipRectangle.Width, e.ClipRectangle.Height);

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。Invalidateメソッドで指定した領域が再描画されます。
描画が必要なエリアの座標値が下部のテキストボックスに表示されます。


動作結果の画面だけでは分かりませんが、描画領域に含まれない部分のマスは描画メソッドが呼び出されない動作になっています。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2023-01-23
iPentec all rights reserverd.