電子書籍の厳選無料作品が豊富!

ウィンドウとボタンのちらつきについて質問です。

以前質問させて頂いた
http://okwave.jp/qa5097510.html
をして、更新は速くなったのですが、
ちらつきがあるのですが、原因が分かりません。

順番としては、
ーーーーーーーーーーーーーーーーー
SendMessage()で再描画阻止
ボタンの移動
ウィンドウのサイズ変更
SendMessage()で再描画阻止を解除
ボタンの再描画
ウィンドウの再描画
ーーーーーーーーーーーーーーーーー
です。

色々試してみて現在はこの順番で処理をしています。
他の所に問題があるのかもしれませんが。
どうすれば、ちらつきが出ないようにできるのでしょうか?

開発環境
XP
Visual Studio 2005
C、C++でWindowsAPIを使用しています。

A 回答 (6件)

 こんにちは。

補足頂きました。

(1)SendMessage()で再描画阻止
(2)ボタンの移動
(3)ウィンドウのサイズ変更
(4)SendMessage()で再描画阻止を解除
(5)ボタンの再描画
(6)ウィンドウの再描画

 上記では順序がマズイのではないでしょうか。

(1)ウィンドウのサイズ変更
(2)SendMessage()で再描画阻止
(3)ボタンの移動
(4)ウィンドウの再描画
(5)SendMessage()で再描画阻止を解除
(6)ボタンの再描画

 辺りが良い様に思います。
 (1)が成立するとWM_SIZEが発生するので、WM_SIZEの中で(2)~(6)を処理すれば大丈夫だと思います。
 お膳立てとして、先程書いた片っ端からオーナードローボタンのウィンドウプロシージャをサブクラス化しておく必要があります。
 本当の所、どの様な実装をしたかによって話が変ってくるかもしれませんので、中々明言が出来ないのですが、取り敢えずチラつかなかった実験台を提示しておきます。
 以下は

(1)ポップアップスタイルのレイヤードウィンドウである事
(2)オーナードローボタンを100個出している事
(3)マウスのキャプチャでウィンドウの端を摘んでリサイズする事
(4)纏めて移動してボタンがチラつかない事
(5)ボタンのウィンドウプロシージャをサブクラス化する事
 
 を条件として書いています。実験台のプロジェクトに貼り付けて試して見て下さい。以下参考程度に。

#include<windows.h>
#include<commctrl.h>
#include<tchar.h>

#pragma comment(lib, "comctl32.lib")

//---------------------------------------------------------------
//以下からレイヤードウィンドウが其の侭使えない古いVC++の場合に必要
//---------------------------------------------------------------
typedef BOOL (WINAPI* PFNSetLayeredWindowAttributes)(HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD);
static PFNSetLayeredWindowAttributes SetLayeredWindowAttributes = (PFNSetLayeredWindowAttributes)GetProcAddress(::LoadLibrary("user32.dll"), "SetLayeredWindowAttributes");

const DWORD WS_EX_LAYERED= 0x80000;
const DWORD ULW_ALPHA= 2;
//---------------------------------------------------------------
//以上までレイヤードウィンドウが其の侭使えない古いVC++の場合に必要
//---------------------------------------------------------------

//動的に作成されるボタンIDの規定値
const DWORD IDC_BUTTON_BASE = 10000;

//横に並べるボタンの数
const INT COLUMN= 10;

//縦に並べるボタンの数
const INT LINE= 10;

//取り出したボタンのウィンドウプロシージャをしまっておくプロパティに使われる鍵
const LPCTSTR TSTR_PROC = "Owner Draw Button Proc";

//ドラッグされている状態を示す
static LPARAM s_hitState = HTNOWHERE;

