C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 Q&A
C++Builder Programming Q&A
[1437] [답변] ZPIKNC86/ 임프님 1395 답변에 대해서.../프포
박지훈.임프 [cbuilder] 4364 읽음    1999-06-14 17:33
안녕하세요. 천리안 프로그래머 포럼 C++Builder 담당 임펠리테리입니다.

죄송하군요. 아래는.. 빌더라운지에 올렸던 말씀하신 리스트뷰 배경 팁입니다.

참고로.. 제가 빌더라운지에 올렸던 팁들은, 얼마전에 만든 제 홈페이지에도 올려져

있습니다. (http://myhome.thrunet.com/~cbuilder/)

그럼 도움되시길...




임펠리테리 박지훈이었습니다.

(cbuilder, cbuilder@thrunet.com)



┌───────────────────────────────────┐
│  ▶ 번  호 : 1718/1933             ▶ 등록자 : ZPDSPF2               │
│  ▶ 등록일 : 98년 12월 28일 09:07                                    │
│  ▶ 제  목 : [임프] 팁! 리스트뷰의 배경에 그림 넣기                  │
└───────────────────────────────────┘

임프랍니다.. 오늘의 팁은...
리스트뷰(혹은 트리뷰 등등)의 배경에 그림 넣기입니다.

리스트뷰 관련 api를 암만 뒤져봐도, 리스트뷰의 배경에 그림을 넣을 방법이 전혀
없다는 걸 알게 될겁니다.
하지만.. 윈도우즈의 바탕화면도 리스트뷰인데.. 그 배경 그림은??
분명히 방법이 있기는 있다는 말인데.. 그쵸? ^^

오늘은, 이 리스트뷰의 배경에 무언가를 그려넣기 위해, 리스트뷰를 서브클래싱
해봅시다. 서브클래싱? 좀 생소한 말인가요? 뭐.. 별건 아니고..
원래는 상속받는다는 의미이지만, api 함수를 사용하는데 있어서는 의미가 좀
달라서, 윈도우 프로시저를 바꿔치워서 해당 윈도우에 전달되는 메시지를 가로채는
것을 말합니다. (후킹..이라고도 하더군요.)

슬프게도...(?) 오늘의 팁은, 제 작품이 아니고, 컨닝이랍니다.
원 출처는 bcbdev.com(www.bcbdev.com)입니다.
이 사이트는 저번에도 한번 소개드렸던 것 같은데.. 정기적으로 새로운 팁들이
추가되는, 아주 유용한 사이트죠.

자아.. 그럼 본론으로 들어가볼까요?
서브클래싱의 원론적인, 아주 기초적인 개념부터 간단하게나마 훑어보고 지나갑시다.
모든 윈도우(윈도우 핸들을 갖고 있는, VCL에서 윈도우 컨트롤로 만들어져 있는
컨트롤들을 포함한 모든! 윈도우)들은 자체의 핸들을 통해 전달되어 들어오는
윈도우 메시지들을 처리하기 위한 윈도우 프로시저를 가지고 있습니다.
가물거리는 제 기억을 뒤적거려보면.. 이 윈도우 프로시저는 api 수준의 프로그래밍
에서, 윈도우 클래스의 멤버중 하나로 등록됩니다. api를 기초라도 좀 공부해 보신
분이라면, 윈도우 클래스는 그 윈도우가 최초로 생성되기 전에 먼저 생성해서
등록해야만 한다는 것 정도는 기억하실 겁니다.

이 윈도우 프로시저는, 앞에서도 잠깐 설명했다시피, 윈도우로 들어오는 모든
메시지를 switch~case 구조로 처리하는 구조로 되어있습니다.
그리고, 만약 이미 생성되어있는 윈도우라도, 이 윈도우 프로시저를 바꿔칠
방법만 있다면, 당연히 특정 윈도우 메시지에 대해서 기본적인 원래의 기능 외에
부가적인, 혹은 원래의 기능과는 다른 어떤 동작을 하게 할 수 있겠지요.

그런데, 불행히도(자주도 불행하지요?) vcl 내부적으로는 이 윈도우 프로시저를
바꿔칠 방법이 없습니다. 뭐.. 불행하다기 보단, 당연한 일이겠지요.
그래서.. api 수준에서 윈도우 프로시저를 바꿔치는 방법을 그대로 쓰면 가능하죠.

api 프로그래밍에서, 윈도우 클래스로 등록된 내용을 수정하려면(물론 윈도우
SetWindowLong() 함수를 사용합니다. 물론, 당연히 윈도우 프로시저를 바꿔칠 때도
이 함수를 사용합니다. 사용법은,
SetWindowLong(해당윈도우핸들, 바꿔칠필드, 바꿔칠값);
이렇습니다. 여기서처럼 윈도우 프로시저를 바꿔친다면,
SetWindowLong(해당윈도우핸들, GWL_WNDPROC, 새프로시저이름);
이렇게 하면 되지요.

윈도우 프로시저 자체는 물론 함수지요. 그리고, 메시지를 처리하는 것이 목적이므로
넘겨주고 받는 인자들의 리스트는 SendMessage() 함수의 경우와 완전히 동일합니다.
하지만 리턴값은 LRESULT로 해야 하고, 호출규칙은 CALLBACK으로 지정해야 합니다.
(사실, LRESULT는 LONG, 즉 long형이고, CALLBACK도 __stdcall일 뿐이지만.. ^^)
그래서, 최종적으로 함수의 선언은 다음과 같이 됩니다.
LRESULT CALLBACK ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                                LPARAM lParam)
뭐, 이정도는 윈도우 api 헬프에 보면 나옵니다.

자아.. 그럼 이제 본격적으로 코드를 작성해봅시다.
먼저, 빈 프로젝트를 하나 만들고, 그 위에 리스트뷰 하나와 이미지 하나를
놓읍시다. 별로 중요한 것은 아니지만, 코딩을 줄이기 위해 직접 그리는 루틴을
넣기 보다는 이미지에 미리 올려놓은 그림을 BitBlt()로 리스트뷰에 보내도록
합시다.

먼저, 바꿔칠 새 윈도우 프로시저를 작성해봅시다.

LRESULT CALLBACK ListViewProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                              LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_ERASEBKGND:
        BitBlt((HDC)wParam, 0, 0, Form1->ListView1->Width,
            Form1->ListView1->Height, Form1->Image1->Canvas->Handle, 0, 0,
            SRCCOPY);
        return 0;

        default:
        return CallWindowProc((FARPROC)FOriginalProc, hWnd, uMsg,
                   wParam, lParam);
    }
}

