Create a custom filter for Dialog Filter (Industry 8.1)
7/8/2014
Learn how to create, deploy, and troubleshoot a custom dialog filter for your Windows Embedded 8.1 Industry (Industry 8.1) device.
Learn how to create a custom filter for Dialog Filter that blocks the new Windows UI dialog box that appears when you attempt to open a file that Windows Embedded 8.1 Industry (Industry 8.1) does not know how to open. You can also use these instructions as a starting point to creating your own custom dialog filter.
Introduction
Dialog Filter can block any window or dialog box that has a title and at least one button. However, in Industry 8.1, not all dialog boxes meet these criteria. If you attempt to open a file of an unknown type, Industry 8.1 presents a dialog box without a title that asks how you want to open the file, similar to the following image:
Since this dialog does not have a title, Dialog Filter cannot block it by default. However, you can block this dialog by using the Dialog Filter API to create a custom filter to extend or replace the standard filter functionality in Dialog Filter.
This dialog box is created by the openwith.exe process. Since this dialog box is the only dialog box that the openwith.exe process creates, we can create a filter that closes any dialog box that is created by the openwith.exe process.
Lab exercise purpose
In this lab exercise, you will use Microsoft Visual Studio 2012 to create a custom filter for Dialog Filter that extends the standard filter by blocking any window created by the openwith.exe process.
Important
This is a complex process, so make sure that you follow each step carefully and do not skip any required steps.
Overview of steps
In this lab exercise, you will create a new ATL project in Visual Studio and add code to create a custom filter DLL that extends Dialog Filter. You will then register the new DLL on your device running Industry 8.1.
- Step 1: Create a new project in Visual Studio
Create a new ATL project in Visual Studio 2012.
- Step 2: Modify project settings
Modify the project settings so that you can register your DLL on your device running Industry 8.1.
- Step 3: Get the Dialog Filter type library binary file
Export the Dialog Filter type library binary file from DialogFilter.dll.
- Step 4: Update the SampleFilter.idl
Update the interface definition language (.idl) file that defines the interface for your custom filter.
- Step 5: Add RemoteFilterObject.h to your project
Add the header file for your implementation of the IObjectFilterEx interface interface.
- Step 6: Add RemoteObjectFilter.cpp to your project
Add the source code file for your implementation of the IObjectFilterEx interface.
- Step 7: Update the SampleFilter.rgs file
Update the SampleFilter.rgs file that contains the registry settings to register the DLL server.
- Step 8: Update the stdafx.h header file
Update the stdafx.h header file to include important header and library files.
- Step 9: Build the project
Build the project in Visual Studio.
- Step 10: Copy the SampleFilter.dll to your Industry 8.1 device
Copy the new custom filter DLL from your development computer to your device running Industry 8.1.
- Step 11: (Optional) Verify standard dialog behavior
Verify that the dialog box created by the openwith.exe process is not blocked before the custom filter DLL is registered.
- Step 12: Register your custom filter
Register your new custom filter DLL.
- Step 13: (Optional) Verify that your custom filter works
Verify that your custom filter successfully blocks dialog boxes created by the openwith.exe process.
Prerequisites
To perform the steps in this lab exercise, the following are required:
- A development computer running Microsoft Visual Studio 2012.
- A device running Windows Embedded 8.1 Industry (Industry 8.1).
- Your device has Dialog Filter turned on.
- A way to transfer files between your device and your development computer.
Required lab exercises or knowledge
Knowledge of the following is recommended before you begin this lab exercise:
Step 1: Create a new project in Visual Studio
In this step, your will create a new DLL project in Visual Studio 2012 by using the ATL Project template.
To create a new DLL server project
On your development computer, start Visual Studio 2012.
In Visual Studio, open the File menu, and click New > Project.
In the New Project dialog box, under Templates/Visual C++/ATL, select ATL Project.
For the purpose of this lab, change the name of the project to SampleFilter. You can choose a different name if you want, but you will need to replace all occurrences of SampleFilter in the code samples with the name of your project.
Click OK to start the project wizard.
On the ATL Project Wizard window, click Finish to accept the default settings and create the project.
Step 2: Modify project settings
In this step, you will modify the project settings so that you will be able to register your DLL server on your device running Industry 8.1.
Important
You must set these project settings for each build configuration that you want to build.
To modify the project settings
In Visual Studio, open the Project menu, and click Properties.
On the left pane of the SampleFilter Property Pages dialog box, navigate to Configuration Properties > General.
On the right pane, change the Use of ATL setting to Static Link to ATL.
Navigate to Configuration Properties > C/C++ > Code Generation.
Change the Runtime Library setting to Multi-threaded Debug (/MTd).
On the left pane, navigate to Configuration Properties > Linker > General.
On the right pane, change the Register Output setting to No.
Click OK.
Save the project.
Troubleshooting
If you do not change the Use of ATL and Runtime Library settings, you may not be able to register the DLL.
Step 3: Get the Dialog Filter type library binary file
In this step you will use export the type library binary file for Dialog Filter from the Dialog Filter DLL.
To get the DialogFilterXmlFilter.tlb file
On your device running Industry 8.1, copy <system drive>:\Windows\System32\DialogFilterXmlFilter.dll to a location that you can access from your development computer.
On your development computer, in Visual Studio, open DialogFilterXmlFilter.dll.
In Visual Studio, on DialogFilterXmlFilter.dll, double-click “TYPELIB” to expand the node.
Under “TYPELIB”, right-click 1, and select Export.
In the Save File As dialog box, navigate to your SampleFilter project folder, typically Libraries\Documents\Visual Studio 2012\Projects\SampleFilter\SampleFilter.
Change File name to DialogFilterXmlFilter.tlb.
Click Save.
Step 4: Update the SampleFilter.idl
In this step, you update the .idl file that defines the interface for your custom filter.
To update SampleFilter.idl
On your development computer, in Visual Studio, open the Solution Explorer and double-click SampleFilter.idl.
In the SampleFilter.idl file, after the
import
statements, add the following line of code:typedef void* FILTER_HWND;
Find the following code block in SampleFilter.idl:
library SampleFilterLib { importlib("stdole2.tlb");
Insert the following code immediately after the previous code block to implement the IOjbectFilter interface:
[ uuid(ECDA10D8-BC94-4140-98EB-0BFB77BFF5A4), helpstring("IObjectFilter Implementation") ] coclass RemoteObjectFilter { [default] interface IUnknown; }
Save SampleFilter.idl.
Verify SampleFilter.idl
Your SampleFilter.idl file should look similar to the following code, although the initial UUID in line 13 will be unique to your project:
// SampleFilter.idl : IDL source for SampleFilter
//
// This file will be processed by the MIDL tool to
// produce the type library (SampleFilter.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
typedef void* FILTER_HWND;
[
uuid(AEA895FF-90A0-4922-9FB3-87BEF1BCC9C0),
version(1.0),
]
library SampleFilterLib
{
importlib("stdole2.tlb");
[
uuid(ECDA10D8-BC94-4140-98EB-0BFB77BFF5A4),
helpstring("IObjectFilter Implementation")
]
coclass RemoteObjectFilter
{
[default] interface IUnknown;
}
};
Step 5: Add RemoteFilterObject.h to your project
In this step you add the header file for your implementation of the IObjectFilterEx interface.
To add RemoteObjectfilter.h to your project
On your development computer, in Visual Studio, in the Solution Explorer for your SampleFilter project, right-click Header Files, then select Add > New Item.
In the Add New Item dialog box, select Header File (.h).
Change the name to RemoteObjectFilter.h.
Click Add.
Add the following code to RemoteObjectFilter.h:
// RemoteObjectFilter.h : Declaration of the CRemoteObjectFilter // // This implementation intercepts dialog boxes created by the openwith.exe // process. // // This project is created as an ATL In-Process COM Server. // // This project file should be configured to NOT automatically // register the output DLL. This is to get around permission issues when // running as a limited/non administrator user. After building and after // updating the interfaces/GUIDs, register the DLL in an administrator // command prompt: // // regsvr32 [/u] DemoFilter.dll // #ifndef _RemoteObjectFilter_H #define _RemoteObjectFilter_H #pragma once #include "resource.h" // main symbols // CObjectFilter class ATL_NO_VTABLE CRemoteObjectFilter : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CRemoteObjectFilter, &CLSID_RemoteObjectFilter>, public IDispatchImpl<IObjectFilterEx, &IID_IObjectFilterEx, &LIBID_SampleFilterLib, /*wMajor =*/ 1, /*wMinor =*/ 0> { public: CRemoteObjectFilter() { } DECLARE_REGISTRY_RESOURCEID(IDR_SAMPLEFILTER) BEGIN_COM_MAP(CRemoteObjectFilter) COM_INTERFACE_ENTRY(IObjectFilter) COM_INTERFACE_ENTRY(IObjectFilterEx) END_COM_MAP() DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct(); void FinalRelease(); public: // BEGIN - functions implemented from IObjectFilter STDMETHOD(CreateObjectInfo)(__in ULONG ProcessId, __in BSTR ProcessName, __in FILTER_HWND Handle, __in BSTR WindowClass, __in BSTR WindowTitle, __in LONG XPos, __in LONG YPos, __out IObjectInfo** pObjectInfo); STDMETHOD(CheckWindow)(__in IObjectInfo* pObjectInfo, __out CheckWindowResult* Action); STDMETHOD(CheckWindowContents)(__in IObjectInfo* pObjectInfo); STDMETHOD(LogMessage)(__in MessageType type, __in IObjectInfo* pObjectInfo, __in BSTR szData); STDMETHOD(OnConfigurationInit)(__in void* pvHwndUIA, __in DWORD dwMsg); STDMETHOD(OnConfigurationUpdate)(__in BSTR confnamespace); STDMETHOD(OnConfigurationDestroy)(void); STDMETHOD(LogMessageEx)( __in MessageType type, __in IObjectInfo* pObjectInfo, __in BSTR szActionPerformed); private: // is this the openwith.exe process? HRESULT _IsBlockedProcessDialog(__in IObjectInfo* pObjectInfo); CComPtr<IObjectFilterEx> _defaultFilter; }; OBJECT_ENTRY_AUTO(__uuidof(RemoteObjectFilter), CRemoteObjectFilter) #endif // _RemoteObjectFilter_H
If you did not name your project SampleFilter, replace all occurrences of SampleFilter in the code with the name of your project.
Save RemoteObjectFilter.h.
Step 6: Add RemoteObjectFilter.cpp to your project
In this step you add the source code file for your implementation of the IObjectFilterEx interface.
To add RemoteObjectFilter.cpp to your project
On your development computer, in Visual Studio, in the Solution Explorer for your SampleFilter project, right-click Source Files, then select Add > New Item.
In the Add New Item dialog box, select C++ File (.cpp).
Change the name to RemoteObjectFilter.cpp.
Click Add.
Add the following code to RemoteObjectFilter.cpp:
// ObjectFilter.cpp : Implementation of IObjectFilterEx // // This implementation intercepts windows created with the openwith.exe process. // When a windows is opened from the openwith.exe process, // it is redirected off screen by the QueryWindow function and the file name // is searched for in QueryWindowContents using UIAutomation. // // See RemoteObjectFilter.h for more information about building and // debugging. // #include "stdafx.h" #include "SampleFilter_i.h" #include "RemoteObjectFilter.h" // header for this class #define MAX_MSG_LEN 255 // Testing code - this is called to verify the return value of an HR and should // be redirected to a log file #define CHECK_HR(hr) if (FAILED(hr)) \ { \ return hr; \ } \ // // Hard coded filter items for demo purposes. Ideally this should be data driven // // The ProcessName passed down will contain the full path. // For this example, we want to block windows created by the openwith.exe process. // #define OPENWITH_EXE L"%SystemRoot%\\system32\\openwith.exe" CComBSTR szOpenWithPath = NULL; //----------------------------------------------------------------------------- // CObjectFilter implementation //----------------------------------------------------------------------------- // do initialization HRESULT CRemoteObjectFilter::FinalConstruct() { HRESULT hr = S_OK; if (szOpenWithPath == NULL) { //Construct the full path for openwith.exe. WCHAR szPath[MAX_PATH] = { 0 }; if (ExpandEnvironmentStrings(OPENWITH_EXE, szPath, MAX_PATH) == 0) { hr = HRESULT_FROM_WIN32(GetLastError()); } else { szOpenWithPath.Append(szPath); hr = szOpenWithPath.ToLower(); } } // Initialize default XML filter. This is optional if you implement your // own filter logic. In this example, we are extending the default filter. if (SUCCEEDED(hr)) { hr = _defaultFilter.CoCreateInstance(CLSID_ObjectFilter); } return hr; } //----------------------------------------------------------------------------- // do cleanup void CRemoteObjectFilter::FinalRelease() { } //----------------------------------------------------------------------------- // Call this function to get an instance of IObjectInfo. This will load the // windows information on initialization. STDMETHODIMP CRemoteObjectFilter::CreateObjectInfo(__in ULONG ProcessId, __in BSTR ProcessName, __in FILTER_HWND Handle, __in BSTR WindowClass, __in BSTR WindowTitle, __in LONG XPos, __in LONG YPos, __out IObjectInfo** ppNewObjectInfo) { // Get an instance of the default IObjectInfo. Alternatively, you may define your // own instance. This implementation will use UIAutomation to get a list of buttons // which define actions. CComPtr<IObjectInfo> Result; HRESULT hr = Result.CoCreateInstance(CLSID_ObjectInfo); // CLSID comes from the #import in stdafx.h CHECK_HR(hr); // Pass on these parameters to the IObjectInfo implementation hr = Result->SetWindowInfo(ProcessId, ProcessName, Handle, WindowClass, WindowTitle, XPos, YPos); CHECK_HR(hr); // result will get freed if this exits // Since we created a smart pointer, we'll need to pass the pointer back. // Detach will increment the reference count and keep the object alive. *ppNewObjectInfo = Result.Detach(); return S_OK; } //----------------------------------------------------------------------------- // This function looks at the object's basic info returns true if it is one of // OpenWith.exe's system dialog boxes (Class=#32770) // Can return: // S_OK - this is a dialog box from a blocked process // S_FALSE - this is not a dialog box from a blocked process // E_* - most likely a COM error HRESULT CRemoteObjectFilter::_IsBlockedProcessDialog(__in IObjectInfo* pObjectInfo) { HRESULT hr; // Get the window information. CComBSTR strProc, strClass; hr = pObjectInfo->get_ProcessName(&strProc); CHECK_HR(hr) hr = strProc.ToLower(); CHECK_HR(hr) // Compare to see if the process name is openwith.exe return (CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, strProc, -1, szOpenWithPath, -1) == CSTR_EQUAL) ? S_OK : S_FALSE; } //----------------------------------------------------------------------------- // BEGIN - filters //----------------------------------------------------------------------------- // CheckWindow filters only the outer parts of the window, including the // process name, window class, and window title. Note that this function // blocks the creation of a window so long delays will affect application // load times. This is also multithreaded so it must be thread safe. STDMETHODIMP CRemoteObjectFilter::CheckWindow(__in IObjectInfo* pObjectInfo, __out CheckWindowResult* Action) { // Check the window's basic information. This step must be as fast as // possible because this window is waiting for our response. HRESULT hr; // Check to see if this window was opened by a blocked process try { hr = _IsBlockedProcessDialog(pObjectInfo); CHECK_HR(hr) } catch (const CAtlException& err) { return err; } if (hr == S_OK) { // hide the window, then call CheckWindowContents *Action = cwHide; HWND hWnd; hr = pObjectInfo->get_Handle((void**)&hWnd); if (SUCCEEDED(hr) && hWnd != NULL) { PostMessage(hWnd, WM_CLOSE, 0, 0); } return S_OK; } // Call the default filter return _defaultFilter->CheckWindow(pObjectInfo, Action); } //----------------------------------------------------------------------------- // CheckWindowContents can filter the inner and outer parts of the window, // including process name, window title, window class, and top-level control // names and control types. Calls to this function are serialized. STDMETHODIMP CRemoteObjectFilter::CheckWindowContents(__in IObjectInfo* pObjectInfo) { // This function enters with the pObjectInfo's Action array loaded. This // information may be used in filtering as well. This function is // responsible for calling the action, unless passed onto the default XML // filter. return _defaultFilter->CheckWindowContents(pObjectInfo); } // END - filters //----------------------------------------------------------------------------- STDMETHODIMP CRemoteObjectFilter::LogMessage(__in MessageType type, __in IObjectInfo* pObjectInfo, __in BSTR szData) { return _defaultFilter->LogMessage(type, pObjectInfo, szData); } STDMETHODIMP CRemoteObjectFilter::OnConfigurationInit(__in void* pvHwndUIA, __in DWORD dwMsg) { return _defaultFilter->OnConfigurationInit(pvHwndUIA, dwMsg); } STDMETHODIMP CRemoteObjectFilter::OnConfigurationUpdate(__in BSTR confnamespace) { return _defaultFilter->OnConfigurationUpdate(confnamespace); } STDMETHODIMP CRemoteObjectFilter::OnConfigurationDestroy(void) { return _defaultFilter->OnConfigurationDestroy(); } STDMETHODIMP CRemoteObjectFilter::LogMessageEx( __in MessageType type, __in IObjectInfo* pObjectInfo, __in BSTR szActionPerformed) { return _defaultFilter->LogMessageEx(type, pObjectInfo, szActionPerformed); }
If you did not name your project SampleFilter, replace all occurrences of SampleFilter in the code with the name of your project.
Save RemoteObjectFilter.cpp.
Step 7: Update the SampleFilter.rgs file
In this step, you will update the SampleFilter.rgs file that contains the registry settings to register the DLL server.
To update SampleFilter.rgs
On your development computer, in Visual Studio, in the Solution Explorer for your SampleFilter project, expand Resource Files and double-click SampleFilter.rgs.
Replace the contents of SampleFilter.rgs with the following code:
HKCR { NoRemove AppID { '%APPID%' = s 'SampleFilter' 'SampleFilter.DLL' { val AppID = s '%APPID%' } } NoRemove CLSID { ForceRemove {ECDA10D8-BC94-4140-98EB-0BFB77BFF5A4} = s 'RemoteObjectFilter Class' { ProgID = s 'SampleFilterLib.RemoteObjectFilter.1' VersionIndependentProgID = s 'SampleFilterLib.RemoteObjectFilter' ForceRemove 'Programmable' InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } 'TypeLib' = s '{5AC36767-6A8F-420F-BB15-F2D0DE2A7129}' } } SampleFilterLib.RemoteObjectFilter.1 = s 'RemoteObjectFilter Class' { CLSID = s '{ECDA10D8-BC94-4140-98EB-0BFB77BFF5A4}' } SampleFilterLib.RemoteObjectFilter = s 'RemoteObjectFilter Class' { CLSID = s '{ECDA10D8-BC94-4140-98EB-0BFB77BFF5A4}' CurVer = s 'SampleFilterLib.RemoteObjectFilter.1' } } HKLM { NoRemove System { NoRemove CurrentControlSet { NoRemove services { NoRemove DialogFilter { NoRemove Parameters { val FilterDll = s '{ECDA10D8-BC94-4140-98EB-0BFB77BFF5A4}' } } } } } }
If you did not name your project SampleFilter, replace all occurrences of SampleFilter in the code with the name of your project.
Save SampleFilter.rgs.
Step 8: Update the stdafx.h header file
In this step, you will update the stdafx.h header file to include important header and library files.
To updated stdafx.h
On your development computer, in Visual Studio, in the Solution Explorer for your SampleFilter project, expand Header Files and double-click stdafx.h.
Replace the contents of stdafx.h with the following code:
// stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, // but are changed infrequently #pragma once #ifndef STRICT #define STRICT #endif #define _ATL_APARTMENT_THREADED #define _ATL_NO_AUTOMATIC_NAMESPACE #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit #include "resource.h" #include <atlbase.h> #include <atlcom.h> #include <atlctl.h> using namespace ATL; #import "dialogfilterxmlfilter.tlb" no_smart_pointers raw_interfaces_only raw_native_types no_implementation named_guids no_namespace // Access to UIAutomation library #include <UIAutomation.h>
Save stdafx.h.
Step 9: Build the project
In this step, you will build the project in Visual Studio.
To build the project
In Visual Studio, open the Build menu, and click Build Solution.
If the build succeeds, move on to the next step.
Troubleshooting
If the build does not succeed, verify that you followed each step in this lab exercise correctly. Some of the more common errors may be resolved by the following:
- Verify that you extracted the DialogFilterXmlFilter.tlb file correctly in Step 3: Get the Dialog Filter type library binary file and copied it to the correct location.
- If you used a different name for the project, make sure that you replace all occurrences of SampleFilter with the name of your project.
- Verify that you did not overwrite the generated UUID in the .idl file in Step 4: Update the SampleFilter.idl.
- If Visual Studio cannot find UIAutomation.h, you may need to download and install the Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1 (ISO).
Debugging
Since Dialog Filter intercepts and interacts with windows as they appear, debugging locally by using Visual Studio can cause the system to deadlock.
To work around this problem, you can use unit tests to test logic and perform integration testing and debug remotely. You can do this by deploying and registering your filter on a system running Industry 8.1 and by using a remote debugger such as the Visual Studio remote debugger. For more information, see the MSDN article Verifying Code by Using Unit Tests.
Step 10: Copy the SampleFilter.dll to your device running Industry 8.1
In this step, you will copy your new DLL from your development computer to your device running Industry 8.1.
To copy SampleFilter.dll to your device running Industry 8.1
On your development computer, locate the SampleFilter.dll file in either the debug or release folder in your project.
Copy the SampleFilter.dll to a location that you can access from your device.
On your device, copy SampleFilter.dll to the <system drive>:\Windows\System32 directory.
Step 11: (Optional) Verify standard dialog box behavior
In this optional step, you will verify that the dialog box created by the openwith.exe process is not blocked before the custom filter DLL is registered.
To verify the standard dialog box behavior on Industry 8.1
On your device running Industry 8.1, sign in to an administrator account.
Open a command prompt or a Windows PowerShell prompt.
Navigate to the desktop folder for the account. For example, C:\Users\Administrator\Desktop.
At a command prompt, type the following to create a new file named OpenDialog.unknown:
Copy NUL OpenDialog.unknown
-or-
At a Windows PowerShell prompt, type the following to create a new file named OpenDialog.unknown:
New-Item OpenDialog.unknown –type file
On your desktop, double-click the new file. You should see a dialog box similar to the following:
Click anywhere outside the dialog box to close it.
Step 12: Register your custom filter
In this step, you will register your new custom filter DLL.
To register your custom filter DLL
On your device running Industry 8.1, open a command prompt or a Windows PowerShell prompt as an administrator.
Navigate to the <system drive>:\Windows\System32 directory.
Type the following command to register SampleFilter.dll:
regsvr32 SampleFilter.dll
Sign out of the account and then sign back in.
Troubleshooting
If you cannot register your DLL, verify that you changed the project settings correctly in Step 2: Modify project settings.
Step 13: (Optional) Verify that your custom filter works
In this optional step, you will verify that your custom filter successfully blocks dialog boxes created by the openwith.exe process.
To verify your custom filter
- If you created the OpenDialog.unknown file in Step 11: (Optional) Verify standard dialog behavior, double-click the file to attempt to open it. The dialog box may flash briefly on the screen, but should instantly close.
Troubleshooting
If the dialog box still appears and does not close instantly, verify the following:
- Dialog Filter is turned on.
- You have signed out and back in to your account after registering the DLL.
If you need to unregister the SampleFilter.dll, you can type the following command at a command prompt or a Windows PowerShell prompt with administrator rights:
regsvr32 /u SampleFilter.dll
Conclusion
In this lab exercise you created a custom filter to extend Dialog Filter by creating a DLL server, and then registering the DLL server on your device running Industry 8.1.
Next steps
This lab exercise demonstrates the process of creating and registering a custom filter that extends the standard Dialog Filter functionality to also block windows from a specific process.
Once you have a custom filter working, you can modify the code for your specific needs. Because Dialog Filter invokes the custom filter any time that a window is opened, your custom filter has the opportunity to take specific actions, such as writing an event log message or playing a sound when a specific dialog box is opened. Because your code is invoked every time a window or dialog box is created, you want to make sure that your code is as fast as possible so that you don’t significantly degrade the performance of the system.
The functions you will most likely need to modify are CheckWindow and CheckWindowContents.