함수 포인터

함수 포인터란 함수를 가리키는 포인터를 말한다.
(선언형식)
함수의 type  (*함수포인터명) (함수전달인자);

#include <stdio.h>
#include <stdlib.h>

int plus(int, int);
int minus(int,int);
int myCalc(int,int,int (*)(int,int)); // 선언
int main(int argc, char *argv[])
{
  int a=10, b=20;
  /*
  int hap;
  int (*fptr)(int,int); //함수포인터
 
  fptr = plus; //함수포인터(fptr)로 함수(plus) 가리키게 한다.
  hap = fptr(a,b);
 
  printf("%d + %d = %d\n", a,b,hap);
 
 
  int (*fptr2)(int,int);
  fptr2 = minus;
  printf("%d - %d = %d\n", a,b,fptr2(a,b));
  */
 
  myCalc(a,b,plus); //plus는 함수명
  myCalc(a,b,minus); //minus는 함수명
  system("PAUSE");   
  return 0;
}
int minus(int a,int b)
{
 return a-b;  
}

int plus(int a, int b)
{   
    return a+b;   
}
int myCalc(int a,int b,int (*fptr)(int,int) ) //fptr=함수포인터
{
 printf("%d  %d = %d\n", a,b,fptr(a,b)); //fptr=plus(), minus()
 return 0;  
   
}

void형 포인터

void포인터는 가리키는 대상체의 type에 제한이 없는 포인터를 말한다.
(선언형식)
void* 포인터명;

void *p;
int intArray[5] = {1,2,3,4,5};
double doubleArray[2][3] = {{1.5,2.5,3.5},{4.5,5.5,6.5}};
char *charPrtArray[4] = {"apple", "pear","banana","pineapple"};

p = intArray;
printf("%d\n", *((int*)p+2));

p=doubleArray;
printf("%lf\n", *(*((double(*)[3])p+1)+2) );

p=charPrtArray;
printf("%s\n", *((char**)p+2) );



포인터배열과 배열을 가리키는 포인터

int **p1; // p1은 1차원 int형 포인터를 가리키는 더블포인터
int *p2[5];  //p2는 요소가 5개인 배열로 각 배열의 요소는 int형 포인터다. ( 포인터 배열 )
int (*p3)[5]; //p3은 포인터로 열이 5개인 2차원 배열을 가리키는 포인터 변수로 배열의 각 요소는 모두 int형이다.



#include <stdio.h>
#include <stdlib.h>

    int num[3][4];
    int sum_h[3],sum_y[4];
int main(int argc, char *argv[])
{

    int max=0;
    int temp=0;   
    int input[10]={0,};
    int i,j;
    for(i=0; i<10; i++)
    scanf("%d", &input[i]);
    fflush(stdin);

    //선택정렬
    for(i=0; i<9; i++)
    {    //1 3 2 4 5 6 7 8 9 50
        max = i;
        for(j=i+1; j<10; j++)
        {
            if(input[max]<input[j])
            {
            max = j; //최대값이 있는 인덱스 저장
            }   
           

        }

        temp = input[i];
        input[i] = input[max];
        input[max] = temp;

    }

    for(i=0; i<10; i++)
        printf("%d ", input[i]);
 
  system("PAUSE");   
  return 0;
}

static 변수는 외부와 내부로 구분되어 진다.

static int b; //외부 정적변수
{
 static int a; //내부 정적변수
}

외부 정적변수와 내부 정적변수는 활동범위에 제약이 있다. 외부 정적변수는 자기가 속해 있는 모듈안에서만 사용할 때 사용한다. 모든 모듈에서 사용하고 싶을 땐 extern 키워드를 사용해야 한다. 내부 정적변수는 함수안에서 사용시 그 함수안에서만 사용이 가능하다. 즉 C++로 따졌을 때 class에서 private 변수로 사용하여 보호하는 것과 비슷한 맥락이다.

#include <stdio.h>
#include <stdlib.h>

void sub();

int main(int argc, char *argv[])
{
    int i;
    for(i = 0; i<3; i++)
    {
     sub();
     printf("main i : %d\n\n", i);         
    } 
  system("PAUSE");   
  return 0;
}

void sub()
{
     static int i=1;
     auto int k =3;
     printf("sub i : %d\t k : %d \t\n", i++,k++);
    
     }

다음은 틀리기 쉬운 기억클래스 예제이다. 결과값을 예측해볼것.

#include <stdio.h>
#include <stdlib.h>

void sub();

int reset(), next(int), last(int), now(int);
int i=1;


int main(int argc, char *argv[])
{
 auto int i,j;
 i = reset();
 
 for(j=0; j<3; j++)
 {
          printf("i = %d\t j = %d\n", i,j);
          printf("next(i) = %d\n", next(i));
          printf("last(i) = %d\n", last(i));
          printf("now(i+j) = %d\n\n", now(i+j));
          }
  system("PAUSE");   
  return 0;
}

int reset(){return(i);}
int next(int j){return (j=i++);}
int last(int j){
    static int i=10;
    return (j=i--);
    }
int now(int i)
{
    auto int j=10;
    return (i=j+=i);
   
    }
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

void gotoxy(int x, int y);
void blue();
void red();
void white();
void textcolor(int i);

int main(int argc, char *argv[])
{
      red();
      gotoxy(10,2); //10열 2행
      printf("hi\n");
     
      int i;
      for(i=0; i<=15; i++)
      {
       textcolor(i);
       printf("testing\n");       
      }


//      system("cls");   
  system("PAUSE");   
  return 0;
}


void gotoxy(int x, int y)
{
 COORD Pos={x - 1 , y - 1};
 SetConsoleCursorPosition(
                          GetStdHandle( STD_OUTPUT_HANDLE ), Pos
 );    
}

void blue()
{
 SetConsoleTextAttribute(
                          GetStdHandle( STD_OUTPUT_HANDLE ),
                          FOREGROUND_INTENSITY | FOREGROUND_BLUE
                          );
                        
     
}

void red()
{
 SetConsoleTextAttribute(
                          GetStdHandle( STD_OUTPUT_HANDLE ),
                          FOREGROUND_INTENSITY | FOREGROUND_RED
                          );
                        
     
}
void white()
{
 SetConsoleTextAttribute(
                          GetStdHandle( STD_OUTPUT_HANDLE ),
                          FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE 
                          );
                        
     
}
void textcolor(int i) //0:검정 ~ 15:흰색
{
 SetConsoleTextAttribute(
                         GetStdHandle(STD_OUTPUT_HANDLE), i
                         );
 
}

#include <stdio.h>
#include <stdlib.h>

int to_binary(int num);
int to_hex(int num);


int main(int argc, char *argv[])
{
    int num=0;
    printf("이진수화 할 십진수를 입력하시오 : ");
    scanf("%d", &num);
    //int num = 123;
       
   
    printf("십진수 %d 는 이진수로 : ", num);
    to_binary(num); // num=123;
    printf("\n");
   
    printf("16진수로 : 0x");
    to_hex(num);
    printf("\n");
   
   
 
  system("PAUSE");   
  return 0;
}


int to_binary(int num) //num = 123
{
   
    int remain = num%2; //1
   
   
    if(num >=2) // 123, 61, 30,
           {
            to_binary(num/2);    //61 , 30, 15, 7
               }
    printf("%2d", remain);        //마지막 호출부터 나옴
       
}

int to_hex(int num) //num = 123
{
   
    int remain = num%16;
   
   
    if(num >=16)
           {
            to_hex(num/16);    
    }
    /*
    switch(remain)
    {
     case 10:
          printf("a");
          break;
          case 11:
               printf("b");
               break;
               case 12:
                    printf("c");
                    break;
                    case 13:
                         printf("d");
                         break;
                         case 14:
                              printf("e");
                              break;
                              case 15:
                                   printf("f");
                                   break;
          default:
                  printf("%2d", remain);  
                  break;
                  }
     */
         printf("%2c", (remain < 10) ? remain +'0' : remain - 10 + 'a');
         //0은 48
        //printf("%d", '0');
}

GDI+는 GDI의 매핑모드와는 비교할 수 없을 정도의 좌표계 변환 기능을 제공하는데, DirectX 라이브러리가 제공하는 수준에 근접하는 정도이다. 따라서 이것을 제대로 이해하고 활용하려면 행렬의 변환과 삼각함수의 지식이 필요하다. 다음은 Matrix 클래스와 Graphics 클래스의 메서드를 이용하여 변환된 좌표계를 활용하여 동일한 사각형을 좌표계를 바꾸어 두 번 그린 예이다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeHighQuality);

 Pen pen1(Color::Black,3);
 Pen pen2(Color::Red,3);
 Pen penLine(Color::Green, 1);
 penLine.SetDashStyle(DashStyleDot);

 graphics.DrawRectangle(&pen1, 30,30,150,150);

 Matrix transformMatrix;
 transformMatrix.Translate(100.0f,100.0f); //100,100좌표가 0,0이 되도록 변환한다.
 transformMatrix.Rotate(45.0f); //45도 만큼 각도를 회전시킨다.

 graphics.SetTransform(&transformMatrix);
 graphics.DrawLine(&penLine, Point(-200,0), Point(200,0));
 graphics.DrawLine(&penLine, Point(0,-200), Point(0,200));
 graphics.DrawRectangle(&pen2, Rect(30,30,150,150));

}

다음 코드는 Graphics 클래스의 TranslateTransform() 메서드는 기준 좌표를 변경하는 함수이며, RotateTransform() 메서드는 좌표계를 주어진 각도만큼 회전시키는 함수다. 따라서 단순한 2차원 회전 변환이라면 두 함수를 활용하자.


void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeHighQuality);

 Pen pen1(Color::Black,3);
 Pen pen2(Color::Red,3);
 Pen penLine(Color::Green, 1);
 penLine.SetDashStyle(DashStyleDot);

 graphics.DrawRectangle(&pen1, 30,30,150,150);
 /*
 Matrix transformMatrix;
 transformMatrix.Translate(100.0f,100.0f);
 transformMatrix.Rotate(45.0f);
 */

 graphics.TranslateTransform(100.0f,100.0f);
 graphics.RotateTransform(45.0f);

 //graphics.SetTransform(&transformMatrix);
 graphics.DrawLine(&penLine, Point(-200,0), Point(200,0));
 graphics.DrawLine(&penLine, Point(0,-200), Point(0,200));
 graphics.DrawRectangle(&pen2, Rect(30,30,150,150));

}

다각형을 그릴 때는 필요한 좌표들을 배열로 열거하여 그리게 되며, 카디널 곡선을 그릴때도 방법은 사실상 동일하다. 그러나 이것만으로는 특정 영역을 만들어내는 것이 어려우며, 일반 GDI 프로그래밍에서도 곡선을 이용해 영역을 만드는 과정도 쉽지가 않다. 때문에 마스크 이미지를 만들어서 비트맵을 이용한 영역 만들기를 사용한다. 특히 스킨과 다양한 곡선 모양의 윈도우를 지원하는 프로그램을 개발할려면 이런 기법은 아주 당연한것처럼 여겨지고 있으며 GDI+ 에서도 이 기능을 지원하는 것은 물론 더욱 개선된 기능을 제공한다.

경로(Path)

일반 GDI 프로그래밍에서 경로를 지정하는 경우는 선과 도형 그리기에서 꺽인 부분을 어떻게 렌더링할 것인지 결정할 때였으나 GDI+ 에서는 이런 기본적인 경우말고도 다양한 경우에 경로를 지정할 수 있다. 심지어 문자열 그리기도 경로로 지정할 수 있다. 다음은 사각형, 원,문자열을 하나의 경로로 연결하여 그린 예이다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeHighQuality);

 Pen pen(Color(255,0,0,0),3);
 SolidBrush sbrush(Color(255,192,192,192));
 
 GraphicsPath path;
 path.AddRectangle(Rect(10,10,100,100));
 path.AddEllipse(Rect(70,70,120,120));

 FontFamily fontfamily(_T("Arial"));
 path.AddString(_T("Test String"), -1, &fontfamily, FontStyleBold, 48, Point(20,20), NULL);
 path.SetFillMode(FillModeWinding);

// 실제 그리는 코드, 두 개의 함수의 실행순서를 바꾸면 결과가 달라짐에 유의하자.
 graphics.DrawPath(&pen, &path);
 graphics.FillPath(&sbrush, &path);

}

영역(Region)