원래 제가 훔쳐본 소스는 좀더 복잡한 형태였지만, 필요없는 군더더기는 다
날려버리고 간단한 형태로 만들었습니다.

배경을 그려야 하므로, 그리는 문장은 WM_ERASEBKGND 메시지의 처리 부분에서
해줘야 하는 건 당연하겠구요..

BitBlt() 함수는 뭐 그냥 한 dc에서 다른 dc로 그려져 있는 이미지를 블럭
카피해주는 함수입니다.
(StretchDraw()와 같은 익숙한 vcl 함수들도 내부적으로 BitBlt()를 사용하죠.)

그리고 마지막의 default: 레이블 뒤에 적어준 CallWindowProc() 함수는 지정한
윈도우 프로시저에 메시지를 넘기면서 실행시키는 함수입니다.
여기서는 원래의 윈도우 프로시저를 호출하기 위해 사용되었죠.
(수만가지가 넘을 윈도우 메시지들을 다 일일이 처리하고 싶으신 분은, 원래의
프로시저를 실행시키지 않고 직접 다 처리해주셔도 좋습니다만.. 그럴 분은
설마 없으시겠지요? ^^)

이번엔, 윈도우 프로시저를 실제로 바꿔치는 루틴을 작성합시다.
이 코드를 작성할 수 있는 곳은 두군데 중 하나입니다.
폼의 OnCreate 핸들러이거나, 혹은 폼의 생성자죠. 왜냐하면, 리스트뷰가 생성되기
전에 실행되어야 하기 때문입니다.
원래의 소스처럼, 저는 폼 클래스의 생성자에다가 작성해보죠.

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    FOriginalProc = (WNDPROC)SetWindowLong(ListView1->Handle, GWL_WNDPROC,
                                           (LONG)ControlWndProc);
}

원문에서는, GetWindowLong()으로 원래의 윈도우 프로시저를 얻고 SetWindowLong()
으로 새 프로시저를 할당하는 코드였습니다만, SetWindowLong()이 원래의
윈도우 프로시저를 리턴하므로, 간단하게 한 문장으로 줄였습니다.
바로 FOriginalProc 이 원래의 프로시저를 저장하고 있는 거구요.

그럼 이 FOriginalProc을 선언하는 부분이 있어야겠지요? 폼 클래스의 멤버로
만들지 말고, 그냥 유닛파일, 즉 cpp 파일 내에다가 선언해버립시다.

이번엔.. 당연히 이 원래의 프로시저 FOriginalProc 를 복구시키는 코드가
필요하겠죠?
생성자에서 바꿔쳤으니 간단히 생각해서 파괴자에서 복구시키도록 하지요.