//ボタンのサブクラス化(ちらつきを止める為)
static LRESULT WINAPI WndProcButton(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
WNDPROC oldProc = (WNDPROC)::GetProp(hWnd, TSTR_PROC);
if(uMsg == WM_ERASEBKGND)
{
return 0;
}
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}
//ボタンの作成
static VOID CreateButton(HWND hWnd)
{
for(int y = 0, i = 0; y < LINE; ++y)
{
for(int x = 0; x < COLUMN; ++x, ++i)
{
//ボタン作成
HWND hButton = ::CreateWindowEx(0, TEXT("BUTTON"), TEXT("OK"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_OWNERDRAW, x * 24, y * 24, 24, 24, hWnd, (HMENU)(IDC_BUTTON_BASE + i), ::GetModuleHandle(NULL), NULL);

//ボタンのプロシージャを取って
HANDLE hOldButtonProc = (HANDLE)::GetWindowLong(hButton, GWL_WNDPROC);

//プロパティ(自由データ)に記憶する
::SetProp(hButton, TSTR_PROC, hOldButtonProc);

//ボタンのプロシージャをサブクラス化
::SetWindowLong(hButton, GWL_WNDPROC, (LONG)&::WndProcButton);
}
}
}
//ボタンの移動
static VOID MoveButton(HWND hWnd)
{
int x;
int y;
int i;

//ボタンの移動
for(y = 0, i = 0; y < LINE; ++y)
{
for(x = 0; x < COLUMN; ++x, ++i)
{
//抑止を有効化
//::SendMessage(hButton, WM_SETREDRAW, FALSE, 0);

//ボタンハンドルを取る
HWND hButton = ::GetDlgItem(hWnd, IDC_BUTTON_BASE + i);
RECT rc;
::GetWindowRect(hButton, &rc);

//x+4, y+4の位置に動かす
POINT pt = {rc.left, rc.top};
::ScreenToClient(hWnd, &pt);
::SetWindowPos(hButton, NULL, pt.x + 4, pt.y + 4, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW);
}
}

//ウィンドウ領域の無効化
::InvalidateRect(hWnd, NULL, TRUE);

//ボタンの書き直し
for(y = 0, i = 0; y < LINE; ++y)
{
for(x = 0; x < COLUMN; ++x, ++i)
{
//抑止の解除
//::SendMessage(hButton, WM_SETREDRAW, FALSE, 0);

//ボタンハンドルを取る
HWND hButton = ::GetDlgItem(hWnd, IDC_BUTTON_BASE + i);

//無効化するだけで消去はしない
::InvalidateRect(hButton, NULL, FALSE);
}
}
}
//ウィンドウクラス登録
static ATOM Regist(LPCTSTR szClassName, WNDPROC wndProc)
{
WNDCLASSEX wndclass= {sizeof(wndclass)};
wndclass.hCursor= ::LoadCursor(NULL,IDC_ARROW);
wndclass.hIcon= NULL;
wndclass.lpszMenuName= NULL;
wndclass.lpszClassName= szClassName;
wndclass.hbrBackground= (HBRUSH)::GetStockObject(WHITE_BRUSH);
wndclass.hInstance= ::GetModuleHandle(NULL);
wndclass.style= 0;
wndclass.lpfnWndProc= wndProc;
wndclass.cbClsExtra= 0;
wndclass.cbWndExtra= 0;
return ::RegisterClassEx(&wndclass);
}
//ウィンドウを作成して開く
static HWND OpenWindow(LPCTSTR szClassName, LPCTSTR szTitleName, INT w, INT h, LPVOID pData)
{
HINSTANCE hInst = ::GetModuleHandle(NULL);

HWND hWnd = ::CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_ACCEPTFILES, szClassName, szTitleName,
WS_POPUPWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT,CW_USEDEFAULT, w, h,
NULL, NULL,
hInst, pData);
return hWnd;
}

static LRESULT OnCreate(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//レイヤードウィンドウの透過率設定
::SetLayeredWindowAttributes(hWnd, 0, 128, ULW_ALPHA);

//ボタンを沢山配置
::CreateButton(hWnd);
return 0;
}

static LRESULT OnClose(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
::DestroyWindow(hWnd);
return 0;
}

static LRESULT OnNCDestroy(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
::PostQuitMessage(0);
return 0;
}

static LRESULT OnCancelMode(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
s_hitState = HTNOWHERE;
return 0;
}