GDI+의 영역은 Region 클래스로 객체화되어 있으며 사용원리는 일반 GDI 프로그래밍과 같다. 다음 코드는 앞서 작성한 코드가 만든 경로를 하나의 영역으로 만든 후 문자열을 출력하여 일부만 화면에 나오도록 한 예이다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeHighQuality);

 Pen pen(Color(255,0,0,0),3);
 SolidBrush sbrush(Color(255,192,192,192));
 
 GraphicsPath path;
 path.AddRectangle(Rect(10,10,100,100));
 path.AddEllipse(Rect(70,70,120,120));
 FontFamily fontfamily(_T("Arial"));
 path.AddString(_T("Test String"), -1, &fontfamily, FontStyleBold, 48, Point(20,20), NULL);
 path.SetFillMode(FillModeWinding);
 //graphics.FillPath(&sbrush, &path);
 graphics.DrawPath(&pen, &path);


 //GraphicsPath 클래스 객체의 주소를 인자로 Region 클래스 객체를 생성 후 SetClip() 을 사용하면 영역이 설정된다.
 Region Rgn(&path);
 graphics.SetClip(&Rgn);//영역설정

 Font font(_T("Arial"), 100, FontStyleBold, UnitPixel);
 HatchBrush htBrush(HatchStyleDiagonalBrick, Color::Black, Color::Chocolate);

 //높이가 100셀인 문자열을 설정 영역과 겹치는 부분만 출력한다.
 graphics.DrawString(_T("Test String2"), -1, &font, PointF(10,10), &htBrush);

}


만일 두 영역을 합치거나 빼는 등의 연산을 하고 싶다면 적절한 메서드를 사용하면 되는데 다음은 이를 보여주는 코드다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeHighQuality);

 Pen pen(Color(255,0,0,0),3);
 SolidBrush sbrush(Color(255,192,192,192));

 Region Rgn1(Rect(10,10,140,140));
 Region Rgn2(Rect(80,50,140,140));

 //Rgn2.Xor(&Rgn1); //겹치는 부분제외하고 출력
 //Rgn2.Intersect(&Rgn1); //겹치는 부분만 출력
 Rgn2.Exclude(&Rgn1); //빼고 남은 것 출력
 graphics.FillRegion(&sbrush, &Rgn2);

}


'Windows > MFC' 카테고리의 다른 글

GDI+ , 좌표계 변환  (0) 2011.12.13
GDI+ , 글꼴과 문자열 그리기 그리고 출력 형식  (0) 2011.12.13
GDI+ , 외부 이미지 파일의 처리 (Image Class)  (0) 2011.12.13
GDI+ , 브러쉬(Brush)  (0) 2011.12.13
GDI+ , 도형 그리기  (0) 2011.12.13

Graphics 클래스의 DrawString() 메서드를 이용하면 문자열을 그릴수 있다. 다음은 그 예이다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);

 LinearGradientBrush lgBrush(Point(0, 0), Point(0, 200),
  Color::WhiteSmoke, Color::Chocolate);
 graphics.FillRectangle(&lgBrush, Rect(0, 0, 600, 150));

 Font font(_T("Arial"), 100, FontStyleBold, UnitPixel); //Pixel단위로 100만큼 bold를 적용하여 Arial 글씨체로 적용하라
 PointF ptText(10.0f, 10.0f);
 HatchBrush brush(HatchStyleSmallCheckerBoard,
       Color(255, 128, 0, 0), Color::Transparent);  //붉은색으로 그리되 배경을 투명하게 처리
 graphics.DrawString(_T("Test String"), -1, &font, ptText, &brush); //빗살무늬브러시를 적용
//-1이면 문자열이 NULL로 끝나는 것으로 판단하며 값을 명시하면 해당 길이만큼 출력
//ptText는 문자열을 그릴 좌표를 담은 PointF 클래스 객체에 대한 참조(&)
//&brush는 문자열을 그릴 때 어떤 색상으로 할 것인지 명시 
 


}

출력 형식

일반 GDI 에서 DrawText() 함수를 이용하면 특정 사각형을 기준으로 문자열을 정렬하여 출력이 가능하다. GDI+ 에서도 이와 같은 출력 형식을 지정할 수 있는데 특이한 점은 출력 형식 자체가 StringFormat 클래스로 객체화되어 있다는 것이다. 그리고 DrawString() 클래스는 당연히 StringFormat() 클래스의 객체를 이용한 그리기를 지원한다. 다음은 그 예이다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);

 /*
 LinearGradientBrush lgBrush(Point(0, 0), Point(0, 200),
  Color::WhiteSmoke, Color::Chocolate);
 graphics.FillRectangle(&lgBrush, Rect(0, 0, 600, 150));

 Font font(_T("Arial"), 100, FontStyleBold | FontStyleItalic, UnitPixel);
 PointF ptText(10.0f, 10.0f);
 HatchBrush brush(HatchStyleSmallCheckerBoard,
       Color(255, 128, 0, 0), Color::Transparent);
 graphics.DrawString(_T("Test String"), -1, &font, ptText, &brush);
 */

 Font font(_T("Arial"), 50, FontStyleBold, UnitPixel);
 SolidBrush sbrush(Color::Black);
 Pen pen(Color::Blue);

 StringFormat format;
 format.SetAlignment(StringAlignmentCenter); //가로와 세로를 기준으로 문자열을 어떻게 정렬할것인가 명시
 format.SetLineAlignment(StringAlignmentCenter);
 //StringAlignmentNear는 수평기준으로 왼쪽정렬
 //StringAlignmentFar는 수평기준으로 오른쪽정렬

 format.SetFormatFlags(StringFormatFlagsDirectionVertical); //문자열을 세로로출력
 graphics.DrawRectangle(&pen, RectF(10,10,200,200));
 graphics.DrawString(_T("Test String2"), -1, &font, RectF(10,10,200,200), &format, &sbrush);
 //RectF 기준으로 정렬된다.

 format.SetFormatFlags(format.GetFormatFlags() & 0); //플래그값 앤드연산으로 없애기
 format.SetTrimming(StringTrimmingEllipsisCharacter); //너무 길면 ...으로 표시하라
 graphics.DrawString(_T("Test String with Trimming"), -1, &font, RectF(300,300,300,50), &format,&sbrush);


 format.SetHotkeyPrefix(HotkeyPrefixShow); //바로가기 키를 &으로 된것을 만든다. 메뉴인터페이스 구현시 사용
 graphics.DrawString(_T("Test &String"), -1, &font, RectF(300,10, 300,50), &format, &sbrush);

 

}

'Windows > MFC' 카테고리의 다른 글

GDI+ , 좌표계 변환  (0) 2011.12.13
GDI+ , 경로와 영역( Path , Region )  (0) 2011.12.13
GDI+ , 외부 이미지 파일의 처리 (Image Class)  (0) 2011.12.13
GDI+ , 브러쉬(Brush)  (0) 2011.12.13
GDI+ , 도형 그리기  (0) 2011.12.13

사용자가 임의로 디자인을 변경할 수 있도록 스킨 형태의 인터페이스를 제공해야 하는 경우에는 내장된 리소스가 아니라 외부 이미지를 가져다 사용하는 것이 일반적이며 이미지를 보는 프로그램을 개발해야 하는 경우 당연히 출력 대상은 이미지 파일이 된다. GDI+ 에서는 외부 이미지 파일을 처리하려고 Image 클래스와 파생 클래스인 Bitmap 클래스를 제공하며 기본적으로 이미지를 로드하거나 저장할 때는 Image 클래스 객체를 사용하고, 이미지 프로세싱 같은 추가 기능이 필요하면 Bitmap 클래스 객체를 사용한다. 또한 다른 Image 클래스의 파생 클래스로 MetaFile 클래스가 있으며 WMF 파일이나 EMF 파일을 손쉽게 처리할 수 있다.



Image 클래스

GDI+ 의 Image 클래스는 외부 이미지 파일을 로드하여 처리하는 기본(base) 클래스이며 이와 비슷한 클래스로 CImage 클래스가 있는데 이 두 클래스가 지원하는 메서드는 상당부분 유사하다. 두 클래스는 결과적으로 같은 처리 루틴을 갖기 때문일 것이며 다음 코드는 Image클래스를 이용하여 JPG 파일을 로드 및 출력하는 예이다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);

 Image Image(_T("Test.jpg"));
 
 Point pts[] ={
  Point(200,0), //왼쪽위
  Point(Image.GetWidth(),0), //오른쪽위
  Point(0,Image.GetHeight()) //왼쪽아래
 };

 graphics.DrawImage(&Image, pts, 3); //세 개의 좌표로 이미지를 출력하라 즉 평행사변형

 

}

Bitmap 클래스

Image클래스의 파생 클래스로 외부 파일을 로드하는 기능 외에 비트맵 핸들(HBITMAP)이나 아이콘 핸들(HICON)에 대한 비트맵 이미지를 생성하고 관리하는 기능을 제공하는데, 만일 외부 파일이 아니라 프로젝트에 등록된 비트맵 리소스를 로드하여 화면에 출력할 때 Bitmap 클래스를 활용하면 편리하다. 다음은 Bitmap 클래스를 이용해 출력하는 예이다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting

 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);

 Bitmap bmp(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_Test));
 graphics.DrawImage(&bmp, 10,10);

 

}


CachedBitmap 클래스

JPG든 GIF파일이든 화면에 출력될 때는 모두 BMP 파일이 되며, 좀더 정확히는 장치에 의존적인 비트맵이라고 할 수 있다. 다만 장치 의존적인 비트맵으로 출력한다는 말은 출력을 위해서 비트맵 이미지의 변환 ( DIB -> DDB ) 과정이 필요하다는 것을 의미한다. 이는 Image 클래스의 파생 클래스인 Bitmap 클래스가 빠른 이미지 처리를 위해서 이미지 캐싱(Caching)을 지원하기 때문이다.

GDI 에서의 전형적인 이미지 출력 과정은 CBitmap 클래스 객체를 생성하여 비트맵 이미지(DIB)를 로드하고 화면 DC에 호환되는 메모리 DC를 만들어 SelectObject() 한 다음 BitBlt() 같은 함수를 이용해 화면 DC로(DDB) 출력한다. 이 과정에서 비트맵 이미지의 변환이 이루어지며 만일 같은 비트맵을 여러번 출력한다면 이 과정은 불필요하게 된다. 이런 불필요한 연산을 최적화하는 것이 비트맵 캐싱이며, CachedBitmap 클래스로 이 기능을 구현한다. 비트맵 캐싱이란 출력할 비트맵을 DDB로 변환해서 보관! 하는 것을 의미하며 따라서 화면에 출력할 때 별도의 변환 과정없이 화면 출력이 가능하므로 훨씬 효율적이다.
다음은 CachedBitmap 클래스를 이용해 이미지를 출력하면서 코드수행시간을 측정한 예이다. 약 3배정도의 차이가 난다.


void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);


 Bitmap bmp(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_Test2));
 CachedBitmap cachedbmp(&bmp, &graphics);

 /*코드 수행시간 계산코드*/
 CString strTmp=_T("");
 LARGE_INTEGER InFreq, InStart, InEnd, InResult;
 ::QueryPerformanceFrequency(&InFreq);

 ::QueryPerformanceCounter(&InStart);
 graphics.DrawImage(&bmp, 10,10);
 ::QueryPerformanceCounter(&InEnd);

 InResult.QuadPart = (InEnd.QuadPart - InStart.QuadPart) * 1000000 / InFreq.QuadPart;
 strTmp.Format(_T("DrawImage() Counter %u "), InResult.QuadPart);
 dc.TextOut(10,10,strTmp);

 ::QueryPerformanceCounter(&InStart);
 graphics.DrawCachedBitmap(&cachedbmp, bmp.GetWidth() + 20 , 10);
 ::QueryPerformanceCounter(&InEnd);

 InResult.QuadPart = (InEnd.QuadPart - InStart.QuadPart) * 1000000 / InFreq.QuadPart;
 strTmp.Format(_T("Drawcachedbitmap() Counter %u "), InResult.QuadPart);
 dc.TextOut(bmp.GetWidth() + 20 ,10,strTmp);
 
 


}


















 

GDI 와 기능적으로 같지만 50가지가 넘는 다양한 패턴을 제공하며, 그라데이션 채우기도 가능하다. 다음은 청색의 단색 브러시를 만들어서 도형을 채우는 예이며 주의할 점은 도형을 채우는 함수는 그리는 함수와 다르다는 것이다.

void CGdiPlusDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
Graphics graphics(dc);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);

//SolidBrush 클래스는 단색 브러시를 객체화한 클래스이다.
SolidBrush solidbrush(Color(255,0,0,255));
//다음은 FillXXX() 로 되어있는데 이는 앞서 배운 DrawXXX() 메서드에 대응한다.
graphics.FillRectangle(&solidbrush, 20,20,100,100);
graphics.FillEllipse(&solidbrush, 150,20, 100,100);
graphics.FillPie(&solidbrush, 300,20,100,100,180.0f, 90.0f);
//graphics.FillClosedCurve(&solidbrush,pts,6,FillModeAlternate);
}




빗살 무늬 브러쉬

다음 코드는 빗살 무늬 브러쉬로 어떤 것이 있는지 나열한 예이다.

void CGdiPlusDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
Graphics graphics(dc);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);

int nStyle = HatchStyleHorizontal;
int nCounter = 0;

for(int y=0; y<6; ++y)
{
for(int x=0; x<10; ++x)
{

HatchBrush hatchbrush(
(HatchStyle)(nStyle+nCounter), //HatchStyle 열거형
Color::Black, //전경색
Color::Transparent); //배경색

graphics.FillRectangle(&hatchbrush,
x*50+20, //x좌표
y*40+20, //y좌표
40,30); //너비 및 높이
nCounter++;
if(nCounter >= HatchStyleMax) break;

}
}



