4.윈도우 레지스트리에 데이터 저장과 로드

본장에서는 윈도우 레지스트리에 데이터를 저장하고 로드하는 방법에 대해서 설명합니다. 윈도우 레지스트리는 윈도우 시스템을 구동하는데 필요한 여러 정보가 데이터베이스 형태로 저장되는 곳입니다. 이 윈도우 레지스트리에 어플리케이션 프로그램들은 필요한 정보를 저장하고 로드합니다. 본장을 통해서 레지스트리에 각 어플리케이션 정보를 저장하고 로드하는 방법을 알수 있을것입니다.

대상자: 중급,고급

Chapter 등급: 선택

공부방법:

본항목에서는 꼭알아야 할 내용은 없습니다. 후에 레지스트리에 데이터를 저장할 필요성이 있을경우 이때 본항목을 읽어보시기 바랍니다.

1. 윈도우 레지스트리

윈도우 레지스트리는 윈도우에 수행되는 여러 정보를 저장하는 곳입니다.

윈도 레지스터리는 윈도 안에 있는 모든 프로그램의 정보를 가지고 있습니다. 예를 든다면 *.wav 파일은 음성 파일이고, *.avi는 영상 파일이고, *.bmp 이미지 파일이라는 것을 파일 확장자만 보고도 알 수 있고 또한 그런 파일들을 클릭하면 그에 해당하는 프로그램을 실행시켜 주는 기능, 예를 든다면 bmp 파일을 클릭하면 이미지 편집기가 실행되고 현재 선택된 파일을 불러 화면에 도시하는 기능, 또는 OLE를 사용할 때 서로간에 통신시 프로그램을 호출할 때 호출하는 ID 등록 또는 어떤 프로그램의 종료시 위치는 어디며 도구바는 어떤 형태로 위치해 있어서 종료하는가 등 여러 가지 정보를 등록하고 그것을 읽어 오는 파일입니다.

결국 윈도 안에서 실행되는 프로그램의 필요한 정보를 기억하고 있는 파일이라고 말할 수 있습니다. 윈도우 레지스트에 기록된 정보를 보거나 수정하고자 할경우에는 regedit.exe 파일을 실행시키면 됩니다. 그림 2-4-1은 윈도우 레지스트리 편집기를 실행시킨 형태입니다.

(그림 파일)

(그림 2-4-1)윈도우 레지스트리 편집기 실행결과

레지스트리안에는 매우 많은 정보들이 설정되어 있습니다. 새로운 프로그램을 설치하면 해당 프로그램이 주로 사용하는 파일 확장자 명을 등록하는 부분과 윈도우 시스템에서 파일 관리나 파일 처리에 필요한 정보를 설정하는 부분이 주 목적입니다.

여기에서는 윈도우 레지스트리를 이용하는 주된 목적을 알아야 할것 같습니다. 요즘 많은 어플리케이션이 프로그램에 필요한 정보를 무조건 윈도우 레지스트리에 저장하는 경우 매우 많습니다. 레지스트리에 저장하게 되면 그정보는 OS가 무너지기 전까지는 보존이 되기 때문에 유리한 잇점은 있으나 많은 어플리케이션에서 윈도우 레지스트리를 이용하여 정보를 저장하고 있기 때문에 로드가 많이 걸리는 단점도

있습니다. 필자의 주된 의견일지도 모르나 프로그램의 개인정보는 레지스트리에 저장하기 보다 프로그램 디렉토리에 따로 파일을 만들고 이곳에 저장하는것이 오히려 좋을때로 있습니다. 레지스트리에 많은 정보를 넣는다고 멋진 프로그램이 아니라는 점을 알았으면 합니다.

<1>레지스트리에 데이터 저장과 로드

레지스트리에 데이터를 저장할때 사용하는 함수는  CWinApp클래스의 WriteProfile계열의 함수입니다. 이함수는 다음과 같습니다.

CWinApp::WriteProfileInt

BOOL WriteProfileInt( LPCTSTR lpszSection, LPCTSTR lpszEntry, int nValue );

