http://wiki.forum.nokia.com/index.php/Displaying_controls_in_Symbian_exe_programs
If we now wanted to create and display a CCoeControl, we'd need to have a CONE environment (CCoeEnv) as a minimum, as this class is used internally by the control. This makes things easier, given that CCoeEnv, as its name implies, creates a whole environment for us, thus limiting our task to the proper control creation:
Library required:
LIBRARY euser.lib //CTrapCleanup
LIBRARY ws32.lib //RWsSession,RWindow
LIBRARY cone.lib //CCoeEnv
LIBRARY eikcoctl.lib //CEikTextListBox,CTextListBoxModel
Source:
#include <coedef.h> // TCoeWinPriority
#include <e32base.h> // CTrapCleanup
#include <w32std.h> // RWsSession
#include <coecntrl.h> // CCoeControl
#include <coemain.h> // CCoeEnv
class CMyControl : public CCoeControl
{
public:
void ConstructL(const TRect& aRect);
private:
void Draw(const TRect& aRect) const;
};
void CMyControl::ConstructL(const TRect& aRect)
{
CreateWindowL();
SetRect(aRect);
ActivateL();
}
void CMyControl::Draw(const TRect& aRect) const
{
// Just paint it blue
CWindowGc& gc = SystemGc();
gc.SetBrushStyle(CGraphicsContext::ESolidBrush);
gc.SetBrushColor(KRgbBlue);
gc.SetPenStyle(CGraphicsContext::ENullPen);
gc.Clear(aRect);
}
// S60 screen size
const TUint screenWidth = 176;
const TUint screenHeight = 208;
LOCAL_C void ExeMainL()
{
CCoeEnv::Static()->RootWin().SetOrdinalPosition(0, ECoeWinPriorityAlwaysAtFront);
CMyControl* ctrl = new(ELeave) CMyControl;
CleanupStack::PushL(ctrl);
ctrl->ConstructL(TRect(0, 0, screenWidth, screenHeight));
ctrl->DrawNow();
CCoeEnv::Static()->WsSession().Flush();
CleanupStack::PopAndDestroy(ctrl);
}
GLDEF_C TInt E32Main()
{
#if defined(__WINS__)
// WINS already creates environment for us
CCoeEnv* coe = CCoeEnv::Static();
#else
CCoeEnv* coe = new CCoeEnv;
TRAPD(err, coe->ConstructL());
__ASSERT_ALWAYS(!err, User::Panic(_L("EXECTRL"), err));
#endif
TRAPD(error, ExeMainL());
__ASSERT_ALWAYS(!error, User::Panic(_L("EXECTRL"), error));
User::After(3*1000*1000);
#if !defined(__WINS__)
coe->DestroyEnvironment();
#endif
return 0;
}
#if defined(__WINS__)
EXPORT_C TInt WinsMain()
{
E32Main();
User::Exit(0);
return KErrNone;
}
TInt E32Dll(TDllReason)
{
return KErrNone;
}
#endif
Note that the emulator creates on our behalf an environment when loading an exedll/epocexe as .app, so we have to avoid creating a new environment on emulator builds, otherwise, we'd get a CONE 2 panic (Environment already exists)
Now moving a step further, we'd like to use existing controls, such as listboxes, dialogs, etc. In most cases, a CONE environment won't be enough, so we'll have to resort on the Eikon environment:
#include <coedef.h> // TCoeWinPriority
#include <e32base.h> // CTrapCleanup
#include <w32std.h> // RWsSession
#include <coecntrl.h> // CCoeControl
#include <eikenv.h> // CEikonEnv
#include <eiktxlbx.h> // CEikTextListBox
#include <eiktxlbm.h> // CTextListBoxModel
class CMyControl : public CCoeControl
{
public:
void ConstructL(const TRect& aRect);
~CMyControl();
private:
void SizeChanged();
TInt CountComponentControls() const;
CCoeControl* ComponentControl(TInt aIndex) const;
void Draw(const TRect& aRect) const;
private:
CEikTextListBox* iListBox;
};
void CMyControl::ConstructL(const TRect& aRect)
{
CreateWindowL();
// You may use CAknSingleStyleListBox, etc for Series 60..
iListBox = new(ELeave) CEikTextListBox;
iListBox->SetContainerWindowL(*this);
// iListBox->SetMopParent(this);
iListBox->ConstructL(this);
CDesCArray* items = static_cast<CDesCArray*> (iListBox->Model()->ItemTextArray());
_LIT(KItem1, "First");
items->AppendL(KItem1);
_LIT(KItem2, "Second");
items->AppendL(KItem2);
iListBox->HandleItemAdditionL();
iListBox->SetFocus(ETrue);
// iListBox->CreateScrollBarFrameL();
// iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOff, CEikScrollBarFrame::EAuto);
SetRect(aRect);
ActivateL();
}
CMyControl::~CMyControl()
{
delete iListBox;
}
void CMyControl::SizeChanged()
{
iListBox->SetRect(Rect());
}
TInt CMyControl::CountComponentControls() const
{
return 1;
}
CCoeControl* CMyControl::ComponentControl(TInt aIndex) const
{
switch (aIndex)
{
case 0:
return iListBox;
default:
return 0;
}
}
void CMyControl::Draw(const TRect& aRect) const
{
CWindowGc& gc = SystemGc();
gc.Clear(aRect);
}
// S60 screen size
const TUint screenWidth = 176;
const TUint screenHeight = 208;
LOCAL_C void ExeMainL()
{
CCoeEnv::Static()->RootWin().SetOrdinalPosition(0, ECoeWinPriorityAlwaysAtFront);
CMyControl* ctrl = new(ELeave) CMyControl;
CleanupStack::PushL(ctrl);
ctrl->ConstructL(TRect(0, 0, screenWidth, screenHeight));
ctrl->DrawNow();
CCoeEnv::Static()->WsSession().Flush();
CleanupStack::PopAndDestroy(ctrl);
}
GLDEF_C TInt E32Main()
{
#if defined(__WINS__)
// WINS already creates environment for us
CEikonEnv* coe = CEikonEnv::Static();
#else
CEikonEnv* coe = new CEikonEnv;
TRAPD(err, coe->ConstructL());
__ASSERT_ALWAYS(!err, User::Panic(_L("EXECTRL"), err));
#endif
TRAPD(error, ExeMainL());
__ASSERT_ALWAYS(!error, User::Panic(_L("EXECTRL"), error));
User::After(3*1000*1000);
#if !defined(__WINS__)
delete coe;
#endif
return 0;
}
#if defined(__WINS__)
EXPORT_C TInt WinsMain()
{
E32Main();
User::Exit(0);
return KErrNone;
}
TInt E32Dll(TDllReason)
{
return KErrNone;
}
#endif
The example above shows a pretty simple compound control, consisting of just one lodger control, a text listbox. But to make this a bit more useful, we'd need to receive events (such as key presses) and pass them to the control. The example bellow shows a simple implementation. Actually, it mimics somehow CCoeEnv::ExecuteD() functionality, which basically starts the active scheduler and waits for events from the window server (note that CCoeEnv is a CActive)
LOCAL_C void ExeMainL()
{
CCoeEnv* coeEnv = CCoeEnv::Static();
coeEnv->RootWin().SetOrdinalPosition(0, ECoeWinPriorityAlwaysAtFront);
CMyControl* ctrl = new(ELeave) CMyControl;
CleanupStack::PushL(ctrl);
ctrl->ConstructL(TRect(0, 0, screenWidth, screenHeight));
ctrl->DrawNow();
coeEnv->WsSession().Flush();
// Create a basic UI and set control to receive events
CMyAppUi* appUi = new(ELeave) CMyAppUi;
appUi->SetRootControl(ctrl);
coeEnv->SetAppUi(appUi); // takes ownership
for (;;)
{
// Wait synchronously for event
TRequestStatus status;
coeEnv->WsSession().EventReady(&status);
User::WaitForRequest(status);
if (status.Int() == KErrNone)
{
TWsEvent event;
coeEnv->WsSession().GetEvent(event);
// Check exit key
if (event.Key()->iCode == EKeyDevice3)
break;
// Pass event to control
appUi->HandleWsEventL(event, ctrl);
}
}
CleanupStack::PopAndDestroy(ctrl);
}
、、、、、、、、、、、、、、、、、、、、、、、、、、
everything isn't as straight foward as it seems in symbian
everything isn't as straight foward as it seems in symbian
在工作中慢慢地感触到这句话的涵义,也庆幸能够在BeiJing Site最大的symbian项目中负责AGPS网络监听部分的编写。
从一个很小的地方引出了一系列strange问题,但随着问题一个一个地被解决,逐渐对编写symbian,S60程序和bug fixing有了感觉。
一个简单的use case,当SUPL服务器对终端发出一个mobile terminated的定位请求时,会走packet switched网络,引发GPRS流量计费,mobile terminal需要针对用户的设置而引起不同的行为,如果用户选择automatic in home network并且mobile在漫游状态,会pop up个supl connection dialog,显示类似数据连结会引起费用,是否继续?这个显示的字符串已经被集成到S60 Position framwork,我只需要用StringLoader从编译后资源文件rsc中读取,可我load时总是调用失败,查看API document,看到LoadL有两个参数,一个是资源ID,一个是控件环境CCoeEnv*,默认值为NULL,在NULL时内部实现会调用CCoeEnv类工厂函数返回当前控件环境。在S60 GUI application中,app framework会创建CCoeEnv,但网络监听是个后台的symbian exe,并不存在CCoeEnv。所以我自己创建了CEikEnv *coe = new (ELeave) CEikEnv; coe->ConstructL();
创建后恶梦开始了,先是panic E32User Base 43,系统已经安装了活动调度器,查了所有代码,除了自己安装了一个活动调度器,并没有显式的创建其他.治表不治理,我在创建Active scheduler前加上CActiveScheduler::Install(NULL)卸载系统已有调度器,结果问题依旧.
伪代码如下:
CActiveScheduler::Install(NULL);
//创建自己的活动调度器
CEikEnv *coe = new (ELeave) CEikEnv;
TRAP(errorcode,coe->ConstructL());
最后请教了韩总,一语道破天机,说控件环境会创建活动调度器.更改了顺序如下:
CEikEnv *coe = new (ELeave) CEikEnv;
TRAP(errorcode,coe->ConstructL());
CActiveScheduler::Install(NULL);
//创建自己的活动调度器
E32User Base 43 panic消失了,又冒出E32User Base 46游离信号了.对于游离信号的产生,无外乎
1.活动对象创建后没有Add到活动调度器
2.没有set active标志位为真
3.同时发出多个异步请求,致使后续请求游离
查看了系统中所有active object,都是严格安装规则编写的.到底TM是哪个AO给我捣乱呢.看了看CEikonEnv的类层次结构,发现直接基类是CCoeEnv,爷爷类是CActive.my god!它会监听keypad,pointer,窗口前后台切换,焦点gained/lost等事件,它加到了自己own的活动调度器,并没有加入到我创建的调度器,等异步请求完成产生游离信号。所以,我把它加进来
CEikEnv *coe = new (ELeave) CEikEnv;
TRAP(errorcode,coe->ConstructL());
CActiveScheduler::Install(NULL);
//创建自己的活动调度器
CActiveSheduler::Add(coe);
结果在CAcitveScheduler::Add(coe)处,panic E32User Base 41错误,重复添加同一活动对象到调度器,晕,明明uninstall了coe创建的调度器了,怎么coe还是添加到自己创建的调度器.换种思路,我不自己创建调度器,只使用控件环境创建的调度器,伪代码如下:
CEikEnv *coe = new (ELeave) CEikEnv;
TRAP(errorcode,coe->ConstructL());
//创建自己的网络监听器
//启动监听
coe->ExecuteD();//ExecuteD内部会调用CActiveScheduler::StartL()启动调度器
这时panic Kern-Exec 3错误,Kern-Exec 3目前是最令人头疼的错误,SDK上讲,主要是引用空指针导致,如P指针为空,还要p->Method()来解引用.研究半天,原来coe会调用UI类的方法,在GUI appliation,app framework会创建UI,而现在我的程序中没有UI类,需要
CEikEnv *coe = new (ELeave) CEikEnv;
TRAP(errorcode,coe->ConstructL());
//创建UI
coe->SetAppUi(刚创建的UI类对象);
//创建自己的网络监听器
//启动监听
coe->ExecuteD();
Kern-Exec 3即可以解决,但是为了读个字符串资源,却莫须有添加这么多东西,所以放弃了这种方案.有没有一种读取资源而不参杂控件环境的方法呢,经过内部研究跟踪,发现了RResourceFile,TResourceReader
API(猜测Control enviroment内部也是使用这两个东西),最后成功解决了这个问题:).
再举几个symbian里陷阱重重的例子.
1. 如果我把发出异步请求,和启动活动调度器的顺序颠倒,这样写,
CActiveScheduler::StartL();//启动活动调度器
//创建自己的网络监听器
//启动监听(异步请求,会马上返回)
这样手机就当机,程序进入死循环.原因在于StartL内部的这句调用
User::WaitForAnyRequest();//等待异步请求完成而挂起线程
但异步请求是在其后发起,所以线程永远不会resume.
2.关于活动对象的DoCancel
CActive子类override这个纯虚函数,一定要真正取消异步请求,否则程序将进入endless loop,因为CActive::Cancel内部会
DoCancel();
User::WaitForRequest();//等待着KErrCancel事件
而Cancel往往在某个C类对象析构函数内.