그라데이션 브러시

그라데이션 브러시를 활용하면 CDC 클래스의 GradientFill() 메서드를 이용하는 코드를 대체할 수 있으며 영역을 칠하는 개념이 아닌 브러시로 객체화됨으로써 그라데이션 채우기를 적용하는 범위가 매우 다양해졌다. 다음은 그라데이션 브러시를 생성하여 도형을 채우는 예제다.

void CGdiPlusDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
Graphics graphics(dc);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);

dc.TextOut(25,25,_T("Gdi+ gradient test string"));

LinearGradientBrush lgBrush(
Point(0,0), //0,0좌표에서
Point(100,100), //100,100 좌표를 향해 그라데이션 채우기를 적용하라.
Color(128,221,236,255),
Color(255,86,125,204)
);
// 즉 두 좌표가 만든 선과 수직인 선을 기준으로 기울기가 결정한다.


//LinearGradientBrush lgBrush(
// Rect(10,10,200,200),
// Color(128,221,236,255),
// Color(255,86,125,204),
// LinearGradientModeVertical //수직으로 주자.
// );

//다음이 직접 각도를 명시하는 직관적인 코딩이다.
//LinearGradientBrush lgBrush(
// Rect(10,10,200,200),
// Color(128,221,236,255),
// Color(255,86,125,204),
// 90.0f, FALSE // 직접 각도를 45도로 주자.
// );

graphics.FillRectangle(&lgBrush, 0,0,600,200);


}



텍스처(Texture) 브러쉬

비트맵 패턴 브러시를 만드는 것은 GDI 에서도 가능했으며, 단순히 바둑판 형식으로 배열하는 것만 지원했었다. 그리고 JPG나 GIF 파일을 로드하여 브러시로 만드는 것에도 복잡한 과정이 필요했으나 GDI+ 에서는 Image 클래스를 활용하여 다양한 이미지 리소스를 활용하도록 지원이 된다. 또한 TextureBrush 클래스를 제공하여 이미지의 배열 규칙의 다양성을 확대하고 있다. 다음 코드는 뷰 윈도우의 클라이언트 영역을 칠하는 예이며, 화면을 둘로 나누어 왼쪽은 GDI처럼 바둑판식으로, 오른쪽은 이미지 배열 모드를 변경하여 수평/수직으로 뒤바뀐 패턴으로 배열한다.

void CGdiPlusDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
Graphics graphics(dc);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);

Image Image(_T("Texture.jpg"));
TextureBrush txBrush(&Image, WrapModeTile);

CRect rectClient;
GetClientRect(&rectClient);
graphics.FillRectangle(&txBrush,0,0,rectClient.Width()/2, rectClient.Height());

txBrush.SetWrapMode(WrapModeTileFlipXY); //FlipX는 좌우를 뒤집어서 출력하라. FlipY는 상하를 뒤집어라.
graphics.FillRectangle(&txBrush,rectClient.Width()/2, 0, rectClient.Width()/2, rectClient.Height());


}






사각형 그리기

Graphics 클래스의 DrawRectangle() 메서드를 이용하면 손쉽게 그릴 수 있으며, 펜의 속성을 변경하여 사각형의 모서리가 각진 것과 둥근 것을 그릴 수 있다. 다음은 둥근 모서리가 확연히 드러나게 펜의 두께를 20으로 하였다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);


 Pen BlackPen(Color(255,0,0,0), 20.0f);
 graphics.DrawRectangle(&BlackPen, 30,30,100,100);

 BlackPen.SetLineJoin(LineJoinRound);
 graphics.DrawRectangle(&BlackPen, 170,30,100,100);


}



원 그리기

DrawEllipse() 메서드를 이용하여 원과 타원을 그릴 수 있으며 다음 코드는 원과 폭이 두 배인 타원을 그린다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);


 Pen BlackPen(Color(255,0,0,0), 10.0f);
 graphics.DrawEllipse(&BlackPen, 30,30,100,100);
 graphics.DrawEllipse(&BlackPen, 150,30,200,100);


}



원호와 부채꼴 그리기

원 그리기가 사각형 그리기와 비슷한 것처럼 원호나 부채꼴도 그리는 방법이 유사하다. 그리고 원호나 부채꼴의 시작과 끝을 명시하는 방법도 사각형을 그릴 때처럼 시작 각도를 기준으로 벌어진 각도를 명시한다. 다음은 원 하나를 그리고 여기에 원호와 부채꼴을 겹쳐서 그린다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);


 Pen BlackPen(Color(255,0,0,0), 2.0f);
 Pen GrayPen(Color(255,192,192,192), 15.0f);

 graphics.DrawEllipse(&GrayPen, 30,30,150,150);


 graphics.DrawArc(&BlackPen, 30,30,150,150, 0.0f, 90.0f);
 graphics.DrawPie(&BlackPen, 30,30,150,150, 180.0f, 90.0f);

 // 수평선을 기준으로 시계 방향으로 각도가 증가하는데 0.0f와 90.0f 의 의미는
 // 0 에서 원호가 시작해서 90 만큼 원호를 그린 후 끝난다는 의미가 된다.

}



다각형 그리기

DrawPolygon() 메서드를 이용하면 사각형이나 원을 제외한 나머지 다각형을 그릴 때 유용하다. 이 함수의 인자나 사용법은 DrawCurve() 함수와 거의 같다. 다음 코드는 'ㄱ'자 모양의 다각형을 그린 예이다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeAntiAlias);


 Pen BlackPen(Color(255,0,0,0), 2.0f);

 Point pts[6] = {
      Point(30,30),
      Point(180,30),
      Point(180,130),
      Point(130,130),
      Point(130,80),
      Point(30,80)
 
 };


 graphics.DrawPolygon(&BlackPen, pts, 6);


}















void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);

 Pen BluePen(Color(255,0,0,255), 20.0f);  //Color의 첫번째 인자는 알파채널이고 뒤부터는 rgb값.
 Pen BlackPen(Color(128,0,0,0), 20.0f);   //뒤의 20.0f 값인 REAL 형은 float형을 다른 이름으로 재정의한 것이며,
// 부동 소수점단위로 펜의 두께를 명시하는 역활을 한다.

 graphics.DrawLine(&BluePen, Point(10,50), Point(210,50));
 graphics.DrawLine(&BlackPen, Point(10,250), Point(210,250));
}


Graphics 클래스는 CDC 클래스처럼 그리기의 대상을 추상화한 GDI+ 클래스이며, 대부분의 그리기 코드는 이 클래스의 메서드에 집중된다. 그러므로 SelectObject() 함수를 호출하지 않아도 된다.





꺽임 처리

다음은 만일 여러 직선을 이어서 그리거나 다각형을 그릴 때 각각의 직선이 만나는 부분이 발생하면 펜의 설정에 따라 적절히 렌더링을 하게 된다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);

 Pen BluePen(Color(255,0,0,255), 20.0f);

 Point pt1(30,10);
 Point pt2(30,110);
 Point pt3(230,20);
 Point pt4(230,120);
 Point points[4] = {pt1,pt2,pt3,pt4};

 BluePen.SetLineJoin(LineJoinRound);//꺽인 부분을 어떻게 렌더링할지 설정.
 //GdiplusEnums.h 파일에 열거형으로 정의되어 있다.
 BluePen.SetDashStyle(DashStyleDot); // 점선으로 변환.
 graphics.DrawLines(&BluePen, points, 4);
}


끝부분 처리

일반 GDI 프로그래밍에서는 펜의 시작 부분(start cap)과 끝 부분(end cap)을 렌더링하는 방법이 세 종류였으나, GDI+ 에서는 다양한 모양을 구현할 수 있다. 다음은 펜의 시작과 끝 부분을 둥근 원과 화살표로 렌더링하는 예다.

void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);

 Pen BluePen(Color(255,0,0,255), 20.0f);

 Point pt1(30,10);
 Point pt2(30,110);
 Point pt3(230,20);
 Point pt4(230,120);
 Point points[4] = {pt1,pt2,pt3,pt4};

 BluePen.SetLineJoin(LineJoinRound);

 BluePen.SetStartCap(LineCapRoundAnchor); //둥근 원으로 시작
 BluePen.SetEndCap(LineCapArrowAnchor); //화살표로 끝

 graphics.DrawLines(&BluePen, points, 4);
}


끝부분 처리와 안티 에일리어싱

그래프를 개발할 때 각각의 선을 잇는 곡선으로 그래프를 만드는데, 이때 Graphics 클래스의 DrawCurve() 메서드를 활용하면 구현이 쉽다. 다음은 안티 에일리어싱까지 적용한 코드다. 안티 에일리어싱은 단 한줄로 끝난다.

 void CGdiPlusDemoView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 Graphics graphics(dc);
 graphics.SetSmoothingMode(SmoothingModeHighQuality); //안티 에일리어싱 적용

 Pen RedPen(Color(255,255,0,0), 2.0f);
 Pen GreenPen(Color(255,0,255,0), 2.0f);
 Pen BluePen(Color(255,0,0,255), 2.0f);
 
 Point pts[6] = { 
      Point(10,150),
      Point(110,10),
      Point(170,250),
      Point(220,120),
      Point(270,150),
      Point(350,150)
    };

 graphics.DrawCurve(&RedPen, pts, 6, 0.0f); //마지막인자는 tension 값으로 곡선을 그릴 때 끝을 강제로 늘려준다.
 graphics.DrawCurve(&GreenPen, pts, 6, 0.5f);
 graphics.DrawCurve(&BluePen, pts, 6, 1.0f);

//곡선을 그리는 함수로는 DrawBezier() 라는 함수도 있으며, 4개의 좌표를 이용하여 베지어 곡선을 그린다.

}






'Windows > MFC' 카테고리의 다른 글

GDI+ , 브러쉬(Brush)  (0) 2011.12.13
GDI+ , 도형 그리기  (0) 2011.12.13
데이터베이스 :: OLE-DB  (0) 2011.11.23
MFC 소켓 프로그래밍 ( TCP/IP 이용 )  (0) 2011.11.20
데이터베이스 ( Database ) - DBMS,ODBC  (0) 2011.11.20

OLE-DB 는 ODBC 보다 훨씬 진보된 개념으로 매우 강력한 데이터베이스를 구축할 수 있게 해주는데, OLE-DB는 텍스트뿐만 아니라 이미지까지도 포함시킬 수 있다. OLE-DB는 SQL 을 지원하지 않는 데이터베이스까지 확장한 개념이며, 이를 구현하고자 COM을 사용한다. OLE-DB는 내부적으로 공급자와 소비자로 나뉘며, 공급자는 데이터베이스 제작사에서 ODBC 드라이버를 제공하듯 제작사가 제공하는 인터페이스이고, 소비자는 제공받은 인터페이스를 활용하여 실제로 이비를 사용하는 인터페이스이다. OLE-DB 코드는 각종 템플릿 클래스들로 구성되어 있으며 모두 ATL(Active X Template Library) 이다. 그러므로 코드를 작성할 때 객체화된 클래스가 어떤 역활을 하는지 이해해야 한다.


 COleDBRecordView::OnInitialUpdate();

 if(m_List.GetHeaderCtrl()->GetItemCount() <= 0)
 {
  m_List.InsertColumn(0, _T("Name"), LVCFMT_LEFT, 100);
  m_List.InsertColumn(1, _T("Phone Number"), LVCFMT_LEFT, 150);
  m_List.InsertColumn(2, _T("Address"), LVCFMT_LEFT, 200);
  m_List.InsertColumn(3, _T("Age"), LVCFMT_LEFT, 80);

 }

 ListupAllRecords(); 

}


// COleDBDemo2View 메시지 처리기
void COleDBDemo2View::ListupAllRecords(void)
{
 CString strTmp=_T("");
 m_pSet->MoveFirst();

 do{
  m_List.InsertItem(0, m_pSet->m_Name, 0);
  m_List.SetItemText(0,1, m_pSet->m_PhoneNumber);
  m_List.SetItemText(0,2, m_pSet->m_Address);
  
  strTmp.Format(_T("%ld"), m_pSet->m_Age);
  m_List.SetItemText(0,3, strTmp);

  
  // movenext는 레코드가 있으면 S_OK 를, 없으면 DB_S_ENDOFROWSET 을 반환한다.
 }while(m_pSet->MoveNext() != DB_S_ENDOFROWSET);

 m_pSet->MoveFirst();
}