WriteProfileInt는 레지스트리에 숫자값을 기록하는 함수입니다. lpszSection은 섹션 데이터를 의미합니다. 섹션 데이터라는 의미는 집합체 항목이라는 뜻입니다. 이렇게 이야기 하여도 말자체가 어렵군요. 만득이네 집에는 천득이,만득이,백득이가 살고 천득이는 10살,만득이는 15살,백득이는 20살이라고 가정을 합시다. 이때 만득이의 나이를 알고자 할경우 다음과 같이 표현할수 있습니다.

만득이네집->만득이->나이?

이때 만득이네 집이 바로 섹션이 되는 것입니다. lpszEntery는 만득이가 되는것이고 nValue가 숫자가 되는것입니다. "testdata" 라는 섹션에 "intdata" 라는 엔트리 에 50이라는 값을 넣고자 한다면 다음과 같이 할수 있습니다.

WriteProfileInt("testdata","intdata",50);

CWinApp::WriteProfileString

BOOL WriteProfileString( LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszValue );

WriteProfileString는 lpszSection이라는 섹션에 lpszEntry 라는 엔트리명의 값을 문자열로 설정하고자 할때 사용하는 것입니다. WriteProfileInt와 같은 의미인데 저장되는 값이 문자열인것이 다릅니다. □testdata" 라는 섹션에 "stringdata"라는 엔트를 만들고 이곳에 □테스트□ 라는 값을 넣고자 한다면 다음과 같이 할수 있습니다.

