这些天做UI控件,碰到个棘手的问题: 怎么样让用户看到实际效果了再应用设置.说详细点就是一个UI控件如何让用户能够对他所做的设置有个直观的认识,并根据效果判断是否应用设置.
想想要解决这样一个问题,无非就是弄个窗口出来,里面能够根据用户的设置绘制我的控件,用户觉得配置满意了,Apply一下,实际操作的控件也就跟着变样了.要实现的东西就多了, 首先得让控件能够将他的样式(这里都是属性)导出,然后由Demo控件中导入, 接着你得实现一个对话框, 并用他做为Demo控件的容器,在打开属性页的时候把他show出来.
对话框很好做,在Resource Editor中随便加一个框框,然后把编译好的你自己的控件拖上去.接着从CAxDialogImpl<T>派生一个对话框类,加入一个CAxWindow类对象,运行时将其与对话框上的控件绑定,重载CAxDialogImpl类的OnInitDialog. 通过一个静态的指针来在各个属性页中维护同一个窗口.
CMyCtlPrevDlg * CMyCtlPrevDlg ::CreateUniqueInstance(
HWND hParent /*= NULL*/,
LPSIZEL pszExt /*= NULL*/ )
{
if ( NULL == m_pUniqueInstance )
{
SIZEL szTemp = {0};
if ( pszExt != NULL )
{
szTemp = *pszExt;
}
m_pUniqueInstance = new CMyCtlPrevDlg ( hParent, szTemp );
if ( NULL == m_pUniqueInstance )
{
ATLTRACE( "Out of memory! ===CMyCtlPrevDlg ::CreateUInst===/n" );
return NULL;
}
HWND hWndThis = m_pUniqueInstance ->Create( hParent );
if ( NULL == hWndThis )
{
ATLTRACE( "Failed to create! ===CMyCtlPrevDlg ::CreateUInst===/n" );
m_pUniqueInstance ->Release();
return NULL;
}
}
m_pUniqueInstance ->AddRef();
return m_pUniqueInstance ;
}
LRESULT CAdvCtlPrevDlg::OnInitDialog(
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
BOOL& bHandled )
{
if ( m_hWnd == NULL )
{
return E_UNEXPECTED;
}
// Get interface
m_axMyCtrl.Attach( GetDlgItem( IDC_PREDLG_MYCTRL ) );
m_axMyCtrl.QueryControl( IID_IMyControl, (void**)&m_spMyCtrl );
if ( m_spMyCtrl == NULL )
{
return E_UNEXPECTED;
}
// Restore the container's size
if ( m_hParent != NULL )
{
::GetWindowRect(
m_hParent,
&m_rcPtWnd );
::GetClientRect(
m_hParent,
&m_rcPtClt );
}
// Init the size
UpdateExtent( m_szExtent );
bHandled = TRUE;
return 0L;
}
这样做出来的Preview Window将整合到属性页中, 当然也可以把他做出来,不过这么做可得小心,后面会提到.
再回到控件的属性页中
LRESULT CMyControlPP::OnInitDialog(
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
BOOL& bHandled )
{
.......
// Initialize preview window
CComQIPtr<IMyControl, &IID_IMyControl> pCtrl = m_ppUnk[ 0 ];
ATLASSERT( pCtrl != NULL );
// Init
CComQIPtr<IOleObject, &IID_IOleObject> pOleObj = pCtrl ;
SIZEL szPos = {0};
if ( pOleObj != NULL )
{
pOleObj->GetExtent( DVASPECT_CONTENT, &szPos );
}
// Create
m_pwndPreview = CAdvCtlPrevDlg::CreateUnitInstance( m_hWnd, &szPos );
if ( m_pwndPreview != NULL )
{
// Get the demo control
m_pwndPreview->GetTheControl(
IID_IMyControl,
(void**)&m_pDemoCtrl );
// Import style
CComQIPtr<IUnknown, &IID_IUnknown> pUnk = pCtrl ;
m_pwndPreview->ImportStyleIn( pUnk );
// Show the Preview Window
m_pwndPreview->ShowWindow( SW_SHOW );
}
.......
}
之后修改所有属性页中的代码,让所有的操作直接应用到m_pDemoCtrl上,当用户点击Apply时,从m_pDemoCtrl上导出样式,再一个个Objects轮流导入就ok了
这无形中还解决了另一个问题,反正这是我头一遭碰到,那就是:
前提:你的控件在操作过程中需要动态生成或移除"子"对象,就好比踢场足球你可能要换人,也可能有人被罚出场
如果是这样,当客户程序中存在多个同类的控件,且其内部的成员组成不一样(曼联上了11个人,阿森纳只上了7个),当用户将他们都框起来,然后打开属性页,一旦访问到有歧义的对象时(第8个队员),异常就蹦出来了,然后客户程序就崩溃了.
通过上面说的方法能够避免这种异常,并且允许用户对多个同类的控件做统一配置.(只闻上帝说道:"费厄泼赖", 曼联和阿森纳霎时间只留下两个守门员大眼瞪小眼了)
前面提到如果要把Preview Window做成浮动的必须小心,原因就是:
杀千刀的美国国家仪器(这里就不提英文名了,免得被找到)把这项技术注册为专利了......