// 다음은 레코드를 추가하기 위해 DB 속성을 변경한다.

 void GetRowsetProperties(CDBPropSet* pPropSet) //Set.h
 {
  
  //DB 에 속성을 추가함,
  //1번째 인자는 속성의 인덱스값으로 추가한 DBPROP_UPDATABLITY 속성은 디비 접근권한과 관련이 있다.
  //2번째 인자는 새 로크드를 추가,갱신, 지우는 권한이며 사실상 모든 권한을 준 것이다.
  pPropSet->AddProperty(DBPROP_CANFETCHBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
  pPropSet->AddProperty(DBPROP_CANSCROLLBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
  pPropSet->AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);

 }




class COleDBDemo2Set : public CCommand<CAccessor<COleDBDemo2SetAccessor> >
{ //CCommand 부분에 올래는 CTable 이었음. CTable 클래스를 이용하면 레코드를 단순히 보는 것에 그치지만
//CCommand 를 쓰면 SQL 문을 실행할 수 있기 때문이다.
public:
 HRESULT OpenAll()
 {
  HRESULT hr;
  hr = OpenDataSource();
  if (FAILED(hr))
   return hr;
~~~
}

 HRESULT OpenRowset(DBPROPSET *pPropSet = NULL)
 {
//  HRESULT hr = Open(m_session, L"UserData", pPropSet); // CTable의 Open메서드였는데 단순히 테이블을 연다.

  CString strSQL = _T("Select * from UserData");

// CCommand의 Open() 메서드는 첫번째 인자(세션)에 대해 세번째 인자로 주어진 권한으로 SQL 문을 실행함.

  HRESULT hr = Open(m_session, strSQL, pPropSet);
   ~~
}

그 밑에다가 다음처럼 RunSQL() 멤버 함수를 선언하고 작성한다.

 //OpenRowset() 함수와 같지만, 새로운 SQL 문을 실행하기에 앞서 CCommand::Close(),
 //ReleaseCommand()를 호출하여 앞서 실행한 SQL 문의 결과를 모두 정리한다.
 //내부적으로 Close() 함수는 관련된 레코드(Rowset)를 해제하고, ReleaseCommand()는 관련된 접근 지정자를 해제한다.
 
 HRESULT RunSQL(CString strSQL, DBPROPSET* pPropSet = NULL)
 {
  Close();
  ReleaseCommand();
  HRESULT hr = Open(m_session, strSQL, pPropSet);
  return hr;
 }

// 다음은 레코드를 추가하는 코드다.


void COleDBDemo2View::OnBnClickedButtonAddnew()
{
 CCommand<CDynamicAccessor> Cmd;
//CDynamicAccessor, 여러 접근 지정자 객체 중 하나로, 테이블의 구조를 모르는 상황에서 사용가능.


 //이 접근 지정자를 이용해 디비 접근시 어떤 권한을 갖도록 할 것인지 속성설정.
 CDBPropSet propset(DBPROPSET_ROWSET);
 propset.AddProperty(DBPROP_IRowsetChange, true);
 propset.AddProperty(DBPROP_UPDATABILITY,DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT);
 
 //이 속성을 가지고 현재 연결된 세션에서 SQL문을 실행한다.
 HRESULT hr = Cmd.Open(m_pSet->m_session, _T("select * from UserData"), &propset);

 if(FAILED(hr))
 {
  AfxMessageBox(_T("ERROR : Failed to execute SQL"));
 }

 TCHAR *pszValue = NULL;
 //CDynamicAccessor::SetStatus() ,현재 레코드의 특정 필드의 상태를 지정한다.
 //DBSTATUS_S_IGNORE 은 필드를 무시하라는 의미. 왜냐면 1번 필드(컬럼)이 일련번호(autoincrement)값이기 때문.
 Cmd.SetStatus(1, DBSTATUS_S_IGNORE);


 //GetValue() 는 필드값이 저장된 버퍼의 주소를 void형 포인터로 반환하며,
 //이 주소가 가리키는 메모리에 새 레코드의 필드 값을 써주고,
 //SetLength()로 길이(바이트 단위)를 지정한 후,
 //SetStatus() 메서드를 호출해 제대로 값이 쓰였다고 지정하면 한 필드에 대한 값의 설정이 완료된다.

 pszValue = (TCHAR*) Cmd.GetValue(2);
 wsprintf(pszValue, _T("%s"), _T("김성민"));
 Cmd.SetLength(2,lstrlen(pszValue)*2);

 //BSTATUS_S_OK 를 사용하면 SetLength() 에서 지정한 길이만큼 소비자 버퍼에 값을 설정하라는 의미
 Cmd.SetStatus(2, DBSTATUS_S_OK);

 pszValue = (TCHAR*) Cmd.GetValue(3);
 wsprintf(pszValue, _T("%s"), _T("02-2222-1111"));
 Cmd.SetLength(3,lstrlen(pszValue)*2);
 Cmd.SetStatus(3, DBSTATUS_S_OK);

 pszValue = (TCHAR*) Cmd.GetValue(4);
 wsprintf(pszValue, _T("%s"), _T("서울시 강서구 화곡동"));
 Cmd.SetLength(4,lstrlen(pszValue)*2);
 Cmd.SetStatus(4, DBSTATUS_S_OK);

 ULONG* plAge = (ULONG*) Cmd.GetValue(5);
 *plAge = 100;
 Cmd.SetLength(5,sizeof(ULONG));
 Cmd.SetStatus(5, DBSTATUS_S_OK);
 //이렇게 레코드 입력받는 것을 하드 코딩하였는데, 제대로 하려면 사용자로부터 입력을 받아야 한다.
 //구현해볼것.

 hr = Cmd.Insert(); //CRowset::Insert() , 새 레코드를 만들어 테이블에 추가한다.
 if(FAILED(hr))
 {
  AfxMessageBox(_T("ERROR : Failed to insert new record"));
 }

 Cmd.Close();
 Cmd.ReleaseCommand();

 if(m_List.GetItemCount() > 0) m_List.DeleteAllItems();
 m_pSet->RunSQL(_T("select * from UserData"));
 ListupAllRecords();

}

//다음은 레코드를 수정, 삭제하는 코드다.

void COleDBDemo2View::OnBnClickedButtonModifyrecord()
{
  CCommand<CDynamicAccessor> Cmd; //CDynamicAccessor, 여러 접근 지정자 객체 중 하나로, 테이블의 구조를 모르는 상황에서 사용가능.


 //이 접근 지정자를 이용해 디비 접근시 어떤 권한을 갖도록 할 것인지 속성설정.
 CDBPropSet propset(DBPROPSET_ROWSET);
 propset.AddProperty(DBPROP_IRowsetChange, true);
 propset.AddProperty(DBPROP_UPDATABILITY,DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT);
 
//where문으로 조건을 걸어 결과가 하나만 나오게 하였다.
 HRESULT hr = Cmd.Open(m_pSet->m_session, _T("select * from UserData where Name='김성민'"), &propset);

 if(FAILED(hr))
 {
  AfxMessageBox(_T("ERROR : Failed to execute SQL"));
 }

//MoveFirst() 로 하나나온 레코드에 접근한다.
 Cmd.MoveFirst();
 TCHAR *pszValue = NULL;
 //CDynamicAccessor::SetStatus() ,현재 레코드의 특정 필드의 상태를 지정한다.
 //DBSTATUS_S_IGNORE 은 필드를 무시하라는 의미. 왜냐면 1번 필드(컬럼)이 일련번호(autoincrement)값이기 때문.
 Cmd.SetStatus(1, DBSTATUS_S_IGNORE); 

//나이에 접근하여 값을 변경한다.
 ULONG* plAge = (ULONG*) Cmd.GetValue(5);
 *plAge = 999999;
 Cmd.SetLength(5,sizeof(ULONG));
 Cmd.SetStatus(5, DBSTATUS_S_OK);

//CRowset::SetData() 함수로 DBSTATUS_S_OK 인 필드의 값을 수정한다. 
 hr = Cmd.SetData();  //삭제할때는 Cmd.Delete(); 으로만 바꿔주면 된다.
 if(FAILED(hr))
 {
  AfxMessageBox(_T("ERROR : Failed to modify record"));
 }

 Cmd.Update();  //실제 디비 테이블에 결과가 반영되도록 한다.
 Cmd.Close();
 Cmd.ReleaseCommand();

 if(m_List.GetItemCount() > 0) m_List.DeleteAllItems();
 m_pSet->RunSQL(_T("select * from UserData"));
 ListupAllRecords();
}



CSocket 클래스와 CAsyncSocket 클래스는 MFC가 제공하는 소켓클래스이며, 비동기적인 네트워크 이벤트가 발생하면 알아서 적절한 함수를 호출하도록 하는 구조이다. 그러므로 네트워크 지식이 많지 않아도 소켓 프로그래밍이 가능하다. 채팅서버는 클라이언트로부터 접속을 받으면 해당 클라이언트와 통신하는 CSocket 객체를 만들고 목록으로 관리한다. 이 목록에는 서버와 연결된 모든 클라이언트의 정보가 들어 있다. 한 클라이언트로부터 채팅 메시지를 수신하면 목록에 등록된 모든 클라이언트에게 같은 메시지를 전달해서 모든 클라이언트가 동일한 메시지를 볼 수 있게 하는 것이 핵심이다.

CAsyncSocket 클래스는 입출력이 비동기적으로 이루어지며,
CSocket 클래스는 동기적으로 이루어지는 소켓이다.
클라이언트의 접속을 기다려야 하는 소켓은 비동기적이어야 하는데
이는 클라이언트의 접속시점을 예측할 수 없기 때문이다.

DBMS 는 데이터베이스를 관리하는 프로그램을 통칭한다. 일반적으로 사용자 다수가 동시에 접속해 정보를 조회하거나 변경하는 것이 가능하다. 대표적으로 MS ACCESS, MS SQL, Oracle 등이 있다. DB 데이터를 시스템에 등록한 후 해야한다.

ODBC 는 데이터베이스와의 연결 방법을 말한다. 다음은 ODBC를 이용한 예제다. 원리를 이용하기 위해
따로 함수를 분리하여 사용치 않았다.

void COdbcDemo2View::OnInitialUpdate()
{
 m_pSet = &GetDocument()->m_OdbcDemo2Set;
 CRecordView::OnInitialUpdate();

 if( m_List.GetHeaderCtrl()->GetItemCount() <= 0)
 {
  m_List.InsertColumn(0, _T("Name"), LVCFMT_LEFT, 100);
  m_List.InsertColumn(1, _T("Phone Number"), LVCFMT_LEFT, 150);
  m_List.InsertColumn(2, _T("Address"), LVCFMT_LEFT, 200);
  m_List.InsertColumn(3, _T("Age"), LVCFMT_LEFT,50);
 }

 CString strTmp=_T("");
 if(m_pSet->IsBOF()) return;
 
 while(!m_pSet->IsEOF())
 {
  m_List.InsertItem(0,m_pSet->m_Name,0);
  m_List.SetItemText(0,1,m_pSet->m_PhoneNumber);
  m_List.SetItemText(0,2,m_pSet->m_Address);

  strTmp.Format(_T("%ld"), m_pSet->m_Age);
  m_List.SetItemText(0,3,strTmp);
  m_pSet->MoveNext();


 }
 m_pSet->MoveLast();

}



// 레코드 추가 버튼 클릭시

void COdbcDemo2View::OnBnClickedButton1()
{
 if(!UpdateData(TRUE)) return;

 m_pSet->MoveLast();
 m_pSet->AddNew();
 m_pSet->SetFieldNull(NULL);

 m_pSet->m_Name = m_strName;
 m_pSet->m_Age = m_lAge;
 m_pSet->m_PhoneNumber = m_strPhone;
 m_pSet->m_Address = m_strAddress;

 m_pSet->Update();
 m_pSet->Requery();

 if(m_List.GetItemCount() >0) m_List.DeleteAllItems();
 


 CString strTmp=_T("");
 if(m_pSet->IsBOF()) return;

 while(!m_pSet->IsEOF())
 {
  m_List.InsertItem(0,m_pSet->m_Name,0);
  m_List.SetItemText(0,1,m_pSet->m_PhoneNumber);
  m_List.SetItemText(0,2,m_pSet->m_Address);

  strTmp.Format(_T("%ld"), m_pSet->m_Age);
  m_List.SetItemText(0,3,strTmp);
  m_pSet->MoveNext();


 }
 m_pSet->MoveLast();

}



// 업데이트 버튼 클릭시

void COdbcDemo2View::OnBnClickedButtonUpdate()
{
 
 if(!UpdateData(true)) return;

 m_pSet->Edit();

 m_pSet->m_Address = m_strAddress;
 m_pSet->m_Age = m_lAge;
 m_pSet->m_Name = m_strName;
 m_pSet->m_PhoneNumber = m_strPhone;

 

 
 m_pSet->Update();
 m_pSet->Requery();

 if(m_List.GetItemCount() > 0) m_List.DeleteAllItems();


  CString strTmp=_T("");
 if(m_pSet->IsBOF()) return;

 while(!m_pSet->IsEOF())
 {
  m_List.InsertItem(0,m_pSet->m_Name,0);
  m_List.SetItemText(0,1,m_pSet->m_PhoneNumber);
  m_List.SetItemText(0,2,m_pSet->m_Address);

  strTmp.Format(_T("%ld"), m_pSet->m_Age);
  m_List.SetItemText(0,3,strTmp);
  m_pSet->MoveNext();


 }
 m_pSet->MoveLast();


}

//삭제 버튼 클릭시

void COdbcDemo2View::OnBnClickedButtonDelete()
{
 m_pSet->Delete();
}


// 특정 자료를 찾을 때

void COdbcDemo2View::OnBnClickedButtonWhere()
{
 m_pSet->m_strFilter = _T("Name='홍길동'");
 m_pSet->Requery();

 if(m_List.GetItemCount() >0 ) m_List.DeleteAllItems();

 

 CString strTmp=_T("");
 if(m_pSet->IsBOF()) return;

 while(!m_pSet->IsEOF())
 {
  m_List.InsertItem(0,m_pSet->m_Name,0);
  m_List.SetItemText(0,1,m_pSet->m_PhoneNumber);
  m_List.SetItemText(0,2,m_pSet->m_Address);

  strTmp.Format(_T("%ld"), m_pSet->m_Age);
  m_List.SetItemText(0,3,strTmp);
  m_pSet->MoveNext();


 }
 m_pSet->MoveLast();
}



오너 드로우 버튼은 기능적으로는 버튼 컨트롤이지만 화면에 보이는 외관은 모두 다시 그려지게 된다. 여기서 그린다는 표현은 GDI 기법을 이용해 버튼의 색상이나 선 하나하나를 모두 그린다는 의미이며, 오너 드로우 기법은 버튼 컨트롤에만 적용되는 것이 아니라 대부분의 컨트롤 윈도우에서 속성을 통해 지원하며, 그리기가 이루어지는  MFC 가상 함수는 DrawItem() 함수다.

그리고 MFC 에서 오너 드로우 버튼을 직접 개발하는 일은 기본적으로 서브 클래싱을 전제로 하며, 앞서 설명한 방식보다 지금부터 실습할 기법을 주로 의미한다. 다음 예제는 비트맵 이미지를 이용하여 버튼 컨트롤을 구현한 예이다.


// ImageButton.cpp : 구현 파일입니다.
//

#include "stdafx.h"
#include "ImageButtonDemo2.h"
#include "ImageButton.h"


// CImageButton

IMPLEMENT_DYNAMIC(CImageButton, CButton)

CImageButton::CImageButton()
{
 m_bHover = false;
 m_bTracking = false;
}

CImageButton::~CImageButton()
{
}


BEGIN_MESSAGE_MAP(CImageButton, CButton)
 ON_WM_MOUSEMOVE()
 ON_WM_MOUSEHOVER()
 ON_WM_MOUSELEAVE()
END_MESSAGE_MAP()

 

// CImageButton 메시지 처리기입니다.

 

void CImageButton::OnMouseMove(UINT nFlags, CPoint point)
{

 if(!m_bTracking)
 {
  TRACKMOUSEEVENT tme;
  ::ZeroMemory(&tme, sizeof(tme));

  tme.cbSize = sizeof(tme);
  tme.hwndTrack = m_hWnd;
  tme.dwFlags = TME_LEAVE | TME_HOVER;
  tme.dwHoverTime = 1;

  m_bTracking = ::_TrackMouseEvent(&tme);

  // 마우스 이벤트를 추적하기 시작하면 m_bTracking 멤버가 true 가 되며,
  //조건을 만족하는 경우는 아직 이벤트를 추적하지 않은 상태에서 마우스 메시지가 발생한 경우이다.
  //그리고 추적할 메시지는 MOUSEHOVER와 MOUSELEAVE 메시지이므로 FLAG값을 HOVER와 LEAVE로 설정했다.

 }
 CButton::OnMouseMove(nFlags, point);
}

void CImageButton::OnMouseHover(UINT nFlags, CPoint point)
{
 // TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
 m_bHover = true;
 RedrawWindow(NULL,NULL, RDW_INVALIDATE | RDW_UPDATENOW);


 CButton::OnMouseHover(nFlags, point);
}

void CImageButton::OnMouseLeave()
{
 m_bHover = false;
 m_bTracking = false;
 RedrawWindow(NULL,NULL,RDW_INVALIDATE | RDW_UPDATENOW);

 CButton::OnMouseLeave();
}

void CImageButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{

 CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

 CDC MemDC;
 MemDC.CreateCompatibleDC(pDC);

 CBitmap Bmp;
 Bmp.LoadBitmap(IDB_Button_Image);
 CBitmap* pOldBitmap = MemDC.SelectObject(&Bmp);
 
 if(lpDrawItemStruct->itemState & ODS_SELECTED)
 {
  pDC->BitBlt(0,0,40,40, &MemDC , 40,0, SRCCOPY);
 }
 else
 {
  if(m_bHover) pDC->BitBlt(0,0,40,40, &MemDC, 80,0, SRCCOPY);
  else   pDC->BitBlt(0,0,40,40,&MemDC, 0,0, SRCCOPY);
 }
 MemDC.SelectObject(pOldBitmap);
}
------------------------------------------------------------------------------------



#pragma once


// CImageButton

class CImageButton : public CButton
{
 DECLARE_DYNAMIC(CImageButton)

public:
 CImageButton();
 virtual ~CImageButton();

protected:
 DECLARE_MESSAGE_MAP()

public:
 bool m_bHover;
 bool m_bTracking;

 afx_msg void OnMouseMove(UINT nFlags, CPoint point);
 afx_msg void OnMouseHover(UINT nFlags, CPoint point);
 afx_msg void OnMouseLeave();
 virtual void DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/);
};

------------------------------------------------------------------------------------


// ImageButtonDemo2Dlg.h : 헤더 파일
//

#pragma once
#include "afxwin.h"
#include "ImageButton.h"

// CImageButtonDemo2Dlg 대화 상자
class CImageButtonDemo2Dlg : public CDialog
{
// 생성입니다.
public:
 CImageButtonDemo2Dlg(CWnd* pParent = NULL); // 표준 생성자입니다.

// 대화 상자 데이터입니다.
 enum { IDD = IDD_IMAGEBUTTONDEMO2_DIALOG };

 protected:
 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 지원입니다.


// 구현입니다.
protected:
 HICON m_hIcon;

 // 생성된 메시지 맵 함수
 virtual BOOL OnInitDialog();
 afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
 afx_msg void OnPaint();
 afx_msg HCURSOR OnQueryDragIcon();
 DECLARE_MESSAGE_MAP()
public:
 CImageButton m_Btn_Image; // 앞으로 작성할 새로운 클래스는 CButton 클래스의 파생 클래스이며, 이 코드가 변하게 된다.
};
------------------------------------------------------------------------------------

CButton 클래스의 DrawItem() 메서드의 인자로 DRAWITEMSTRUCT 구조체가 전달된다.












서브 클래싱이란 이미 생성되어 있는 특정 윈도우의 윈도우 프로시저 함수를 임이의 다른 함수로 대체하여 처리하는 기법으로 특정 메시지의 처리나 윈도우의 기능을 확장할 목적으로 사용한다.

서브클래싱의 분류
1) 특정 컨트롤 윈도우의 서브 클래싱
2) 특정 윈도우 컨트롤 전체의 서브 클래싱

