2008年7月27日 星期日

適合跑者的八大伸展操

身體的柔軟度可確保運動傷害的降低,而運動後又比運動前適合作身體的拉筋伸展。以下介紹八種相當適合跑者使用的伸展操。在作伸展操的過程中記得保持均勻的呼吸,每個動作至少維持15秒以上,最好能重複每個伸展動作2次。

步驟1.小腿肌

小腿上方:面牆站立,雙手貼牆。右腳滑向後方約一步寬,身體前傾左膝彎曲。右腳跟貼緊地面伸直右腿使臀部向前,你將會感到右小腿緊張,此時拉伸的正是右小腿的肌肉。注意右腳尖朝向正前方而不是朝身體外側。

小腿下方:右腳向前滑約一足寬,此時右膝盡可能彎曲使右腳跟仍能貼於地面。

以上動作做完換另一側腳進行。

步驟2.衝刺時所用的髖部肌肉

雙腳張開與肩同寬,雙膝彎曲使雙手分別於身體兩側貼於地面。左腿向後 直到完全伸直,此時右小腿和地面正成一直角。以你的雙手指尖將身體向上推,使你的胸部面向前方略為打開。此時正形成以右腳衝刺的姿勢。接下來可跳到步驟5,但記得要回 到此步驟並換另一側施行。

步驟3.四頭肌

一手靠牆支撐,另一隻手在身體後方握住一足,另一隻腳正直站立。將腳跟緩緩拉近臀部,但兩膝的位置仍相互靠近。此時緩緩將你的尾椎骨前移,你會得到更多的拉伸感覺。做完一 側腳後再換另一側腳進行。

 

步驟4.膝韌帶

兩腳一前一後交叉站立,雙手下垂。吸氣時軀幹伸直,呼氣時以髖部(非腰部)為軸使身體盡可能下彎。雙手著地或以某物支撐以保持平衡。保持後腳伸直。兩腿做完後,可雙腳打開與臀同寬,進行相同的身體彎下動作。

步驟5.全身拉伸

可從步驟2的動作跳到此步驟,這個動作對肌腱、小腿、肩膀和後背都有很 好的伸展作用。首先,右腿向後與左腿併排,雙腳打開與臀同寬,雙手分開與肩膀同寬。彎膝舉臀使骨盆傾斜,下背部成拱形。

接下來以雙手做好穩固支撐,緩緩地伸直你的腿,同時保持臀部抬高,雙腳跟向地面下沉但不必去貼緊 地面。頸部放鬆使頭部自由下垂,均勻呼吸維持1到2分鐘。完成時以雙膝下沉,坐於腳跟上。

步驟6.拉伸肌腱

背靠地面躺下,將左膝拖移至胸部。雙手握住大腿後側握緊,保持大腿緊張,緩緩將左腿向上方拉伸直到伸直為止。使左腳後跟朝上而不是腳尖朝上。過程中保持右腿伸直右大腿向下壓,且右腳尖朝上。交換另一側腿 重複此動作 。也可用皮帶來代替雙手拉住大腿,使大腿能充分拉伸。

步驟7.髖部外側

背靠地面躺下,雙膝彎曲使雙腳靠近臀部。使左腳外側靠於右膝蓋上方,雙手盤於右大腿後側並將其緩緩向胸部拉近。頭部放鬆或以軟物支撐。若想做更進一步的拉伸可使臀部更 向前直到下背略呈拱形。換腿後拉伸另一側髖部。

步驟8.靠腿舉牆

將腿舉高是擺脫疲勞最快速的方法。將雙腿靠牆伸直,將全身重量靠於背部下方的地板上。臀部盡可能靠近牆角,使下背部完全支撐於地板上,閉眼休息5分鐘。這個動作能緩緩的伸展肌腱和下背部,而且適合在練習結束後作為收操的動作。



資料來源

2008年7月24日 星期四

設定fedora的3d特效

一直以來,
我還以為我的fedora的顯示卡驅動程式預設有支援,
所以,當3d打不開時,我也沒有想太多,就不理它了。

在今天不小心被我看到有nvidia顯卡的驅動程式,
想說,就來試試看吧,
沒有想到剛好就可以了,
就在這裡記錄下來吧~

因為我的是fedora 9
所以要下載http://rpm.livna.org/livna-release-9.rpm

wget http://rpm.livna.org/livna-release-9.rpm


下載完驅動程式之後,就安裝吧

rpm -ivh livna-release-9.rpm


再用yum來安裝驅動程式

yum install xorg-x11-drv-nvidia


再用yum安裝以下程式
* emerald-themes
* compiz-fusion-extras
* emerald
* compiz-fusion
* compiz-manager
* compiz-fusion-extras-gnome
* gnome-compiz-manager
* libcompizconfig
* compiz-fusion-gnome
* ccsm

啟用 Compiz Fusion, 執行 系統 > 偏好設 > 外觀與感覺 > 桌面特效 將桌面特效開啟

設定 Compiz Fusion, 請執行 系統 > 偏好設 > 外觀與感覺 > CompizConfig Settings Manager
詳細設定的部分可以參考這裡
參考資料
Fedora 8 3D 桌面特效進階安裝設定

2008年7月23日 星期三

處理在ubuntu下DNS被重設的問題

快一個月了吧,
每一次打開ubuntu時,
筆電由無線AP取得網路位址之後,
同時也取得由AP送過來的DNS設定,
也就是說,透過AP我可以取得DNS的服務,
基本上我可以確定ap的設定沒有問題,
我的筆電有四個作業系統「Windows XP」、「Windows Vista」、「Ubuntu 7.10」、「Fedora 9」
除了ubuntu每一次連上網路之後,要重新設定dns之外,其它作業系統都ok,
所以,我猜應該是bug吧~
每一次重開機它都會被系統重新設定
因為,我真的受夠了每一次開機都要修改DNS的設定值的/etc/resolv.conf檔案


「做重覆的事情是罪惡的!」
因此,我打算解決這一個罪惡的事情。
只好把/etc/resolv.conf改成唯讀,而且是超級唯讀,連root也沒有辦法修改哩

chattr +i /etc/resolv.conf

經過這一個指令之後,/etc/resolv.conf就不能修改了,
連在linux下的神root也沒有辦法修改了
若要修改檔案的話,就把i這一個屬性去掉吧


chattr -i /etc/resolv.conf

That's it!!

c++ - 什麼是「參照」- reference

在C++中,副程式參數的傳遞分為三種:「call by value」、「call by address」、「call by reference」

而C++語言中可以對變數取另一個別名。這一個功能稱為參照(reference),例如使用參照對變數num取newnum這一樣一個別名,就會如下所示

int num;
int &newnum = num;


製作別名時,不管是原變數名還是別名,都可以做相同的存取。只要變更任何一方的值,另一方也會跟著改變。

newnum = 13;
cout << "num = " << num << endl;
cout << "newnum = " << newnum << endl;

注意最後一行,存取newnum時,不需要&

在C語言中使用指標,也可以得到同樣的效果。但指標在進行存取時,必需要對所指的資料加上*,因此比較複雜。

int num;
int *newnum = &num;
*newnum = 400;


而參照使用的地方最主要是「對函數的引數使用」
像下列一樣宣告引數時,使用參照可以對函數傳值

double Addnum(double &a, double &b)
{
  return a + b;
}


但是,這樣其實跟平常的call by value感覺好像差不多,以下就建立一個我覺得最有用的地方。


void caculate(double a, double b, double &sum, double &sub)
{
  sum = a + b;
  sub = a - b;
}

而我們這樣使用此函數

double first = 6;
double second = 4;
double ans1,ans2;
caculate(first,second,ans1,ans2);
printf("ans1 is %lg\nans2 is %lg\n",ans1,ans2);

注意到沒?
在之前若我們要由副程式傳回兩個結果,我們通常必需在副程式中建立一個陣列,
並且傳回那一個陣列的指標,
但是,若透過參照的話,就簡單很多了。

其實,參照就像「肥仔魚」這一個變數,我們另外把它的名字取作「阿肥」、「阿仔」、「阿魚」,其實,這是個都是同一個東西,我們對其中一個做改變,那麼當讀取任何一個值時,都會是改變過時候的值。
有一點類似指標啦,只不過在使用的時候更加便利,不需要加*哩

這裡另外提到把參照加上const的形式

int x;
const int &y = x;
x = 100; // ok
y = 200; // error

由x去改值沒有問題,但若由y去改值會出現錯誤


這裡有一個程式

...
struct Person
{
  char name[50];
  int age;
};

void PrintPersonRef(Person &psn); // 傳參照
void PrintPersonPtr(Person *psn); // 傳址
void PrintPersonVal(Person psn); // 傳值

int main()
{
  PrintPersonRef(shain1); // 不加&
  PrintPersonPtr(&shain1); // 加&
  PrintPersonVal(shain1); // 不加&
...
}

看到這裡就會浮現一個問題
因為傳參照與傳值函數的呼叫方法相同,會導致不知道要不要變更引數的值
為了避免這一個問題,在函數中要變更引數的值時,使用「傳址」比較好
若沒有要變更引數的話,利用const 加上參照速度會比傳值還要快
這樣比較方便以後程式的維護

整理如下:
不變更引數+小型資料=傳值
不變更引數+結構體等大資料=執行const後的傳參照值
變更引數+小型資料=傳址
變更引數+結構體等大資料=傳址


[2008.9.5]補充
回傳值為reference
例:
const QWMatrix & QCanvasView::inverseWorldMatrix () const

參考資料:博碩文化-新C++學習繪本

瘦腰:七種高纖水果 讓你變身楊柳腰減輕體重

原來香蕉熱量還可以,只是每一次我吃一定是吃三條以上,太沒有飽足感了
其實,比較起來,香蕉確實熱量高出很多~

  1、香蕉

七种水果 让你变身杨柳腰
  熱量:87千卡/100克
  香蕉潤腸通便的功效是大家耳熟能詳的了,堅持每天吃一兩根香蕉,有助 於排出體內毒素,收縮腰腹,煥發由內而外的健康美麗。而一根香蕉(淨重約100克左右)的卡路里,只有87卡而已,與一餐的白飯量(150克220卡)比 起來,大約只有一半以下的低卡路里。既排毒又瘦身。

  2、木瓜

七种水果 让你变身杨柳腰

  熱量:27卡/100克

  木瓜具有美白、豐胸等美容功效。用鮮木瓜炖湯或者是加蜂蜜的蒸木瓜,可是豐胸的上品!木瓜裡內含木瓜酵素,這些木瓜酵素不僅可分解蛋白質、糖類,更可分解脂肪 通過分解脂肪可以去除贅肉。

  3、奇異果

七种水果 让你变身杨柳腰
  熱量:30千卡/100克
  奇異果甜美多汁,又含有豐富維生素C的特色,果膠、果酸等成為最受歡迎的美容和塑身水果。帶點酸甜好滋味的奇異果,能防止便秘、幫助消化和美化肌膚。

  4、酪梨

七种水果 让你变身杨柳腰
  熱量:45千卡/100克
  酪梨中含量豐富的不飽和脂肪酸,能增加胸部組織彈性;含有的維生素A能促進女性荷爾蒙分泌,維生素C能防止胸部變形,維生素E則有助胸部發育。

  5、蘋果

七种水果 让你变身杨柳腰
  熱量:56千卡/100克
  蘋果是我們再熟悉不過的水果了,它有豐富的果膠,可以幫助腸胃蠕動和排除體內毒素,最棒的是還可以降低熱量吸收,再加上蘋果的鉀質很多,可以防止腿部水腫。另外,如果保有每天吃蘋果的飲食習慣的話,可以使肌膚紅潤有光澤。

  6、西柚

七种水果 让你变身杨柳腰

  熱量:60卡/個

  西柚的熱量十分低,根據美國一項研究表面,如果正常三餐都能吃上半個西柚,保健效果會非常好。當然如果覺得一下子吃半個西柚實在不行的話,那喝西柚汁的效果也是相當令人滿意的。

  7、番茄

(注意:空肚子不能吃,蕃茄中含有大量的膠質、困質以及肺膠酚等可溶性收劍劑成分。這些物質會與胃酸發生作用,形成難溶解的「結石」,從而阻塞胃的出口幽門,使胃內壓加升高,造成急性胃擴張,引起腹痛、嘔吐,甚至發生失液性休克。因此,最好是在飯後再食用番茄。 )

七种水果 让你变身杨柳腰
  熱量:15千卡/100克
  番茄含豐富的果膠等食物纖維,讓人有飽足感。有助消除便秘及促進新陳代謝,對減肥相當有幫助。還能補充人體缺乏的維他命和礦物質。

  七款美味水果,讓你在享受飲食的同時,可以擁有如楊柳般嬌細的美腰!





