GSI

Google
 
CView를 사용해서 해당 뷰를 만들고 그 위에 CStatic를 생성해서 처리를 하고 있습니다.
CStatic에는 해당 컬러 정보를 사용해서 드로잉이 되며, 마우스로 이동, 크기변경 등의 이벤트가
발생하게 되는데요.

이때 드로잉 되는 순서가 CView가 먼저 드로잉 되고, CStatic가 드로잉 되게 되면서
강한 Flicker 현상(번쩍임)이 발생을 하네요..

이것저것 처리를 해봐도 언뜻 보면 드로잉 순서로 인해서 어쩔 수 없을거 같다는 생각이 드네요.

그래서 조금 우회를 시켜서 CStatic의 드로잉 부분을 CView에 직접 그리기로 했습니다.
CStatic는 보이지는 않지만 자기 기능을 대신 하고 있는거구요.

이렇게 하니 Flicker는 해결이 되었네요 ^^..

원래 이렇게 하는걸까요?.. 아님 다른 방법이 있는 걸까요?
Posted by gsi
:

Google
 


마우스를 통한 개발툴을 만들다 보면 대부분 스크롤 기능을 포함하는 경우가 많습니다.

CScrollView 화면을 통한 마우스 기반의 툴을 제작 하게 되면 불가피 하게 OnLButtonDown(), OnLButtonUp(), OnMouseMove()를 제어 하게 되어 있습니다.
여기서 부터 작업이 시작 되지요.
스크롤 뷰의 화면을 좀 크게 잡는다 싶으면, 스크롤 바를 움직여 보면 아시겠지만, 제대로 되지 않습니다. 16bit 형태의 값이였나, 아뭍턴 값의 한계로 인해서 스크롤 바의 위치가 제대로 처리 되지 않습니다.
그 경우 아래의 코드를 추가 해야 합니다.

BOOL CScrollTest2View::OnScroll(UINT nScrollCode, UINT nPos, BOOL bDoScroll)
{
// When you drag the scroll box, the nPos value send from WM_HSCROLL/WM_VSCROLL is 16bit value.
// Therefore retrieve the 32bit scroll box position value.
if(SB_THUMBTRACK == LOBYTE(nScrollCode)) // WM_HSCROLL
{
SCROLLINFO info;
if(GetScrollInfo(SB_HORZ, &info, SIF_TRACKPOS))
{
nPos = info.nTrackPos; // 32bit position value.
}
}
else if(SB_THUMBTRACK == HIBYTE(nScrollCode)) // WM_VSCROLL
{
SCROLLINFO info;
if(GetScrollInfo(SB_VERT, &info, SIF_TRACKPOS))
{
nPos = info.nTrackPos; // 32bit position value.
}
}

return CScrollView::OnScroll(nScrollCode, nPos, bDoScroll);
}

위의 코드를 추가 하고 나면 넓은 영역의 뷰도 스크롤이 제대로 표현되는 것을 확인하실 수 있을겁니다.

이제 확인해야 될 부분이 마우스 이벤트에 들어 오는 CPoint point 부분입니다.
스크롤 뷰에서 제일 맘에 안드는 부분이 이 부분인데요. point가 왜 스크롤 되고 나면 현재 위치를
구해 주지 않는지 모르겠습니다.
사용자 삽입 이미지
위의 화면에서와 같이 마우스 위치의 지점을 클릭해 보겠습니다. 클릭하고 나면 대략 값이 x:188, y:49 가 나오게 됩니다.
하지만 아래의 스크롤 바를 보면 우측으로 많이 가 있는 것을 볼 수 있습니다.
즉, 스크롤이 움직여도 마우스의 위치 값은 화면에 보이는 영역에 대한 상대 값임을 알 수 있습니다.

그렇다면 여기서 스크롤 되는 정보를 얻어 와서 처리 해주어야 한다는 사실을 알게 될겁니다.
스크롤 정보를 알기 위해서 GetScrollPosition() 를 사용합니다.

이제 스크롤 된 후의 현재 마우스 위치 정보를 아래와 같이 구합니다.
CPoint scrollpos = point + GetScrollPosition();