전역 클래싱의 경우 윈도우 클래스 구조체 WNDCLASS 의 내용을 직접 변경하는 방식을 말하며,
일반적으로 서브 클래싱이란 특정 윈도우의 윈도우 프로시저 함수를 임의의 다른 함수로 바꾸는 행위를 컨트롤 윈도우에 적용하는 것을 말한다.

서브 클래싱을 Win32 프로그래밍 방식에서 구현할 때는 고려할 게 많은데, 우선 윈도우 프로시저를 다른 함수로 변경하려면 ::SetWindowLong() 함수를 사용하고, 별도의 전역 함수를 만들고, 기존 윈도우 프로시저를 대체한 새 윈도우 프로시저 함수는 반드시 ::CallWindowProc() 함수로 호출해야 한다. 이런 일련의 과정은 API 후킹 기법과 매우 유사하다고 할 수 있다.

MFC 프로그래밍에서는 특정 컨트롤 윈도우를 확장할 때 서브 클래싱 구조를 기반으로 하며, 서브 클래싱은 단순히 파생 클래스를 등록하는 간단한 처리 하나만으로 완성된다. 그러므로 스태틱(Static) 텍스트를 서브 클래싱으로 확장할 때 단순히 CStatic 클래스를 상속받는 새로운 파생 클래스를 만드는 것으로 끝난다.

그러나 이와 같은 방법으로 기능을 확장하기 어려울 때는 명시적으로 해당 컨트롤 윈도우를 서브 클래싱해야 하는데, 대표적인 예로 리스트 컨트롤에 내장된 헤더 컨트롤이나 에디트 컨트롤처럼 이미 만들어진 컨트롤 윈도우에 대해 확장을 해야 하는 경우이다.

다음은 리스트 컨트롤의 헤더 컨트롤(CHeaderCtrl)을 명시적으로 서브 클래싱하여 확장할 때 사용한 주요 코드다. 



메인 다이얼로그 헤더에 추가.

public:
 CListCtrl m_List;
 CSubClassTestWnd m_wndTest; //리스트 컨트롤의 헤더 컨트롤을 서브 클래싱후 별도의 코드를 동작하게 한다.


void CSubClassTestWnd::OnPaint()
{
 //CPaintDC dc(this); // device context for painting 없애야 한다.
.

//상위 클래스의 OnPaint() 를 명시적으로 호출함으로써 서브 클래싱 될 대상 컨트롤 윈도우의 그리기가 완성됨
 CWnd::OnPaint();    //여기부터는 그리기가 완료된 헤더 컨트롤에 덧칠하듯 사각형을 그리는 코드다.
 CClientDC dc(this);

 CRect Rect(3,3,16,16);
 if(m_bFlag) dc.FillSolidRect(&Rect, RGB(192,0,0)); //검색
 else  dc.FillSolidRect(&Rect, RGB(255,255,255)); //흰색
}

void CSubClassTestWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
 
 CWnd::OnLButtonDown(nFlags, point);

 CRect Rect(3,3,16,16);
 if(PtInRect(&Rect, point))
 {
  m_bFlag=!m_bFlag;
  RedrawWindow();
 }

}

void CSubClassTestWnd::OnDestroy()
{

 UnsubclassWindow(); //서브클래싱된 컨트롤 윈도우의 윈도우 프로시저 함수를 원래의 함수로 되돌려준다.
 CWnd::OnDestroy();


}




BOOL CSubClassDemo2Dlg::OnInitDialog()

//생략~~
 CHeaderCtrl* pHeaderCtrl = m_List.GetHeaderCtrl();
 m_wndTest.SubclassWindow(pHeaderCtrl->m_hWnd);
}




만일 이미 생성된 컨트롤 윈도우가 아니라 생성할 컨트롤 윈도우의 기능을 확장하고자 한다면 파생 클래스를 등록하는 간편한 방법으로 서브 클래싱을 구현하자.




'Windows > MFC' 카테고리의 다른 글

데이터베이스 ( Database ) - DBMS,ODBC  (0) 2011.11.20
오너 드로우 버튼 ( Owner-Draw )  (0) 2011.11.20
특별한 메시지 - 사용자 정의 메시지  (0) 2011.11.20
MFC 객체 간의 접근 방법  (0) 2011.11.19
CArchive 클래스  (0) 2011.11.19

사용자 정의 메시지란, WM_USER 메시지에 임의의 정수(양의 정수)를 더하여 정의한 메시지로 아직 기능이 정해지지 않은 메시지를 말한다. 메시지가 발생하는 시점이나 처리 방법은 전적으로 개발자가 결정한다. 다음은 예제소스다.
먼저 StdAfx.h 파일에 UM_TESTMESSAGE 라는 사용자 정의 메시지를 정의한다. 그리고 뷰 클래스 헤더에 다음과 같이 메서드의 원형을 정의한다.

LRESULT afx_msg OnTestMessage(WPARAM wParam, LPARAM lParam);

afx_msg 라는 상수는 아무런 의미가 없으므로 생략이 가능하나 이것을 명시하는 이유는 이 메서드가 메시지 핸들러 함수임을 알릴려고 하는 것이다. 이후 View 클래스에 메시지맵에 추가한다.

BEGIN_MESSAGE_MAP(CUserMsgDemo2View, CView)
 // 표준 인쇄 명령입니다.
 ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
 ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
 ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
 ON_MESSAGE(UM_TESTMESSAGE, &CUserMsgDemo2View::OnTestMessage)
END_MESSAGE_MAP()


이 후 메시지가 발생하면 호출될 함수를 뷰 클래스에 만든다.

LRESULT CUserMsgDemo2View::OnTestMessage(WPARAM wParam, LPARAM lParam)
{
 AfxMessageBox(_T("CUserMsgDemo2View::OnTestMessage()"));
  return 0;

}

그 다음 메인프레임에 이벤트 처리기를 통해 핸들러함수를 추가한다.

void CMainFrame::OnMenuTest1()
{
 GetActiveView()->PostMessage(UM_TESTMESSAGE,0,0);
}