資料來源

天天喝豆漿 28天改善血太油

八成有乳糜血的捐血者飲食油又甜 天天喝豆漿九成五獲得改善

經由捐血者的驗血統計,發現乳糜血的問題日益嚴重,台北醫學大學附設醫院首次針對有乳糜血的捐血者進行飲食習慣調查。調查結果發現八成有乳糜血的捐血者飲食習慣「油又甜」。連續4星期飲用具有健康食品認證的低糖高纖豆漿,並搭配北醫營養室設計之健康飲食準則。九成五的參加者成功改善乳糜血,一圓捐血夢,會中並邀請民眾分享成功經驗。

台北醫學大學附設醫院營養室 蘇秀悅主任說明,在這次的調查中,有乳糜血的捐血者,多為30-50歲年齡層,佔五成以上;且八成五都是男型捐血者。其中八成五的比例,屬於體重過重或肥胖,身體質量指數(BMI)超過24公斤/公尺2標準值。顯示體重過重與乳糜血症的相關性,也是身體健康的警訊。

蘇秀悅主任說明,乳糜血的成因主要是因為不當的飲食習慣造成,除油脂攝取過多外,過多的精緻醣類攝取,尤其甜食、飲料,都是造成乳糜血的原因。

根據2006年台北捐血中心調查,平均每月有2千袋乳糜血,造成捐血民眾的愛心浪費。為協助民眾改善乳糜血,台北捐血中心、台北醫學大學附設醫院營養室與統一企業於七月初合辦了『拒絕血太油認證豆漿宅配送』活動。邀請捐血民眾中有乳糜血症狀者報名參加,活動由北醫營養室設計符合乳糜血民眾的健康飲食準則,並搭配具有健康食品認證,可有效降低膽固醇的低糖高纖豆漿,進行一個月乳靡血改善計劃。2007/9/1於台北市南海捐血站抽血驗收,結果有高達九成五的乳糜血問題民眾因此成功改善血太油問題,順利完成捐血夢。

資料來源

清涼而不傷身 吃冰講究方法

可以從食物的性味著手,不一定要溫度低才算酷涼。像綠豆湯、蓮藕茶、西瓜都算性冷食物。

炎炎夏日,不少人愛吃上一碗剉冰,感覺清涼又退火。中醫師表示,吃冰對身體反而火上加油,燥熱更甚。

署立新營醫院中醫科主任何裕鈞醫師指出,暑假以來,因吃冰引起不適症狀的患者特別多,許多家長甚至不知道是吃冰引發的問題。門診有位讀小五的小胖弟,主訴胸部悶痛、喉中經常有痰,肩背酸痛,尤其是背部肩膀內側的膏肓穴位一帶特別酸痛,問診後發現,原來家中開火鍋店,經常隨手一瓶冰涼飲料,長期下來所致。

■小孩吃冰過量 會常清喉嚨

何裕鈞表示,冰吃多了,許多小朋友會感覺有痰,經常不自覺清喉嚨,而且舌頭上一層白白的舌苔很厚,是因為體內腸胃的濕氣所導致,家長還以為是感冒,治療許久仍未改善,細問之下才知是冰涼飲品吃多造成。

為何會變燥熱?何裕鈞解釋,吃冰會降低體溫,而人是恆溫36-37℃的動物,腦中樞有溫度調節機制,人體為保持恆溫以保護身體,會升高產熱系統的運作,讓血液循環增快,以提高溫度,原本體內臟腑已經高於36℃、37℃,胃部收到增溫的生理保護訊息,會讓人愈吃愈熱、愈吃愈渴。同時,有的冰品糖水多,熱量也會較高。

另一方面,現代人因缺乏運動,生活作息不正常,喜吃重鹹口味、烤辣炸物,體質以氣陰兩虛、兼夾濕熱居多,導致循環代謝能力下降,冰吃多了,猶如寒氣包火,加重身體代謝困難,衍生各種病症。何裕鈞表示,小至經常性咳嗽、咽中痰阻、胸悶痛、頭暈痛、鼻塞、腸胃不適、腹瀉、腹痛、女性痛經、白帶多,大到氣喘發作、血壓升高…等都有可能發生。

還有位婦女,長期早晨起床後即喝一大杯現打冰涼蔬果汁,以為生機飲食有益身體,卻導致腹瀉、白帶多等症狀。後來發現因為過量飲用,且加入冰塊,導致不適。

中醫師強調,想要清涼而不傷身,可以從食物的性味著手,不一定要溫度低、吃冰才算清涼。像是綠豆湯、蓮藕茶便是清涼的,西瓜也算是冷食物。至於酷冰的薄荷糖也屬涼性小心吃過量也會有寒涼的徵狀,有人就是猛吃薄荷糖而流鼻水。另外,小麥草汁、牧草等草類,也多屬寒性。

吃冰是否有宜忌?是否吃藥就不能吃冰呢?何裕鈞指出,不管是服用中藥或西藥,藥物溶解度都會因為溶劑的溫度降低而下降,也就是吃藥最好還是配溫開水服用,才能產生原先預期的效果。

如果真想要吃冰飲料,建議飲料去冰且放置室溫一段時間後再吃,避免溫度太低。吃冰塊,可先含在口中待其融化後再吞下肚。同時,寧可吃冰飲料也不要吃含有冰塊成份的冰沙。有人將水果放冰庫當成水果冰,也要小心勿囫圇吞,以接近室溫再享用較宜。

■吃冰有時辰上的忌諱

依中醫經絡理論來推算,吃冰有時辰上的忌諱!何裕鈞指出,暑假有學生夜遊、或在KTV唱歌通宵、或是大人打麻將,要特別留意:容易咳嗽、氣喘的人避免在凌晨 3-5點吃冰。容易腹瀉、消化不良、營養吸收不佳者,避免在清晨5點至早上11點吃冰。有心血管疾病者則避開中午11-1點時間。有腎泌尿生殖系統問題者避免在下午3-7點吃冰。肝膽系統疾病者午夜11點至凌晨3點則是禁忌。

轉錄至「中時電子報」

2008年7月22日 星期二

日喝青花椰菜汁 英癌患病情控制

現在飲食當中,許多人喜歡將蔬菜水果打成汁飲用,一名英國膀胱癌末期患者,每天喝下一杯青花椰菜汁,沒想到竟然有效控制了體內癌細胞擴散,台灣營養師就說,抗癌關鍵就在青花椰菜內的硫化物可幫助肝臟排毒,但青花椰菜到底能不能百分百抗癌,醫界態度還是很保留。

把青花椰菜放到果菜汁裡打成汁,這樣一杯翠綠的蔬果汁,真的有抗癌效用嗎?一名英國膀胱癌末期患者,每天飲用一杯現打的青花椰菜汁,竟然體內癌細胞停止擴散,這樣的綠色奇蹟,營養師說青花椰菜的確內藏玄機。

台安醫院營養師鄭雅分:「花椰菜裡面,它是有含硫天然的化合物,有一個抗癌作用,那因為這些含硫的化合物,其實它可以幫助我們,在肝臟中解毒酵素的能力。」

為了增加口感,營養師建議另外可加入維他命A豐富的胡蘿蔔,和含有大量多酚的蘋果,多重營養下或許可創造出更多醫學奇蹟,但青花椰菜像花瓣一朵朵樣的外觀,如何確實清洗,也讓民眾大傷腦筋。台安醫院營養師鄭雅分:「建議是用流動的水沖洗,來回洗個2、3次就可以了。」

雖然青花椰菜汁有抑癌作用,但在醫學上還是沒有百分百證明,因此營養師還是強調,一天3菜兩蔬果才是最有效的抗癌飲食。

台南美食-part 2

畢格先生
台南市南區金華路二段55號

東興蚵嗲
台南市安平區古堡街1號

夏天‧轉角‧冰淇淋
台南市中西區民族路二段407號

Green Olive 綠橄欖生活飲食
台南市東區大學路18巷10號

洋蔥咖哩工房
台南市中西區永福路2段67號(小林煎餅斜對面,PLUS louge bar旁)

老家創意料理
台南市中西區西華南街120巷13號,巷口是來點子簡餐

奇美咖啡館(成大店)
台南市東區大學路1號(自強校區‧奇美樓)

媲美哈跟大支的評價冰淇淋-瑞比冰淇淋
台南市中西區民權路四段208號

永樂蝦仁肉圓
台南市中西區國華街三段177號

卓記汕頭魚麵
台南市中西區民生路一段158號

貴記鼎邊趖
台南市安平區延平街93號

黃氏鱔魚意麵
台南市中西區民權路三段44-46號

開元紅燒土魨
台南市北區富台新村10之1號(近開元路295號)

夫妻肺片
台南市東區東和路149號

雙全紅茶
台南市中西區中正路131巷2號

2008年7月21日 星期一

QT-chap 14

這一個章節要了解如何使用QMouseEvent:mousePressEvent、mouseMoveEvent、mouseReleaseEvent;QWMatrixQAccel

這裡要注意的是, mouse的任何事件都是由系統幫我們偵測,而我們要做的部分就是實作每一個事件後應該要做的事情。

cannon.h

CannonField現在可以接收滑鼠事件,使得用戶可以通過點擊和拖拽炮筒來瞄準。
CannonField也有一個障礙物的牆。


protected:
  void paintEvent( QPaintEvent * );
  void mousePressEvent( QMouseEvent * );
  void mouseMoveEvent( QMouseEvent * );
  void mouseReleaseEvent( QMouseEvent * );

除了常見的事件處理器,CannonField實現了三個滑鼠事件處理器。名稱說明了一切。


void paintBarrier( QPainter * );

這個非公開函數繪製了障礙物牆。


QRect barrierRect() const;

這個私有函數返回封裝障礙物的矩形。


bool barrelHit( const QPoint & ) const;

這個私有函數檢查是否一個點在加農炮炮筒的內部。


bool barrelPressed;

當用戶在炮筒上點擊滑鼠並且沒有放開的話,這個私有變量為TRUE。

cannon.cpp


barrelPressed = FALSE;