WriteProfileInt("testdata","stringdata","테스트□);

위와같이 데이터를 저장하고 저장된 데이터를 읽고자 할때는  GetProfile계열의 함수를 이용합니다. 다음은 CWinApp의 GetProfile계열의 함수입니다.

CWinApp::GetProfileInt

UINT GetProfileInt( LPCTSTR lpszSection, LPCTSTR lpszEntry, int nDefault );

lpszSection은 섹션 명이며 lpszEntry는 섹션안의 엔트리 명입니다. nDefault는 만일 섹션안에 lpszEntry로 설정한 엔트리가 없다면 기본적으로 설정한 정수 값을 의미합니다. 위에서 "testdata"의 섹션에 "intdata"라는 엔트리에 50을 저장하는 예를 설명하였습니다. 위의 예를 수행하여 레지스트리에 값이 저장되었을때 이값을 얻는 방법은 다음과 같습니다.

int num=GetProfileInt("testdata","intdata",0);

위와 같이 하면 "intdata"엔트리에 50을 설정하였다면 이값이 num에 저장되게 됩니다.

CWinApp::GetProfileString

CString GetProfileString( LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszDefault = NULL );

GetProfileString는 GetProfileInt와 같은 형태입니다. 다만 리턴되는것이 CString형태입니다. 이이유는 문자열을 얻기 때문입니다. lpszDefault는 lpszEnter항목이 없을 때 설정되는 값입니다. 이인자를 사용하지 않으면 항목이 없을경우 NULL를 리턴하게 됩니다. 다음은 GetProfileString함수를 이용하여 "testdata"라는 섹션에 "stringdata"라는 엔트리에 들어 있는 문자열을 얻는 예입니다.

CString data=GetProfileString("testdata","stringdata");

위와 같이 하면 stringdata에 □테스트□ 라고 설정되었을경우 data에는 "테스트□ 라는 문자열이 설정되게 됩니다.

<2>레지스트리에 데이터 저장과 로드 예제 ExStatusSv

ExStatusSv 레지스트리에 데이터를 저장하고 로드하는 예제입니다. 이 예제의 전체구조는 그림 2-4-2와 같습니다.

(그림 파일)

(그림 2-4-2)ExStatusSv 전체 구조

그림 2-4-2의 ⓐ항목은 ExStatusSv에서 대화상자를 실행한 화면입니다. 대화상자에는 레지스트리의 문자열값을 저장하는 항목과(그림 2-4-2의 ①과 ②항목) 정수형값을 저장한는 항목입있습니다.(그림 2-4-2의 ②와 ③항목) 이두항목에 데이터를 입력하고  ⑥항목을 클릭하면 해당 데이터가 레지스트리에 저장됩니다. 반대로 ①과 ③항목에 엔트리명을 설정하고 ⑤항목을  클릭하면 레지스트리에 저장된 데이터가 로드됩니다. 이 예제는 ExStatusSv에서 CStatusDlg 클래스에서만 설정되어있습니다. 먼저 CStatusDlg의 헤더에 다음과 같이 섹션을 저장할 변수를 설정합니다.

CString m_strProfile;

생성자에 위와 같이 설정한 m_strProfile 에 "testdata"값을 설정합니다.

m_strProfile="testdata";

위와 같이 설정한후에 그림 2-4-2의 ①~④까지의 항목처럼 ID와 CString를 연결하고 ⑤항목의 버튼을 클릭하였을때 다음과 같이 기록합니다.

void CStatusDlg::OnLoad()

{

UpdateData(TRUE);

m_strStringData=AfxGetApp()->GetProfileString(m_strProfile,m_strStringName);

m_nIntData=AfxGetApp()->GetProfileInt(m_strProfile,m_strIntData,0);

UpdateData(FALSE);

}

OnLoad함수는 그림 2-4-2의 ①과 ③에 기록한 엔트리명을 이용하여 레지스트리에서 입력된 값을 얻고 이것을 다시 대화상자에 기록한 것입니다.

⑥항목을 선택하였을때 실행되는 함수는 다음과 같습니다.

void CStatusDlg::OnSavedata()

{

UpdateData(TRUE);

AfxGetApp()->WriteProfileString(m_strProfile,m_strStringName,m_strStringData);

AfxGetApp()->WriteProfileInt(m_strProfile,m_strIntData,m_nIntData);

}

OnSavedata는 그림 2-4-2의 ①~④항목에 기록한 엔트리와 데이터값을 레지스트리에 저장하는 것입니다.

ExStatusSv예제는 매우 간단합니다. 이 예제를 보시면 쉽게 레지스트리의 개념을 이해할수 있을것입니다. 다음은 ExStatusSv에서 CStatusDlg의 전체 소스입니다.

(프로그램 소스)

#if !defined(AFX_STATUSDLG_H__2951C901_3E5C_11D2_8B95_006097AD9DDC__INCLUDED_)

#define AFX_STATUSDLG_H__2951C901_3E5C_11D2_8B95_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// StatusDlg.h : header file

//

/////////////////////////////////////////////////////////////////////////////

// CStatusDlg dialog

class CStatusDlg : public CDialog

{

// Construction

public:

CStatusDlg(CWnd* pParent = NULL);   // standard constructor

// Dialog Data

//{{AFX_DATA(CStatusDlg)

enum { IDD = IDD_STATUSDATA };

CString	m_strStringName;

CString	m_strStringData;

CString	m_strIntData;

int	m_nIntData;

CString m_strProfile;

//}}AFX_DATA

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CStatusDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CStatusDlg)

afx_msg void OnLoad();

afx_msg void OnSavedata();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STATUSDLG_H__2951C901_3E5C_11D2_8B95_006097AD9DDC__INCLUDED_)

// StatusDlg.cpp : implementation file

//

#include "stdafx.h"

#include "ExStatusSv.h"

#include "StatusDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CStatusDlg dialog

CStatusDlg::CStatusDlg(CWnd* pParent /*=NULL*/)

: CDialog(CStatusDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CStatusDlg)

m_strStringName = _T("");

m_strStringData = _T("");

m_strIntData = _T("");

m_nIntData = 0;

//}}AFX_DATA_INIT

m_strProfile="testdata";

}

void CStatusDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CStatusDlg)

DDX_Text(pDX, IDC_STRINGNAME, m_strStringName);

DDX_Text(pDX, IDC_STRINGDATA, m_strStringData);

DDX_Text(pDX, IDC_INTNAME, m_strIntData);

DDX_Text(pDX, IDC_INTDATA, m_nIntData);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CStatusDlg, CDialog)

//{{AFX_MSG_MAP(CStatusDlg)

ON_BN_CLICKED(IDC_LOAD, OnLoad)

ON_BN_CLICKED(IDC_SAVEDATA, OnSavedata)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CStatusDlg message handlers

void CStatusDlg::OnLoad()