이렇게 코딩을 하면, 멀티 쓰레드 프로그래밍의 특정 작업자 스레드에서 메인 프레임 윈도우나 뷰 윈도우에 접근할 때 유용하다. 작업자 쓰레드는 응용 프로그램의 흐름과 상관없이 별도로 처리되는 코드로, 작업자 스레드의 진행 상황을 사용자 인터페이스에 표시하려면 정기적으로 사용자 정의 해당 윈도우에 전달하면 된다.

또한 대용량 파일을 복사하는 경우에도 사용하는데, 메인 스레드가 멈추는 것을 방지하기 위해 복사 과정을 작업자 스레드로 작성하고, 진행 상황을 ::PostMessage() 함수를 이용해 메인 프레임 윈도우에 전달하면, 메인 프레임 윈도우에서 메시지의 파라미터로 전달받은 값을 계산하여 프로그레스 컨트롤의 현재 위치를 갱신하면 된다. AfxGetMainWnd() 함수를 이용하면 어디서든 메인 프레임 윈도우의 주소를 알아낼 수 있으므로 작업자 스레드가 사용자 정의 메시지를 보낼 때는 주로 메인 프레임 윈도우를 주로 활용한다.







'Windows > MFC' 카테고리의 다른 글

오너 드로우 버튼 ( Owner-Draw )  (0) 2011.11.20
서브 클래싱과 확장 컨트롤  (0) 2011.11.20
MFC 객체 간의 접근 방법  (0) 2011.11.19
CArchive 클래스  (0) 2011.11.19
CFile 클래스  (0) 2011.11.19

SDI 구조에서 객체 간의 접근 방법





AfxGetMainWnd() : MFC 전역함수로, 응용 프로그램의 최상위 프레임 윈도우의 주소를 반환.

AfxGetApp() : 전역함수로, 응용 프로그램의 자체 객체의 주소를 반환. 즉 theApp 주소반환

GetActiveView() : 메인프레임윈도우 -> 클라이언트뷰, 뷰가 여러개라면 활성화된 뷰 윈도우의 주소반환

GetActiveDocument() : 활성화된 문서의 주소를 반환

GetDocument() : 뷰윈도우에서 문서에 접근할 때

m_viewList : 문서에서 뷰윈도우에 접근할 때 사용하며, CDocument 클래스의 멤버다. 등록된 뷰 윈도우의 주소를
목록으로 관리하며 CPtrList 클래스의 GetHead() 메서드를 호출하여 첫번째에 등록된 뷰 윈도우의 주소를 알아낼 수 있다.

MDI는 나중에 알아보자.






'Windows > MFC' 카테고리의 다른 글

서브 클래싱과 확장 컨트롤  (0) 2011.11.20
특별한 메시지 - 사용자 정의 메시지  (0) 2011.11.20
CArchive 클래스  (0) 2011.11.19
CFile 클래스  (0) 2011.11.19
SDI 템플릿  (0) 2011.11.19


CArchive 클래스는 CObject 클래스처럼 기본클래스가 없는 MFC 클래스 중 하나로 직렬화를 완성하는데 중요한 역활을 한다. CFile 클래스와 더불어 파일 객체를 하나의 아카이브로 추상화하고, << 와 >> 을 이용해 변수나 클래스 객체를 해당 파일에 입.출력하는 인터페이스를 제공한다. MFC가 제공하는 문서,뷰 구조에서 파일은 CArchive 클래스 객체로 추상화되어 제공되며, CArchive 클래스와의 연동과 활용 방법을 이해하면 개발의 편의를 누릴 수 있다.

한가지 독특한 점은 CFile 클래스가 아닌 CSocket 과 CSocketFile 클래스로 네트워크 입/출력을 추상화할 수 있으며, 이 것은 GDI 프로그래밍에서 DC를 다루는 코드 하나로 프린터 출력까지 한 번에 했던 것과 비슷한 이치이다. 그러나 직렬화를 이용한 네트워크 프로그래밍은 추천하지 않으며, 절대 하지 않는게 바람직하다.

다음은 CArchive 클래스와 CFile 클래스를 연동하여 파일을 읽어들이는 예제다.



void CFileOPDlg::OnBnClickedButtonReadfromarchive()
{
 CString strTmp = _T("");
 char szBuffer[32] ={0};

 CFile File;
 if(!File.Open(_T("MakingFile2.txt"), CFile::modeRead ))
  return;

 

 //CFile 클래스와 연동하여 객체생성. 읽기모드, File객체에도 읽기모드가 있어야 한다. 
 //1번째 인자에, CSocketFile 클래스 객체의 주소를 명시한다면 소켓을 직렬화할 수도 있다.
 CArchive ar(&File, CArchive::load);  

// ar>>strTmp;

 for(int i=0; i<File.GetLength(); ++i) //파일에 있는 문자열만큼 루프를 돈다.
 {
  ar >> ch;  //파일(ar)을 읽어서 ASCII 영문자 하나를 ch에 넣는다.
  strTmp += ch;   //ch의 내용을 strTmp에 하나씩 누적하여 저장한다.
  //이렇게해도 되지만 For문 없이 ar>>strTmp; 도 가능하다. 그러나 그렇게 할려면 저장할때도 ar<<strtmp;해야한다.

 }

 AfxMessageBox(strTmp);
}

'Windows > MFC' 카테고리의 다른 글

특별한 메시지 - 사용자 정의 메시지  (0) 2011.11.20
MFC 객체 간의 접근 방법  (0) 2011.11.19
CFile 클래스  (0) 2011.11.19
SDI 템플릿  (0) 2011.11.19
더블 버퍼링  (0) 2011.11.19

CFile 클래스는 파일을 객체화한 MFC 클래스로 CArchive 클래스와 더불어 직렬화를 구현할 때도 사용하고, 관계없이 파일 입/출력에도 사용한다. 일반적인 파일 입/출력의 경우 MFC 기반으로 작성하더라도 ::Createfile(), ::ReadFile(), ::WriteFile() 함수를 사용하거나 fopen(), fprintf(), fscanf(). fread(), fwrite() 같은 ANSI-C 함수를 사용하는 경우가 더 많다.

CFile 클래스의 생성자는 다중 정의되어 있는데 ::CreateFile() 함수를 이용하여 생성한 파일 핸들을 인자로 갖는 생성자가 있다는 사실도 알아두어야 한다. hFile 인자가 CreateFile() 함수가 반환하는 파일의 핸들을 말하는데, 주로 API 프로그래밍 방식의 코드에 CFile 클래스를 적용할 때 유용하다. lpszFileName은 파일의 경로를 담는 버퍼의 주소가 되고, nOpenFlags 인자는 파일을 어떤모드로 열 것인지 명시하는 플래그이며, 이 값은 fopen() 함수의 모드처럼 다양한 조합이 가능하다.
다음은 CFile 클래스와 Write() 메서드를 활용한 예다.



void CFileOPDlg::OnBnClickedButtonOpen()
{
 CString strTmp = _T("CFile class test string");
 char* strTmp2 = "CFile class test string\r\n";
 //\r\n 을 해줘야 문자열이 안깨진다. 또한 _T 로 감싸면 안된다.
 CFile File(_T("MakingFile.txt"),
  CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate);
 
 File.SeekToEnd(); //파일의 포인터를 제일 끝으로 가게한다.
 File.Write(strTmp, strTmp.GetLength());

 //modeCreate 파일을 생성하라는 의미
 //modeNoTruncate 를 조합하면 파일의 길이가 0으로 강제조정안되고 본래 크기 유지.
 //modeReadWrite 읽기 쓰기 권한을 모두 부여한다. 만일 이 플래그를 빼면 접근시도시 오류

 CFile File2(_T("MakingFile2.txt"),
  CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate);

 File2.SeekToEnd();
 File2.Write(strTmp2, strlen(strTmp2));

 //Close() 메서드를 호출하는 코드가 없는 이유는 소멸자가 호출될 때 자동으로 호출하기 때문.
 //Open() 메서드는 생성자와 동일한 기능(파일 열기)을 하며,
 //bool형의 값을 반환하므로 파일 열기에 성공했는지 확인이 가능하다
 //또한 CFileException 클래스 객체의 주소를 인자로 받아 구조적인 예외 처리가 가능하다.

}





void CFileOPDlg::OnBnClickedButtonRead()
{
 CString strTmp=_T("");
 char szBuffer[32] = {0};

 CFile File;
 if(   !File.Open(_T("MakingFile2.txt"),   CFile::modeRead | CFile::shareDenyRead | CFile::shareDenyWrite, NULL))
 //DenyRead,Write 플래그는 다른 프로세스가 파일에 접근하지 못하도록 설정할 때 사용한다.
 File.Read(szBuffer, sizeof(szBuffer));
 strTmp = szBuffer;
 AfxMessageBox(strTmp);


 CFile File2;
 File2.Open(_T("Makingfile1.txt"),
  CFile::modeCreate | CFile::modeReadWrite); //새로 만들고 읽기쓰기 권한가진다.
 ULONGLONG dwNewLength = 10000000000; //바이트 단위로
 File2.SetLength(dwNewLength);  //파일크기를 강제로 설정한다(현 10기가)

}



'Windows > MFC' 카테고리의 다른 글

MFC 객체 간의 접근 방법  (0) 2011.11.19
CArchive 클래스  (0) 2011.11.19
SDI 템플릿  (0) 2011.11.19
더블 버퍼링  (0) 2011.11.19
스크롤 뷰 ( Scroll View )  (0) 2011.11.19
SDI 형식의 프로젝트를 생성하면 CWinApp 클래스를 상속받아 만들어진 클래스에 InitInstance() 가상 함수가 재정의 되어 있다. SDI 형식의 구조를 만드는 것도 이 함수의 많은 역활 중 하나인데, 이 구조를 만드는 과정은 단지 CSingleDocTemplate 클래스 객체를 new 연산을 통해 생성하는 것으로 시작해서 CWinApp 클래스의 AddDocTemplate() 메서드를 호출하는 것으로 끝난다. (p645)

'Windows > MFC' 카테고리의 다른 글

CArchive 클래스  (0) 2011.11.19
CFile 클래스  (0) 2011.11.19
더블 버퍼링  (0) 2011.11.19
스크롤 뷰 ( Scroll View )  (0) 2011.11.19
MDI 를 닮은 SDI  (0) 2011.11.19

'Windows > MFC' 카테고리의 다른 글

CFile 클래스  (0) 2011.11.19
SDI 템플릿  (0) 2011.11.19
스크롤 뷰 ( Scroll View )  (0) 2011.11.19
MDI 를 닮은 SDI  (0) 2011.11.19
다중 뷰 - 동적 분할 윈도우와 정적 분할 윈도우  (0) 2011.11.19

스크롤 뷰는 수직/수평 스크롤 막대를 뷰 윈도우에 적용하여 한 화면에 표시할 수 없는 큰 이미지나 정보를 보여줄 때 사용함.
다음은 간단한 예제다. 뷰 클래스의 기본 클래스를 CScrollView로 변경한다. 이 예제는 폭과 넓이가 1600 * 1200인 비트맵 이미지를 뷰윈도우에 뿌리는데, 윈도우 크기가 이보다 작으면 스로크롤이 가능하도록 하는 예제다.


void CImageScrollView::OnInitialUpdate()
{
 CScrollView::OnInitialUpdate();

 CSize sizeTotal;
 // TODO: 이 뷰의 전체 크기를 계산합니다.
 sizeTotal.cx = 1600;
 sizeTotal.cy = 1200;
 SetScrollSizes(MM_TEXT, sizeTotal); //스크롤의 최대 범위를 설정한다. 1) 뷰 윈도우의 매핑 모드, 2) 최대 범위
// 이미지뷰어를 개발할 때는 출력할 이미지의 폭과 높이를 알아내어 그에 맞는 값으로 sizeTotal.cx,cy 값을 변경해야한다.
}

void CImageScrollView::OnPaint()
{
 CPaintDC dc(this); // device context for painting
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 // 그리기 메시지에 대해서는 CScrollView::OnPaint()을(를) 호출하지 마십시오.
 int nVertScroll = GetScrollPos(SB_VERT);
 int nHorzScroll = GetScrollPos(SB_HORZ);

 CImage Image;
 Image.LoadFromResource(AfxGetInstanceHandle(), IDB_Image);
 Image.BitBlt(dc.m_hDC, -nHorzScroll, -nVertScroll);
}


'Windows > MFC' 카테고리의 다른 글

SDI 템플릿  (0) 2011.11.19
더블 버퍼링  (0) 2011.11.19
MDI 를 닮은 SDI  (0) 2011.11.19
다중 뷰 - 동적 분할 윈도우와 정적 분할 윈도우  (0) 2011.11.19
Modal, Modeless 대화상자, 공용 대화상자  (0) 2011.11.19