__fastcall TForm1::~TForm1()
{
    SetWindowLong(ListView1->Handle, GWL_WNDPROC, (LONG) FOriginalProc);
}

생성자는 폼의 유닛 파일에 기본적인 틀을 만들어주지만, 파괴자는 안만들어주니
직접 작성해야 합니다. 물론, 폼 클래스 내의 선언도 직접 해줘야죠.
__fastcall ~TForm1();


자아... 그럼 이제 모든 코딩은 끝났습니다.
지금까지 작성한 코드는 모두 다음과 같습니다.
(제 유닛 파일의 내용을 그대로 덤프합니다..)

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------

WNDPROC FOriginalProc;

LRESULT CALLBACK ListViewProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM
lParam)
{
    switch(uMsg)
    {
        case WM_ERASEBKGND:
        BitBlt((HDC)wParam, 0, 0, Form1->ListView1->Width,
Form1->ListView1->Height, Form1->Image1->Canvas->Handle, 0, 0, SRCCOPY);
        return 0;

        default:
        return CallWindowProc((FARPROC)FOriginalProc, hWnd, uMsg, wParam,
lParam);
    }
}

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    FOriginalProc = (WNDPROC)SetWindowLong(ListView1->Handle,
GWL_WNDPROC, (LONG)ListViewProc);//ControlWndProc);
}

__fastcall TForm1::~TForm1()
{
    SetWindowLong(ListView1->Handle, GWL_WNDPROC, (LONG) FOriginalProc);
}

F9를 눌러 실행해봅시다.
보이지 않게 했던 이미지에 불러왔던 그림이 리스트뷰의 배경에 잘 그려지고 있죠?
한가지 아쉬운 거라면, 비트맵이 없는 부분에 아무런 처리를 해주지 않아서
윈도우 밑에 있던 배경이 그대로 복사되고 있지만.. 그 외엔 모두 괜찮을겁니-.
배경이 그대로 그려지는 부분에 대한 처리는.. 직접 알아서 하세요! ^^;;;;


주의할 것이 있습니다. 원문에도 Note: 라고 하여 설명해놨구, 또 실제로도
중요한 사항인데..
SetWindowLong()으로 지정할 새 윈도우 프로시저는 클래스의 멤버로 만들어서는
안됩니다. 이것에 대해, 원문에서는 상당히 흥미로운 근거를 알려주고 있는데..
간단히 말해서, 클래스의 멤버인 함수와 멤버가 아닌 함수는, 아무리 같은
인자형을 지정해주더라도 윈도우 프로시저로 지정할 수 없다는 겁니다.

무슨말이냐 하면..
클래스의 멤버함수는, 내부적으로는 첫번째 인자로서 클래스의 인스턴스 포인터,
즉 this를 가진다고 하더군요. 그렇게 해서 클래스의 멤버함수가 클래스 멤버들을
인식한다는 거죠. 재미있죠? ^^
어쨌든.. 그래서.. 윈도우 프로시저를 클래스의 멤버로 선언하면, 내부적으로
처리될때는 인자가 하나 더 늘어버리므로, 윈도우 프로시저로 지정할 함수로서
인식되지 않는다는 겁니다.
c++에서는 아무리 함수 이름이 같아도 인자 리스트와 리턴값이 일치하지 않으면
다른 함수로 인식한다는 것(오버로딩, 다형성이라고 하죠?)은 c++의 기본이죠.

그래서, 윈도우 프로시저는 클래스의 멤버로 만들면 안됩니다.
여기에 대한 단 하나의 예외도 원문에서는 설명해놨던데.. 그건 집어치웁시다.
뭐 원래 이 팁을 쓰는 취지와는 관계없이 너무 깊이 들어가야 하니까요.


어쨌든.. 이렇게 해서 리스트뷰의 배경을 그리는 멋진 팁을 모두 끝냈습니다.
알려둘 것은.. 원문의 소스를 알기 쉽고 간략하게 만들기 위해 여기저기
손을 좀 많이 봤다는 거죠. 뭐, 근본적으로 차이나는 부분은 없습니다.

그럼.. 내일 또 뵙죠..
여러분.. 안녕히..~ ^^




독립문에서 임펠리테리였습니다.

(cbuilder, cbuilder@thrunet.com)



+ -

관련 글 리스트
1432 [질문] 임프님 1395 답변에 대해서.../프포 zpiknc86 3205 1999/06/12
1437     [답변] ZPIKNC86/ 임프님 1395 답변에 대해서.../프포 박지훈.임프 4364 1999/06/14
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.