※예로 제가 작업했던 코드 일부 여기에 포스트...
void CScrollTest2View::OnMouseMove(UINT nFlags, CPoint point) { if( m_oldPoint == point ) return; CPoint scrollpos = GetScrollPosition(); TRACE2("현재 스크롤값:%d, %d\n", scrollpos.x, scrollpos.y); int x = scrollpos.x % GridSize.cx; int y = scrollpos.y % GridSize.cy; m_ptGrid = CPoint( ((int)((point.x+x) / GridSize.cx)) * GridSize.cx, ((int)((point.y+y) / GridSize.cy)) * GridSize.cy ); TRACE2("현재 그리드 위치:%d, %d\n", m_ptGrid.x, m_ptGrid.y); m_ptGrid.x -= x; m_ptGrid.y -= y; TRACE2("현재 그리드 위치:%d, %d\n", m_ptGrid.x, m_ptGrid.y); XORPen(); m_oldPoint = point; CScrollView::OnMouseMove(nFlags, point); } void CScrollTest2View::OnLButtonDown(UINT nFlags, CPoint point) { CPoint pos = CPoint(0, 0); GetCursorPos( &pos ); this->ScreenToClient( &pos ); CScrollView::OnLButtonDown(nFlags, point); } void CScrollTest2View::OnLButtonUp(UINT nFlags, CPoint point) { TRACE2("MousePos:%d, %d\n", point.x, point.y); CPoint scrollpos = point + GetScrollPosition(); TRACE2("ScrollPosition:%d, %d\n", scrollpos.x, scrollpos.y); m_ptGrid = CPoint( ((int)(scrollpos.x / GridSize.cx)) * GridSize.cx, ((int)(scrollpos.y / GridSize.cy)) * GridSize.cy ); m_vPos.push_back( m_ptGrid ); OutCursorDCxRop(); Invalidate(); CScrollView::OnLButtonUp(nFlags, point); }

이 값을 사용해서 여러가지 동작들을 추가 하시면 됩니다.
하지만 이것저것 하다 보면 GetScrollPosition()의 값을 보정해서 해줘야 하는 경우가 많이 발생을 하게 됩니다. 그때 마다 너무 많은 작업량이 좀 힘들더라구요.

팁을 드리면 CScrollView에 CView를 추가한 후에 CScrollView의 Create(), OnSize() 부분에 코드를 적당히 넣어서 CView를 내부에 크기 만큼 심어 놓습니다.
그렇게 한후에 마우스 픽킹을 해보시면 마우스 이벤트가 CScrollView로 가지 않고 CView로 가는 것을
확인할 수 있을겁니다.
더욱더 놀라운건 ^^.. 스크롤로 해서 화면을 움직여도 현재 찍힌 화면의 좌표는 스크롤로 이동한 좌표 즉, 화면이 스크롤 만큼 이동한 후에 CView의 이동된 좌표값을 바로 리턴해 준다는 겁니다.

이상.. 가볍게 적었지만 CView 추가 하는 부분은 생략하도록 하겠습니다. (귀찮네요 ^^)
모르거나 필요하시면 제가 바로 답변 드릴테니 뎃글 달아 주세요..
Posted by gsi
:

Google
 

테스트를 위해서 프로젝트를 생성한다.
응용 프로그램 종류를 단일 문서로 설정하고 기본 클래스는 CView로 설정합니다.
사용자 삽입 이미지

기본 프로젝트는 아래와 같습니다.
사용자 삽입 이미지

위의 응용 프로그램에 새로운 CView를 추가해 보겠습니다.
CDialog, CStatic 등의 컨트롤은 Create() 등의 함수를 통해서 생성하고 처리를 하게 되는데요.
CView는 Create()로 하게 되면 삭제 하는데 오류가 생기거나 안되더라구요.
그래서 CView는 아래의 코드와 같이 CCreateContext를 사용해서 처리 합니다.

CWnd* pFrameWnd = this;
CCreateContext pContext;
pContext.m_pCurrentDoc = new CDummyDoc();
pContext.m_pNewViewClass = RUNTIME_CLASS(CPixelMapView);
m_pPixelMapView = (CPixelMapView *) ((CFrameWnd*)pFrameWnd)->CreateView(&pContext);
ASSERT(m_pPixelMapView);
m_pPixelMapView->ShowWindow(SW_NORMAL);
m_pPixelMapView->MoveWindow( 20, 20, 100, 100 );