{

UpdateData(TRUE);

m_strStringData=AfxGetApp()->GetProfileString(m_strProfile,m_strStringName);

m_nIntData=AfxGetApp()->GetProfileInt(m_strProfile,m_strIntData,0);

UpdateData(FALSE);

}

void CStatusDlg::OnSavedata()

{

UpdateData(TRUE);

AfxGetApp()->WriteProfileString(m_strProfile,m_strStringName,m_strStringData);

AfxGetApp()->WriteProfileInt(m_strProfile,m_strIntData,m_nIntData);

}

(프로그램 소스끝)

2. 프로그램 환경정보 저장 예제 MenuEx

MenuEx는 툴바가 있으며 툴바에는 콤보박스가 설정된 예제입니다. 이예제에서 중요한것은 프로그램이 종료될때 현재 툴바와 다이얼로그바의 위치와 윈도우크기의 정보를 윈도우 레지스트리에 저장하였다가 새롭게 프로그램이 구동되면 이 내용을 로드하는 것입니다.

MenuEx이 실행 종료될 때 MenuEx 프로그램은 윈도 레지스터리에 □현재 내 위치는 어디이고 현재 내 도구바는 어떤 상태로 어떻게 위치되어 있으며 나의 윈도 모습은 어떤 형태다□라는 정보를 기록합니다.

다음 MenuEx 프로그램이 실행될 때 다시 윈도 레지스터리에서 정보를 읽고 난 후 그에 맞게 프로그램을 실행시켜 줍니다.

<1>윈도 레지스터리 등록하기

App 클래스 안에 BOOL CMenuExApp::InitInstance() 함수를 보면 SetRegistryKey라는 함수가 있습니다. 이함수를 다음과 같이 수정합니다.

SetRegistryKey("MenuEx 프로그램");

윈도 레지스터리에 MenuEx 프로그램이라는 것을 기록하는 것입니다. 이렇게 되면 레지스터리의 HKEY_CURRENT_USER 안의 Software 안에 MenuEx 프로그램이라는 항목이 등록됩니다.

다음 프로그램이 종료될 때 레지스터리에 정보를 기록해야겠지요. 프로그램 종료시 MainFrame에는 WM_CLOSE 메시지가 올 것입니다. 이 메시지가 발생되었을 때 윈도 레지스터리에 데이터를 기록하면 됩니다. ClassWizard에서 WM_CLOSE 메시지를 선택하면 void CMainFrame::OnClose() 함수가 생성됩니다.

이곳에 프로그램을 하면 됩니다.

제일 먼저 윈도의 위치를 알아야겠지요. 윈도의 위치를 저장하는 구조체가 WIN32에서 지원됩니다. 그 구조체는  WINDOWPLACEMENT입니다. 우선 구조체를 만듭니다.

WINDOWPLACEMENT wndpl;

wndpl.length = sizeof(WINDOWPLACEMENT);

다음 윈도의 위치를 받습니다.

BOOL bRet = GetWindowPlacement(&wndpl);

wndpl 구조체의 showCmd는 윈도의 크기가 보통인지 최대 크기인지 아니면 아이콘 형태인지에 따라 그 값을 SW_SHOWNORMAL, SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED로 설정합니다.

아이콘인지 아니면 최대로 되어 있는지를 확인하는 플래그 m_nIcon과 m_nMax를 두어서 이 값을 아이콘이면 m_nIcon을 1, 최대이면 m_nMax=1, 그 외 보통 크기이면 둘 다 0으로 세팅을 하면 되겠지요. 다음과 같이 하면 됩니다.

if(wndpl.showCmd == SW_SHOWNORMAL){

m_nIcon=FALSE;

m_nMax = FALSE;

}

else if(wndpl.showCmd == SW_SHOWMAXIMIZED){

m_nIcon=FALSE;

m_nMax = TRUE;

}

else if(wndpl.showCmd==SW_SHOWMINIMIZED){

m_nIcon=TRUE;

if(wndpl.flags)

m_nMax=TRUE;

else

m_nMax=FALSE;

}

다음, 데이터를 모두 레지스터리에 저장합니다. 먼저 윈도의 크기를 레지스터리에 저장합니다.