一開始在建構子中,表示一開始滑鼠沒有在炮筒上點左鍵


} else if ( shotR.x() > width() || shotR.y() > height() || shotR.intersects(barrierRect()) ) {

有了障礙物之後,我們有三種表示沒有命中的判斷情況


void CannonField::mousePressEvent( QMouseEvent *e )
{
  if ( e->button() != LeftButton )
    return;
  if ( barrelHit( e->pos() ) )
    barrelPressed = TRUE;
}

這是QT的事件處理器,當滑鼠指標在視窗元件上按滑鼠按鍵時,
此函數會被呼叫。
一開始先判斷是不是按左鍵,若不是的話,則跳出。
我們檢查滑鼠標指針是否在加農炮的炮筒內。
如果是的,我們設置barrelPressed為TRUE。
注意pos()函數返回的是窗口元件坐標系統中的點。


void CannonField::mouseMoveEvent( QMouseEvent *e )
{
  if ( !barrelPressed )
    return;
  QPoint pnt = e->pos();
  if ( pnt.x() <= 0 )
    pnt.setX( 1 );
  if ( pnt.y() >= height() )
    pnt.setY( height() - 1 );
  double rad = atan(((double)rect().bottom()-pnt.y())/pnt.x());
  setAngle( qRound ( rad*180/3.14159265 ) );
}


透過移動滑鼠去計算炮台的角度~

這是另外一個Qt事件處理器。
當用戶已經在窗口部件中按下了滑鼠按鍵並且移動/拖拽滑鼠時,它被調用。
(你可以讓Qt在沒有滑鼠按鍵被按下的時候發送滑鼠移動事件。請看QWidget::setMouseTracking()。)

這個處理器根據鼠標指針的位置重新配置加農炮的炮筒。

首先,如果炮筒沒有被按下,我們返回。
接下來,我們獲得滑鼠指針的位置。
如果滑鼠指針到了視窗元件的左面或者下面,我們調整滑鼠指針使它返回到視窗元件中。

然後我們計算在滑鼠指針和視窗元件的左下角所構成的虛構的線和窗口部件下邊界的角度。最後,我們把加農炮的角度設置為我們新算出來的角度。

記住要用setAngle()來重新繪製加農炮。

public static atan(tangent:Number) :Number:計算並傳回參數 tangent 所指定正切值的角度值,以弧度為單位。傳回的值介於負的 pi 除以2與正的 pi 除以2之間。


void CannonField::mouseReleaseEvent( QMouseEvent *e )
{
  if ( e->button() == LeftButton )
    barrelPressed = FALSE;
}

只要用戶釋放滑鼠按鈕並且它是在視窗元件中按下的時候,這個Qt事件處理器就會被調用。
如果鼠標左鍵被釋放,我們就會確認炮筒不再被按下了。

繪畫事件包含了下述額外的兩行:

if ( updateR.intersects( barrierRect() ) )
  paintBarrier( &p );


paintBarrier()做的和paintShot()、paintTarget()和paintCannon()是同樣的事情。

void CannonField::paintBarrier( QPainter *p )
{
  p->setBrush( yellow );
  p->setPen( black );
  p->drawRect( barrierRect() );
}

這個私有函數用一個黑色邊界黃色填充的矩形作為障礙物。


QRect CannonField::barrierRect() const
{
  return QRect( 145, height() - 100, 15, 100 );
}

這個私有函數返回障礙物的矩形。我們把障礙物的下邊界和視窗元件的下邊界放在了一起。


bool CannonField::barrelHit( const QPoint &p ) const
{
  QWMatrix mtx;
  mtx.translate( 0, height() - 1 );
  mtx.rotate( -ang );
  mtx = mtx.invert();
  return barrelRect.contains( mtx.map(p) ); // 先把p透過mtx.map()轉換成新的座標,再判斷barrelRect是否有被包含這一個轉換後的座標
}

如果點在炮筒內,這個函數返回TRUE;否則它就返回FALSE。

這裡我們使用QWMatrix類。它是在頭文件qwmatrix.h中定義的,這個頭文件被qpainter.h包含。
QWMatrix定義了一個坐標系統映射。我把它想成一個存在一個轉換矩陣,正常的座標可以透過它轉換成新的座標。QWMatrix.map()就是透過QWMatrix轉換座標。

它可以執行和QPainter中一樣的轉換。
這裡我們實現同樣的轉換的步驟就和我們在paintCannon()函數中繪製炮筒的時候所作的一樣。首先我們轉換坐標系統,然後我們旋轉它。
現在我們需要檢查點p(在窗口部件坐標系統中)是否在炮筒內。為了做到這一點,我們inverse這個轉換矩陣。倒置的矩陣就執行了我們在繪製炮筒時使用的inverse的轉換。我們通過使用反矩陣來映射點p,並且如果它在初始的炮筒矩形內就返回TRUE。

gamebrd.cpp


#include <qaccel.h>

QAccel類別是用來處理鍵盤的加速鍵和快捷鍵
鍵盤加速鍵是在某個組合鍵按下的時候出發一個動作,加速鍵可以處理窗口部件和它子部件裡所有的鍵盤動作所以它不會被鍵盤焦點所影響。


QVBox *box = new QVBox( this, "cannonFrame" );
box->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
cannonField = new CannonField( box, "cannonField" );

我們創建並設置一個QVBox,設置它的框架風格,並在之後創建CannonField作為這個盒子的子對象。
因為沒有其它的東西在這個盒子裡了,效果就是QVBox會在CannonField周圍生成了一個框架。


QAccel *accel = new QAccel( this );
accel->connectItem( accel->insertItem( Key_Enter ), this, SLOT(fire()) );
accel->connectItem( accel->insertItem( Key_Return ), this, SLOT(fire()) );

現在我們創建並設置一個加速鍵。
加速鍵就是在應用程序中截取鍵盤事件並且如果特定的鍵被按下的時候呼叫相應的slot。
這種機制也被稱為快捷鍵。
注意快捷鍵是窗口部件的子對象並且當窗口部件被銷毀的時候銷毀。
QAccel不是窗口部件,並且在它的父對象中沒有任何可見的效果。

我們定義兩個快捷鍵。我們希望在Enter鍵被按下的時候調用fire()槽,在Ctrl+Q鍵被按下的時候,應用程序退出。
因為Enter有時又被稱為Return,並且有時鍵盤中兩個鍵都有,所以我們讓這兩個鍵都調用fire()。


accel->connectItem( accel->insertItem( CTRL+Key_Q ), qApp, SLOT(quit()) );

並且之後我們設置Ctrl+Q和Alt+Q做同樣的事情。一些人通常使用Ctrl+Q更多一些(並且無論如何它顯示了如果做到它)。
CTRL、Key_Enter、Key_Return和Key_Q都是Qt提供的常量。它們實際上就是Qt::Key_Enter等等,但是實際上所有的類都繼承了Qt這個命名空間類。


QGridLayout *grid = new QGridLayout( this, 2, 2, 10 );
grid->addWidget( quit, 0, 0 );
grid->addWidget( box, 1, 1 );
grid->setColStretch( 1, 10 );

我們放置box(QVBox),而不是CannonField,在右下的單元格中。

2008年7月20日 星期日

宅男研究生 靠超商豆漿減肥法 6週甩油12公斤

好耶~暑假來試試看~
看能不能減肥並練出腹肌~

碩二魏姓研究生從小胖到大,試過許多網路減肥偏方,體重數字就像溜溜球一樣越減越高。眼看即將畢業,為了能在求職過程讓主管留下良好形象,順利進入理想的公司,魏同學求助於專業醫師後,利用「超商豆漿減肥法」,輕輕鬆鬆在6週內瘦了12公斤

楊氏減重中心楊名權醫師表示,魏同學是代謝症候群患者,平時的飲食習慣屬於外食族,食物熱量難以衡量。為了幫助他輕鬆、有效的將過重的體重減下來,於是設計出一套便利的減重菜單,藉由超商食品包裝上完整的熱量標示,幫助民眾計算並控制每日攝取總量。一般而言,每人每日所需的熱量約為體重的25至30倍,攝取超過25倍,熱量就容易轉換成體脂肪囤積體內。想減重者,女生每日熱量攝取標準應訂在體重的15倍大約1,200大卡左右,男生為體重的20倍大約1,500大卡在熱量控制的原則下,要盡量提高蛋白質與纖維素的補充,並降低糖份及油份的攝取,楊醫師建議,像無糖高纖豆漿就是不錯的蛋白質及纖維素的來源之一。

楊醫師進一步說明,無糖高纖豆漿的糖分低,營養價值卻很高,不僅含有豐富的植物性蛋白質,可補充身體所需熱量外,還有能加速身體代謝的鈣質。此外,豆漿有促進細胞修護的卵磷脂,與可調理女性荷爾蒙分泌的大豆異黃酮,有助於女性在減重期間維持良好氣色。市售豆漿選擇多元,部分盒裝豆漿獲得健康食品認證,並標榜添加膳食纖維可增加飽足感,促進腸胃蠕動,是很好的選擇。不過楊醫師也說明,豆漿減重的方式不見得適合每個人,像是有痛風病史的人,就必須避免攝取過多豆類製品。

醫師也提醒除了飲食控制外,每天要攝取2,000cc以上的水分幫助代謝體內廢物,同時也應持續運動,運動量需達到有點喘又不太喘的程度,且最好能持續流汗5分鐘,才能加速瘦身進度。夏天減重已成為一種全民運動,市面上的減重方法琳瑯滿目,民眾減重前應該先徵詢專業醫師或營養師的意見,依照個人狀況選擇合適的減重方式,切記勿使用沒有科學根據的減重方法,才不會勞財又傷身。

【超商豆漿減重食譜】
‧早餐:無糖高纖豆漿450cc + 茶葉蛋一顆 + 一份低糖水果或生菜沙拉
‧午餐:隨意(原則上熱量控制在600大卡內)
‧晚餐:無糖高纖豆漿450cc + 一份低糖水果或生菜沙拉
‧注意事項:每日飲用2,000cc白開水及運動30分鐘,更能輔助減重效果的達成。

糖量少的水果:如番茄(空肚子不能吃,蕃茄中含有大量的膠質、困質以及肺膠酚等可溶性收劍劑成分。這些物質會與胃酸發生作用,形成難溶解的「結石」)、楊桃、小番茄、櫻桃、葡萄柚、蓮霧、木瓜(膳食纖維)、水梨(膳食纖維)、芭樂(膳食纖維)、柚子
不要多吃:蘋果(膳食纖維)、西瓜、橙(膳食纖維)
糖量高的水果:如梨(膳食纖維)、荔枝、龍眼、葡萄、香蕉(膳食纖維)(空腹吃香蕉可誘發心肌梗塞)、柿子、榴槤、芒果,菠蘿

資料來源

在網路上找到的相關文章,作者是matika

幾年來, 一直都想減肥, 但是都斷斷續續的, 意志力不夠, 方法不對, 都會造成減了又復胖.

最近半年多來, 不斷嘗試各種方式, 總算有些成果, 而且沒有復胖. 但離我的理想還有一段距離就是.

目前成果: 94 KG, 175cm, body fat 29.5%
半年多前: 破百, body fat 37%

首先, 有幾項要請你先放棄:
1. 所有減肥藥, 請把錢省下來, 買健康的食材或是運動用品都好.
2. 加糖飲料, 能不喝就不喝, 買茶請改無糖.
3. 戒掉油炸, 燒烤食物
4. 澱粉類, 諸如麵飯馬鈴薯地瓜等盡量少吃, 一週內吃的次數勿超過三次.
5. 三餐時間要正常, 晚餐後盡量不吃東西, 頂多喝點湯或是低糖水果.
6. 宵夜絕對禁斷

三餐的原則
1. 早餐要豐富, 牛奶/豆漿(低糖), 穀類(雜糧麵包或是早餐穀片). 蛋. 火腿或德國香腸, 豆腐
2. 午餐勿吃外面的便當(500 - 700大卡), 可吃生菜沙拉+優酪. 配上少許水煮肉類或水煮蛋
3. 晚餐, 以低糖水果為主, 蓮霧, 芭樂, 蘋果, 番茄, 再加一杯無糖的茶

以我最近常吃的舉例:
早餐:
Set A- 水煮蛋+雜糧土司*2+脫脂牛奶+低脂起司*1+火腿*2 or 德國香腸*1+番茄*1
Set B- 脫脂牛奶+早餐穀片+生菜+德國香腸*1+番茄*1
Set C- 豆腐+低鹽醬油+番茄*2

熱量A>B>C

午餐:
Set A- 7-11沙拉+優酪+茶葉蛋或是博克香腸(7-11一次買齊)
Set B- 自製生菜沙拉+和風醬+水煮火鍋肉片


熱量B>A


晚餐:
千萬要準時吃, 太晚吃或不吃都不對, 再忙去7-11買包番茄來吃都好.
盡量以低糖水果為主.


幾個錯誤的觀念千萬要注意:
1. 不要不吃某一餐. 對胃腸不好. 熱量過低也會讓人有昏厥的感覺
2. 承上, 攝取熱量過低, 人體會有調節機制, 就像電腦會進入低電壓待機狀態一樣,.
新陳代謝變慢, 人也會比較沒精神. 所以不要節食或不吃!!
3. 長期服用減肥藥勞民傷財, 會不會有副作用也很難說.

運動:
快走半小時約耗300大卡(每天進行最好)
適量重量訓練, 手持裝滿水的寶特瓶進行, 或是到健身房.
適量的仰臥起坐即伸展運動
游泳尤其適合體重較重者, 比較部會讓關節有太大的負擔.

結語:
1. 要減的輕鬆又健康, 朋友的聚餐不是不能去, 但是往後幾天要加強運動份量把他消耗掉!
2. 自己DIY適合自己的低卡餐飲. 有趣又不違背自己對食物的喜好
3. 每天早晚各量一次體重及體脂, 數字的變化會讓你有成就感, 也同時有警惕作用.

祝大家減肥順利!!

安平古堡一日遊

昨天下午三點多,
眼睛看電腦看到有一點痛,
所以決定出去逛逛,
嗯,就決定去安平古堡逛一下吧~
順便收集一下台南小吃。

看了一下地圖,就民生路一直騎就到安平路,再一直騎,easy啦~

在路途中發現「周氏蝦捲」,
噹噹~
又收集到一枚台南美食了,
可是我覺得還ok耶,不過,它有加「哇沙米」,
感覺很爽哩~

然後,就先到「安平古堡」參觀一下囉,
嗯,全票50元、學生票25元、台南人免費(聽說台南市學生也是免費哩),爽度無價~
台南人去參觀台南古績都是免費的哩~爽~
古堡裡面有一個遼望塔,可以看到紅樹林和海埔新生地哩~

然後,就是重點的,「安平老街」啦,
若有人要來台南,但是,沒有什麼時間的話,
那唯一選擇就是安平老街了,
感覺就很像淡水老街與九份老街,
有很多商家進駐,但是,風味就是台南古都。

蚵仔煎首推:古堡蚵仔煎
蝦捲:大家都推「周氐蝦捲
還有一個新的蔥燒餅,老闆把它弄的很像pizza,
我個人是覺得不錯吃啦~
蝦餅:之前有買到軍中請大家吃,為啥選它?因為,台南大部分是小吃,沒有辦法遠行啦~

蜜餞的話以下是擷取自ptt
如果是想吃安平最有名的蜜餞 可以去永泰興~
但想吃特別的中藥蜜餞 找正合興~
想要吃有趣的蜜餞 找金泉興(正合興隔壁 有很多小玩意)

另外,還有蠻多好玩的東西,
像是木頭做的青蛙,跟一個小木棒一起買,
去滾它的背,可以發出青蛙的聲音。

還有劍獅音樂盒、一堆吊飾、皮包啦~不過,價錢~嗯~
好啦,反正,要來玩過才知道,而我這次又沒有帶數位像機,所以,只能這樣囉~

在回家的路上往另一條路回去,
就碰到「億載金城」,嗯,還是一樣,台南人免費,
炮台還蠻大的,很適合跟它合照~

好吧,要回去了,去逛到「林默娘公園」,嗯,有人在那裡放風箏耶~
有一個林默娘的雕像好大啊,應該超過六樓的高度吧~
旁像就是海的感覺~不然應該是河啦~
反正,就是很大~

嗯,這次真的要回來了~
來台市中心又繞了一下遠路,
噗~看到了「延平郡王祠」,外面有一個雕像應該就是鄭成功騎馬的樣子吧~
逛了一下,這次真的要回家了~

路上看到一個黑輸2元,停下來吃了一下,耶,這裡是孔廟商圈,
那孔廟在哪裡啊?
嗯,逛了一下,耶,找到了,原來「延平郡王祠」跟「孔廟」附近我之前都來過了啊
只是一直沒有進去而以~

嗯,台南真的是古跡和小吃的地方,
隨便亂晃都可以看到~

嗯,忘記說,回來找網路資料,發現,有「安平樹屋」和「德記洋行」這兩樣東西,
也是在安平附近,下次再去看看吧~

大家有空要來台南玩喔~


安平古堡:台南市安平區國勝路82號
周氏蝦捲:台南市安平區安平路125號
安平老街:台南市安平區延平街上
紅樹林
海埔新生地
古堡蚵仔煎:台南市安平區效忠街85號
永泰興:台南市安平區安中里延平街84號
正合興:台南市安平區延平街85號(益生堂中藥對面)
金泉興:台南市安平區延平街77號
億載金城:台南市安平區光州路三號
林默娘公園:台南市安平區安平港附近
台南漁人碼頭:台南市安平區
延平郡王祠:台南市中西區開山路152號
孔廟:台南市中西區南門路2號
安平樹屋: 台南市 古堡街108號
德記洋行:台南市安平區古堡街106號
南方公園:台南市中西區中山路(新光三越對面,市立醫院前)

府城觀光護照

2008年7月19日 星期六

台南景點

虎頭埤風景區。
part2
台南縣新化鎮中興路42巷36號
騎卡打車。
看圖片感覺還不錯,可以去玩玩看

台南大橋國中旁的永康創意園區花海
台南縣永康市東橋十街1號
感覺還蠻棒的,
花海耶~

台南的後花園~中興林場
台南縣新化鎮知義里口埤76號
耶仔細一看,我還蠻喜歡大自然的哩

小吃一條街--延平老街
台南市安平區延平街上(古堡街與平生路間,街長約270m)

台南新商圈─中正淺草商圈
台南市中西區中正路與國華街

吳園及原台南公會堂
台南市中西區民權路二段30號

巴克禮紀念公園
台南市東區崇明里中華東路三段文化中心正對面

超豪華的Motel—綠驛MOTEL
台南市北區海安路三段259號

安平運河博物館
台南市安平區安平路97-15號(安億橋安平端橋下)

府城藝術轉角
台南市南區健康路一段88號

台南市體育場 - 體育公園
台南市南區體育路10號

安平海山館
台南市安平區文朱里效忠街52巷7號

向日葵花田
台南市東區生產路台糖研究所對面

鹿耳門天后宮
台南市安南區媽祖宮顯草街三段一巷236號

五妃廟
台南市中西區五妃街201號

延平郡王祠
台南市中西區開山路152號

大南門
台南市中西區南門路

台南漁人碼頭
台南市安平區

台南公園
台南市北區公園路356號


安平小砲台

台南市安平區西門里安平小段1006之七地號

正統鹿耳門聖母廟
台南市安南區城安路160號

2008年7月18日 星期五

台南美食

雖然我是台南人,
其實我台南不熟,
其實台南有很多好吃的東西,好玩的地方,
還是開一個文章來記錄我在台南的歷程,
順便督促我去認識台南這一個地方。

一開始先把ptt上台南版的推荐的一些地方吧

1. 周氏蝦捲 241 票
台南市安平區安平路408-1號
2008/07/19
其實我覺得還ok啦,沒有想像中的好吃,價錢是很貴啦~
有一點名過其實,可能之前真的不錯吃吧,不過,現在客人多了,
就先做好,都是冷凍的啦~
不過,它有一包調味包是哇沙米,很嗆,吃起來很爽哩~
★★☆☆☆

2. 安平陳記(氏/家)蝦捲、蚵捲 201 票
台南市安平路786號
2008/07/21
今天吃了蚵捲和蝦捲,還蠻不錯吃的,
跟上次吃的周氏更有彈性,裡面還有廣告說王建民說這裡的蚵仔煎是他必點的美食喔,
不過,因為我要去吃古堡蚵仔煎,所以,這裡的下次吃吧~
★★★★☆

3. 福記肉圓 196 票
台南市中西區府前路一段215號
2008/8/16
裡面的內餡是用一塊一塊大肉塊組成的,
好像是2塊26元,湯是免費的喔
★★★☆☆

4. 武廟肉圓 169 票
台南市永福路二段225號
2008/?/?
裡面的內餡是一小碎肉壓成一整塊,價錢我忘記了哩~
★★★☆☆

5. 阿松割包 155 票
台南市中西區國華街三段181號

6. 雙品豬排 146 票
台南市金華路四段72號 (與民權路交叉)

7. 富盛號碗粿 140 票
台南市西門路二段333巷8號

8. 阿憨鹹粥 133 票
台南市北區公園南路169號
2008/07/19
主要的內容有魚肚切片與蚵仔50元(魚肚外加40元),
與我平常
在市場買的:飯加熱湯加魚肉加蚵仔,蚵仔有時候會吃到殼
育樂街買的:飯加魚肉加其它料,飯當場煮成粥,其它料是看你點什麼
阿憨鹹粥的:粥加魚肚切片加蚵仔,跟其它粥比起來就是別人用魚肉,它是用魚肚切片吧,蚵仔也不錯,另外,老闆會問你要不要加魚肚,我是覺得沒有必要,雖然魚肚也蠻好吃的,不過,魚肚到處都吃的到,這樣cp值就變低了,一樣若沒有加魚肚的話適合觀光,因為好吃又吃不飽
★★★★☆

9. 老曾羊肉 130 票
台南市中西區民族路二段133號
?/?/?
昨天我去看(2008/07/18)我才發現,這一家我在高中的時候就吃過了,
是還蠻好吃的,店面小小舊舊的,羊肉炒沙茶青菜,羊肉清湯兩個很推荐
★★★☆☆

10. 金得春捲 126 票
台南市民族路三段19號

11. 沙卡里巴棺材板 121 票 
台南市中正路康樂市場內180號

12. 赤崁棺材板 115 票
台南市中正路康樂市場內180號
(這兩個明明就同一家阿?)
2008/7/16
真的是蠻好吃的,料實在,不過量少,
真的還蠻適合給觀光客吃的啦,為啥?
因為好吃又吃不飽啊,馬上可以往下一個地方吃下一個美食
★★★★★

12. 萬川號包子 115 票
台南市民權路一段205號(原青年路76號)

14. 阿國鵝肉 111 票
台南市南區育南街21-27號

15. 進福炒鱔魚意麵 103 票
台南市府前路一段46號
膳魚意麵要80元,有沒有搞錯啊~等哪一天有閒錢再去吃吧~

16. 卓家魚麵 99 票
台南市民生路一段158號(開山廟旁)(老店)
台南市中華東路3段50號(總店)

17. 國華街小卷米粉 95 票
台南市國華街三段菜市場一樓

18. 阿堂鹹粥/蝦仁飯 95 票
台南市西門路一段728號 (早上)

19. 阿明豬心 88 票
台南市保安路72號(晚上)

20. 圓環頂菜粽 86 票
台南市府前路一段40號

21. 開元路 虱目魚和肉燥飯 85 票
台南市北區富台新村8號(近開元路295號)

21. 友誠蝦仁肉圓 85 票
台南市開山路118號(位於轉角,黃色招牌,非常好找)

21. 阿鐵鱔魚意麵 85 票
台南市西門路二段352號
2008/7/20
什麼?阿鐵膳魚也是三十大美食之一?!
我這麼驚訝不是因為他難吃,
而是我大學同學他家對面就是阿鐵膳魚,
我同學說可以去吃吃看,
當天我去吃了之後,嗯,覺得真的是超好吃的啦~
膳魚超多的,吃起來又脆,
不過,因為我比較喜歡吃油麵,
所以,我點的是膳魚油麵,
超好吃的~只要60元,而且,吃一完就飽了~
我會驚訝是因為,我怎麼隨隨便便就可以吃到美食啦?
以後,沒有辦法這麼隨便吃到怎麼辦啊?
我給他滿分
★★★★★

24. 五妃街慶中街口巷子豬血湯 82 票
台南市慶中街24號

25. 小杜意麵 75 票
台南市友愛街143號

26. 安平豆花對面的福記水煎包 73 票
台南市安北路400號

27. 祿記包子 71 票
台南市中西區開山路3巷27號

27. 保安宮 阿鳳浮水魚羹 71 票
台南市保安路(茂雄對面)

29. 榮盛米糕 67 票
台南市友愛街康樂市場106號

連得堂煎餅
從公園路轉進北忠街就可以接到崇安街
有人說好吃,有人說明聲,有人說太硬,有人說那是特色
有空去看看

古堡蚵仔煎
安平古堡門口往前看就知道了
聽說不錯吃
2008/7/21
今天跟室友一起去吃,是還蠻不錯吃的~
不過,我因為看了照片的蚵仔超大顆的,果然照片會騙人,
不過還不錯吃啦~我室友台北人認為加番茄醬和豆芽菜很怪,
但是,台南人應該會覺得沒有加這兩樣才是很怪吧,
果然是南北大不同~
★★★☆☆

Mobile01 台南市一覽
Mobile01 台南縣一覽

2008年7月17日 星期四

換氣學習中

今天是第三天晨泳了,
在換氣的過程中,
我發現一件事情:

本來之前我是用鼻子吐氣,用嘴巴吸氣,
但是,怎麼感覺好像還在憋氣在游,
而且,肚子滿是空氣,
我就想說,嗯,可能是我吸到空氣之後,馬上就把它吞到肚子裡面,
難怪我跟我憋氣游的距離差不多。

所以,今天我就用鼻子吸氣和呼氣,
但是,結果還是一樣,肚子很飽,距離還沒有進步多少?!

所以,目前我推論出來,我的空氣的使用率在水中很差。
所以,今天一半的時間我都在水中,頭露出水面呼吸,
嗯,心臟有水的壓力,果然不太一樣~
最近這幾天先試這一個練習方式吧~

另外,目前作習是:
11點睡,五點半起床,看一下程式,六點半出發游泳,七點半離開游泳池,買早餐回宿舍放,八點睡覺,睡到九點,起床吃早餐,看程式~

QT-chap 13

這一章要學會QVBoxLayoutQHBoxLayout要怎麼使用

* t13/lcdrange.h包含LCDRange類別定義。
* t13/lcdrange.cpp包含LCDRange類別實現。
* t13/cannon.h包含CannonField類別定義。
* t13/cannon.cpp包含CannonField類別實現。
* t13/gamebrd.h包含GameBoard類別定義。
* t13/gamebrd.cpp包含GameBoard類別實現。
* t13/main.cpp包含MyWidget和main。

lcdrange.h

class LCDRange : public QWidget

注意,現在LCDRange不是繼承QVBox,而是繼承QWidget
而我們選用功能強大且有一點困難的QVBoxLayout來管理我們的視窗元件

lcdrange.cpp


#include <qlayout.h>

我們現在需要qlayout來管理我們的API(Application Programming Interface)


LCDRange::LCDRange( QWidget *parent, const char *name ) : QWidget( parent, name )

之前lcdrange是繼承QVBox,
現在改繼承QWidget,並且由layout管理視窗元件


QVBoxLayout * l = new QVBoxLayout( this );

我們建立一個QVBoxLayout,其所有的設定值都是用預設的,其parent是this(也就是QWidget)
用來管理此視窗的子元件


l->addWidget( lcd, 1 );

加入lcd進入視窗元件中,並且,它的stretch factor是1,表示會延展
若有一個是1另一個是2,則最後的長寬比是1比2


l->addWidget( slider );
l->addWidget( label );

再加入兩個元件,其延展因數是預設值的0
這個伸展控制是QVBoxLayout(和QHBoxLayout,和QGridLayout)所提供的,
而像QVBox這樣的類別卻不提供。在這種情況下我們讓QLCDNumber可以伸展,而其它的不可以。

cannon.h

新增了遊戲結束和新的函數


bool gameOver() const { return gameEnded; }

若遊戲結束則回傳TRUE,否則回傳FALSE


void setGameOver();
void restartGame();

這是兩個新增的函數,分別是設定「遊戲結束」與「遊戲重新開始」


void canShoot( bool );

這一個函數是用來控制shoot的按紐是否有效


bool gameEnded;

這一個非公開變數是用來記錄遊戲是否結束,TRUE為結束;FALSE則還沒有結束

cannon.cpp


gameEnded = FALSE;

一開始在建構子中,設定遊戲尚未結束


void CannonField::shoot()
{
  if ( isShooting() )
    return;
  timerCount = 0;
  shoot_ang = ang;
  shoot_f = f;
  autoShootTimer->start( 50 );
  emit canShoot( FALSE );
}

我們現在改用isShooting這一個函數代替isActive
並且由shoot觸發canShoot不能再發設子彈


void CannonField::setGameOver()
{
  if ( gameEnded )
    return;
  if ( isShooting() )
    autoShootTimer->stop();
  gameEnded = TRUE;
  repaint();
}

我們把游戲結束的這一個函數獨立出來,以便可以讓外面呼叫
當此副程式被呼叫時,
則停止timer的運行,把gameEnded的標誌設定為TRUE
並重畫


void CannonField::restartGame()
{
  if ( isShooting() )
    autoShootTimer->stop();
  gameEnded = FALSE;
  repaint();
  emit canShoot( TRUE );
}

如果炮彈還在空中,則停止timer計時,設定標誌為FALSE,並且重畫
就像hit()或miss()一樣,moveShot()同時也發射新的canShoot(TRUE)信號。


void CannonField::paintEvent( QPaintEvent *e )
{
  QRect updateR = e->rect();
  QPainter p( this );

  if ( gameEnded ) {
    p.setPen( black );
    p.setFont( QFont( "Courier", 48, QFont::Bold ) );
    p.drawText( rect(), AlignCenter, "Game Over" );
}

先把目前要更新的區域放在updateR中,
若遊戲結束則設定字型與要顯示的字放在中間


if ( updateR.intersects( cannonRect() ) )
  paintCannon( &p );
if ( isShooting() && updateR.intersects( shotRect() ) )
  paintShot( &p );
if ( !gameEnded && updateR.intersects( targetRect() ) )
  paintTarget( &p );
}

我們只有在炸彈在空中的時候,才會畫炮彈

gamebrd.h

這是一個新的文件,可以把它想成是最底下的一個白板,就包含左邊的輸入介面,與右邊的顯示介面


protected slots:
  void fire();
  void hit();
  void missed();
  void newGame();

我們增加了四個slot,只能讓內部使用與繼承的類別內部使用

gamebrd.cpp

這一個文件是新的,是用來做為MyWidget中的gameboard類別的實作


cannonField = new CannonField( this, "cannonField" );

在gamebrd類別的建構子中宣告一個CannonField的物件


connect( cannonField, SIGNAL(hit()),this, SLOT(hit()) );
connect( cannonField, SIGNAL(missed()),this, SLOT(missed()) );

CannonField類別中有一個signal hit(),且GameBoard類別中也有一個slot hit()
當CannonField觸發hit()時,會使得GameBoard類別中的hit()同時運作(使命中的lcd加1,且若炮彈數為0時,則遊戲結束)
同理
當CannonField類別中的missed()運作時,GameBoard類別中的missed()同時運作(,若炮彈為0時,則遊戲結束)


connect( shoot, SIGNAL(clicked()), SLOT(fire()) );

當shoot的pushbutton被點後,本物件的fire()會同時被執行(把炮彈送出去[cannonField->shoot()],並且把炮彈減一)


connect( cannonField, SIGNAL(canShoot(bool)),shoot, SLOT(setEnabled(bool)) );

用canShoot來決定shoot的pushbutton按鈕生效和失效


QPushButton *restart = new QPushButton( "&New Game", this, "newgame" );
restart->setFont( QFont( "Times", 18, QFont::Bold ) );
connect( restart, SIGNAL(clicked()), this, SLOT(newGame()) );

設定一個新按紐來決定否是重新一個新的遊戲


hits = new QLCDNumber( 2, this, "hits" );
shotsLeft = new QLCDNumber( 2, this, "shotsleft" );
QLabel *hitsL = new QLabel( "HITS", this, "hitsLabel" );
QLabel *shotsLeftL = new QLabel( "SHOTS LEFT", this, "shotsleftLabel" );

這裡我們建立四個視窗元件,這裡說明我們在GameBoard類別建構子中才宣告hitsL與shotsLeftL指標主要是因為,我們沒有要對它們做任何改變
當GameBoard視窗元件被銷毀的時候,Qt將會刪除它們,並且佈局類會適當地重新定義它們的大小。
換句話說,當我們宣告指標在標頭檔時,當視窗元件被銷毀時,Qt不會刪除它?!


QHBoxLayout *topBox = new QHBoxLayout;
grid->addLayout( topBox, 0, 1 );
topBox->addWidget( shoot );
topBox->addWidget( hits );
topBox->addWidget( hitsL );
topBox->addWidget( shotsLeft );
topBox->addWidget( shotsLeftL );
topBox->addStretch( 1 );
topBox->addWidget( restart );

我們在右上的補足了一些視窗元件,並且讓它展現出適當的大小
並且在new game的左邊增加了一個可以延展的元件,當視窗變大時,new game 與其它元件的距離就會變大


  newGame();
}