1. CDummyDoc 클래스를 생성합니다.
2. CPixelMapView 클래스를 생성합니다.
3. CInsertViewView 부모 클래스에 헤더를 선언합니다.
   - #include "DummyDoc.h"
   - #include "PixelMapView.h"
4. 클래스 내부에 View 접근을 위한 포인터 변수를 선언합니다.
   - CPixelMapView* m_pPixelMapView;
5. CPixelMapView 를 등록하기 위해서 OnCreate()를 선언합니다.

int CInsertViewView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 if (CView::OnCreate(lpCreateStruct) == -1)
  return -1;

 CWnd* pFrameWnd = this;
 CCreateContext pContext;
 pContext.m_pCurrentDoc = new CDummyDoc();
 pContext.m_pNewViewClass = RUNTIME_CLASS(CPixelMapView);
 m_pPixelMapView = (CPixelMapView *) ((CFrameWnd*)pFrameWnd)->CreateView(&pContext);
 ASSERT(m_pPixelMapView);
 m_pPixelMapView->ShowWindow(SW_NORMAL);
 m_pPixelMapView->MoveWindow( 20, 20, 100, 100 );

 return 0;
}

추가를 하고 나면 아래와 같이 화면이 나오게 됩니다.
사용자 삽입 이미지

이후 작업은 입맛에 맞게 ^^
Posted by gsi
:

Google
 

몇달 전부터 Scroll View 페이지를 응용한 개발툴을 몇개 작성을 하게 되었네요.
CView 보다 더 복잡하고, 스크롤이 들어 가면서 포지션 정보를 무시할 수 없더군요.

가장 귀찮게 여겨 지는 것은 Client Area 부분의 마우스가 Down, Up Move 등의 행위를 할때
항상 ScrollToPosition()을 달고 다녀야 한다는게 너무나 맘에 안들었습니다.

마우스의 위치 좌표가 왜 스크롤된 위치가 나오지 않고 현재 보이는 화면 영역의 상대 값이 나오는지.
정말 귀찮습니다.

그래서 CScrollView 클래스에 CView를 삽입 하고,
TotalSize 만큼 CView를 확장해서 처리를 하게 되었습니다.

스크롤 정보는 CSCrollView로 보내야 되고,
화면의 마우스 처리는 CView가 담당하게 처리 하였죠.

근데 .. 이렇게 하니 마우스의 좌표가 스크롤된후의 CView의 위치 정보가 보이게 되더군요.
참으로 편합니다. ^^ 그 위치가 스크롤된 화면 위치니까요.

조금더 나아지는 MFC 방법들이 재미나게 느껴 집니다.

요즘은 애매한 컨트롤은 CWnd, CStatic, CView, CDialog 등과 같은 컨트롤을
여러개 겹치면서 다양한 효과를 구현하고 있네요 ^^..

이번에 테스트뷰로 만들고 있는 화면입니다.
사용자 삽입 이미지

이 프로그램의 기능은 휠을 통한 좌우 스크롤 기능과 화면의 픽킹 처리가 CView에서 담당하는게 특징입니다.

배움에 있어서 여러가지 새로운 ms의 기술들과 플렉스 등을 보면서.
MFC의 기술 또한 같이 성장하는거 같습니다.

아.. 이제 자야 겠네요.. @.@.. 몽롱합니다.
Posted by gsi
:

프로그램 짜다가 저장한 이미지를 확인 하기 위해서 탐색기에서 이미지를 읽기 보다는
해당 알씨와 같은 이미지 프로그램에 인자로 해당 파일을 자동으로 열리게 하는게 좋을때가 있다.
아래와 같이 하면 해당 실행 파일을 실행할 수 있다.

char szProgramName[MAX_PATH]= {0, };
wsprintf (szProgramName, "C:\\Program Files\\ESTsoft\\ALSee\\alsee d:\\aaa.bmp");
STARTUPINFO si = {0,};
PROCESS_INFORMATION pi;
si.cb = sizeof(STARTUPINFO);
si.dwFlags = 0;

