예: 속성 페이지 구현
Visual Studio 2019 이상에서는 ATL 속성 페이지 마법사를 사용할 수 없습니다.
이 예제에서는 문서 클래스 인터페이스의 속성을 표시하고 변경을 허용하는 속성 페이지를 빌드하는 방법을 보여 줍니다.
예제는 ATLPages 샘플을 기반으로 합니다.
이 예제를 완료하려면 다음을 수행합니다.
클래스 추가 대화 상자 및 ATL 속성 페이지 마법사를 사용하여 ATL 속성 페이지 클래스를 추가합니다.
Document
인터페이스의 흥미로운 속성에 대한 새 컨트롤을 추가하여 대화 상자 리소스를 편집합니다.사용자가 변경한 내용을 속성 페이지 사이트에 알리는 메시지 처리기를 추가합니다.
하우스키핑 섹션에 몇 개의
#import
문과 typedef를 추가합니다.속성 페이지에 전달되는 개체의 유효성을 검사하도록 IPropertyPageImpl::SetObjects를 재정의합니다.
속성 페이지의 인터페이스를 초기화하도록 IPropertyPageImpl::Activate를 재정의합니다.
개체를 최신 속성 값으로 업데이트하도록 IPropertyPageImpl::Apply를 재정의합니다.
간단한 도우미 개체를 만들어 속성 페이지를 표시합니다.
속성 페이지를 테스트할 매크로를 만듭니다.
ATL 속성 페이지 클래스 추가
먼저 DLL 서버에 대해 ATLPages7
이라는 새 ATL 프로젝트를 만듭니다. 이제 ATL 속성 페이지 마법사를 사용하여 속성 페이지를 생성합니다. 속성 페이지의 짧은 이름으로 DocProperties를 지정한 다음, 문자열 페이지로 전환하여 아래 표에 나와 있는 속성 페이지 관련 항목을 설정합니다.
항목 | 값 |
---|---|
제목 | TextDocument |
문서 문자열 | VCUE TextDocument 속성 |
Helpfile | <blank> |
이 마법사 페이지에서 설정한 값은 IPropertyPage::GetPageInfo
를 호출할 때 속성 페이지 컨테이너로 반환됩니다. 그 이후의 문자열 처리는 컨테이너에 따라 다르지만, 일반적으로 사용자에게 페이지를 식별하는 데 사용됩니다. 제목은 일반적으로 페이지 위쪽의 탭에 표시되고, 문서 문자열은 상태 표시줄이나 도구 설명에 표시될 수 있습니다(표준 속성 프레임에서는 이 문자열이 사용되지 않음).
참고 항목
여기서 설정한 문자열은 마법사에 의해 문자열 리소스로 프로젝트에 저장됩니다. 페이지의 코드를 생성한 후에 이 정보를 변경해야 하는 경우 리소스 편집기를 사용하여 이러한 문자열을 쉽게 편집할 수 있습니다.
확인을 클릭하면 마법사에서 속성 페이지를 생성합니다.
대화 상자 리소스 편집
속성 페이지가 생성되었으므로, 이제 페이지를 나타내는 대화 상자 리소스에 몇 개의 컨트롤을 추가해야 합니다. 편집 상자, 정적 텍스트 컨트롤, 확인란을 하나씩 추가하고 아래와 같이 ID를 설정합니다.
이러한 컨트롤은 문서의 파일 이름과 읽기 전용 상태를 표시하는 데 사용됩니다.
참고 항목
대화 상자 리소스는 프레임 또는 명령 단추를 포함하지 않으며, 예상과 달리 탭 모양도 아닙니다. 이러한 기능은 OleCreatePropertyFrame을 호출하여 만든 것과 같은 속성 페이지 프레임에서 제공합니다.
메시지 처리기 추가
컨트롤이 배치되고 나면, 컨트롤의 값이 변경될 때 페이지의 변경 상태를 업데이트하는 메시지 처리기를 추가할 수 있습니다.
BEGIN_MSG_MAP(CDocProperties)
COMMAND_HANDLER(IDC_NAME, EN_CHANGE, OnUIChange)
COMMAND_HANDLER(IDC_READONLY, BN_CLICKED, OnUIChange)
CHAIN_MSG_MAP(IPropertyPageImpl<CDocProperties>)
END_MSG_MAP()
// Respond to changes in the UI to update the dirty status of the page
LRESULT OnUIChange(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
wNotifyCode; wID; hWndCtl; bHandled;
SetDirty(true);
return 0;
}
이 코드는 편집 컨트롤 또는 확인란의 변경 내용에 응답하여 IPropertyPageImpl::SetDirty를 호출하고, 여기서 페이지가 변경되었음을 페이지 사이트에 알립니다. 일반적으로, 이 알림에 대한 응답으로 페이지 사이트의 속성 페이지 프레임에서 적용 단추가 활성화되거나 비활성화됩니다.
참고 항목
변경되지 않은 속성의 업데이트를 방지할 수 있도록, 사용자가 변경한 속성을 고유한 속성 페이지에서 정확하게 추적해야 할 수도 있습니다. 이 예제에서는 원래 속성 값을 추적하고, 변경 내용을 적용할 때 UI의 현재 값과 비교하여 해당 코드를 구현합니다.
하우스키핑
이제 컴파일러에서 Document
인터페이스에 대해 알 수 있도록 몇 개의 #import
문을 DocProperties.h에 추가합니다.
// MSO.dll
#import <libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52> version("2.2") \
rename("RGB", "Rgb") \
rename("DocumentProperties", "documentproperties") \
rename("ReplaceText", "replaceText") \
rename("FindText", "findText") \
rename("GetObject", "getObject") \
raw_interfaces_only
// dte.olb
#import <libid:80CC9F66-E7D8-4DDD-85B6-D9E6CD0E93E2> \
inject_statement("using namespace Office;") \
rename("ReplaceText", "replaceText") \
rename("FindText", "findText") \
rename("GetObject", "getObject") \
rename("SearchPath", "searchPath") \
raw_interfaces_only
기본 클래스를 참조 IPropertyPageImpl
해야 합니다. 클래스에 다음 typedef
을 CDocProperties
추가합니다.
typedef IPropertyPageImpl<CDocProperties> PPGBaseClass;
IPropertyPageImpl::SetObjects 재정의
재정의해야 하는 첫 번째 IPropertyPageImpl
메서드는 SetObjects입니다. 여기서는 단일 개체만 전달되었는지, 예상되는 Document
인터페이스를 지원하는지 확인하는 코드를 추가하겠습니다.
STDMETHOD(SetObjects)(ULONG nObjects, IUnknown** ppUnk)
{
HRESULT hr = E_INVALIDARG;
if (nObjects == 1)
{
CComQIPtr<EnvDTE::Document> pDoc(ppUnk[0]);
if (pDoc)
hr = PPGBaseClass::SetObjects(nObjects, ppUnk);
}
return hr;
}
참고 항목
사용자가 개체의 파일 이름을 설정할 수 있도록 허용할 예정이므로, 어떤 위치에 있든 파일 한 개만 존재할 수 있도록 이 페이지에 대해 단일 개체만 지원하는 것이 타당합니다.
IPropertyPageImpl 재정의::Activate
다음 단계로, 페이지를 처음 만들 때 기본 개체의 속성 값을 사용하여 속성 페이지를 초기화합니다.
이 경우에는 페이지 사용자가 변경 내용을 적용할 때 초기 속성 값도 비교에 사용하므로 클래스에 다음 멤버를 추가해야 합니다.
CComBSTR m_bstrFullName; // The original name
VARIANT_BOOL m_bReadOnly; // The original read-only state
Activate 메서드의 기본 클래스 구현에서 대화 상자와 해당 컨트롤을 만들기 때문에, 이 메서드를 재정의하고 기본 클래스 호출 뒤에 고유 초기화를 추가할 수 있습니다.
STDMETHOD(Activate)(HWND hWndParent, LPCRECT prc, BOOL bModal)
{
// If we don't have any objects, this method should not be called
// Note that OleCreatePropertyFrame will call Activate even if
// a call to SetObjects fails, so this check is required
if (!m_ppUnk)
return E_UNEXPECTED;
// Use Activate to update the property page's UI with information
// obtained from the objects in the m_ppUnk array
// We update the page to display the Name and ReadOnly properties
// of the document
// Call the base class
HRESULT hr = PPGBaseClass::Activate(hWndParent, prc, bModal);
if (FAILED(hr))
return hr;
// Get the EnvDTE::Document pointer
CComQIPtr<EnvDTE::Document> pDoc(m_ppUnk[0]);
if (!pDoc)
return E_UNEXPECTED;
// Get the FullName property
hr = pDoc->get_FullName(&m_bstrFullName);
if (FAILED(hr))
return hr;
// Set the text box so that the user can see the document name
USES_CONVERSION;
SetDlgItemText(IDC_NAME, CW2CT(m_bstrFullName));
// Get the ReadOnly property
m_bReadOnly = VARIANT_FALSE;
hr = pDoc->get_ReadOnly(&m_bReadOnly);
if (FAILED(hr))
return hr;
// Set the check box so that the user can see the document's read-only status
CheckDlgButton(IDC_READONLY, m_bReadOnly ? BST_CHECKED : BST_UNCHECKED);
return hr;
}
이 코드에서는 Document
인터페이스의 COM 메서드를 사용하여 관심 있는 속성을 가져옵니다. 그런 다음, CDialogImpl에서 제공하는 Win32 API 래퍼와 해당 기본 클래스를 사용하여 사용자에게 속성 값을 표시합니다.
IPropertyPageImpl 재정의::Apply
사용자가 변경 내용을 개체에 적용하려고 하면, 속성 페이지 사이트에서 Apply 메서드를 호출합니다. 여기서는 Activate
의 코드와 반대 작업을 수행합니다. Activate
는 개체에서 값을 가져와 속성 페이지의 컨트롤에 푸시한 반면, Apply
는 속성 페이지의 컨트롤에서 값을 가져와 개체에 푸시합니다.
STDMETHOD(Apply)(void)
{
// If we don't have any objects, this method should not be called
if (!m_ppUnk)
return E_UNEXPECTED;
// Use Apply to validate the user's settings and update the objects'
// properties
// Check whether we need to update the object
// Quite important since standard property frame calls Apply
// when it doesn't need to
if (!m_bDirty)
return S_OK;
HRESULT hr = E_UNEXPECTED;
// Get a pointer to the document
CComQIPtr<EnvDTE::Document> pDoc(m_ppUnk[0]);
if (!pDoc)
return hr;
// Get the read-only setting
VARIANT_BOOL bReadOnly = IsDlgButtonChecked(IDC_READONLY) ? VARIANT_TRUE : VARIANT_FALSE;
// Get the file name
CComBSTR bstrName;
if (!GetDlgItemText(IDC_NAME, bstrName.m_str))
return E_FAIL;
// Set the read-only property
if (bReadOnly != m_bReadOnly)
{
hr = pDoc->put_ReadOnly(bReadOnly);
if (FAILED(hr))
return hr;
}
// Save the document
if (bstrName != m_bstrFullName)
{
EnvDTE::vsSaveStatus status;
hr = pDoc->Save(bstrName, &status);
if (FAILED(hr))
return hr;
}
// Clear the dirty status of the property page
SetDirty(false);
return S_OK;
}
참고 항목
이 구현의 시작 부분에 있는 m_bDirty 검사는 Apply
가 두 번 이상 호출될 경우 개체의 불필요한 업데이트를 방지하기 위한 초기 검사입니다. 이 밖에도 변경 내용이 있을 때만 Document
에 대한 메서드 호출이 발생하도록 하는 각 속성 값 검사가 있습니다.
참고 항목
Document
는 FullName
을 읽기 전용 속성으로 공개합니다. 속성 페이지의 변경 내용에 따라 문서의 파일 이름을 업데이트하려면 Save
메서드를 사용하여 파일을 다른 이름으로 저장해야 합니다. 따라서 속성 페이지의 코드를 속성 가져오기 또는 설정으로 제한하지 않아도 됩니다.
속성 페이지 표시
이 페이지를 표시하려면 간단한 도우미 개체를 만들어야 합니다. 도우미 개체는 단일 개체에 연결된 단일 페이지를 표시하기 위한 OleCreatePropertyFrame
API를 간소화하는 메서드를 제공합니다. 이 도우미는 Visual Basic에서 사용할 수 있도록 설계됩니다.
클래스 추가 대화 상자 및 ATL 단순 개체 마법사를 사용하여 새 클래스를 생성하고 짧은 이름으로 Helper
를 사용합니다. 클래스가 생성되면, 아래 표와 같이 메서드를 추가합니다.
항목 | 값 |
---|---|
메서드 이름 | ShowPage |
매개 변수 | [in] BSTR bstrCaption, [in] BSTR bstrID, [in] IUnknown* pUnk |
bstrCaption 매개 변수는 대화 상자의 제목으로 표시할 캡션입니다. bstrID 매개 변수는 표시할 속성 페이지의 CLSID 또는 ProgID를 나타내는 문자열입니다. pUnk 매개 변수는 속성 페이지에서 속성을 구성할 개체의 IUnknown
포인터입니다.
아래와 같이 메서드를 구현합니다.
STDMETHODIMP CHelper::ShowPage(BSTR bstrCaption, BSTR bstrID, IUnknown* pUnk)
{
if (!pUnk)
return E_INVALIDARG;
// First, assume bstrID is a string representing the CLSID
CLSID theCLSID = {0};
HRESULT hr = CLSIDFromString(bstrID, &theCLSID);
if (FAILED(hr))
{
// Now assume bstrID is a ProgID
hr = CLSIDFromProgID(bstrID, &theCLSID);
if (FAILED(hr))
return hr;
}
// Use the system-supplied property frame
return OleCreatePropertyFrame(
GetActiveWindow(), // Parent window of the property frame
0, // Horizontal position of the property frame
0, // Vertical position of the property frame
bstrCaption, // Property frame caption
1, // Number of objects
&pUnk, // Array of IUnknown pointers for objects
1, // Number of property pages
&theCLSID, // Array of CLSIDs for property pages
NULL, // Locale identifier
0, // Reserved - 0
NULL // Reserved - 0
);
}
매크로 만들기
프로젝트를 빌드했으면, Visual Studio 개발 환경에서 만들고 실행할 수 있는 간단한 매크로를 사용하여 속성 페이지와 도우미 개체를 테스트할 수 있습니다. 이 매크로는 도우미 개체를 만든 다음, DocProperties 속성 페이지의 ProgID 및 Visual Studio 편집기에서 현재 활성화된 문서의 IUnknown
포인터를 사용하여 해당 ShowPage
메서드를 호출합니다. 이 매크로에 필요한 코드는 다음과 같습니다.
Imports EnvDTE
Imports System.Diagnostics
Public Module AtlPages
Public Sub Test()
Dim Helper
Helper = CreateObject("ATLPages7.Helper.1")
On Error Resume Next
Helper.ShowPage( ActiveDocument.Name, "ATLPages7Lib.DocumentProperties.1", DTE.ActiveDocument )
End Sub
End Module
이 매크로를 실행하면 현재 활성화된 텍스트 문서의 파일 이름 및 읽기 전용 상태를 보여 주는 속성 페이지가 표시됩니다. 문서의 읽기 전용 상태는 개발 환경에서 문서에 쓸 수 있는지 여부만 반영하고, 디스크에 있는 파일의 읽기 전용 특성에는 영향을 주지 않습니다.