在完成視窗元件的配置之後,我們直接以newGame函數開始執行


void GameBoard::fire()
{
  if ( cannonField->gameOver() || cannonField->isShooting() )
    return;
  shotsLeft->display( shotsLeft->intValue() - 1 );
  cannonField->shoot();
}

此函數會使得炮彈被送出


void GameBoard::hit()
{
  hits->display( hits->intValue() + 1 );
  if ( shotsLeft->intValue() == 0 )
    cannonField->setGameOver();
  else
    cannonField->newTarget();
}

當CannonField確定擊中目標時,會通知此函數,並且增加lcd上顯示的命中次數
當炮彈沒有了,則遊戲結束了


void GameBoard::missed()
{
  if ( shotsLeft->intValue() == 0 )
    cannonField->setGameOver();
}

當CannonField確定沒有命中目標時,通知此函數,則減少lcd上顯示的剩餘炮彈數,
當炮彈沒有了,則遊戲結束了


void GameBoard::newGame()
{
  shotsLeft->display( 15 );
  hits->display( 0 );
  cannonField->restartGame();
  cannonField->newTarget();
}

當new game的按紐被按下去時,則把lcd上顯示的命中次數歸0與剩下炮彈個數恢復成15

main.cpp

刪掉一些宣告MyWidget的程式碼,並且把它移到gamebrd中