static LRESULT OnLButtonDown(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//リサイズ枠と見なす幅
LONG ResizeEdge = 4;

//マウス位置を取る
POINT pt;
::GetCursorPos(&pt);

//ウィンドウ位置を取る
RECT rc = {0};
::GetWindowRect(hWnd, &rc);

//リサイズ枠の位置ではないと見なす
if(pt.x < rc.right - ResizeEdge && pt.y < rc.bottom - ResizeEdge)
return 0;

if(pt.x >= rc.right - ResizeEdge && pt.y >= rc.bottom - ResizeEdge)
{
s_hitState = HTBOTTOMRIGHT;
}
else if(pt.x >= rc.right - ResizeEdge)
{
s_hitState = HTRIGHT;
}
else if(pt.y >= rc.bottom - ResizeEdge)
{
s_hitState = HTBOTTOM;
}

//キャプチャをする
::SetCapture(hWnd);
return 0;
}

static LRESULT OnLButtonUp(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//キャプチャを解く
s_hitState = HTNOWHERE;
::ReleaseCapture();
return 0;
}

static LRESULT OnMouseMove(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//移動量
LONG lPct = 16;

//キャプチャされてないので引き返す
if(s_hitState == HTNOWHERE)
return 0;

POINT pt;
::GetCursorPos(&pt);

RECT rc = {0};
::GetWindowRect(hWnd, &rc);

switch(s_hitState)
{
case HTRIGHT:
case HTBOTTOMRIGHT:
//横に対して
if(((pt.x - rc.right) % lPct) == 0)
{
//値に変化があったら
if(rc.right != pt.x - rc.left)
{
::SetWindowPos(hWnd, NULL, 0, 0, pt.x - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER);
}
}
};

switch(s_hitState)
{
case HTBOTTOM:
case HTBOTTOMRIGHT:
//縦に対して
if(((pt.y - rc.bottom) % lPct) == 0)
{
//値に変化があったら
if(rc.bottom != pt.y - rc.top)
{
::SetWindowPos(hWnd, NULL, 0, 0, rc.right - rc.left, pt.y - rc.top, SWP_NOMOVE | SWP_NOZORDER);
}
}
};

return 0;
}
//オーナードロー
static LRESULT OnDrawItem(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPDRAWITEMSTRUCT pdis = (LPDRAWITEMSTRUCT)lParam;

HPEN hPen = ::CreatePen(PS_SOLID, 1, (pdis->CtlID & 0x1) ? RGB(255, 120, 120) : RGB(120, 120, 255));
HBRUSH hBrush = ::CreateSolidBrush((pdis->CtlID & 0x1) ? RGB(64, 64, 96) : RGB(96, 64, 64));

hPen = (HPEN)::SelectObject(pdis->hDC, hPen);
hBrush = (HBRUSH)::SelectObject(pdis->hDC, hBrush);

::Rectangle(pdis->hDC, pdis->rcItem.left, pdis->rcItem.top, pdis->rcItem.right, pdis->rcItem.bottom);

if(pdis->itemState & ODS_FOCUS)
::DrawFocusRect(pdis->hDC, &pdis->rcItem);

::DeleteObject(::SelectObject(pdis->hDC, hPen));
::DeleteObject(::SelectObject(pdis->hDC, hBrush));

return 0;
}
//リサイズ後
static LRESULT OnSize(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//ボタンの移動
::MoveButton(hWnd);
return 0;
}

LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CREATE : ::OnCreate(hWnd, uMsg, wParam, lParam); break;
case WM_CLOSE : ::OnClose(hWnd, uMsg, wParam, lParam); break;
case WM_NCDESTROY : ::OnNCDestroy(hWnd, uMsg, wParam, lParam); break;
case WM_LBUTTONDOWN : ::OnLButtonDown(hWnd, uMsg, wParam, lParam); break;
case WM_LBUTTONUP : ::OnLButtonUp(hWnd, uMsg, wParam, lParam); break;
case WM_MOUSEMOVE : ::OnMouseMove(hWnd, uMsg, wParam, lParam); break;
case WM_CANCELMODE : ::OnCancelMode(hWnd, uMsg, wParam, lParam); break;
case WM_DRAWITEM : ::OnDrawItem(hWnd, uMsg, wParam, lParam); break;
case WM_SIZE : ::OnSize(hWnd, uMsg, wParam, lParam); break;
}

