縦横比を固定したままウィンドウ(Form)を拡大縮小したい

タイトル通り、縦横比を保ったまま、.NETのFormのサイズを変更したかった。で、FormのResizeイベントのドキュメントを見てみると、Formを正方形に保つサンプルが載ってた。それを参考に、FormのClientSizeを4:3に保つコードを書いてみた。

const double fixedRate = (double)4 / 3;
private void Form1_Resize(object sender, EventArgs e) {
    ClientSize = new Size((int)(fixedRate * ClientSize.Height + 0.5),ClientSize.Height);
}

これだけで4:3に固定されたよ!やったね!と言いたい所だけど、これだけだとまずい。まず、サイズが変わるたびにちらつく。ついでに、角とか辺とかでサイズ変更すると挙動が若干怪しい。まぁ、挙動はちゃんと書けばどうにかなるような気はするが、SuspendLayoutとか入れたりしたけどちらつきはどうにもならなかった。
他にいろいろイベントを探したけど、うまく行きそうなイベントは見つからなかった。で、.NETじゃないWindouws APIだとどうやってるんだろう、って調べてみると、どうもWM_SIZINGメッセージを受け取ったときにごにょごにょしてあげるといいらしい(c‰¡‚̔䗦‚ðˆê’è‚É‚µ‚ăTƒCƒY•ÏX)。というわけで、FormのWndProcをoverrideしてごにょごにょしてみよう。

//using System.Runtime.InteropServices;
const double fixedRate = (double)4 / 3;
const int WM_SIZING = 0x214;
const int WMSZ_LEFT = 1;
const int WMSZ_RIGHT =2;
const int WMSZ_TOP = 3;
const int WMSZ_TOPLEFT =4;
const int WMSZ_TOPRIGHT =5;
const int WMSZ_BOTTOM =6;
const int WMSZ_BOTTOMLEFT =7;
const int WMSZ_BOTTOMRIGHT = 8;
[StructLayout(LayoutKind.Sequential)]
struct RECT {
    public int left;
    public int top;
    public int right;
    public int bottom;
}
protected override void WndProc(ref Message m) {
    switch(m.Msg){
        case WM_SIZING:
            RECT r = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
            int w = r.right - r.left - (Size.Width - ClientSize.Width);
            int h = r.bottom - r.top - (Size.Height - ClientSize.Height);
            int dw = (int)(h * fixedRate + 0.5) - w;
            int dh = (int)(w / fixedRate + 0.5) - h;
            switch(m.WParam.ToInt32()) {
                case WMSZ_TOP:
                case WMSZ_BOTTOM:
                    r.right += dw;
                    break;
                case WMSZ_LEFT:
                case WMSZ_RIGHT:
                    r.bottom += dh;
                    break;
                case WMSZ_TOPLEFT:
                    if(dw > 0) r.left -= dw;
                    else r.top -= dh;
                    break;
                case WMSZ_TOPRIGHT:
                    if(dw > 0) r.right += dw;
                    else r.top -= dh;
                    break;
                case WMSZ_BOTTOMLEFT:
                    if(dw > 0) r.left -= dw;
                    else r.bottom += dh;
                    break;
                case WMSZ_BOTTOMRIGHT:
                    if(dw > 0) r.right += dw;
                    else r.bottom += dh;
                    break;
            }
            Marshal.StructureToPtr(r, m.LParam, false);
            goto default;
        default:
            base.WndProc(ref m);
            break;
    }
}

こうすると、ちらつきもなく、挙動もちゃんとしたままサイズの変更が出来るようになった。やったね!