CreateProcess (NULL,
      szProgramName,
      NULL,
      NULL,
      FALSE,
      0,
      NULL,
      NULL,
      &si,
      &pi);
Posted by gsi
:

Flicker Free Drawing In MFC

이런 주제의 CodeProject 의 내용을 볼 수 있다.
테스트해본 코드 주소는 http://www.codeproject.com/KB/GDI/flickerfree.aspx 이며,
해본 결과 Flicker 를 줄일 수 있었다.

기존의 다른 코드를 사용해서 처리 할때
Release 시 드로잉이 되지 않는 문제점이 발생하였다.
솔직히 이거 때문에 디버깅에 시간이 좀 걸렸다.
나머지 모듈을 다 제거 하고나서야 이 클래스를 사용하지 않고 디버깅을 해봐야 겠다.
생각이 들었고, 빼고 보니 제대로 되었다.
즉, CMemDC 클래스가 이상하다는 것을 알았고,
위의 주소에 있는 내용을 복사해서 처리 하니 되었다.

물론 생성자에 CRect 의 내용도 포함될 수 있는 코드도 있는걸로 봐서
ActiveX 와 같은 곳에서도 사용될 수 있을거 같다.

관련 페이지 >

Sample Image

Posted by gsi
:

MFC 의 Doc, View 부분의 구조를 취하다 보면,
여러개의 다이얼로그를 구성하고 그곳에 데이터의 정보가 연동되는 경우가 있는데,
사실상 이때 new, open, save, saveas 를 고려 해서 작업을 해야 할듯 하다.

어느정도 된 후에 이것을 고려 할려다 보면 많은 부분 수정이 가해진다.

그래서 생각이 든건데, new, save, saveas 가 고려되지 않을 경우에
그 함수를 생성해서 데이터 로직 처리 해보는게 더 좋은 구조를 취할 수 있을거 같다.

더 좋은 방법이 있을까?.
혹.. 더 잘아시는분 있으시면.. 연락주세요 ^^
Posted by gsi
:

파일 저장 다이얼로그

 static char BASED_CODE szFilter[] = "Bitmap Files(*.*)|*.bmp|Jpeg Files(*.*)|*.jpg|Colorplay Files(*.*)|*.colorplay|All Files(*.*)|*.*|";
 CFileDialog dlg(TRUE, _T("*.*"), _T("*.*"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szFilter);
 dlg.m_ofn.lpstrInitialDir = "";     //기본 경로를 설정한다.

 if(dlg.DoModal() == IDOK)
 {
 }

Posted by gsi
:

파일 저장 다이얼로그

 static char BASED_CODE szFilter[] = "DMXD Files (*.dmxd)|*.dmxd||";
 CFileDialog dlg(FALSE, _T("*.dmxd"), _T("*.dmxd"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szFilter);

 if(dlg.DoModal() == IDOK)
 {
 }

Posted by gsi
:

마우스가 해당 View의 영역 밖으로 나갈때 처리 방법.

해당 View의 영역에 커서가 위치할 때랑 커서가 밖으로 나갔을때 다르게 처리 하고 싶을때가 발생한다. 이때는 SetCursor를 이용하면 된다.

즉, 해당 어플리케이션의 MainFrame 에서 SetCursor 부분에서 해당 View의 클래스 포인터의 값을 비교해서 처리 하면 된다.

코드는 아래와 같다.
BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
 if( pWnd == g_PixelMapView )
 {
 }
 else // 해당 포인터가 다를 경우, 즉 다른 윈도우일 경우...
 {
  if( g_PixelMapView )
   g_PixelMapView->OutCursorDCxRop();
 }
 return CFrameWnd::OnSetCursor(pWnd, nHitTest, message);
}

이때 한가지 문제점은 있다. 마우스가 갑자가 해당 메인 프레임 밖으로 이동하게 되면
이 함수는 호출 되지 않는다. 그정도를 감안한다면 이 코드도 괜찮을듯 하다.
Posted by gsi
:

사용자 삽입 이미지

CStatic를 사용한 Outlook 스타일의 컨트롤
해당 아이템을 누르면 뒤에 있는 아이템은 아래로 내려간다.
아이템헤더의 구조체를 Link 구조의 형태로 구성해서 처리 하였다.

더 추가되어야할 것은 에니메이션 처리 되는 형태와
아이템이 펼쳐 지면 해당 아이템의 내용이 표시되어야 한다.
이 부분은 ListCtrl로 처리 하면 될듯 하다.

소스코드 :

Posted by gsi
:

사용자 삽입 이미지

해당 이미지의 특정 칼라의 외곽픽셀을 선별해서 최대, 최소 사각형을 구하는 프로그램을
구성중에 있습니다.
아무래도 이것을 사용할 수 있는 용도가 어떤게 있을지는 모르지만.

개선해야될 부분도 많으며, 가운데가 비어 있는 이미지의 경우 오류가 조금 나기도 한다.

앞으로 테스트로 몇가지 더 해볼만한 프로그램이 될듯..

소스코드 :



혹 필요하신분은 받아 가세요 ^^..
Posted by gsi
:

ListCtrl의 LPARAM의 포인터 변수 지우는 방법!!

가끔 생각해 보면 참 바보 같다는 생각이 든다.
기존의 작업 스탈을 보면 ListCtrl의 하나하나의 열에 해당 포인터 변수를 추가해서
사용하게 되는 경우가 많은데,
이때 포인터 변수이기 때문에 메모리를 소거해야 하는 부담감에 휩싸이게 된다.
하지만 난 이제까지 몰랐던것이다.. -.-;

ListCtrl의 흐름 자체를 말이다.

삭제를 할때 나는 해당 아이템을 DeleteItem(i); 이런식으로 하기 전에 사용자 데이터를
지워주어야 하는줄 알았다.
하지만 이 작업은 중간 중간 수정을 하거나 모두 지우거나 추가 하면서
몇가지 부수적인 작업패턴을 부적절하게 발생 시키게 된다.

하지만 이것은 아래의 함수에서 DeleteItem(), DeleteAllItem()을 할때 매번 호출 되는
오버라이드 함수가 있었다는 사실을 알게 되었다.

즉,
ON_NOTIFY(LVN_DELETEITEM, IDC_LIST_IMAGELIST, OnLvnDeleteitemListImagelist)
이 함수를 오버라이딩 해서 사용하게 되면 해당 아이템이 지워질때
이 함수가 호출 되게 된다. 다시 말해서 이 함수 안에서 해당 하는 아이템만
지워 주면 다른건 신경 쓰지 않아도 되는 것이다.


그렇다면 모두 지우는 과정에서는 어떻게 되는가 하고 모르는 분들이라면 질문을 할텐데요.
위의 함수가 그 개수만큼 호출 됩니다.

그래서 위의 함수에서 해당 데이터를 지워 주기만 하면 된다는 것이죠.
아래 처름요.

void CDlgProgrammingPage::OnLvnDeleteitemListImagelist(NMHDR *pNMHDR, LRESULT *pResult)
{
 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
 
 // 메모리 삭제하기
 CString* pStr = (CString*)m_ImageList.GetItemData(pNMLV->iItem);  //<-- 이 부분은 작업자가 포함시킨 포인터 변수(가정)
 SAFE_DELETE(pStr);

 *pResult = 0;
}

Posted by gsi
:

원격지에 있는 파일의 사이즈 체크 하는 방법

DWORD GetUrlFileLength (CString url)
{
 DWORD filesize;

 TCHAR   szCause[255];
 CString CauseOfError;
 TRY
 {
  CInternetSession session;
  CHttpFile *remotefile= (CHttpFile*)session.OpenURL(url,1,INTERNET_FLAG_TRANSFER_BINARY);
  TCHAR szContentLength[32];
  DWORD dwInfoSize = 32;
  DWORD dwFileSize = 0;
  BOOL bGotFileSize = FALSE;
  if (remotefile->QueryInfo ( HTTP_QUERY_CONTENT_LENGTH, szContentLength, &dwInfoSize , NULL))
  {
   bGotFileSize = TRUE;
   dwFileSize = (DWORD) _ttol(szContentLength);
   filesize = dwFileSize;// Return 값에 할당.
  }
  else
  { // 에러나 나서 파일이 없을경우에
   filesize = -1 ;
  }
  remotefile->Close ();
  session.Close ();
  delete remotefile;
  delete session;
 }

 CATCH_ALL(error)
 {
  AfxMessageBox ("서버로부터 파일 크기를 얻어오는 과정에서 에러발생");
  error->GetErrorMessage(szCause,254,NULL);
  CauseOfError.Format("%s",szCause);
  AfxMessageBox (CauseOfError);
 }
 END_CATCH_ALL;

 return filesize;
}

원격지에 있는 파일 다운로드 받기

void DownloadUrlFileBuffer (CString url)
{
 DWORD dwServiceType = AFX_INET_SERVICE_HTTP;
 CString szServer, szObject, szInfo;

 INTERNET_PORT nPort;
 INTERNET_PROXY_INFO m_proxyinfo;

 CInternetSession m_SessionDownload;
 CHttpConnection* m_pConnection = NULL;
 CHttpFile* m_pHttpFile = NULL;
 CFile FileWrite;

 DWORD d_BytesRead=0;
 DWORD d_FileSize=0;

 char szHTTPBuffer[199926];

 ZeroMemory(szHTTPBuffer, sizeof(szHTTPBuffer));

 //start Download Routine
 ::AfxParseURL(url.GetBuffer(url.GetLength()), dwServiceType, szServer, szObject, nPort);

 try
 {
  m_pConnection = m_SessionDownload.GetHttpConnection(szServer,INTERNET_FLAG_KEEP_CONNECTION, nPort,NULL, NULL);
  m_pHttpFile= m_pConnection->OpenRequest("GET",szObject,NULL,0, NULL, NULL, INTERNET_FLAG_KEEP_CONNECTION);
 }
 catch(CInternetException* m_pException)
 {
  //exception found
  //lots of clean up code
  return;
 }

 if(m_pHttpFile)
 {
  if(!FileWrite.Open("d:\\aaa.gif", CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyNone ))
  {
   //exception found
   //lots of clean up code
   return;
  }

  try
  {
   m_pHttpFile->SendRequest(NULL);
  }
  catch(CInternetException* m_pException)
  {
   //exception found
   //lots of clean up code
   return;
  }

  m_pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_LENGTH, d_FileSize);
  m_pHttpFile->QueryInfo(HTTP_QUERY_MAX, szInfo);

  while(d_BytesRead = m_pHttpFile->Read((void*)szHTTPBuffer,199926))
  {
   FileWrite.Write((void *)szHTTPBuffer,d_BytesRead);
   memset(szHTTPBuffer, 0, sizeof(szHTTPBuffer));
  }

  szServer.Empty();
  szObject.Empty();

  m_SessionDownload.Close();
  m_pConnection->Close();
  m_pHttpFile->Close();

  delete m_SessionDownload;
  delete m_pConnection;
  delete m_pHttpFile;

  FileWrite.Close();
 }
}