return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hInstancePrev, LPSTR lpCmdLine, int nShowCmd)
{
const TCHAR tstrClassName[] = TEXT("test resize frame");
const TCHAR tstrTitleName[] = TEXT("title");
MSG msg;

::InitCommonControls();

//ウィンドウクラスの登録
::Regist(tstrClassName, &::WndProc);

//ウィンドウを作成して開く
HWND hWnd = ::OpenWindow(tstrClassName, tstrTitleName, 800, 600, NULL);

//ウィンドウの表示
::ShowWindow(hWnd, SW_SHOW);

//メッセージ回転
while(::GetMessage(&msg, NULL, 0, 0) == TRUE)
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}

//終了
return msg.wParam;
}

この回答への補足

何度もすみません。

WndProcButton()関数の、
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
なのですが、エラー(アクセス違反)が出るのですが、どういう原因が考えられるでしょうか?
何度も見直したり、色々試してみたのですが、分かりませんでした。
どこか見落としてる所があるのかもしれませんが。

補足日時:2009/07/10 17:01
    • good
    • 0
この回答へのお礼

補足質問内容が足りませんでした。
すみません。

エラーは別の所(return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);ではない所)で出たのですが、試しに
return 0;
に変えた所、エラーが出なくなったので、
これが原因ではないかと思いました。

もしかしたら違うかもしれませんが。

お礼日時:2009/07/10 17:36

 こんばんは。

補足頂きました。遅れてすみません、外出していました。

 VisualStudio6.0で組んでいるので、VisualStudio2005の方で試した所、見落としておりました。以下を修正してください。

×const LPCTSTR TSTR_PROC = "Owner Draw Button Proc";
 ↓
○const LPCTSTR TSTR_PROC = TEXT("Owner Draw Button Proc");

 VisualStudio2005のデフォルトでUNICODEであるため、上記の理由でSetProp()/GetProp()関数が失敗して、oldProc変数がNULLに成っている様です。
 
static LRESULT WINAPI WndProcButton(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//以下の変数がNULLで不正終了に陥る
WNDPROC oldProc = (WNDPROC)::GetProp(hWnd, TSTR_PROC);
if(uMsg == WM_ERASEBKGND)
{
return 0;
}
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}

この回答への補足

試してみたのですが、アクセス違反のエラーが出ました。

書いて頂いたソースは実行できるので、
作っているものに入れる時に間違ったのかもしれないので、
やり直してみます。

ちなみに、プロパティの文字セットは設定なしにしています。

補足日時:2009/07/10 23:16
    • good
    • 0
この回答へのお礼

すみません。
私のミスでした。
何度か入れ直し(ちらつき防止処理)てみたら、エラーは出なくなりました。
原因は分かりませんが。

ですけど、まだちらついているので色々試してみようと思います。

お礼日時:2009/07/11 12:43

 こんにちは。


 解決にはならないかもしれませんが一応。
 実験をして見たのですが、「ボタンの移動に5秒かかる・矢鱈にちらつく」理由がある程度分かった様な気がします。
 もしかして、ボタンの下のウィンドウがレイヤードウィンドウなのではないでしょうか。
 レイヤードウィンドウは、通常のウィンドウとは大分違うようです。半透明で描かれる為非常に負担が大きいです。
 どうやら、レイヤードウィンドウ特有の問題の様です。オーナードローボタンのウィンドウプロシージャを片っ端からサブクラス化して、

WNDPROC oldProc = ...;

static LRESULT WINAPI WndProcButton(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if(uMsg == WM_ERASEBKGND)
{
return 0;
}
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}

 の様にして見た所、ちらつきがピタリと止まりました。逆に普通のウィンドウであれば、こんな事をしなくてもちらついたりはしませんでした。

この回答への補足

返事遅れてすみません。
ネットで調べている時にシステムに異常が出て、
リカバリをしてました。

ちらつきの件ですが、確かにレイヤードウィンドウです。
レイヤードウィンドウをしないようにしてみたら、
ちらつかないようになりました。

