最近C#を勉強してます(というか一通り勉強し終わった感じです)。
WPFだとドロップシャドウを簡単に付けれるのですが、Formだと出来ないっぽいので、ちょっと作ってみました。

Edge1

影の色や不透明度、ぼかし具合も自由に変えられます。
対象がBitmapなので、やろうと思えば文字列にドロップシャドウを加えることも出来ます。

/// <summary>
/// ドロップシャドウを加えた新しいビットマップを作成します。
/// </summary>
/// <param name="srcBmp">対象のビットマップ</param>
/// <param name="blur">ぼかし</param>
/// <param name="distance">影の距離</param>
/// <param name="shadowColor">影の色</param>
/// <param name="baseOffset">描画先オフセット値の格納先</param>
/// <returns>ドロップシャドウが加えられた新しいビットマップ</returns>
private static Bitmap AppendDropShadow(Bitmap srcBmp, int blur, Point distance, Color shadowColor, out Point baseOffset)
{
    baseOffset = new Point(0, 0);
    if (srcBmp == null || blur < 0)
    {
        return null;
    }

    // ドロップシャドウを含めた新しいサイズを算出
    Rectangle srcRect = new Rectangle(0, 0, srcBmp.Width, srcBmp.Height);
    Rectangle shadowRect = srcRect;
    shadowRect.Offset(distance.X, distance.Y);
    Rectangle shadowBlurRect = shadowRect;
    shadowBlurRect.Inflate(blur, blur);
    Rectangle destRect = Rectangle.Union(srcRect, shadowBlurRect);
    baseOffset.X = destRect.X - srcRect.X;
    baseOffset.Y = destRect.Y - srcRect.Y;

    Bitmap destBmp = new Bitmap(destRect.Width, destRect.Height, PixelFormat.Format32bppArgb);

    // 影部分をレンダリングする
    BitmapData destBmpData = destBmp.LockBits(new Rectangle(0, 0, destRect.Width, destRect.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
    BitmapData srcBmpData = srcBmp.LockBits(srcRect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

    unsafe
    {
        byte* destLine = (byte*)destBmpData.Scan0 + (shadowBlurRect.Y - destRect.Y) * destBmpData.Stride + (shadowBlurRect.X - destRect.X) * 4;
        byte* srcBeginLine = (byte*)srcBmpData.Scan0 + (- blur - blur) * srcBmpData.Stride + (- blur - blur) * 4;

        int destWidth = shadowBlurRect.Width;
        int destHeight = shadowBlurRect.Height;

        int srcWidth = srcBmp.Width;
        int srcHeight = srcBmp.Height;

        int div = (1 + blur + blur) * (1 + blur + blur);

        byte r = shadowColor.R;
        byte g = shadowColor.G;
        byte b = shadowColor.B;
        float alpha = shadowColor.A / 255.0f;

        int destStride = destBmpData.Stride;
        int srcStride = srcBmpData.Stride;

        for (int destY = 0; destY < destHeight; destY++, destLine += destStride, srcBeginLine += srcStride)
        {
            byte* dest = destLine;
            byte* srcBegin = srcBeginLine;
            for (int destX = 0; destX < destWidth; destX++, dest += 4, srcBegin += 4)
            {
                // α値をぼかす
                int total = 0;
                byte* srcLine = srcBegin;
                for (int srcY = destY - blur - blur; srcY <= destY; srcY++, srcLine += srcStride)
                {
                    if (srcY >= 0 && srcY < srcHeight)
                    {
                        byte* src = srcLine;
                        for (int srcX = destX - blur - blur; srcX <= destX; srcX++, src += 4)
                        {
                            if (srcX >= 0 && srcX < srcWidth)
                            {
                                total += src[3];
                            }
                        }
                    }
                }

                dest[0] = b;
                dest[1] = g;
                dest[2] = r;
                dest[3] = (byte)((total / div) * alpha);
            }
        }
    }

    srcBmp.UnlockBits(srcBmpData);
    destBmp.UnlockBits(destBmpData);

    // 元の画像を重ねる
    using (Graphics g = Graphics.FromImage(destBmp))
    {
        g.DrawImage(srcBmp, srcRect.X - destRect.X, srcRect.Y - destRect.Y, srcBmp.Width, srcBmp.Height);
    }

    return destBmp;
}

LockBitsはtry catchした方が良かったかな、まあいいか。

MFCでコマンドライン引数を解析する方法と言えば、
CWinApp::ParseCommandLine
を使うやり方が一般的・・・と言いますか、SDIやMDIプロジェクトだったら、最初からこれが使われているため、ここを拡張して独自の起動引数なんかを解析するのが普通かと思います。

長年その方法でやっていたのですが、最近になって __argc__targv (頭にアンダースコア2個) を使えば、解析済みの引数が参照出来る事を知りました。
ParseCommandLineを使うとわざわざCCommandLineInfoの派生クラスを作る必要があったりして面倒くさいのですが、__argcと__targvならば簡単に参照できるので、場合によってはこちらを使った方が手軽そうです。

データ構造として、起動引数を1クラスに管理させたい、とか思い始めるとParseCommandLineの方が良いのかなとも思ったりして、悩みどころではあります。

ホイールイベントはフォーカスを持つウィンドウで優先的に処理されるようなのですが、場合によってはフォーカスよりもマウスカーソル位置を優先したい事があります。

今まで結構回りくどいやり方をしていたのですが、PreTranslateMessageにちょろっと書いたら上手く動いたようなので、載せておきます。
(あまり動作検証していませんが)

BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
	if (pMsg->message == WM_MOUSEWHEEL) {
		CWnd* pWnd = WindowFromPoint(pMsg->pt);
		if (pWnd && IsChild(pWnd)) {
			pMsg->hwnd = pWnd->GetSafeHwnd();
		}
	}

	return __super::PreTranslateMessage(pMsg);
}

ショートカットキーやホットキー、それからゲーム操作のカスタマイズ時にキー名を表示する事があると思いますが、キーコードからキー名を取得する方法をご紹介。

TCHAR szBuf[128];
UINT nScanCode = MapVirtualKey(btKeyCode, 0);
if (nScanCode) {
	switch (btKeyCode) { 
	case VK_LEFT:
	case VK_UP:
	case VK_RIGHT:
	case VK_DOWN:
	case VK_PRIOR:
	case VK_NEXT:
	case VK_END:
	case VK_HOME: 
	case VK_INSERT:
	case VK_DELETE: 
	case VK_DIVIDE:
	case VK_NUMLOCK: 
		nScanCode |= 0x100;
		break; 
	}
	if (GetKeyNameText(nScanCode << 16, szBuf, sizeof(szBuf))) {
		// szBufにキー名が入る
	}
}

日本のサイトで方法を紹介されているページも有ったのですが、Homeキー等一部のキー名の取得がうまく行っていませんでした。
海外のサイトに解決策が載っていたので、そこから一部拝借してみました。
たぶんこれで良いと思います。

Edge1

↑右がリソースエディタで貼ったツリーコントロールで、
左が↓のコードで貼ったツリーコントロール。

// m_TreeはCTreeCtrl型
m_Tree.CreateEx(WS_EX_CLIENTEDGE, WS_CHILD | WS_VISIBLE | TVS_SHOWSELALWAYS, CRect(10, 10, 200, 200), 
this, 0);
m_Tree.InsertItem(_T("ああああ"));

見てお判りの通りWS_EX_CLIENTEDGEが付きません。
CTreeCtrl::CreateExのソースを見てみると、変なことやってますので、そりゃ付かないですね。

BOOL CTreeCtrl::CreateEx(DWORD dwExStyle, DWORD dwStyle, const RECT& rect,
	CWnd* pParentWnd, UINT nID)
{
	BOOL bRet = Create(dwStyle, rect, pParentWnd, nID);
	if (bRet && dwExStyle != 0)
	{
		bRet = ModifyStyleEx(0, dwExStyle);
	}
	return bRet;
}

かと言って

m_Tree.CreateEx(0, WS_CHILD | WS_VISIBLE | TVS_SHOWSELALWAYS, CRect(10, 10, 200, 200), this, 0);
m_Tree.ModifyStyleEx(0, WS_EX_CLIENTEDGE, SWP_FRAMECHANGED);
m_Tree.InsertItem(_T("ああああ"));

こんなコードにしても、↓のようにビジュアルテーマ(?)が適用されません。
Edge5
(WS_BORDERだとちょっと濃い枠が付くため、これもちょっと違います。)

回避方法としては、

m_Tree.CWnd::CreateEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, _T(""), WS_CHILD | WS_VISIBLE | TVS_SHOWSELALWAYS, CRect(10, 10, 200, 200), this, 0);
m_Tree.InsertItem(_T("ああああ"));

というように、CWnd::CreateExを直接呼び出してしまう事ぐらいでしょうか。

これで一応リソースエディタで貼った物と同じ枠が付きます。
Edge6

MFCのバグ一覧とかどっかにまとまってたりしないのかな・・・。

記事検索

アーカイブ