윈도의 크기는 □left top right bottom□의 크기로 문자열로 저장합니다. 레지스터리에 문자열로 저장하는 함수는 WriteProfileString입니다. WriteProfileString에는 3개의 인자를 넣어야 합니다.

만약 현재 윈도의 크기와 위치를 알고 싶다고 레지스터리에 물어 볼 때 □난 MenuEx프로그램이다□ 나의 □윈도 크기□에서 □CRect□의 좌표값을 넣겠다.

WriteProfileString("윈도크기□,□CRect",좌표)하면 되겠지요. 즉 레지스터리와 대화할 때 무엇의 무엇의 어떤 형 이런 대화를 만들기 위해서 3개의 인자를 만드는 것입니다. 일단 인자를 설정할 수 있는 변수를 헤더에 다음과 같이 설정합니다.

char m_strProfileHead[40];

char m_strProfileIcon[40];

char m_strProfileSize[40];

char m_strProfileMax[40];

다음 생성자에서 다음과 같이 초기화합니다.

sprintf(m_strProfileHead,"window size");

sprintf(m_strProfileIcon,"icon");

sprintf(m_strProfileSize,"rect");

sprintf(m_strProfileMax,"max");

그럼 CString 구조체를 갖는 strMessage에 다음과 같이 윈도의 위치를 넣으면 됩니다.

strMessage.Format("%04d %04d %04d %04d",

wndpl.rcNormalPosition.left,

wndpl.rcNormalPosition.top,

wndpl.rcNormalPosition.right,

wndpl.rcNormalPosition.bottom);

wndpl 안의 rcNormalPositon은 윈도의 위치 정보를 기억하고 있습니다.  다음 이것을 레지스터리에 등록합니다.

AfxGetApp()->WriteProfileString(m_strProfileHead,m_strProfileSize,strMessage);

즉 "window size"를 □rect"형으로 strMessage값을 넣는다는 말이죠.

다음 아이콘 상태의 정보를 레지스터리에 넣습니다. 이 때 m_nIcon은 정수형이므로 WriteProfileInt 함수를 사용합니다.

AfxGetApp()->WriteProfileInt(m_strProfileHead,m_strProfileIcon,m_nIcon);

즉 □window size"를  "icon" 유무로, 그 값을 m_nIcon 값으로 저장한다는 말입니다. 마찬가지로 m_nMax 값을 저장합니다.

AfxGetApp()->WriteProfileInt(m_strProfileHead,m_strProfileMax,m_nMax);

이제 현재의 도구바 상태를 저장하면 됩니다. 윈도 레지스터리에 현재 도구바 상태를 저장하는 함수는 아주 간단합니다. 다음과 같이 쓰면 됩니다.

SaveBarState(AfxGetApp()->m_pszProfileName);

<2>윈도 레지스터리에서 읽어 오기

다음은 윈도 레지스터리에서 읽어 오는 방법을 설명하겠습니다. 위의 내용을 이해했다면 그렇게 많은 설명이 필요없습니다. 쓰는 것이 Write이니까 읽는 것은 Get이겠지요. 즉 GetProfileString이나 GetProfileInt이겠지요. 이 때 레지스터리에서 읽어 오는 시점은 현재 프레임이 활성화되었을 때를 이용합니다.

클래스 위자드의 메인프레임에서 message와 ActivateFrame을 선택하면 현재 프레임이 활성화될때의 함수 void CMainFrame::ActivateFrame(int nCmdShow)가 실행됩니다.

레지스터리에서 정보는 프로그램이 처음 실행될 때 한 번만 읽어 오면 됩니다. 그러나 ActivateFrame은 현재 윈도가 활성화되어 있는 상태에서 계속 수행됩니다. 그렇기 때문에 플래그를 하나 만들어 놓습니다.

헤더에 BOOL m_nFirstTime;라고 설정하고 생성자에서 m_nFirstTime=1;로 설정합니다.

다음 ActivateFrame(int nCmdShow) 함수에서

if(m_nFirstTime)