ですけど、
ーーーーーーーーーーーーーーーーー
SendMessage()で再描画阻止
ボタンの移動
ウィンドウのサイズ変更
SendMessage()で再描画阻止を解除
ボタンの再描画
ウィンドウの再描画
ーーーーーーーーーーーーーーーーー
の順番だと、ウィンドウのサイズを変更した時にボタンの移動をしているのですが、
ウィンドウを小さくした時に描画したものが消されずに残ってしまうのですが、
どうすればいいのでしょうか?
「ウィンドウのサイズ変更」と「SendMessage()で再描画阻止を解除」を
入れ替えるとちらついてしまいます。

レイヤードウィンドウをする場合は、
載せて頂いたソースはどう使用すればいいのでしょうか?
試してみたのですが、使い方が悪いのか、出来ませんでした。

WindowsAPIはまだ分からない事ばかりなので、
分かりやすく教えて頂けると助かります。

ところで、半透明処理はそんなに負担が大きいんですね。

補足日時:2009/07/09 16:42
    • good
    • 0
この回答へのお礼

最新の回答のお礼の欄を使っているので、こちらに書かせて頂きます。

まだちらつきはありますけど、だいぶ良くなったのでいったん締め切らせて頂きました。
もし、解決しないようであれば再度質問させて頂くと思います。

時間をかけて解決に協力して頂きありがとうございました。

親ウィンドウはちらついていますけど(背景)、
ボタンはちらついていないので、何とかなると思います。

お礼日時:2009/07/11 22:44

ちらつき…で、他に浮かぶとすると……



http://www.google.co.jp/search?hl=ja&q=WM_ERASEB …
くらいでしょうか。

WS_CLIPCHILDREN等でコントロールの部分は消去が走らない…と思いましたが……
# 記憶違いかも知れない…

この回答への補足

返事遅れてすみません。

レイヤードウィンドウにしていたので、
処理に負担がかかっていたみたいです。
レイヤードウィンドウにしなかったら、ちらつかなくなりました。
ただし、ウィンドウのサイズを変更した時にボタンの移動をしているのですが、
ウィンドウを小さくした時に描画したものが残ったままになってますが。

できれば、レイヤードウィンドウを使用したいです。
どうすれば出来るのでしょうか?

何度もすみません。

補足日時:2009/07/09 16:54
    • good
    • 0

ウィンドウをサイズ変更すると、再描画を抑止してあっても、ウィンドウの背景が再描画された後に、ウィンドウ上にあるコントロールが描画されます。



つまり、ウィンドウのリサイズで「背景で描画済みのコントロール(ボタン)が消される」「乗ってるコントロール(ボタン)が再描画される」と言うのが繰り返され、チラツキが起きます。

防止の方法は
http://www.atmarkit.co.jp/fdotnet/dotnettips/194 …
http://www5f.biglobe.ne.jp/~sayox/tec/mfc27.html
http://www.google.co.jp/search?hl=ja&safe=off&nu …
などを参考に。

但し、リサイズ時の背景の再描画を抑止すると、リサイズによって移動したコントロール(ボタン)が消されず、移動する前の位置に表示が残ってしまい、ウィンドウにボタンの残骸が残りまくる可能性があります。

この回答への補足

回答ありがとうございます。

リンク先を見る限り、裏画面処理ということでよろしいのでしょうか?
裏画面処理は、以前質問させて頂いた際に教えて頂いたのでやっています。
http://okwave.jp/qa4871646.html

それとも、裏画面処理ではないのでしょうか?

私が何か誤解しているのかもしれませんが。

補足日時:2009/07/07 17:30
    • good
    • 0

親ウィンドウの作成時にWS_CLIPCHILDRENを指定したり、ボタンを作成するときにWS_CLIPSIBLINGSを指定してみ

たり…はどうでしょう?

この回答への補足

回答ありがとうございます。

WS_CLIPCHILDRENとWS_CLIPSIBLINGSは指定しています。
どこか、私がミスをしているのかもしれないのですが、
見つけることが出来ませんでした。

宜しければ、これが原因かもしれないというものを挙げていただくと助かります。

補足日時:2009/07/07 22:18
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!