클라이언트 뷰를 여러개 만들고 필요한 시점에 적절한 뷰 윈도우로 전환하는 형식의 인터페이스이며, 대부분의 응용 프로그램에서 이미 사용하고 있는 인터페이스다. 기본적인 구조는 SDI 형식이지만 문서와 상관없이 여러 다양한 뷰 윈도우를 가질 수 있으므로 프로그램의 구성을 보다 다양하게 해준다. 이런 응용 프로그램의 구현 원리나 동작 방식은 탭 컨트롤에 여러 폼을 붙이는 경우와 비슷한데, 여러 뷰 윈도우를 하나의 클라이언트 뷰에 겹쳐서 생성하고 필요에 따라 뷰 윈도우 하나만 보이도록 하면 된다. 다음은 예제 소스다.

'Windows > MFC' 카테고리의 다른 글

더블 버퍼링  (0) 2011.11.19
스크롤 뷰 ( Scroll View )  (0) 2011.11.19
다중 뷰 - 동적 분할 윈도우와 정적 분할 윈도우  (0) 2011.11.19
Modal, Modeless 대화상자, 공용 대화상자  (0) 2011.11.19
탭 컨트롤  (0) 2011.11.18

동적 분할 윈도우

동적 분할 윈도우는 SDI 처럼 생긴 응용 프로그램의 클라이언트 뷰가 실행 도중 사용자의 요구로 분할되는 인터페이스를 의미하며, 전형적인 SDI 형식으로 뷰 클래스의 기본 클래스를 CHtmlView 클래스로 수정한다는 점에 유의해야 한다. 다음은 예제 소스다.


protected:  // 컨트롤 모음이 포함된 멤버입니다.
 CMFCMenuBar       m_wndMenuBar;
 CMFCToolBar       m_wndToolBar;
 CMFCStatusBar     m_wndStatusBar;
 CMFCToolBarImages m_UserImages;
 CSplitterWnd m_wndSplitterWnd;



BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
 // TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
 return m_wndSplitterWnd.Create(this, 2,2, CSize(1,1), pContext);
 //return CFrameWndEx::OnCreateClient(lpcs, pContext);
}



void CDynSplitDemoView::OnInitialUpdate()
{
 CHtmlView::OnInitialUpdate();

 Navigate2(_T("C:\\"),NULL,NULL);
}


정적 분할 윈도우

정적 분할 윈도우는 아예 분할된 채로 응용 프로그램이 실행되도록 할 때 유용하며, 이렇게 분할된 각 패인에 들어가는 뷰 윈도우를 각기 다른 뷰 윈도우로 생성하여 붙일 수 있다. 중요하므로 코드 작성 순서와 의미를 파악할 것.
다음은 주요 코드다.

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
 
 m_wndSplitHor.CreateStatic(this, 1,2); //정적 분할 윈도우를 생성하는 함수, 메인프레임을 부모로 선언.
 
 m_wndSplitHor.CreateView(0,0, RUNTIME_CLASS(CLeftListView), CSize(200,200), pContext); //뷰 윈도우를 적절한 패인에 생성하는 함수, 내부적으로 new 연산과 CreateWindow() 한다.

 m_wndSplitVer.CreateStatic(&m_wndSplitHor, 2,1, WS_CHILD | WS_VISIBLE, m_wndSplitHor.IdFromRowCol(0,1)); //Hor를 부모로 선언.

 m_wndSplitVer.CreateView(0,0, RUNTIME_CLASS(CTopHtmlView), CSize(100,300), pContext);

 m_wndSplitVer.CreateView(1,0, RUNTIME_CLASS(CBottomEditView), CSize(200,100), pContext);
 //중요한 점은 OnCreateClient() 함수를 호출하면 내부적으로 new 연산 후 CreateWindow() 함수가 호출되며, 이 과정에서
 //RUNTIME_CLASS 매크로가 사용되는데, MFC에서는 이를 통해 임의로 정의되는 하위 클래스의 동적 생성을 지원한다.
 //또한 특정 패인에 속한 뷰 윈도우의 주소를 알아내어 접근할 때는 CSplitterWnd::GetPane() 메소드를 사용해야 한다.
 //AfxGetMainWnd() 함수가 최상위 메인 프레임 윈도우의 주소를 반환하므로 그 주소를 CMainFrame클래스로 캐스팅 한 후
 //CSplitterWnd 클래스의 GetPane() 메서드를 이용하여 각 뷰 윈도우에 접근할 수 있다.

 SetActiveView((CView*) m_wndSplitHor.GetPane(0,0));
 return true;


 //return CFrameWnd::OnCreateClient(lpcs, pContext);
}



'Windows > MFC' 카테고리의 다른 글

스크롤 뷰 ( Scroll View )  (0) 2011.11.19
MDI 를 닮은 SDI  (0) 2011.11.19
Modal, Modeless 대화상자, 공용 대화상자  (0) 2011.11.19
탭 컨트롤  (0) 2011.11.18
드래그 앤 드롭 ( Drag-And-Drop)  (0) 2011.11.18


Modal 생성법

CModalDlg Dlg;
INT_PTR nResult = Dlg.DoModal();
if(nResult == IDOK)
AfxMessageBox(_T("OK를 눌렀음"));


Modeless 생성법

static CModelessDlg Dlg;
 if(Dlg.GetSafeHwnd() == NULL)
 Dlg.Create(IDD_Dialog_Modaless);
 Dlg.ShowWindow(SW_SHOW);

공용대화상자

공용 대화 상자는 사용자 인터페이스의 일관성을 유지하도록 시스템이 정의한 대화상자로서, 특별한 경우가 아니라면 별도의 기능 확장없이 사용가능하며, 운영체제에 따라 자동으로 외형이 달라진다. 다음은 대표적인 공용 대화 상자이다,

CFileDialog   파일 대화상자
CFontDialog  글꼴 대화상자
CColorDialog 색 대화상자
CPageSetupDialog 페이지 설정 대화상자
CPrintDialog  인쇄 대화상자
CFindReplaceDialog 찾기/바꾸기 대화상자
COleDialog  OLE 대화상자



다음은 파일 대화 상자 예제다.

void CCommonDlgDlg::OnBnClickedButtonFile()
{
 CString strTmp = _T("");
 //맨마지막에 ||을 두번넣어서 끝임을 명시한다. 첫번째는 보이는 문자열, 두번째는 확장자명. 한세트씩 만들어야 한다.
// TRUE 일 경우, 파일 열기모드, FALSE이면 파일 저장모드. 
 CFileDialog Dlg(TRUE, _T("exe"), NULL, OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST,
     _T("EXE Files(*.exe) | *.exe | All Files(*.*) | *.* ||"), this);
 if(Dlg.DoModal() == IDOK)
 {
  strTmp.Format(_T("Full Path : %s,  FileName : %s, Extension : %s "), Dlg.GetPathName(), Dlg.GetFileName(), Dlg.GetFileExt());
  AfxMessageBox(strTmp);
 }
}



다음은 글꼴 대화 상자의 예제다.

void CCommonDlgDlg::OnBnClickedButtonFont()
{
 
 CClientDC dc(this);
 CString strTmp = _T("");

 LOGFONT lf;
 ::ZeroMemory(&lf, sizeof(lf));

 // MulDiv 함수는 32비트 간의 곱셈 연산 결과를 64비트 버퍼에 저장한 다음 다시 32비트로 나눈다.
 // 즉 ( 9 * pDC->GetDeviceCaps(LOGPIXELSY)) / 72 와 같으며, 여기서 72는 MM_TEXT 매핑모드에서 1인치당 표시 가능한 픽셀수다.
 // 공식이라고 할 수 있으니 외워도 좋다.
 lf.lfHeight = MulDiv(15, dc.GetDeviceCaps(LOGPIXELSY), 72);
 wsprintf(lf.lfFaceName, _T("%s"), _T("굴림"));

 CFontDialog Dlg(&lf); //폰트다이얼로그에 LogFont 구조체를 넘긴다.
 if( Dlg.DoModal()==IDOK)
 {
  strTmp.Format(_T("Font : %s  Size: %s  "), Dlg.GetFaceName(), Dlg.GetSize());
  AfxMessageBox(strTmp);
 }
}



다음은 색 대화 상자를 활용하는 예제다.

void CCommonDlgDlg::OnBnClickedButtonColor()
{
 CString strTmp=_T("");

 CColorDialog Dlg(RGB(0,0,0), CC_FULLOPEN); //검은색으로 기본값을 생성, 사용자 정의 색 만들기 버튼도 만듬.
 if(Dlg.DoModal()==IDOK)
 {
  COLORREF color = Dlg.GetColor(); //선택한 컬라값(RGB)을 가져옴
  strTmp.Format(_T("RGB : %u %u %u"), GetRValue(color), GetGValue(color), GetBValue(color));
  AfxMessageBox(strTmp);
 }
}






다음은 폴더 찾아보기 대화 상자를 활용하는 예제다.

void CCommonDlgDlg::OnBnClickedButtonFolder()
{
 BROWSEINFO bi; 
 TCHAR szBuffer[MAX_PATH]; //260,  파일주소가 들어갈 곳
 ::ZeroMemory(&bi, sizeof(bi));
 ::ZeroMemory(szBuffer, MAX_PATH);

 bi.hwndOwner = m_hWnd; //다이얼로그를 부모로 삼는다.
 bi.lpszTitle =_T("파일이 저장된 폴더를 선택해 주세요"); //타이틀명

 // 1, 새로운 공용 컨트롤 버전이 제공하는 새로운 스타일을 사용한다,
 // 2, 폴더 이름편집, 에디트컨트롤 가능,
 // 3, 제어판가튼거 안보이고, 실제 폴더 정보만 보이게한다.

 bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_EDITBOX | BIF_RETURNONLYFSDIRS; 

 // bi.pidlRoot = NULL;  기본값은 NULL이며 바탕화면이 루트 폴더가 된다.
 // pidIRoot 멤버는 대화상자에 출력할 폴더 트리 중 루트 폴더의 PIDL(Pointer Item Identifier List) 가 들어간다

 LPITEMIDLIST pItemIdList = ::SHBrowseForFolder(&bi);  // 선택한 폴더의 PIDL 을 반환한다.

 // 그러나 이 값만으로는 구체적인 폴더의 경로를 알수가 없으므로 
 // ::SHGetPathFromIDList() 함수를 이용하여 PIDL 에 대한 상세 경로를 알아낸다.
 // 즉 ::SHBrowseForFolder() 함수와 ::SHGetPathFromIDList() 함수는 늘 함께 사용한다.

 if(::SHGetPathFromIDList(pItemIdList, szBuffer))
 {
  AfxMessageBox(szBuffer);
 }
}











 

'Windows > MFC' 카테고리의 다른 글

MDI 를 닮은 SDI  (0) 2011.11.19
다중 뷰 - 동적 분할 윈도우와 정적 분할 윈도우  (0) 2011.11.19
탭 컨트롤  (0) 2011.11.18
드래그 앤 드롭 ( Drag-And-Drop)  (0) 2011.11.18
트리 컨트롤 ( CTreeCtrl )  (0) 2011.11.18

탭 컨트롤은 단독으로 사용하지 않고 여러 대화 상자와 연동하여 사용하는 컨트롤 윈도우이다. 여러 윈도우가 똑같은 위치에 겹겹이 중복되어 있을 때 필요한 윈도우만 화면에 보이게 하는 것이 탭 컨트롤의 주요 기능이다. 기본적으로 탭 컨트롤 이외에 같은 크기의 여러 윈도우가 필요하며, 대화 상자를 이용할 때는 대화 상자를 여럿 만들어야 한다. 다음은 예제 소스다.
리스트컨트롤과 굉장히 유사하다.


--------------헤더파일--------------------------------------

// 생성입니다.
public:
 CTabDemoDlg(CWnd* pParent = NULL); // 표준 생성자입니다.

 CFormOne  m_formOne;
 CFormSecond  m_formSecond;
 CFormThird  m_formThird;
 CWnd*   m_pwndShow;




--OnInitDialog()-----------------------------------------------

 CBitmap Bmp;
 Bmp.LoadBitmap(IDB_TabImageList);
 
 static CImageList ImgList;
 ImgList.Create(16, 16, ILC_COLOR24 | ILC_MASK, 7, 0);
 ImgList.Add(&Bmp, RGB(192, 192, 192));
 m_Tab.SetImageList(&ImgList);

 CString strTmp = _T("");
 for(int i = 0; i < 7; i++)
 {
  strTmp.Format(_T("%dth Tab"), i);
  m_Tab.InsertItem(i, strTmp, i);
 }

 CRect Rect;
 m_Tab.GetClientRect(&Rect);

 m_formOne.Create(IDD_Form_One, &m_Tab);
 m_formOne.SetWindowPos(NULL, 5, 25,
     Rect.Width() - 10, Rect.Height() - 30,
     SWP_SHOWWINDOW | SWP_NOZORDER);
 m_pwndShow = &m_formOne;

 m_formSecond.Create(IDD_Form_Second, &m_Tab);
 m_formSecond.SetWindowPos(NULL, 5, 25,
     Rect.Width() - 10, Rect.Height() - 30,
     SWP_NOZORDER);

 m_formThird.Create(IDD_Form_Third, &m_Tab);
 m_formThird.SetWindowPos(NULL, 5, 25,
     Rect.Width() - 10, Rect.Height() - 30,
     SWP_NOZORDER);