{

라고 하면 m_nFirstTime이 1이기 때문에 수행이 되고 이 때 m_nFirstTime=0;으로 설정하면 다음 번에는 수행되지 않습니다.

이제 레지스터리에서 데이터를 받습니다.

//윈도 레지스터리에서 "window size" "rect"형으로 데이터를 받는다.

strMessage=AfxGetApp()->GetProfileString(m_strProfileHead,m_strProfileSize);

최소의 프로그램 수행 때는 윈도 레지스터리에 아무것도 없으므로 strMessage도 없습니다. 따라서 기본형으로 만듭니다.

if(strMessage.IsEmpty())

rect =CRect(0,0,500,400);

그 밖의 것은 데이터를 읽어 옵니다. 이 때 sscanf를 사용하면 편리합니다.

sscanf(strMessage.operator const char*(),"%d %d %d %d",

&rect.left,&rect.top,&rect.right,&rect.bottom);

다음은 아이콘 형태인지 최대 크기인지의 상태를 읽어 옵니다.

m_nIcon=AfxGetApp()->GetProfileInt(m_strProfileHead,m_strProfileIcon,0);

m_nMax=AfxGetApp()->GetProfileInt(m_strProfileHead,m_strProfileMax,0);

WINDOWPLACEMENT 구조체에 위 내용을 다시 기록한 다음, 전에는 GetWindowPlacement 함수를 사용했으므로 지금은 SetWindowPlacement를 사용합니다.

위와 같은 내용으로 수정한 void CMainFrame::ActivateFrame(int nCmdShow)은 다음과 같습니다.

void CMainFrame::ActivateFrame(int nCmdShow)

{

WINDOWPLACEMENT wndpl;

CRect rect;

UINT flags;

if(m_nFirstTime)

{

m_nFirstTime=FALSE;

//윈도 레지스터리에서 "window size" "rect"형으로 데이터를 받는다.

strMessage=AfxGetApp()->GetProfileString(m_strProfileHead,m_strProfileSize);

if(!strMessage.IsEmpty())

sscanf(strMessage.operator const char*(),"%d %d %d %d",

&rect.left,&rect.top,&rect.right,&rect.bottom);

else

//최초 프로그램을 실행시키면 strmessage에 아무것도 없으므로

rect = m_rectDefault;

//아이콘 형태인지 최대 크기 형태인지 값을 받는다.

m_nIcon=AfxGetApp()->GetProfileInt(m_strProfileHead,m_strProfileIcon,0);

m_nMax=AfxGetApp()->GetProfileInt(m_strProfileHead,m_strProfileMax,0);

//아이콘 형태이면

if(m_nIcon)

{

nCmdShow = SW_SHOWMINNOACTIVE;

if(m_nMax)

flags= WPF_RESTORETOMAXIMIZED;

else

flags = WPF_SETMINPOSITION;

}

//최대 크기 형태이면

else if(m_nMax)

{

nCmdShow=SW_SHOWMAXIMIZED;

flags = WPF_RESTORETOMAXIMIZED;

}

//보통 형태이면

else

{

nCmdShow= SW_NORMAL;

flags = WPF_SETMINPOSITION;

}

wndpl.length = sizeof(WINDOWPLACEMENT);

wndpl.showCmd = nCmdShow;

wndpl.flags = flags;

wndpl.ptMinPosition = CPoint(0,0);

wndpl.ptMaxPosition =

CPoint(-::GetSystemMetrics(SM_CXBORDER),

-::GetSystemMetrics(SM_CYBORDER));

wndpl.rcNormalPosition = rect;

LoadBarState(AfxGetApp()->m_pszProfileName);

BOOL bRet = SetWindowPlacement(&wndpl);

}

CFrameWnd::ActivateFrame(nCmdShow);

}

MenuEx 예제는 다양하게 많은곳에서 코딩이 되어 있습니다. 본예제는 전 Part의 툴바 기능을 가지고 있으며 이에 윈도우 레지스트리 기록 기능을 설정한것입니다. 따라서 소스를 생략합니다. 제공 되는 CD를 참조해 보시기 바랍니다. 본항목의 예제에서 우리가 알고자하는 중요 요점은 윈도우의 상태를 레지스트리에 저장하고 해당 내용을 다시 로드하는 부분입니다.

그림 2-4-3은 MenuEx의 실행결과입니다.

(그림 2-4-3)MenuEx실행결과