2008年7月14日 星期一

QT-chap 12

這一章要學會rand

t12/lcdrange.h
LCDRange現在有標籤了

class QLabel;

我們先聲明QLabel,因為,我們要用到QLabel的指標,我們之前說過,因為,目前還沒有要實作QLabel,我們目前只有使用QLabel的指標,目前,我們不需要包含qlabel.h這一個頭檔,只先宣告class QLabel,可以加速compiler的速度。


class LCDRange : public QVBox
{
  Q_OBJECT
  public:
    LCDRange( QWidget *parent=0, const char *name=0 );
    LCDRange( const char *s, QWidget *parent=0, const char *name=0 );

這裡我們宣告另一個建構函數,與原本不同的是,新增的建構函數多了一個標籤。


const char *text() const;

回傳標籤字串


void setText( const char * );

設置標籤字串


private:
  void init();

因為我們有兩個建構子,我們選擇把通常的初始化設定在非公開的init函數中

lcdrange.cpp


LCDRange::LCDRange( QWidget *parent, const char *name ) : QVBox( parent, name )
{
  init();
}

這一個建構函數使用最基本的初始化,同時,我們把最基本的初始化設定在init函數中


LCDRange::LCDRange( const char *s, QWidget *parent, const char *name ) : QVBox( parent, name )
{
  init();
  setText( s );
}

這一個建構函數除了使用最基本的初始化之外,還另外設置了標籤的內容


void LCDRange::init()
{
  QLCDNumber *lcd = new QLCDNumber( 2, this, "lcd" );
  slider = new QSlider( Horizontal, this, "slider" );
  slider->setRange( 0, 99 );
  slider->setValue( 0 );

  label = new QLabel( " ", this, "label" );
  label->setAlignment( AlignCenter );

  connect( slider, SIGNAL(valueChanged(int)),lcd, SLOT(display(int)) );
  connect( slider, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int)) );

  setFocusProxy( slider );
}

這裡的init與上一章的建構子的內容是一樣的
除了中間增加了一個設置QLabel的部分,並且讓它顯示的字串與中間對齊;但是,QLabel顯示的字串是"",也就是目前是沒有顯示東西啦


const char *LCDRange::text() const
{
  return label->text();
}

這一個函數會回傳一個字串指標


void LCDRange::setText( const char *s )
{
  label->setText( s );
}

這一個函數就是在設置label的顯示字串

cannon.h

CannonField現在有兩個新的信號:hit()和missed()。另外它還包含一個目標。


void newTarget();

這一個新的slot function生成一個新的目標


signals:
  void hit();
  void missed();

當射中目標時,就會觸發hit function
當炮彈到達右邊或下方的邊界時,就會觸發missed function
觸發是用emit這一個巨集


void paintTarget( QPainter * );

這一個非公開函數是在畫射擊目標的


QRect targetRect() const;

這一個函數傳回目標的方塊


QPoint target;

這一個非公開變數是以QPoint在記錄目標的中心點座標

cannon.cpp


#include <qdatetime.h>

因為,我們要隨機產生目標的位置,所以,
我們要設定rand的seed,
若沒有設定,或每一次seed都是一樣的話,那產生出來的結果就會一樣
因此透過datetime把seed設定成目前的時間,則每一次跑rand的結果就會不一樣


#include <stdlib.h>

因為我們需要用到rand()的函數


newTarget();

這一個函數是在替我們在一個隨機的位置上建立一個新的目標
並且在建構子上呼叫此函數,
在建構函數視窗元件還是不可見的,QT保証在不可見的視窗函數呼叫repaint是無害的


void CannonField::newTarget()
{
  static bool first_time = TRUE;// 當此函數被呼叫第二次時,此行沒有效果
  if ( first_time ) {
    first_time = FALSE;
    QTime midnight( 0, 0, 0 );
    srand( midnight.secsTo(QTime::currentTime()) );
  }
  QRegion r( targetRect() );
  target = QPoint( 200 + rand() % 190, 10 + rand() % 255 );
  repaint( r.unite( targetRect() ) ); // targetRect()會依target值來改變出現的位置
}

這裡對static關鍵字說明一下,
當類別中的資料成員使用static時,所有此類別的物件的此一變數都是共用同一個記憶體。
另一方面當函數宣告成static時,方法將成為類別的方法

另外,為了隨機產生位置,若不設定隨機種子的話,每次的結果都會是一樣的,
因此,我們必需設定隨機種子,並且,隨機種子的數值也必需每一次都不一樣,
所以,我們利用了時間函數,使得隨機種子的數值是由子時0點目前到秒數,所以,每一次執行的結果會不同。請參考:C++ - rand

並且設定目標的位置是x=200~389和y=35~289
注意rand()返回一個>=0的隨機整數


if ( shotR.intersects( targetRect() ) ) {
  autoShootTimer->stop();
  emit hit();

檢查炮彈(shotR)是否與目標(targetRect)是否相交,
若相交,則表示擊中,則我們停止計時器,並且觸發hit()


} else if ( shotR.x() > width() || shotR.y() > height() ) {
  autoShootTimer->stop();
  emit missed();

若離開下面的邊界或右邊的邊界,則表示沒有射中目標,則停止時間計時,並且觸發missed()


if ( updateR.intersects( targetRect() ) )
  paintTarget( &p );

這兩行確認目標是否要被重新繪製


void CannonField::paintTarget( QPainter *p )
{
  p->setBrush( red );
  p->setPen( black );
  p->drawRect( targetRect() );
}

繪出紅色有邊框的方塊


QRect CannonField::targetRect() const
{
  QRect r( 0, 0, 20, 10 );
  r.moveCenter( QPoint(target.x(),height() - 1 - target.y()) );
  return r;
}

一開始先建立一個在(0,0)座標的方塊,在移動到由target()計算出來的目標位置
並且回傳這一個方塊

main.cpp

在main.cpp中沒有新的成員,但是,我們在LCDRange class中有新增一個建構子
設置一個新的標籤
LCDRange *angle = new LCDRange( "ANGLE", this, "angle" );

homework:
創建一個作弊的按鈕,當按下它的時候,讓CannonField畫出炮彈在五秒中的軌跡:
建立一個enum SHOOT {shoot_mode,cheat_mode};
這是原本的connect( shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()) );
這是新增的connect(cheat,SIGNAL(clicked()),cannonField,SLOT(cheat_shoot()));
把shoot()的內容全部都移動到新增的function:move()

void CannonField::shoot()
{
  mode = shoot_mode;
  move();
}


void CannonField::cheat_shoot()
{
  mode = cheat_mode;
  move();
}


void CannonField::moveShot()
{
  QRegion r( shotRect() );
  timerCount++;

  QRect shotR = shotRect();
  if (mode==cheat_mode && timerCount>20) // when the mode equal cheat_mode , then we determine the timerCount bigger than 10
  {
    autoShootTimer->stop();
  }
  else if ( shotR.intersects( targetRect() ) ) {
    autoShootTimer->stop();
    emit hit();
  } else if ( shotR.x() > width() || shotR.y() > height() ) {
    autoShootTimer->stop();
    emit missed();
  } else {
    r = r.unite( QRegion( shotR ) );
  }

  repaint( r );
}

2008年7月13日 星期日

QT的座標系統

看起來蠻重要的,
先記錄下來~

座標系統

視窗的幾何結構

第一天晨泳

現在每一天約十一點睡,五點多就會起床。
然後,中午就會累到想要睡覺。

今天早上,感冒好的差不多了,本來想明天再去游的,
但是,星期一游泳池沒有開,所以,只好提早今天去游。

今天狀況一堆,
6點半出發,到那裡發現沒有帶游泳卡,
再騎腳踏車回來拿,
到那裡先沖一下,會下水時,都七點了。
游個半個小時,
然後,起來洗個澡,身上沒有帶錢,只好先回宿舍再拿錢去買早餐。

換氣還不是很熟,
若真的要游很久,換氣還是要會

為了以後再省時間,
還是把標準流程計畫出來吧
六點半出發(游泳卡、100元、換洗衣物、手錶、泳具、鑰匙);先沖身體後,下水半個小時;上來洗澡,去買早餐;回宿舍吃早餐,聽一下音樂,休息一下;

約每二天去游一次吧~

2008年7月12日 星期六

QT-chap 11

這一章要學會QTimerRegion
* t11/lcdrange.h包含LCDRange類別定義。
* t11/lcdrange.cpp包含LCDRange類別實現。
* t11/cannon.h包含CannonField類別定義。
* t11/cannon.cpp包含CannonField類別實現。
* t11/main.cpp包含MyWidget和main。

cannon.h

現在炮台有射擊能力了


void shoot();

當炮彈沒有在空中時,使用這一個slots射擊炮彈


private slots:
  void moveShot();

當炮彈在空中時,這一個private slots function使用timer來移動炮彈的位置


private:
  void paintShot( QPainter * );

透過這一個private function來畫在空中移動的炮彈


QRect shotRect() const;

當炮彈在空中時,這一個private function回傳它所佔用的空間的矩形
若離開了顯示畫面,回傳一個沒有定義的矩形


  int timerCount;
  QTimer * autoShootTimer;
  float shoot_ang;
  float shoot_f;
};

這是包留炮彈射出去時的資訊,
timerCount表示射出炮彈後所經過的時間,
shoot_ang表示炮彈射出去時的角度
shoot_f表示炮彈射出去的力量

cannon.cpp


CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name )
{
  ang = 45;
  f = 0;
  timerCount = 0;
  autoShootTimer = new QTimer( this, "movement handler" );
  connect( autoShootTimer, SIGNAL(timeout()), this, SLOT(moveShot()) );
  shoot_ang = 0;
  shoot_f = 0;
  setPalette( QPalette( QColor( 250, 250, 200) ) );
}

初始化我們的非公開的變數,並且新增一個timer,當timer發出timeout時,執行moveShot的function


void CannonField::shoot()
{
  if ( autoShootTimer->isActive() )
    return;
  timerCount = 0;
  shoot_ang = ang;
  shoot_f = f;
  autoShootTimer->start( 50 );
}

只有當炮彈不在空中時,此函數才有效果,
炮彈射擊後所經過的時間歸零;並且記錄炮彈射出去時的角度與力量;並開始timer,每50ms一個timerout


void CannonField::moveShot()

這一個function是用來畫出當炮彈在空中時所應該在的位置
此函數的任務就是計算並且畫出炮彈的位置,當QTimer啟動時,每50ms會被呼叫一次,
還有決定是否要停止QTimer的執行


QRegion r( shotRect() );

首先,QRegion可以保留任何Region,我們透過QRegion來保留舊的炮彈位置。


timerCount++;

增加炮彈射出後的時間


QRect shotR = shotRect();

shotRect()函數會傳回炮彈的矩形,這之間包括炮彈的新位置


if ( shotR.x() > width() || shotR.y() > height() )
  autoShootTimer->stop();
else
  r = r.unite( QRegion( shotR ) );
repaint( r ); // 會呼叫paintEvent()函數


當炮彈新的位置移動到下面或是右邊的邊界時,
我們就停止QTimer的執行,
否則我們則添加新的shotRect()到QRegion中
最後我們重畫QRegion
r.unite( QRegion( shotR ) ),是表示舊的炮彈與新的炮彈的聯集形成的區域
repaint( r )要求要重畫r這一個區域


void CannonField::paintEvent( QPaintEvent *e )
{
  QRect updateR = e->rect();
  QPainter p( this );

  if ( updateR.intersects( cannonRect() ) )
    paintCannon( &p );
  if ( autoShootTimer->isActive() && updateR.intersects( shotRect() ) ) // 很重要,當autoShootTimer被stop後,炮彈就不會被畫出來了
    paintShot( &p );
}


實作畫圖事件,當執行repaint時,則會呼叫paintEvent


QRect updateR = e->rect();

rect()的解示是說傳回需要更新的部分。
若要更新的部分與炮台有交集的話,就呼叫paintCannon()
若要更新的部分與炮彈有交集的話,就呼叫paintShot()


QPainter p( this );

把QPainter加入,要繪製的設備是this,也就是QWidget


if ( updateR.intersects( cannonRect() ) )
  paintCannon( &p );

updateR表示需要更新的部分,而cannonRect()回傳的是放置炮台的這一個方塊區域
先判斷是否兩個有交集,若有交集則執行paintCannon,更新炮台畫面


if ( autoShootTimer->isActive() && updateR.intersects( shotRect() ) )
  paintShot( &p );

若炮彈在空中,且需要更新的部分與新的炮彈位置有交集,則重畫炮彈的圖示


void CannonField::paintShot( QPainter *p )
{
  p->setBrush( black );
  p->setPen( NoPen );
  p->drawRect( shotRect() );
}

這一個非公開函數是在準備畫炮彈,黑色的炮彈、沒有邊界線、並且傳回炮彈的位置


void CannonField::paintCannon( QPainter *p )
{
  QRect cr = cannonRect(); // 回傳左下角炮台的矩型
  QPixmap pix( cr.size() ); // 設定一個pixmap的大小與左下角的炮台一樣的大小
  pix.fill( this, cr.topLeft() ); // 把這一個大小的pixmap左上角座標設定成左下角炮台的左上角座標

  QPainter tmp( &pix ); // 把pix設定為等一下要畫在上面的設備
  tmp.setBrush( blue ); // 設定為藍色
  tmp.setPen( NoPen ); // 沒有外框

  tmp.translate( 0, pix.height() - 1 ); // 座標點往下移pix.height()-1
  tmp.drawPie( QRect( -35,-35, 70, 70 ), 0, 90*16 ); // 在一個方型[QRect( -35,-35, 70, 70 )]中畫一個起始為0度,共90度的扇型
  tmp.rotate( -ang ); // 依新的原點旋轉
  tmp.drawRect( barrelRect ); // 畫炮筒
  tmp.end(); // 結束pixmap的畫圖,也就是說已畫在pixmap設備上了
// 因為,上面的是tmp.end();所以,下面是p,跟上面不同,不需要p.begin();
  p->drawPixmap( cr.topLeft(), pix ); // 把pixmap畫到qprinter(這裡指的是CannonField)
}

paintCannon()與前一章中的paintEvent()不同的地方在於tmp.end()後,p不需要begin();前一章因為是同樣個p,所以,當end後,要把pixmap畫到畫布上,必需要begin


const QRect barrelRect(33, -4, 15, 8);


我上ppt了問了一下高手,了解這一行的意思了。
定義barrelRect為一個QRect
並使用(33, -4, 15, 8)初始化barrelRect
(33,-4,15,8)是建構元的參數
const不是#define的意思 他代表barrelRect是constant
也就是回傳的QRect是不能被修改的。

反正,關鍵字barrelRect就是QRect(33,-4,15,8)的這一個物件。


QRect CannonField::cannonRect() const
{
  QRect r( 0, 0, 50, 50 );
  r.moveBottomLeft( rect().bottomLeft() );
  return r;
}

這裡的rect()應該是this.rect()的省略


QRect CannonField::shotRect() const
{
  const double gravity = 4;

  double time = timerCount / 4.0;
  double velocity = shoot_f;
  double radians = shoot_ang*3.14159265/180;

  double velx = velocity*cos( radians );
  double vely = velocity*sin( radians );
  double x0 = ( barrelRect.right() + 5 )*cos(radians);
  double y0 = ( barrelRect.right() + 5 )*sin(radians);
  double x = x0 + velx*time;
  double y = y0 + vely*time - 0.5*gravity*time*time;

  QRect r = QRect( 0, 0, 6, 6 );
  r.moveCenter( QPoint( qRound(x), height() - 1 - qRound(y) ) );
  return r;
}

計算炮彈球中心的位置,使用到重力加速度、射出炮彈的角度、力量和射出去後經過的時間
moveCenter(QPoint &)設置方塊的中心點,而大小不改變
qRound()函數是一個在qglobal.h中定義的inline function(被其它所有Qt頭文件包含)。
qRound()把一個雙精度實數變為最接近的整數。

main.cpp


QPushButton *shoot = new QPushButton( "&Shoot", this, "shoot" );
shoot->setFont( QFont( "Times", 18, QFont::Bold ) );

增加一個shoot的按鈕


connect( shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()) );

把Shoot按鈕的clicked() signal和CannonField的shoot() slot連接起來。

homework:
用一個填充的圓來表示炮彈:用QPainter::drawEllipse()
用一個改變顏色的球:p->setBrush( QColor(rand()%255,rand()%255,rand()%255 ));

2008年7月10日 星期四

QT-chap10

這一章要了解如何使用快速鍵pixmap

* t10/lcdrange.h包含LCDRange類別定義。
* t10/lcdrange.cpp包含LCDRange類別實現。
* t10/cannon.h包含CannonField類別定義。
* t10/cannon.cpp包含CannonField類別實現。
* t10/main.cpp包含MyWidget和main。

cannon.h

CannonField現在除了角度又多了一個力量值。

  int angle() const { return ang; }
  int force() const { return f; }

public slots:
  void setAngle( int degrees );
  void setForce( int newton );

signals:
  void angleChanged( int );
  void forceChanged( int );

力量的介面的實現和角度一樣。


private:
  QRect cannonRect() const;

我們把加農炮封裝的矩形的定義放到了一個單獨的函數中。


int ang;
int f;
};

力量被存儲到一個整數f中。

cannon.cpp

#include <qpixmap.h>

我們包含了QPixmap類別定義。
一段QPixmap的解示
The QPixmap class is an off-screen, pixel-based paint device.
QPixmap is one of the two classes Qt provides for dealing with images; the other is QImage. QPixmap is designed and optimized for drawing; QImage is designed and optimized for I/O and for direct pixel access/manipulation. There are (slow) functions to convert between QImage and QPixmap: convertToImage() and convertFromImage().


CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name )
{
  ang = 45;
  f = 0;
  setPalette( QPalette( QColor( 250, 250, 200) ) );
}

力量(f)被初始化為0。


void CannonField::setAngle( int degrees )
{
  if ( degrees < degrees =" 5;"> 70 )
    degrees = 70;
  if ( ang == degrees )
    return;
  ang = degrees;
  repaint( cannonRect(), FALSE );
  emit angleChanged( ang );
}

我們在setAngle()函數中做了一個小的改變。
它只重畫視窗元件中含有加農炮的一小部分。

透過:void QWidget::repaint ( const QRect & r, bool erase = TRUE ) [slots]
去重畫此單一炮台

FALSE參數說明在一個繪畫事件發送到窗口部件之前指定的矩形將不會被擦去。
(應該就是說把矩形直接蓋過去吧,這樣就不會有閃爍的部分了,我猜的!)
這將會使繪畫過程加速和平滑。
應該是說QT會比對新的圖與舊的圖的差異,然後,只畫出差異的部分


void CannonField::setForce( int newton )
{
  if ( newton < 0 )
    newton = 0;
  if ( f == newton )
    return;
  f = newton;
  emit forceChanged( f );
}

setForce()的實現和setAngle()很相似。
唯一的不同是因為我們不顯示力量值,我們不需要重畫視窗元件。


void CannonField::paintEvent( QPaintEvent *e )
{
  if ( !e->rect().intersects( cannonRect() ) )
    return;

rect():returns the rectangle that should be updated
表示回傳需要update的部分,若沒有應該就是null

通常這一個e是表示這一整個畫布;在我測式的結果是e會自動與X11合作,討論出哪一個部分需要更新,然後,會把這一個要更新的部分e,傳給paintEvent()。


像上面這一個圖片,當左上角的視窗往右移動的話,這麼來說,傳回要更新的部分在畫布的地方,就是本來被擋住的地方,因視窗移動而露出眼的地方,另外一提,因與X11的作用,當有必要時QT會自動呼叫paintEvent(),但是,若左視窗向左移動時,因QT不需要更新畫布,因此不會呼叫paintEvent。


因此,在這一個例子,只有兩個時候會呼叫paintEvent()
1. 像上面的例子,我們把左上角的視窗往右移時,QT會自動呼叫paintEvent()
2. 我們程式中執行repaint()

另外,這裡的paintEvent是CannonField的paintEvent()

intersects:Returns TRUE if this rectangle intersects with rectangle r (there is at least one pixel that is within both rectangles)
當有其中一個pixel有重疊到的話,就傳回true,再加個not,就變成false,因此,會執行下面的程式


QRect cr = cannonRect();
QPixmap pix( cr.size() ); // 建立一個與上一行指令產生的矩型一樣大小的pixmap,用來做為畫圖的緩衝

cannonRect() 回傳的是放置炮台的方塊,
在這一個方塊上設置一個pixmap,
所有的繪畫操作都在這個pixmap中完成(在背景中),
並且之後只用一步操作來把這個pixmap畫到螢幕上。

這是不閃爍繪畫的本質:一次準確地在每一個像素上畫。
若畫的再少一點,你會得到繪畫錯誤。
若畫的再多一點,你會得到閃爍。


pix.fill( this, cr.topLeft() );

我們用這個pixmap來充滿這個窗口部件的背景。
表示由cr物件的左上方的點開始放這一個pixmap物件
基本上,pixmap以被我們宣告為一個矩形的大小了(因為設定為跟左下角的矩形一樣)

void QPixmap::fill ( const QWidget * widget, const QPoint & ofs )
This is an overloaded member function, provided for convenience. It behaves essentially like the above function.

Fills the pixmap with the widget's background color or pixmap. If the background is empty, nothing is done.

The ofs point is an offset in the widget.

The point ofs is a point in the widget's coordinate system. The pixmap's top-left pixel will be mapped to the point ofs in the widget. This is significant if the widget has a background pixmap; otherwise the pixmap will simply be filled with the background color of the widget.


Example:

void CuteWidget::paintEvent( QPaintEvent *e )
{
  QRect ur = e->rect();// rectangle to update
  QPixmap pix( ur.size() ); // Pixmap for double-buffering
  pix.fill( this, ur.topLeft() ); // fill with widget background

  QPainter p( &pix );
  p.translate( -ur.x(), -ur.y() ); // use widget coordinate system
// when drawing on pixmap
// ... draw on pixmap ...


  p.end();

  bitBlt( this, ur.topLeft(), &pix );
}



QPoint QRect::topLeft () const

傳回矩型的左上角位置(應該就是左上角的座標)


QPainter p( &pix );
p.setBrush( blue );
p.setPen( NoPen );
p.translate( 0, pix.height() - 1 );
p.drawPie( QRect( -35,-35, 70, 70 ), 0, 90*16 );
p.rotate( -ang );
p.drawRect( QRect(33, -4, 15, 8) );
p.end();

我們就像第九章中一樣畫,但是現在我們是在pixmap上畫。
在這一點上,我們有一個繪畫工具變量和一個pixmap看起來相當正確,但是我們還沒有在屏幕上畫呢。
看起來就像是先在pixmap畫完,再一次畫到屏幕上


p.begin( this );
p.drawPixmap( cr.topLeft(), pix );

所以我們在CannonField上面打開繪圖工具並在這之後畫這個pixmap。
這就是全部了。在頂部和底部各有一對線,並且這個代碼是100%不閃爍的。
是呼叫此function:void QPainter::drawPixmap ( const QPoint & p, const QPixmap & pm )

這一個原理就是先在pixmap上畫好圖之後,再一次畫到指定的地方,這樣就不會閃爍了。

這裡在對end與begin函數解示一下

bool QPainter::end ()

結束繪製。繪製時使用的任何資源都被釋放。

注意雖然你幾乎不需要調用end(),析構函數將會執行它,但是至少還有一種情況需要它,就是雙重緩衝。

QPainter p( myPixmap, this )
// ...
p.end(); // 停止在myPixmap上的繪製
p.begin( this );
p.drawPixmap( myPixmap );


因為當它正在被繪製的時候,你不能繪製一個QPixmap,它需要關閉激活的繪製工具。



QRect CannonField::cannonRect() const
{
  QRect r( 0, 0, 50, 50 );
  r.moveBottomLeft( rect().bottomLeft() );
  return r;
}

這個函數傳回一個在視窗元件坐標中封裝加農炮的矩形。
首先我們創建一個50*50大小的矩形,然後移動它,使它的左下角和視窗元件自己的左下角相等。

QWidget::rect()函數在窗口部件自己的坐標(左上角是0,0)中返回視窗元件封裝的矩形。

t10/main.cpp


LCDRange *force = new LCDRange( this, "force" );
force->setRange( 10, 50 );

我們加入了第二個LCDRange,用來設置力量。


connect( force, SIGNAL(valueChanged(int)),cannonField, SLOT(setForce(int)) );
connect( cannonField, SIGNAL(forceChanged(int)),force, SLOT(setValue(int)) );

我們把force窗口部件和cannonField窗口部件連接起來,就和我們對angle窗口部件做的一樣。


QVBoxLayout *leftBox = new QVBoxLayout;
grid->addLayout( leftBox, 1, 0 );
leftBox->addWidget( angle );
leftBox->addWidget( force );

在第九章,我們把angle放到了佈局的左下單元。現在我們想在這個單元中放入兩個窗口部件,所一個我們用了一個垂直的盒子,把這個垂直的盒子放到這個網格單元中,並且把angle和force放到這個垂直的盒子中。


force->setValue( 25 );

我們初始化力量的值為25。

homework:
1. 讓加農炮的炮筒的大小依賴於力量:在cannon.cpp裡的paintEvent()裡面在製造炮筒的寬度相依於f;並於setForce()加入repaint(cannonRect(),FALSE);
2. 用+和-來增加或者減少力量:參考1參考2參考3
雖然我先找到參考2的列表,但是,英文太差,我還是沒有找到+、-分別由什麼代表,直到找到參考3才解決homework的問題!

QT-chap 9

* t9/lcdrange.h包含LCDRange類別定義。
* t9/lcdrange.cpp包含LCDRange類別實現。
* t9/cannon.h包含CannonField類別定義。
* t9/cannon.cpp包含CannonField類別實現。
* t9/main.cpp包含MyWidget和main。

cannon.cpp有新增程式碼


void CannonField::paintEvent( QPaintEvent * )
{
  QPainter p( this );


我們要對畫布實作畫圖囉
We'll now start to use QPainter in earnest. We create a painter that operates on this widget.


p.setBrush( blue );

我們先設定顏料是藍色


p.setPen( NoPen );

* Qt::NoPen - 根本就沒有線。比如,QPainter::drawRect()填充但沒有繪製任何邊界線。
* Qt::SolidLine - 一個簡單的線。
* Qt::DashLine - 由一些像素分隔的短線。
* Qt::DotLine - 由一些像素分隔的點。
* Qt::DashDotLine - 輪流交替的點和短線。
* Qt::DashDotDotLine - 一個短線,兩個點,一個短線,兩個點。
* Qt::MPenStyle - 畫筆風格的掩碼。


p.translate( 0, rect().bottom() );


轉換座標:
因位座標最左上角是(0,0),x向下遞增、y向右遞增。
translate(40,100)表示點向右偏移40單位、向下偏移100單位
而x與y的方向還是沒有改變;x向右遞增、y向下遞增。
而rect().bottom()表示窗口元件的底邊的座標
而由(0,0)開始往右x分別為0,1,2,...
而由(0,0)開始往上y分別為0,-1,-2...

例如下面的程式是在同一個點上畫兩次


void MyWidget::paintEvent()
{
  QPainter paint( this );

  paint.drawPoint( 0, 0 );

  paint.translate( 100.0, 40.0 );
  paint.drawPoint( -100, -40 );
}



p.drawPie( QRect(-35, -35, 70, 70), 0, 90*16 );



上面表示在一個矩型畫出一個360度的餅型圖
drawPie()函數使用一個開始角度和弧長在一個指定的矩形內一個餅型圖
座標是表示矩型左上角相對於父元件的座標
角度的度量用的是一度的十六分之一。
零度在三點的位置。
畫的方向是順時針的。這裡我們在窗口部件的左下角畫一個四分之一圓。
這個餅圖被藍色充滿,並且沒有邊框。


p.rotate( -ang );

QPainter::rotate()函數繞QPainter坐標系統的初始座標(0,0)旋轉它
旋轉的參數是一個按度數給定的浮點數(不是一個像上面那樣給的十六分之一的度數)並且是順時針的。這裡我們順時針旋轉ang度數。


p.drawRect( QRect(33, -4, 15, 8) );

QPainter::drawRect()函數畫一個指定的矩形。這裡我們畫的是加農炮的炮筒。

在這種情況下,坐標系統先被轉化後被旋轉。如果矩形QRect(33, -4, 15, 8)被畫到這個轉化後的坐標系統中,它看起來會是這樣:


注意矩形被CannonField窗口部件的邊界省略了一部分。當我們選裝坐標系統,以60度為例,矩形會以(0,0)為圓心被旋轉,也就是左下角,因為我們已經轉化了坐標系統。結果會是這樣:



int main( int argc, char **argv )
{
  QApplication::setColorSpec( QApplication::CustomColor );
  QApplication a( argc, argv );

我們告訴Qt我們在這個程序中想使用一個不同的顏色分配策略。
這裡沒有單一正確的顏色分配策略。因為這個程序使用了不常用的黃色,但不是很多顏色,CustomColor最好。這裡有幾個其它的分配策略,你可以在QApplication::setColorSpec()文檔中讀到它們。

通常情況下你可以忽略這一點,因為默認的是好的。偶爾一些使用常用顏色的應用程序看起來比較糟糕,因而改變分配策略通常會有所幫助。

homework:
修改pen請參考這裡
而修改為Qu&it是表示快速鍵變成Alt+i

參考資料:第九章

2008年7月9日 星期三

QT-chap 8

這一個章節要了解如何畫圖,請參考QT - Paint

包含的檔案:
/lcdrange.h包含LCDRange類別定義。
/lcdrange.cpp包含LCDRange類別實現。
/cannon.h包含CannonField類別定義。
/cannon.cpp包含CannonField類別實現。
/main.cpp包含MyWidget和main。

lcdrange.h
跟上一章比起來只我們多增加一個slots:setRange()
我們可以設定LCDRange的可能範圍
另外,此valueChanged(int)目前這一個範例我們只有宣告,在cpp檔中還沒有實作
應該在之後的章節會實作。
在C++中,function可以先在header file中先宣告,在.cpp檔中若沒有用到的話,可以先不用實作。

lcdrange.cpp

setFocusProxy( slider );

Sets the widget's focus proxy to widget slider. If slider is 0, the function resets this widget to have no focus proxy.
設置這個視窗元件的焦點代理為視窗元件w。如果w為0,這個函數重置這個視窗元件沒有焦點代理。基本上呢,應該是要用在可以接收keyboard的input的設置。
而我認為只有當slider的父視窗元件獲得焦點時,slider才會獲得焦點。


void LCDRange::setRange( int minVal, int maxVal )
{
  if ( minVal > 99 || maxVal < 0 || minVal > maxVal ) {
    qWarning( "LCDRange::setRange(%d,%d)\n"
      "\tRange must be 0..99\n"
      "\tand minVal must not be greater than maxVal",
      minVal, maxVal );
    return;
  }
  setRange( minVal, maxVal );
}


我們使用Qt的qWarning()函數來向用戶發出警告並立即返回。
qWarning()是一個像printf一樣的函數,默認情況下它的輸出發送到stderr。

cannon.h
這一個程式碼用到幾個新的類別
QSizePolicy:
The QSizePolicy class is a layout attribute describing horizontal and vertical resizing policy. More...

QPaintEvent:
The QPaintEvent class contains event parameters for paint events. More...


protected:
  void paintEvent( QPaintEvent * );

這是我們在QWidget中遇到的許多事件處理器中的第二個。
只要一個視窗元件需要刷新它自己(比如,畫窗口部件表面),這個虛函數就會被Qt調用。
這裡在說明一下public、private、friend、protected的分別
public:所有的類別都有存取權
private:除了本類別外的function,其它都不能存取。及便是衍生類別也是沒有權限存取
protected:您可以宣告這些成員為「受保護的成員」(protected member),保護的意思表示存取它有條件限制以保護該成員,當您將類別成員宣告為受保護的成員之後,繼承它的類別就可以直接使用這些成員,但這些成員仍然受到類別的保護,不可被物件直接呼叫使用。
friend:例如:現在宣告函數display為類別的product的友誼,即display可以存取類別product的物件中所有內部資料,所以可以存取傳入到display中的product物件私有成員number

cannon.cpp:建立繪圖區

CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name )
{
  ang = 45; // 建構子初始化角度為45度
  setPalette( QPalette( QColor( 250, 250, 200) ) ); // 設定調色板的背景顏色
}

建構子把角度值初始化為45度並且給這個視窗元件設置了一個自定義調色板。

這個調色板只是說明背景色,並選擇了其它合適的顏色。(對於這個視窗元件,只有背景色和文本顏色是要用到的。)


void CannonField::setAngle( int degrees )
{
  if ( degrees < 5 )
    degrees = 5;
  if ( degrees > 70 )
    degrees = 70;
  if ( ang == degrees )
    return;
  ang = degrees;
  repaint(); // 用背景色填滿畫布,再呼叫paintEvent
  emit angleChanged( ang );
}

這是一個設置角度的函數,
當數值有改變時,
呼叫repaint()重畫畫板:沒有參數,函數清空窗口元件(通常用背景色來充滿)並向窗口元件發出一個繪畫事件。這樣的結構就是調用窗口部件的繪畫事件函數一次。
並且送出一個angleChanged(ang)的訊號:emit angleChanged( ang );

QWidget::repaint()函數清空視窗元件(通常用背景色來充滿)並向視窗元件發出一個繪畫事件。
這樣的結構就是呼叫視窗元件的繪畫事件函數一次。
最後,我們發射angleChanged()信號來告訴外面的世界,角度值發生了變化。
emit關鍵字只是Qt中的關鍵字,而不是標準C++的語法。實際上,它只是一個巨集。

repaint()通過立即呼叫paintEvent()來直接重新繪製視窗元件,
除非更新是失效的或者視窗元件被隱藏。

請參考Qt - QPainter

QPainter p( this );
表示建立一個QPainter物件,且其上層視窗元件是this,也就是cannonField


QSizePolicy CannonField::sizePolicy() const
{
  return QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
}

是傳回以建構子QSizePolicy ( SizeType hor, SizeType ver, bool hfw = FALSE )建立的物件,第三個參數是使用預設值。
QSizePolicy::Expanding - the sizeHint() is a sensible size, but the widget can be shrunk and still be useful. The widget can make use of extra space, so it should get as much space as possible (e.g. the horizontal direction of a slider).

也就是當子元件太大時,父元件可以自動延展。

main.cpp

class MyWidget: public QWidget
{
  public:
    MyWidget( QWidget *parent=0, const char *name=0 );
};

這一次我們在頂層視窗元件中只使用了一個LCDRange和一個CanonField。


LCDRange *angle = new LCDRange( this, "angle" );
angle->setRange( 5, 70 );

在構造函數中,我們創建並設置了我們的LCDRange。
我們設置LCDRange能夠接受的範圍是5-70度。


CannonField *cannonField = new CannonField( this, "cannonField" );

我們創建了我們的CannonField。


connect( angle, SIGNAL(valueChanged(int)),
cannonField, SLOT(setAngle(int)) );
connect( cannonField, SIGNAL(angleChanged(int)),
angle, SLOT(setValue(int)) );

這裡我們把LCDRange的valueChanged()信號和CannonField的setAngle()槽連接起來了。
只要用戶操作 LCDRange,就會刷新CannonField的角度值。我們也把它反過來連接了,這樣CannonField中角度的變化就可以刷新 LCDRange的值。
在我們的例子中,我們從來沒有直接改變CannonField的角度,但是通過我們的最後一個connect()我們就可以確保沒有任何變化可以改變這兩個值之間的同步關係。
注意只有當角度確實發生變化時,才發射angleChanged()是多麼的重要。如果LCDRange和CanonField都省略了這個檢查,這個程序就會因為第一次數值變化而進入到一個無限循環當中。


QGridLayout *grid = new QGridLayout( this, 2, 2, 10 );
//2×2,10像素的邊界

到現在為止,我們沒有因為幾何管理把QVBox和QGrid窗口部件集成到一起。
現在,無論如何,我們需要對我們的佈局加一些控制,所以我們使用了更加強大的QGridLayout類。
QGridLayout不是一個窗口部件,它是一個可以管理任何窗口部件作為子對象的不同的類。

就像註釋中所說的,我們創建了一個以10像素為邊界的2*2的數組。(QGridLayout的構造函數有一點神秘,所以最好在這裡加入一些註釋。)


grid->addWidget( quit, 0, 0 );

我們在網格的左上的單元格中加入一個Quit按鈕:0,0。


grid->addWidget( angle, 1, 0, Qt::AlignTop );

我們把angle這個LCDRange放到左下的單元格,在單元格內向上對齊。(這只是QGridLayout所允許的一種對齊方式,而QGrid不允許。)


grid->addWidget( cannonField, 1, 1 );

我們把CannonField對象放到右下的單元格。(右上的單元格是空的。)


grid->setColStretch( 1, 10 );

我們告訴QGridLayout右邊的列(列1)是可拉伸的。(左邊是列0)
因為左邊的列不是(它的拉伸因數是0,這是默認值),QGridLayout就會在MyWidget被重新定義大小的時候試圖讓左面的窗口部件大小不變,而重新定義CannonField的大小。


angle->setValue( 60 );

我們設置了一個初始角度值。注意這將會引發從LCDRange到CannonField的連接。


angle->setFocus();

我們剛才做的是設置angle獲得鍵盤焦點,
在這種情況下鍵盤輸入會到達LCDRange視窗元件。
LCDRange沒有包含任何keyPressEvent(),所以這看起來不太可能有用。無論如何,它的構造函數中有了新的一行:

setFocusProxy( slider );

LCDRange設置slider作為它的focus proxy。
也就是說當由LCDRange接收到keyborad的輸入時,
動作會由slider表現出來
QSlider有一個相當好的鍵盤介面,所以就會出現我們給LCDRange添加的這一行。

homework:
把AlignTop刪掉,左邊的LCDNumber高度會變大
給左面的列一個非零的拉伸因數,左邊會因為整個視窗變化而變化
試著在QButton::setText()調用中把「Quit」改為「&Quit」。Quit的Q會有底線。
這個時候按Alt+Q會變成這一個按鈕的快捷鍵。
把CannonField的文本放到中間:QWidget類別有一個rect()可以傳回畫布的大小
rect().height():可以得到畫布的高度
rect().width():可以得到畫布的寬度

QT-chap 7

從這一個章節開始程式碼就要寫在多個檔案中
然後檔案要如何分類在不同的資料夾,這是一個很重要的課題
當系統越來越大時,這個時候程式碼檔案要是分類的好
之後,別人要修改資料,就比較容易看懂這系統的架構是如何


#ifndef LCDRANGE_H
#define LCDRANGE_H
...
#endif

所有的預處理器指引指令以#開始
兩個大的預處理器分別為
#include:在目前檔案中插入另一個檔案
#define:讓編譯器可以定義可以在程式碼中使用的常數值
為了避免出現一個head file被包含不止一次的情況。
如果你沒有使用過它,這是開發中的一個很好的習慣。
#include把這個頭文件的全部都包含進去。
#ifndef#endif是相對應的
中間包的程式碼就是此head file的主要內容


class QSlider;

我們不需要在宣告此類別時使用到QSlider
只有在實作此類別時才會使用到
因此我們在此head file中先使用forward declaration
並罝在實作此類別的程式碼中(.cpp檔案)再include此qslider.h檔案
原文哩:
Because we don't need QSlider in the interface of the class,
only in the implementation,
we use a forward declaration of the class in the header file and include the header file for QSlider in the .cpp file.
透過這一個小技巧,可以讓編譯的速度變的更快一點
This makes the compilation of big projects much faster, because when a header file has changed, fewer files need to be recompiled. It can often speed up big compilations by a factor of two or more.


class LCDRange : public QVBox
{
  Q_OBJECT
    public:
      LCDRange( QWidget *parent=0, const char *name=0 );

當要定義signal與slot時,必需要在類別前使用Q_OBJECT
必需要注意的是Q_OBJECT只能放在header file中,而不能放在.cpp file中


  int value() const;
public slots:
  void setValue( int );

signals:
  void valueChanged( int );

這裡宣告三個函數,其中setValue(int)為slots、valueChanged(int)則是signals
而通常我們宣告是宣告public slots、(private) signals


int value() const;

這裡要先說明const的特性

const int function(...) const

前面的const是表示此function return const int
那後面的const則是宣告此 method 不會動到object 裡面的資料
因此我們設定的這一個函數value就不可以動到物件的內容

這裡有一個對const解示不錯的說法
由右往左、用英文讀是最明確的了.
int 就是 int
int * 就是 pointer to int, 指向 int 的 pointer
int * const 就是 const pointer to int, 固定指標,指向 int
const int * 就是 pointer to const int, 指向 const int 的指標
const int * const 就是 const pointer to const int, 也就是固定的指標,指向一個 const int.
原文出處
一個類別只能發射它自己定義的或者繼承來的信號


在lcdrange.cpp檔案中

connect( slider, SIGNAL(valueChanged(int)),lcd, SLOT(display(int)) );
connect( slider, SIGNAL(valueChanged(int)),SIGNAL(valueChanged(int)) );

第一個之前就說過了
那第二個函數就是省略掉this,這一個參數

讓我們來看看當用戶操作這個slider的時候都發生了些什麼。
slider看到自己的值發生了改變,並發射了valueChanged()信號。
這個信號被連接到QLCDNumber的display()槽和LCDRange的valueChanged()信號。
是的,這是正確的。信號可以被連接到其它的信號。
當第一個信號被發射時,第二個信號也被發射。
所以,當這個信號被發射的時候,
LCDRange發射它自己的valueChanged()信號。
另外,QLCDNumber::display()被調用並顯示新的數字。

注意你並沒有保證執行的任何順序——
LCDRange::valueChanged()也許在QLCDNumber::display()之前或者之後發射,這是完全任意的。
這就是一個signal同時觸發兩個slot

參考資料:第七章