void CTabDemoDlg::OnTcnSelchangeTab(NMHDR *pNMHDR, LRESULT *pResult)
{
 if(m_pwndShow != NULL)
 {
  m_pwndShow->ShowWindow(SW_HIDE);
  m_pwndShow = NULL;
 }

 int nIndex = m_Tab.GetCurSel();
 switch(nIndex)
 {
 case 0:
  m_formOne.ShowWindow(SW_SHOW);
  m_pwndShow = &m_formOne;
  break;

 case 1:
  m_formSecond.ShowWindow(SW_SHOW);
  m_pwndShow = &m_formSecond;
  break;

 case 2:
  m_formThird.ShowWindow(SW_SHOW);
  m_pwndShow = &m_formThird;
  break;
 }

 *pResult = 0;
}



----------------------------------------------------------------------------

리스트 컨트롤 안에서 드래그 앤 드롭 구현하기.

드래그 이미지는 CListCtrl 클래스의 CreateDragImage() 메서드를 이용해 만들며, 이것은 리스트 컨트롤이나 트리 컨트롤의 이미지 목록과 관련이 깊다. CImageList 클래스는 드래그 이미지를 만드는 역활을 하는 메서드를 제공하며, CWnd 클래스도 드래그와 관련된 메서드를 제공한다. 다음은 다이얼로그 베이스에서 구현한 예제 소스다.

----------------- OnInitDialog() ------------------------------------------------------------------

 CBitmap Bmp;
 Bmp.LoadBitmap(IDB_ImageList);

 static CImageList ImgList;
 ImgList.Create(32,32, ILC_COLOR32 | ILC_MASK, 5, 0);
 ImgList.Add(&Bmp, RGB(0,0,0));
 m_List_Left.SetImageList(&ImgList, LVSIL_NORMAL);
 m_List_Right.SetImageList(&ImgList, LVSIL_NORMAL); //리스트 컨트롤끼리 이미지리스트를 공유한다.
 //그러므로 Share Image Lists 속성을 True로 설정해야 한다.

 CString strItem = _T("");
 for(int i=0; i<5; ++i)
 {
  strItem.Format(_T("%dth Item"), i);
  m_List_Left.InsertItem(i, strItem, i); //이미지리스트를 이용해서 인썰트!!
 }

 DWORD dwExStyle = m_List_Left.GetExtendedStyle();
 m_List_Left.SetExtendedStyle(dwExStyle | LVS_EX_BORDERSELECT);

 dwExStyle = m_List_Right.GetExtendedStyle();
 m_List_Right.SetExtendedStyle(dwExStyle | LVS_EX_BORDERSELECT);

-------------------------------------------------------------------------------------




void CDragDemoDlg::OnLvnBegindragListLeft(NMHDR *pNMHDR, LRESULT *pResult)
{
 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
 
 CPoint ptDrag, ptAction; //ptDrag 는 드래그 시작위치   
 m_nIndexLeftSel = pNMLV->iItem; //드래그가 시작된 항목의 인덱스를 저장, 이후 LBUTTONUP 에서 활용
 
 // 1, 선택된 아이템, 2, 드래그 시작점에 드래그이미지를 만든다.
 // POINT 구조체에 그 항목의 리스트 컨트롤 기준 좌표를 반환하며,
 // 아래 함수는 내부적으로 new 연산으로 CImageList를 생성하므로 드래그가 끝날 때 delete 연산으로 소멸해야 한다.

m_pImgListDrag = m_List_Left.CreateDragImage(pNMLV->iItem, &ptDrag); 

 m_pImgListDrag->SetBkColor(RGB(0,0,0));
 ptAction = pNMLV->ptAction; //리스트 컨트롤에서 드래그를 시작한 좌표(리스트컨트롤 기준 좌표)가 들어간다.

 SetCapture(); //마우스 캡쳐, 다른창에 넘어가도 문제없게 하기위해서 사용
 m_pImgListDrag->BeginDrag(0,ptAction - ptDrag);
 //1, 화면에 출력할 드래그 이미지의 인덱스,
 //2, 그 이미지에서 마우스 포인터에 맞추어야 할 좌표
 
 m_List_Left.ClientToScreen(&ptAction); //스크린 좌표로 바꾼다
 m_pImgListDrag->DragEnter(NULL, ptAction);  //드래그 상태로 변경하며, 반투명한 드래그 이미지를 출력.
 //1, 이 값을 리스트 컨트롤이 아니라, NULL로 설정하면 바탕화면이 되므로,
 //2, 두번째 인자로 전달되는 좌표는 스크린 기준 좌표가 되어야 하고, 이 좌표에 드래그 이미지 출력


 
 *pResult = 0;
}

void CDragDemoDlg::OnMouseMove(UINT nFlags, CPoint point)
{
 //대화상자 기준의 좌표로 마우스 좌표가 들어오므로 ClienToScreen() 으로 변환해야한다.

 if(m_pImgListDrag != NULL)
 {
  ClientToScreen(&point);

  CWnd* pWnd = CWnd::WindowFromPoint(point); //point 좌표에 해당하는 윈도우 객체의 주소를 구한다.
  // 이 후 이 주소가 대화 상자 자체이거나, 자식 윈도우에 해당하는 주소면 드래그 상태를 유지해라.
  if(pWnd != NULL)
  {
   if(this == pWnd || IsChild(pWnd))
   {
    m_pImgListDrag->DragEnter(NULL, point);
    m_pImgListDrag->DragMove(point);
   }else //대화상자가 아니거나, 자식 윈도우가 아니면 드래그하지마라.
   {
    m_pImgListDrag->DragLeave(NULL); //드래그 이미지를 지운다. 다시 나오게 하려면 DragEnter() 를 다시 호출하면된다
   }
  }

 }
 CDialog::OnMouseMove(nFlags, point);
}

void CDragDemoDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
 
 CString strTmp = _T("");
 if(m_pImgListDrag != NULL)
 {
  ClientToScreen(&point);
  m_pImgListDrag->DragLeave(NULL);
  m_pImgListDrag->EndDrag();
  ReleaseCapture();

  //오른쪽 리스트에 아이템추가하기
  CWnd* pWnd = CWnd::WindowFromPoint(point);
  if(pWnd == &m_List_Right && m_nIndexLeftSel >= 0)
  {
   LVITEM lvItem;
   TCHAR szBuffer[256];
   ::ZeroMemory(&lvItem, sizeof(lvItem));
   ::ZeroMemory(szBuffer, sizeof(szBuffer));

   lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
   lvItem.iItem = m_nIndexLeftSel;
   lvItem.pszText = szBuffer;
   lvItem.cchTextMax = 256;
   m_List_Left.GetItem(&lvItem);

   m_List_Right.InsertItem(0, lvItem.pszText , lvItem.iImage);
  }else //왼쪽 리스트이면 리스트안의 어디에 놓았는지 구체적인 아이템 인덱스 출력
  {
   m_List_Left.ScreenToClient(&point);

   //HitText() 함수는 인자로 전달받은 좌표에 해당항목있는지 검사후 인덱스를 반환한다. 아주 중요하다.
   int nIndex = m_List_Left.HitTest(point);
   if(nIndex >= 0)
   {
    strTmp.Format(_T("Drop on %dth Item"), nIndex);
    AfxMessageBox(strTmp);

   }

  }

  delete m_pImgListDrag; //소멸하는 것은 드래그이미지를 삭제한다는 뜻.
  m_pImgListDrag = NULL; //다시 초기화
  m_nIndexLeftSel = -1;  //다시 초기화

  /* 이것들을 손에 익을때까지 연속적으로 반복 숙달할 것! */
 }
 CDialog::OnLButtonUp(nFlags, point);
}

'Windows > MFC' 카테고리의 다른 글

Modal, Modeless 대화상자, 공용 대화상자  (0) 2011.11.19
탭 컨트롤  (0) 2011.11.18
트리 컨트롤 ( CTreeCtrl )  (0) 2011.11.18
이미지 프로세싱  (0) 2011.11.15
RGB 와 CMYK, HSV, HLS  (0) 2011.11.15

트리 컨트롤의 기본 사용법, 예제 코드.
 
CBitmap Bmp; 
 Bmp.LoadBitmap(IDB_TreeImageList); //트리항목앞의 이미지를 위해 만듬

 static CImageList ImgList;
 ImgList.Create(16,16, ILC_COLOR24, 6, 0); // 16*16, 24비트,6개이미지
 ImgList.Add(&Bmp, RGB(255,0,0)); //이미지 리스트에 이미지설정
 m_Tree.SetImageList(&ImgList, TVSIL_NORMAL); //트리컨트롤에 이미지 리스트를 설정


 HTREEITEM hItem = NULL; //트리 항목의 핸들
 hItem = m_Tree.InsertItem(_T("바탕화면"), 0,5,TVI_ROOT); //이 항목을 루트로 설정함
 hItem = m_Tree.InsertItem(_T("내 문서"), 1,5, hItem); 
 hItem = m_Tree.InsertItem(_T("내 그림"), 2,5, hItem);
 
//자식 항목을 등록하지 않아도 되면 저장할 필요가 없다.
 m_Tree.InsertItem(_T("내꼬"), 3,5, hItem); 
 m_Tree.InsertItem(_T("내꼬2"), 4,5, hItem);

// 추가한 항목이 너무 많을 경우 화면에 보여주지 못하는 경우에는 자동으로 스크롤된다.
 m_Tree.EnsureVisible(hItem); //hItem 설정한 곳까지는 다 보이게 한다. 여기서는 '내 그림'까지는 펼쳐서 보인다.

 m_Tree.Expand(hItem,     TVE_EXPAND); //특정 항목의 자식 항목이 펼쳐보이게 한다. 그 이하는 불가능
//여기서 다 보이게 된다.

주요 통지 메시지 처리
TVN_SELCHANGED 통지 메시지는 트리 컨트롤에서 선택 항목이 변경되었을 때 발생하는데, 윈도우 탐색기의 폴더 보기에서 특정 폴더를 선택하면 오른쪽 파일 목록 보기의 내용이 해당 폴더의 내용으로 변경되는 상황같은 것을 구현하고자 할때 주로 사용한다. 다음은 이를 이용한 예제 소스다. 먼저 TVN_SELCHANGED 통지 메시지를 등록한다.

void CTreeCtrlDemoDlg::OnTvnSelchangedTree(NMHDR *pNMHDR, LRESULT *pResult)
{
 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
 
 CString strItem = m_Tree.GetItemText(pNMTreeView->itemNew.hItem);
 GetDlgItem(IDC_Static_Selected)->SetWindowText(strItem);

 

 *pResult = 0;
}




자식 항목의 처리
리스트 컨트롤에서는 인덱스로 항목을 식별하나, 트리 컨트롤에서는 항목간에 부모 자식 관계가 성립하므로 인덱스가 아닌 소속과 관계가 의미를 갖는다. 그러므로 전체 항목에 접근할 때는 재귀 호출을 가져야 한다. 또한 트리 컨트롤에 Check Boxes 속성을 적용하면 각 트리 항목 앞에 확인란이 생기며 자주 사용한다.

void CTreeCtrlDemoDlg::OnBnClickedButtonCheckupchild()
{
 HTREEITEM hItem = m_Tree.GetRootItem();
 if(m_Tree.GetCheck(hItem))
  AfxMessageBox(m_Tree.GetItemText(hItem));

 CheckUpChild(m_Tree.GetRootItem());
}

void CTreeCtrlDemoDlg::CheckUpChild(HTREEITEM hItem)
{
//인자로 전달받은 트리 항목 핸들의 첫 번째 자식 항목의 핸들을 반환한다.
 HTREEITEM hChildItem = m_Tree.GetChildItem(hItem);

 while(hChildItem != NULL) 
 {
  if(m_Tree.GetCheck(hChildItem)) //자식 항목이 체크되었으면
   AfxMessageBox(m_Tree.GetItemText(hChildItem)); 자식 항목의 문자열을 출력

  if(m_Tree.ItemHasChildren(hChildItem)) //자식 항목에게 또 자식이 있으면
   CheckUpChild(hChildItem); //재귀호출해라

  hChildItem = m_Tree.GetNextItem(hChildItem, TVGN_NEXT); //자식이 없으면 다음 아이템으로 넘어가자
 }

}



// Visual Studio 2008 Feature Pack 에 CMFCShellTreeCtrl 클래스에 윈도우 탐색기가 들어있으므로 분석해볼 것!

'Windows > MFC' 카테고리의 다른 글

탭 컨트롤  (0) 2011.11.18
드래그 앤 드롭 ( Drag-And-Drop)  (0) 2011.11.18
이미지 프로세싱  (0) 2011.11.15
RGB 와 CMYK, HSV, HLS  (0) 2011.11.15
ATL, STL, COM 은 무엇인가?  (0) 2011.11.15

+ Recent posts