위에 있는 FileWrite 부분이 그 부분이며, 이 부분을 해당 프로젝트에 맞도록 고쳐야 할 것이다.
Posted by gsi
:

사용자 삽입 이미지

FormView로 구성된 세부 컨트롤 처리

Color1 + Pick... + CheckButton 은 한줄씩 생성 됩니다.
Add, Delete 로 추가하고 삭제할 수 있습니다.

스크롤로 아래 위로 움직일 수 있습니다.

Add를 하게 되면 하단의 버튼이 아래로 내려 간다.

 int pos = GetScrollPos(SB_VERT);
 GetDlgItem(IDC_EFFECTATTRIB8_CHILD_ADD)->MoveWindow(20, m_NewHeightPos + 10 - pos, 100, 30);
 GetDlgItem(IDC_EFFECTATTRIB8_CHILD_DELETE)->MoveWindow(20, m_NewHeightPos + 42 - pos, 100, 30);
 CSize scrollSize;
 scrollSize.cx = 200;
 scrollSize.cy = m_NewHeightPos + 80;
 SetScrollSizes(MM_TEXT, scrollSize);

위의 코드에서 보듯이 컨트롤을 동적으로 생성할 때
스크롤 성분에 의해서 원하는 위치에 생성되지 않습니다.
이 문제를 해결하기 위해서 GetScrollPos()를 사용해서 스크롤 값을 얻어 오고
그 값을 보정해서 생성하면 되네요..

관련코드 :
Posted by gsi
: