Node.js】第九篇 - NPM、NVM
Global是什麼?(。ŏ_ŏ)
在程式語言中Global是指全域變數的意思。
所以...全域變數是什麼?
我們來看看維基百科對於全域變數說了些什麼
在程式設計中,全域變數是在所有作用域都可存取的變數,與之對應的是局部變數。
嗯...維基百科也提到了全域變數相對的就是局部變數
簡單來說全域變數就是你宣告之後不管在哪個地方你使用該變數都可以取得該值,而且不論在何處都可以對他進行修改。
而部局變數就是指說該變數只限定在該函數或是該區塊間使用。
如果還是有點不明瞭
我覺得以現實生活中可以用以下兩個Gif圖來表示我對這兩個的差異
局部變數以現實為例
我偷偷跟你說哦但是你不能跟別人說~
只限定我們這個小團體哦,說好了喔~
全域變數以現實為例
我跟你們說哦#$%$#^#$%@$!!!!
大家都是一家人!大家都可以拿!
(不知道為什麼腦中浮現有一種共產主義的概念)
以上。
希望這樣的比喻能夠幫助一起學習的讀者了解(〃∀〃)
Global能幹嘛?(`・ω・´)
如同我在上面Global是什麼中說的
全域變數就是你宣告之後不管在哪個地方你使用該變數都可以取得該值,而且不論在何處都可以對他進行修改。
比方說以下程式
建立一個html
<!DOCTYPE html>
<html>
<head>
<script src="add.js"></script>
<script>
var globalValue = 10;
console.log(addTwoValue(10));
</script>
</head>
<body>
</body>
</html>
建立一個js 檔案叫做 add.js
var globalValue = 2;
function addTwoValue(input) {
return parseInt(input) + globalValue;
}
這邊你會想說上述add.js的globalValue
都沒宣告啊怎麼有值?
錯!
因為他已經在上面的html宣告了一個全域變數var globalValue = 10;
,所以他抓得到該變數。
所以最後他會Return的值會是20哦!
可以通過全域變數的使用來避免常用變數在一系列函式間的頻繁傳遞。
From 維基百科
舉例來說今天有一個網頁是要介紹主角,該主角名稱名字是固定不會變動的,那就可以在網頁先把主角名稱設定為全域變數,在各個js之間就不用一直透過參數一直傳遞,畢竟主角名稱也不會變。
(但是必須要說這個案例以現在的需求來說我覺得應該是很少會遇到,如果這舉例不好可以留言跟我說有什麼例子會比較ok)
Global不要亂用!(╯‵□′)╯︵┴─┴
上面在Global敘述中感覺Global很棒棒很好啊很方便啊,什麼叫不能亂用?
我記得我在實習時聽到一位資深工程師說過一句話
你的程式老師沒教你全域變數不能亂用嗎?
當時的我一臉矇逼,根本不知道全域變數和部局變數差異和使用時機,只覺得程式能夠work就好啦幹嘛管那麼多@@?
心中OS
我老師沒說Orz ,真的。(也有可能是我沒在聽)
直到我某一次Coding時,找Bug找了超久就只因為某變數我設定到全域變數然後但我卻忘了在該專案的某處被我改動了,此時腦中浮現該工程師說的話(((゚Д゚;)))。
你的程式老師沒教你全域變數不能亂用嗎?
心中OS
沒有,但是感謝你我現在懂了。
一個心態的轉變,我的改變,你看得見!
嗯~能想像今天如果我不止是我自己的Project,萬一與人共寫...
感覺就會非常蛋疼呀。٩(ŏ﹏ŏ、)۶
Node與JavaScript中Global的差異?(((゚Д゚;)))
這邊要講的就是在Node很不一樣的地方是在於
在Node的全域變數不是真的全域,只限定在該模組或應用程式中。
Wait Wait Wait 不對欸~
什麼意思!? 那還叫全域變數嗎?
嗯!
在Node的設計中每一個檔案文件都是獨立的模組對一個模組來說他們是!
(我的觀察啦)
但是上述說的全域變數這件事在Node中不是絕對的,
有些內建對象是 JavaScript 語言本身的一部分,所以它們是真的全域變數都可以存取的。詳細有哪些可以看這個
來用Code看一下他與上面提的Global差異吧~
建立一個add.js模組
# add.js
var globalValue = 2;
exports.add = function(input) {
return parseInt(input) + globalValue;
};
再建立一個Node應用程式test.js
# test.js
var add = require('./add.js').add;
var globalValue = 10;
console.log(add(globalValue));
以上面在js中所看到的test.js的globalValue會被全域變數取代成10對吧!?
但是在Node中在add.js這個模組內是看不到外面的世界的。
也就是他只會吃到他裡面var globalValue = 2;
這行
所以輸出會是12
另外
在Node之中可以使用以下
console.log(global);
來查看目前可用的global物件有哪些
以上。
Process 是什麼它能幹嘛?σ ゚∀ ゚) ゚∀゚)σ
都講到Process了,就順帶提一下跟他非常有關係的Program和Thread
經過我認真又隨便的研究,以我的觀察來看(完全不附帶任何責任)
我們以簡單粗暴的大概認識一下(越粗暴我越愛)
Program = 我們寫的Code(還沒被執行)=> 存在硬碟(Rom)
Process = 我們寫的Code (已經被執行且並已載入到記憶體)=> 存在記憶體(Ram)
Thread = Process的執行單位(Process 是 Thread 的容器)=>存在Process
好了~為了怕模糊焦點我們不要那麼深入探討這個
絕對不是我不懂或是我懶惰喔!如果有大大要補充我也是非常願意copy paste 在文章內的唷xDD
來專注在Process吧!
列出以下我看到的Process是什麼他能幹嘛?
- Process是Node中的一個全域變數(如果不知道什麼是全域變數可以看上一篇),
- 他可以直接使用也可以使用require來使用
const process = require('process');
- 讀:獲取程序資訊
寫:執行程序操作
From Node.js process模組解讀
嗯看起來最蛋疼的就是第三點
Process 入門攻略( ゚∀゚) ノ♡
為什麼說是入門?
因為如果全部寫完我大概主題會改為 => 從入門到放棄的NodeJs (´≖◞౪◟≖)
我們就來了解上述說的
讀:獲取程序資訊
寫:執行程序操作
注意: 下面列舉的內容只是"部分",
有興趣可以看一下官方process的API document。
獲取程序資訊
這邊感覺就是需要再查也可以的部分,我覺得不用特別記。
單純稍微看一下搞不好哪天想到有需要可以直接知道在process就可以get 到了
獲取Process所消耗的記憶體
process.memoryUsage())
{
rss: 21590016, // 總共佔用的記憶體
heapTotal: 4251648, // V8引擎總共使用的記憶體
heapUsed: 2161344, // V8引擎目前使用的記憶體
external: 680685 // 管理V8的記憶體
}
獲取處理user和system的CPU所需時間
process.cpuUsage()
{
user: 33426,
system: 11480
}
獲取PID資訊
目前我不知道這個要拿來幹嘛@@
也許是打開工作管理員可以早點找到他?
或是拿取pid 在某些情境可以砍掉(?)
process.pid
<PID>
獲取目前程式執行目錄(我覺得實用)
實用原因是假使,今天要執行某module,可是在不同的資料夾而且有很多層
使用相對路徑可能會有很多層例如=> ../../../../module/<module>
這時候用這個就滿方便的。
process.cwd()
/Users/robinzhang/nodeLearning
Node執行目錄(感覺會用到)
這個跟上面那個差異在
一個是程式執行的位置
一個是Node的資源位置
process.execPath
/usr/local/bin/node
獲取CPU架構
如果妳朋友不知道他電腦是幾位元的時候你可以包一個這隻程式,就可以馬上知道(?)還可以裝逼
process.arch
x64 //or x86
執行程序操作
我覺得這部分可能是process模組最大需要理解的,
感覺要深入玩這個不可以不知道的感覺。
我的推測啦xD
這部分我覺得必須知道什麼是EventLoop和EventEmiiter
之後理論上再分享我看起來的EventLoop 和EventEmiiter
這部分....我非常抱歉~我下篇再講
實際原因不是因為這週看太多冰與火之歌
也不是因為Netflix太好看
是因為個人私事太多
先說聲抱歉Orz
本篇心得
我發現我到現在的處境很像是剛入門的學徒
如果我是要學著當廚師的人,我現在就在洗碗
如果我是要學著當黑手的人,我現在就在換機油
這些比較枯燥乏味的東西,
說實話學的真的是不知道什麼時候會用到Orz
但是我相信這都是Node給我的考驗
絕對不是廚師懶得洗碗,師傅懶得幫客人換機油。
沒錯!
都是考驗!應該吧
不管如何都是因為以前太混所以我很多東西都不懂啦QQ
俗話說的好
蹲得越低,腳越酸。
我會努力的
感謝大大的觀看
參考文獻
Banner設計
Node.js process模組解讀
process Node.js API
Program/Process/Thread 差異
JavaScript 运行机制详解:再谈Event Loop
任務列隊(task queue)乖乖排隊━(゚∀゚)━( ゚∀)━━(゚∀゚)━
在我寫Javascript之前根本不知道有非同步這件事,
我只知道以前計算機概論老師教的,
程式一行一行執行,執行完一行才會執行下一行。
(這邊不是意味著非同步是跳著讀取的)
先來說說非同步的來由
研究了一下是因為,就單線程而言原本javascript設計者發現,
我們執行程式時,除了要應付大量的計算外,
剩餘時間其實CPU是閒著的,
因為IO(輸入輸出的設備)很慢
比方說DOM鍵盤滑鼠操作或是Ajax操作網路讀取一些資料,
所以大部分都在等待(¯﹃¯)。
所以其實可以先完全不管IO設備,先繼續運行下去,
等到IO設備好了,再回過頭把它執行完成。
(看完覺得滿道理的(`・ω・´))
什麼意思?
舉現實例子來說就是你老闆看到你領著他的薪水但是你卻看起來很閒,
只因為你說:某某某還沒給我東西啊,所以我等他。
老闆(Javascript設計者)這時候就生氣了。(╬゚д゚)▄︻┻┳═一
2老闆:你是勞工!我花錢請你來%^#%@#&^#^$
老闆期望你應該這樣,才不會白花錢請你。
好比喻大概是以上。
我相信看完上述的內容,
明天的新聞頭條我大概想得到是以下
震怒!上千萬顆CPU連署抗議Javascript設計者剝奪每一顆CPU的人權
有了上述基本的概念之後應該會比較好懂
所以他會怎麼運作?以遊戲破關比對來說
- 所有同步任務都會在主線上面執行
就像是遊戲都有一個主線,你必須依序完成他才能繼續破關。
- 除了主線之外還有一條叫做任務列隊(task queue)負責放準備完成的異步任務
這就像是遊戲中支線任務會獨立一個列表讓你查看有哪些支線已經完成了
- 當主線(同步)任務解完了,就會查看任務列隊(task queue)有哪些已經處於等待結束的狀態,就會排入主線開始執行
像是一個遊戲玩家再破主線時,趁主線解完一個章節時順便時不時打開列表看哪些支線任務已經順便解完了。
- 重複執行第三步
像每次主線每個章節都這樣一直檢查支線有沒有已經解完的。
另外任務列隊除了可以放異步任務之外還可以放定時器
(像是setTimeout()和setInterval()這兩個函數)
事件迴圈(EventLoop)格林機槍(゚Д゚)σ━000000━●
看完上述任務列隊之後,
其實事件迴圈(Event loop) 就是指主線任務循環不斷的去讀取
這個動作這個機制就叫做EventLoop
(也就是上面提的第四點)
為什麼我會說格林機槍?
因為我覺得就真的很像機槍的運作方式(๑•́ ₃ •̀๑)
槍管發射就是主線,旁邊的彈鏈就像任務列隊,
裝好的就準備發射,不斷循環。(๑•̀ω•́)ノ
這樣看是不是很像!!?ヽ(́◕◞౪◟◕‵)ノ
(純粹只是覺得起來很像而已ヽ(́◕◞౪◟◕‵)ノ,如果覺得比喻很爛...先說聲抱歉)
如果喜歡看正規圖可以看這個(΄ಢ◞౪◟ಢ‵)
在主線程執行時會產生堆積(Heap)和堆疊(Stack),
這邊就不多做贅述可以看這篇
小結語:
Node 是基於javascript,也就代表他繼承了Javascript單執行緒的本質,所以他是同步的,可是在伺服器應用程式中每個函式要等待他的回傳或是其他物件,會造成伺服器很嚴重的問題,而事件迴圈是開啟非同步功能的其中一種方法。
上述說的事件回圈是指javascript的事件迴圈,
下一篇會介紹
- NodeJs的事件迴圈
- EventEmiiter
本篇學習目標◑ω◐:
- 初步試探Node同步異步執行順序
- 認識 Node’s EventLoop
- 驗收
初步試探Node同步異步執行順序
先試試看在讀這篇的你對於node中同步異步的掌握。
試著把以下的順序寫下來
// foo.js
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
Promise.resolve().then(() => console.log(3));
process.nextTick(() => console.log(4));
(() => console.log(5))();
想好答案了嗎?
答案會是
5
4
3
1 //有可能是2
2 //有可能是1
如果你答對了(猜對不算)!(๑• . •๑)
恭喜你可以把文章關掉了(反正點閱已經被我騙到了)
完全正確也可以接著看我的文章驗證自己和我的敘述的是否相同啦~
沒有答對的不要氣餒讓我們一起學習吧~(●´ω`●)ゞ
Node’s EventLoop 初步認識
接續上篇的JS使用的EventLoop,
在Node中又有什麼不同?
我會以我的觀察來分享一下。
他們聽起來是一樣的,( ͡° ͜ʖ ͡°)
但是實際實作方式真的是相差甚遠Σ(゚Д゚;≡;゚д゚)
Node跟瀏覽器的EventLoop比複雜許多。
Node的EventLoop是基於libuv函式庫實作出的
libuv是使用C++寫出來的,聽說這個函示庫一開始就是為了node而寫的。
我們來看看官方文件說了啥(,,・ω・,,)
When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.
歸納出當一支Node程式執行時執行順序
1. 初始化事件循環
2. 同步任務
3. 提出異步任務的請求
4. 規劃定時器的生效時間
5. 執行process.nextTick()
6. 開始事件循環
Node中異步任務有分本輪循環和次輪循環
這邊你只要知道本輪循環一定比次輪循環還要早執行就對了。
上述的process.nextTick()
的回調(callback)是屬於本輪循環的,千萬不要被他的名詞給騙去了(๑¯∀¯๑)
而除此之外還有微任務,就是指Promise
的回調函數(callback) ,他也是跟process.nextTick()的回調一樣屬於本輪循環。
然後他會在process.nextTick()的回調後執行。
而以下這些回調函數(callback)會加在次輪循環
setTimeout
setInterval
setImmediate
而Node中EventLoop分為六個階段(如圖)
而他們會依序執行圖中的
1. timeers
2. pending callbacks
3. idlem prepare
4. poll
5. check
6. close callbacks
然後他們各自是幹嘛的?借用一下這篇的內容
各 Phase 的責任說明
timer:執行由 setTimeout() 及 setInterval() 排進來的 callbacks
I/O callbacks:有關系統錯誤等 Callbacks 將 queue 在此
idle, prepare:內部使用
poll:向系統取回新的 I/O 事件,執行對應的 I/O callbacks
check:執行由 setImmediate() 排進來的 callbacks
close callbacks:監聽 I/O 'close' 事件的 callbacks (如 socket.on('close', ...))
看完上面的敘述應該會有一點點概念他執行順序...
詳細有興趣可以自行再深入研究或底下留言討論。想想上一篇瀏覽器的單純時光,那是我逝去的青春
驗收
看完以上來看看自己對於Node的同步和非同步執行順序應該是略知一二了。
來驗收看看囉
一樣寫下來看答案對不對~
console.log("A");
setTimeout(() => console.log(1));
console.log("B");
setImmediate(() => console.log(2));
console.log("C");
process.nextTick(() => console.log(4));
console.log("D");
Promise.resolve().then(() => console.log(3));
console.log("E");
(() => console.log(5))();
答案
A
B
C
D
E
5
4
3
1 //有可能是2
2 //有可能是1
看完以上你應該會想說
靠北你還是沒說為什麼會有“可能是1可能是2的情況啊”
這邊轉貼一下別人的說明來源
这是因为setTimeout的第二个参数默认为0。但是实际上,Node 做不到0毫秒,最少也需要1毫秒,根据官方文档,第二个参数的取值范围在1毫秒到2147483647毫秒之间。
也就是说,setTimeout(f, 0)等同于setTimeout(f, 1)。
实际执行的时候,进入事件循环以后,有可能到了1毫秒,也可能还没到1毫秒,取决于系统当时的状况。如果没到1毫秒,那么 timers 阶段就会跳过,进入 check 阶段,先执行setImmediate的回调函数。
如果你沒有想到
沒關係
以上純屬開玩笑
不要告我
本週心得
我還是不要在每篇底下說我下篇要寫什麼好了xDD
每次立Flag,然後一直拆整篇的內容感覺很愧對各位大大 Orz
原本這篇要講滿多東西的但是這週有點忙直接拆了1/3
對不起QQ
參考文獻
Banner設計
Node官方 Document
深入理解js事件循环机制(Node.js篇)
What is the different between JavaScript Event loop and Node.js Event loop?
什麼是EventEmiter?(,,・ω・,,)
當我打開書,看到一堆名詞包括process,
作者都會寫說都是繼承於EventEmiter什麼的。
心裏OS : 大哥! 不是每個人都知道你在公啥好嗎Orz
然後繼續讀下去詳情請洽EventEmiter章節
嗯~
你有要說~但是為什麼你不早說
這就是我對EventEmiter的第一印象 (´ΘωΘ`)
然後接著是第二印象,是我把名詞拿去Google翻譯事件....發射器?
大哥...你到底是什麼啦,發大財我知道但是發射器是什麼?Orz...
說實話直到我要寫這篇文章才知道原來他不叫發射器
啊...原來是我直接ignore底下原文的提示(,゚Д゚)
所以我說...
其實認真思考過後他名稱發射器或是廣播器都說的過去欸(ง๑ •̀_•́)ง
其實這章節重點只有底下幾行xDD
他其實就是Node之中你可以自定義觸發條件和監聽的一個東西
就是有點像是我們平常在js或是vb之類的常用的Listener
我的感覺啦ヽ(́◕◞౪◟◕‵)ノ
聽起來是不是不錯!? (突然想到蠟筆小新)
他能幹嘛?(✪ω✪)
這個EventEmitter類別,廣泛地用被繼承在各種模組,例如之後會講到process的標準輸出入(I/O)。
實際上他主要可以使用兩個方法來操作
- 監聽某事件
EventEmitter.on('<什麼事件>',<如果觸發了要幹嘛?(callback)>)
- 發送某事件
EventEmitter.emit('<要發送什麼事件>','<要傳遞什麼值>','發送時要做什麼?')
是不是看起來是非常簡單?
看起來簡單但是卻能做壞壞的事真的不容易,
我們接著看這篇主要要教你做的壞壞的事~ლ(́◕◞౪◟◕‵ლ)
來做壞壞的事ლ(́◕◞౪◟◕‵ლ)
既然知道他能像JS一樣監聽某事件,並且觸發。
那我們就來做一點壞壞的事吧!
開始做壞壞的事
- 匯入Event模組的EventEmitter
var eventEmitter = require('events').EventEmitter
- 新建一個 EventEmitter的函式到addEvent變數
var addEvent = new eventEmitter();
- 新增一個定時器並使用剛剛新建的變數使用emit發送訊息
setInterval(function () { addEvent.emit('紅綠燈', "右轉", console.log("紅燈要幹嘛?")) }, 1000);
- 新增一個監聽"紅綠燈"事件的監聽器
addEvent.on('紅綠燈', function (event) {
console.log(`要 (((o(*゚▽゚*)o)))... ${event}!\n`)
});
執行結果:
是不是很壞!?ヽ(́◕◞౪◟◕‵)ノ
相信各位看到這一定都會覺得
我看了這麼久,你給我看這個?
完整Code:
var eventEmitter = require('events').EventEmitter
var addEvent = new eventEmitter();
setInterval(function () { addEvent.emit('紅綠燈', "右轉", console.log("紅燈要幹嘛?")) }, 1000);
addEvent.on('紅綠燈', function (event) {
console.log(`要 (((o(*゚▽゚*)o)))... ${event}!\n`)
});
本週心得
我一直以以下這句經典名言當作我寫程式的動力
動機就是Push你學習的糧食
用Youtuber大多數創作者或媒體會下的標題來騙流量看回不會高一點(?
發現上篇幹話幾乎沒有似乎也沒有比較多人觀看xDD
然後說實話質量也很差QQ
看來我之後只能用幹話充滿我的文章了。(重點好像有點搞錯)
免責聲明: 以上資訊不確定是否屬實,謹供各位大大斟酌參考~
最近在公司每個人都喜歡在最後加這句,表示沒有要對於剛剛說的話或是貼的資訊負責xDD
意外覺得滿實用的
參考文獻
Banner設計 - canva
Node官方 Document
Day11 - Node.js EventEmitter
我買的這本書
什麼是CommonJS?
先來看看維基百科怎麼說
CommonJS是一個專案,其目標是為JavaScript在網頁瀏覽器之外建立模組約定。建立這個專案的主要原因是當時缺乏普遍可接受形式的JavaScript指令碼模組單元,模組在與執行JavaScript指令碼的常規網頁瀏覽器所提供的不同的環境下可以重複使用。
簡單來說CommonJS就是一個模組規範
首先,拋棄你那些高階的想法。
想像一下以前JS古人的時代,
每個人在寫JS時沒有分File,
直接在html的script tag 裡面寫JS那個景象嗎?
用想的是不是感覺就非常精彩!?
我是說Debug的部分xDD
此時此刻出現了模組化的概念
而模組化這個思想要實作,各個大神都有自己的一套實作方法
而沒有一個統一的規範與標準
再來也因為Javascript 想要進攻後端版圖,
所以也衍生出來ServerJS,
之後,ServerJS 更名為 CommonJS。
而CommonJS不是一個涵式庫。
再次強調!(メ ゚皿゚)メ
上面有提到他是一個模組規範與標準 (兇屁
而也因為CommonJS在Node之中廣泛地被使用因此聲名大噪。
其實除了CommonJS還有其他的組織提供其他規範啦~
例如AMD,UMD,CMD...等等 ,這些就不贅述。
其實CommonJS對於模組的定義也是非常的簡單大致分為三個部分
- 模組引用
- 模組定義
- 模組標示
(上述名詞可能會因為翻譯而略有不同)
講到這你可能也快睡著了ԅ(¯﹃¯ԅ)
來實際看看在Node之中怎麼實作
在Node中實際使用起來會長什麼樣子?
模組定義
白話就是寫一個程式把他包裝成一個 module,方便reuse。
保持DRY(Don't repeat your self)
// action.js
function turnRight() {
return "右轉"
}
function turnLeft() {
return "左轉"
}
module.exports = {
turnRight,
turnLeft
}
模組引用
白話來說就是用上述定義好的模組
// main.js
const action = require('action')
console.log(`紅燈要~${action.turnRight()}`)
console.log(`紅燈要~${action.turnLeft()}`)
執行結果
模組標示
白話說就是Coding Style的概念
主要就是在說require()
內的參數
- 必須是小駝峰的字串
- 必須是以
.
,..
開頭的絕對路徑或相對路徑 - 可以不必加上.js的後綴字
結語
其實CommonJS沒有想像中的那麼難懂,(●´ω`●)ゞ
雖然之後出現了AMD,UMD甚至是相對較新的ES6等等模組規範,
但是對我來說,這些都不是重點...
重點是...
我現在時常用Node卻不知道我平常用的正是CommonJS規範好的。
覺得有一點丟臉(((゚Д゚;)))
為什麼叫一統江山的CommonJS?
其實不知道欸感覺大家都在用,一個感覺~
雖然新的ES6看起來很香,
但是支援度還是稍嫌不足還是得靠Babel去轉,
講那麼多我還是會用新的啦(X
但是知道這個CommonJS感覺也不是一件壞事,
人嘛總是要溫故知新(根本不是這樣用)
補充一下
CommonJS是同步加載的
AMD是異步的,
有興趣的可以自行研究或是等我哪天有空再分享給你各位
好了~這篇就到這了
感謝各位
期待與您再次相會。
參考文獻
什麼?!我們竟然有 3 個標準? - 你有聽過 CommonJS 嗎?(Day9)
CommonJS规范
試著把切版專案升級到 gulp4.0 吧 Day06 來自 gulp 的 hello world:JavaScript 模組化方案
Day29-JS模組化!(套件結合篇)
什麼是NVM 和 NPM
NVM(Node Version Manager)
nvm is a version manager for node.js, designed to be installed per-user, and invoked per-shell. nvm works on any POSIX-compliant shell (sh, dash, ksh, zsh, bash), in particular on these platforms: unix, macOS, and windows WSL.
簡單來說就是可以讓你自己管理自己 Node 版本的工具,
想用哪個版本,就用哪個版本,而且操作容易。
(我個人感覺有點像是python的pipenv)。
NPM ( Node Package Manager )
npm(全稱 Node Package Manager,即「node包管理器」)是Node.js預設的、以JavaScript編寫的軟體套件管理系統。
就如同標題所說的Robin -> handsome
簡單來說 Node 的應用程式基本上圍繞的都是第三方的模組,
所謂別人造的輪子~就是香
而這些第三方模組在管理方面就由 NPM 大大所管控
而每個人只需要參照 NPM 所產生的 package.json
紀錄的內容
就可以知道這個專案用了哪些模組。
聽起來是不是很色?
安裝 NPM and NVM
如果已經安裝完的可以直接跳過安裝的這節。
安裝 NPM
安裝可以看這篇,Node的安裝方式。
你會想說...
咦!? 這不是 Node 嗎?
我要裝的是 NPM !
對沒錯,通常在安裝 Node 過程中,
其實他已經幫你順便安裝了~
一個買一送一的概念。
形影不離就像Robin 離不開 handsome 這個字一樣。
安裝NVM
官方直接是建議用 curl 或是 wget 的方式
廢話不多說~
直接打開你的 terminal
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.3/install.sh | bash
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
如果是 macOS 的同學 homebrew 也可以喔
brew install nvm
只是 homebrew ,
有網上的大大說好像有機率會有問題(我自己本身是沒遇到過)
所以還是盡量使用官方推薦的方法吧。
NVM
安裝某版本 Node(以 v12.16.1 為例)
nvm install v12.16.1
安裝完之後他會自動幫你把 Default 自動改成你剛才裝的版本。
這點要注意一下~
不過 Terminal 上也會很貼心的提醒你,所以不用太擔心。
切換某版本 Node(以 v12.15.0 -> v12.16.1 為例)
nvm use v12.16.1
目前我主要會用到就這兩個語法
如果有對他指令有興趣可以打 nvm --help
查看唷
看起來是不是真的很方便,說切就切。
NPM 攻略之我全都要
先講一下我還沒研究以前,
我不知道我平常下以下三種差異。npm install <packageName>
npm install <packageName> --save
npm install <packageName> --save-dev
一整個就是
如果在螢幕前的你也一樣,恭喜你這篇你值得看下去xD
預防針:
由於 NPM 真的很廣
我就盡力整理可能會用到的,
如果有漏講先講聲抱歉 QQ
雖然這篇有寫到部分內容,但是我這邊還是會統整再講一次。
那開始囉~
查看NPM版本
npm -v
專案初始化
npm init
當專案開始時,起手式就是npm init
,當下了這行他就會問你...安安~ 你好! 幾歲住哪?
如果你都想用預設之後再改相關設定可以使用
npm init -y
所以npm init
他會做什麼?
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
Seenpm help json
for definitive documentation on these fields
and exactly what they do.Use
npm install <pkg>
afterwards to install a package and
save it as a dependency in the package.json file.
翻譯蒟蒻:
親愛的~我會引導你建立你的
package.json
,不用擔心和怕怕,如果看不懂那些相關設定詳情可以下這個指令npm help json
。當你之後想安裝 package 時,請使用
npm install <pkg>
然後我會幫你把這個項目增加至package.json
的相依項目,之後就可以靠這個檔案打天下囉~
npm init 所產生的 Package.json 內容
Package name
通常會以你的檔案夾名稱做為預設,就單純指你的專案名稱。
如果日後需要修改也可以更動,所以不用擔心。
通常規範是說,要用小寫,且名稱簡潔。
Version
就是該專案的版號,對於專案來說可以說是非常重要的,
使用別人的模組時也應該要特別注意該專案的版號。
正常來說專案有任何改動,版本號應該都要做更動。
Description
這個就跟它字面上的意思一樣,
這邊可以描述你的專案和使用方法等等的。
(如果沒有輸入這個部分的話會一直提示你)
他會提示著你,提示到你心底發寒
除此之外,這個也可以方便他人在 npm search
搜尋到你製作的 Package 唷
Entry point
簡單來說就是你這個專案,要執行的切入點,
也就是如果我要啟動這個專案我該執行哪個程式。
通常是app.js
, index.js
或 server. js
。
Test command
其實這個就是說專案的測試指令,預設是
"echo \"Error: no test specified\" && exit 1"
其實他就是在你的專案下command line 指令
打開 package.json
會發現
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
這個 script
的地方還可以做很多事情,
當你使用
npm run test
他就會執行該Key值裡面的東西
舉例~ 我在 scripts 內增添一個在 Terminal print 出 "Hellow World" 的指令
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"helloWorld": "echo 'Hellow World'"
},
我就可以使用 npm run helloWorld
執行該指令
結果:
你這時可能會想說
我在 Terminal 執行就好啦~幹嘛那麼多此一舉?
Good !
等等會有一個小節講 npm run <scriptKeyName>
這邊你只要知道在init的時候他幫你scripts 那邊多增加了一個 test 的 command。
Git repository
這個很簡單,就是紀錄你的專案 Git 原始碼位置。
Keywords
Put keywords in it. It’s an array of strings. This helps people discover your package as it’s listed in npm search.
這個用途跟主要就是讓其他使用者使用 npm search
能搜尋到你的 package。
(跟 Description 部分用途一樣)
這個概念有點像是 SEO 的 Tag xDD
Author
這個就是指專案作者,正常會用author-name <author@email.com>
來表示
License
是指專案版權。
預設是ISC
詳細可以參閱這個ISC授權條款
除此之外也有其他的授權條款可以參閱這篇
安裝專案的相依
npm install
這個是我覺得 NPM 超強大的地方。
相信在讀這篇的你,不管使用何種語言都曾經需要安裝第三方的模組,
當使用其他人的專案通常是先讀該專案的 README,
看需要安裝哪些 module (package)
但是其實實際時常遇到的步驟是以下
- 下載 或是 使用
git clone
該專案 - 執行該主程式
- 出事... 噴錯
- 看Error message...
- 啊...原來是我的local端沒有裝某些模組...
- 無限輪迴 2-5 步驟 直到可以正常執行。
在 NPM 的淫威之下,你只需要
- 下載別人專案
npm install
- Enjoy 該專案
而你想說...
和運表示:因為有紀錄啊! 笨蛋~
NPM表示:因為有紀錄啊! 笨蛋~
(開玩笑的)
因為 npm install
會根據該專案的 package.json
中的 Dependencies 項目安裝而這些項目怎麼來的?
等等會在 安裝package 的章節提到。
至於安裝的模組會存在該專案資料夾的node_module
你沒看錯!以往我們都是將模組安裝在local端的global,
但是在 Node 的世界希望每個專案都獨立,
雖然說這樣會造成記憶體的浪費,
但是卻能避免掉很多不必要的問題。
這時你可能會想說什麼問題?
例如說你 local 端有很多專案,那每個專案都需要某個模組,但是你以global的方式安裝,那代表你切換專案時,你也必須降版或升版某模組,才能讓該專案正常運行。
而在你下 npm install
的同時也會產生package.lock.json
,這個部分主要是
package-lock.json的作用就是鎖定安裝依賴時包的版本,並且需要上傳到git,以保證其他人npm install時安裝的依賴能夠保持一致
什麼意思?
就是說 package.json 其實只能鎖定大版本,而小版本卻無法鎖定很細小的版本,那如果這些小版本會影響到你,而且這些小版本是很頻繁的更新的,那你大概專案也不用做了光 Debug 就飽了xDD
列出安裝的模組
npm list
npm ll
npm la
列出來會發現...
我這邊只有安裝Express
結果...
怎麼辣麼多Σ(*゚д゚ノ)ノ
如上述所見,其實你安裝的模組可能會依賴其他模組,其他模組又會依賴其他模組,所以就會變得很多層...這時候可以
但是其實可以改成這樣下
npm list --depth=0
就會列出最上層的部分,看起來舒服多了。
安裝 Package
以前 NPM 會有分三種方式安裝
npm install <packageName>
安裝該模組內容至node_module
但是不添加依賴至package.json
的dependencies
和devDependencies
npm install <packageName> --save
安裝該模組內容至node_module
並添加依賴至package.json
的dependencies
npm install <packageName> --save-dev
安裝該模組內容至node_module
並添加依賴至package.json
的devDependencies
(這邊舊版本是指 npm 5 以前,所以時代也有點久遠)
現在差異在於 npm 已經將預設的 npm install
改成有 --save
了
所以目前上述的 1 和 2 會相等。
所以現在新版大多數是使用
npm install <packageName>
or
npm install <packageName> --save-prod
npm install <packageName> --save-dev
如果想要像舊版本不添加至依賴可以用以下的方式
npm install <packageName> --no-save
至於 dependencies
和devDependencies
差異在哪請看下一個章節。
指定版本安裝
指定版本安裝只需要在後面加上@<版號>
npm install <packageName>@<版號>
安裝以Global 的方式
安裝在 local global的方式,非常簡單。
npm install <packageName> -g
(非常不建議使用這種方式,除非真的需要)
這邊分享一下我大約三個月以前的故事,
可以說是非常心酸,浪費了我一整天的時間QQ。
心酸小故事
因為當初對於 npm 不熟悉,所以將自動化測試的框架 Nightwatch 安裝至 global ,然後我一直都是以 global 的方式在執行我的測試,然後我也好巧不巧,不知道哪根筋不對,在該專案把 Nightwatch 升級了,但是 local 的版本少了一個大版本,於是該專案在 Jenkins 大噴錯,怎麼跑怎麼掛,但是我在 local 跑得又很歡樂,心裡覺得非常不可思議,因為 package.json 又顯示版本跟 Jenkins 上ㄧ樣啊。
非常沒道理!
最後追查了一整天才發現,原來我一直都是用 global 的方式跑。 所以一直在跑在舊的版本...(|||゚д゚)
聽起來是不是還好...
但是我還是心有餘悸,當時真的是很無助QQ
分享給你各位。
Dependencies 和 DevDependencies差異
這兩個最主要的差異是開發需要或是該生產專案需要
什麼意思?
例如你撰寫一個 API 的應用
你的用的 module 是 Express ,那這個就該算是 dependencies。
那這個 API 需要測試,你可能會安裝 Jest 來測試,但是這不是這個專案所需要的,單純是開發所需,那這個就該算是 devDependencies。
同理如果是前端的話可能就像是 webpack
解除安裝 Package
install 的相反是什麼?remove 啊
只需要用 uninstall 就好囉
npm uninstall <packageName>
他也會順邊幫你的 package.json
處理好
如果要解除 global 的 Package 也只要在最後加上 -g
就可以囉
npm uninstall <packageName> -g
搜尋Package
npm 可以直接搜尋現在已經發布公開的 Package
npm search <keyword>
例如我想要找 mongoDB
相關的 Package
就如同上面提到的 description 和 keywords 會影響到你被搜尋的結果。
Npm Start
這個真的是我覺得很黑人問號的一個地方,
我在很多地方都看到人家直接使用npm start
就可以直接啟動他的專案,然後我自己開發的時候怎麼用他都會說無法執行,他說我的scripts 裡面沒有設定 start 這個key。
但是我檢查他們專案可以直接用的 package.json 也沒有啊!
結果查到資料...
npm start
預設會執行server.js
檔案...
如果沒有的話就會依據 scripts 設定的執行
假如說你的 app.js
想要藉由 npm start
執行,可以用上面提的方式增加進去 package.json
如下
"scripts": {
"start": "node run app.js"
},
直接使用 Node 執行程式和使用 npm run 的差異
最大的差異在於,直接使用 Node <fileName>
可能會吃到你 global 安裝的 package,不會是該專案所需的版本。
而使用 npm run <scriptKeyword>
就不用擔心上述的問題。
(之前因為這個,找了很久原因 Orz)
題外話
2020年3月16 日,GitHub CEO Nat Friedman 宣布 GitHub 已簽署收購 NPM(npm 背後的公司)的協定,並表示 npm 加入 GitHub 後會繼續免費提供公共軟體註冊中心服務。
當時看到這則新聞,我同事問我為什麼跪在電腦前。
我只能說 GitHub 拔拔就是厲害~
總結
在研究過程中,有看到 yarn 這個第三方的套件,
看很多文章比較起來是各方面都優於 NPM ,
不過那些文章發布時間都是有一段時間的,
我也還沒實際接觸 yarn ,所以也不敢斷定哪個好哪個不好。
所以也不敢斷定哪個好哪個壞~
如果有近期用兩者比較的你也可以分享給我
不過不論哪個好或壞,這篇研究起來讓我對 NPM 有一個飛越性的成長。
然後這篇真的有點多希望對於讀者的你有幫助( • ̀ω•́ )
看滿多文章都是分篇去寫,想說來一個懶人包,一篇學到底。
參考文獻
NVM
NPM 使用介绍
The package.json guide
為什麼我從npm到yarn再到npm?
Difference between npm install something vs. npm install something --save vs. npm install something --save-dev
React Day7 - NPM
從零開始: 使用NPM套件
npm-package.json
Specifics of npm's package.json handling
此篇學習目標 ◑ω◐ :
- REPL 基本介紹
- 進入和離開 REPL 的世界
- 多行的 Javascript
- 特殊的底線變數
- 自訂 REPL 消除無意義的 undefine
REPL 基本介紹
REPL 發音為 repple
,全名又為 read-eval-print loop ,所以他是什麼?
直接看以下GIF
嗯,就是你跟他你一來我一句的互動式元件
就猶如你打開瀏覽器的開發人員工具 console。
我其實也是看到這本書我才知道有這功能Orz
而他除了可以讓你編撰寫你的應用程式同時快速測試之外,REPL 還可以讓你更知道更底層的 javascript
書上說的xDDD
但是我在範例只看到他使用3>2>1
會回傳 false
這個範例。
進入和離開 REPL 的世界
由簡介的 GIF 已經看到了要打開 REPL ,
只需要在 Terminal 輸入 node
,並且不要加上任何 Node 程式。
如圖
會看到圖上出現Node 版本號 和 >
,就代表你已經進去了。
在這裡的世界,任何東西都會被底層的 V8 Javascript 引擎處理。
而退如果要離開這世界也很簡單
(To exit, press ^C again or ^D or type .exit)
- control + C
- control + D
- 輸入 exit
多行的 Javascript
看到上面的範例會想說...
他是不是只能輸入一行啊...
這樣要測試一些 function 之類的不就尷尬了,
No No No~
他不是只能你一言我一句,他可以我一堆話你一句。
也就是可以執行多行運算式,譬如以下列範例
他會判斷以左大括弧{
為開始自動幫你把下面幾行都增加 ···
幫助你撰寫,而結尾就是以又括弧 }
為對應。
而套疊越深的話他的···
也會越多所以不用擔心。
然後!你也可以透過複製貼上的方式也可以給 REPL 執行
特殊的底線變數
這個就是你可以直接使用底線來存取最後一次的運算式
如下
自訂 REPL 消除無意義的 undefine
在第一個範例中 var a = 3
會回傳 undefine ,這對我來說真的算滿沒意義的,那我們可以利用 Node 提供的 API 自訂 REPL 來消除他。
而自訂 REPL 主要的作法是
repl.start(options);
那我們開始實作剛剛說的消除 undefine
- 建立一個檔案名為
repl.js
- 先引入 REPL 模組
var repl = require('repl');
- 寫入options
var repl = require('repl');
repl.start({
prompt: 'robin repl>', //預設是 >
replMode: repl.REPL_MODE_STRICT,
ignoreUndefined: true,
});
- 存擋
- 執行該 Node 應用程式
node repl.js
相關的設定可以參照官方的 ducument
以上。
感謝大大觀看
參考文獻
Node ducument
Node.js REPL(交互式解释器)
- 留言1
- 追蹤0
- 檢舉
1 則留言
1
iT邦新手 5 級 ‧ 2020-04-28 10:07:52
又到了每週一舒壓的時間~
我也順便分享 Python
的 REPL
在 Terminal
or Cmd
輸入:$ python
或$ python3
Python 3.7.6 (default, Dec 30 2019, 19:38:26)
[Clang 11.0.0 (clang-1100.0.33.16)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello World")
Hello World
>>>
Process標準串流是什麼
process 的 I/O 函式其實是繼承自 EventEmitter,這代表說我們可以發出事件或是監聽事件擷取任何資料來做處理,而且這些串流是無敵的!你沒辦法把它關閉或是在應用程式中刪除掉。
聽起來是不是特別的令人興奮啊!
而串流在 Node 中主要分別由 stdin(標準輸入)、 stdout (標準輸出) 、 stderr (標準錯誤) 所組成。
而 process 會以三種函式來使用
- process.stdin 讀取串流
- process.stdout 寫入串流
- process.stderr 寫入串流
這邊你應該會想說...
咦(((゚д゚)))!
怎麼 stdout 和 stderr 都是寫入串流,感覺是不是一樣的啊?
其實他是刻意這樣分的,多建構一個 stderr 是為了想要分別處理預期的輸出與發生問題的輸出,我相信你不會想要特地在同一個地方還要自行判斷和分類的對吧!
我相信正在讀的你已經迫不急待想做一些嘿咻臉的事了 ⁽⁽٩(๑˃̶͈̀ ᗨ ˂̶͈́)۶⁾⁾
利用 stdin 讀取串流
首先必須設定串流的編碼...
process.stdin.setEncoding('utf-8');
不然會發生很可怕的事...
就是... 看不懂 xDDD
如果沒有設定只是結果會輸出 buffer 而不是字串而已啦 (´⊙ω⊙`)
至於 buffer 是什麼有興趣可以自行研究,之後"應該"有機會我也會分享,因為我現在不想誤導各位。
接下來我們監聽 readable
事件,這個事件主要是會讓我們知道有資料可以讀取的事件,然後再藉由 process.stdin.read()
讀取串流
process.stdin.setEncoding('utf-8');
process.stdin.on('readable', () => {
const input = process.stdin.read();
if (input !== null) {
console.log(`我監聽到東西啦 -> ${input}`)
}
});
這邊就判斷如果接收到的資訊不是 null
就會輸出監聽到的東西。
實際執行情況:
利用 stdout 與 stderr 分別處理串流輸出
這部分我會搭配上面的 stdin 使用,
上面有提到這兩個都是串流輸出但是主要是將預期與非預期的輸出結果作分流。
在 browser 的開發人員工具可以明顯分出 error 和 info 的 console,
你只要想成以下這樣就好
console.log() -> process.stdout()
console.error() -> process.stderr()
但是在 terminal 這塊我看不出來差異 Orz
那這邊就用如果輸入 Robin is handsome
就代表是預期的,
並且輸出串流 ✅Very smart! That is truth.
。
反之如果輸入 Kevin have girlfriend
就代表這不是預期的,
並且輸出串流 ❌Liyer! That is not truth.
。
而其餘情況就不多做事。
process.stdin.setEncoding('utf-8');
process.stdin.on('readable', () => {
let input;
while ((input = process.stdin.read()) !== null) {
const command = input.trim(); \\去除兩端的空白字符或是斷行
console.log(`我監聽到東西啦 -> ${command}`)
if (command === 'Robin is handsome') process.stdout.write("✅Very smart! That is truth.")
if (command === 'Kevin have girlfriend') process.stderr.write("❌Liyer! That is not truth.")
if (command === 'exit') process.exit();
}
});
如果不想要連同換行 也被log 出來,可以改用以下判斷
if(command!== '') console.log(`我監聽到東西啦 -> ${command}`)
另外如果不想使用trim() 可以在判斷的最後方加入'\n'
也可以正常運行唷~
實際執行情況:
實際使用!建立簡單的 CLI 來查詢鼠年馬拉松的文章登記情況
這邊就直接實戰,以直接想查詢自己在六角學院這次辦的鼠年馬拉松,登記情況為例,剛好六角學院這邊有提供 API 真的方便。
雖然說有人有做網頁版滿方便且漂亮的,但是實際上還是要開瀏覽器,
如果想單純使用 terminal 查詢就可以參考我這個範例喔~
先看成果~
我的使用情境是我希望我能夠打 get 關鍵字然後再輸入我的keyID來查詢我的文章登記情況,像我的keyID就是 188
那我就希望我只需要在 termnial 輸入 get 188
,就能拿到我要的東西。
來吧~
首先寫一個獲取鼠年馬拉松API資料的的module。
//getW3hexschool.js
const request = require('request');
const w3hexschool_API_URL = 'https://raw.githubusercontent.com/hexschool/w3hexschool-API/master/data.json'
module.exports = function (keyID) {
return new Promise((resolve, reject) => {
request.get({ 'url': w3hexschool_API_URL }, (error, response) => {
if (error) reject(new Error(error));
resolve(response);
});
});
}
再來就是今天學到的使用 process 標準輸出入
// cli.js
const getW3hexschool = require('./getW3hexschool');
process.stdin.setEncoding('utf-8');
process.stdin.on('readable', () => {
let input;
while ((input = process.stdin.read()) !== null) {
const command = input.trim();
if (command.includes('get') && command.split(' ').length === 2) {
const keyID = parseInt(command.split(' ')[1]);
getW3hexschool(keyID).then(val => {
const targetVal = (JSON.parse(val.body)).filter((item) => {
return item.keyID === keyID;
});
console.log('-------------------------------------')
process.stdout.write(`name: ${targetVal[0].name}\n`);
process.stdout.write(`blogUrl: ${targetVal[0].blogUrl}\n`);
process.stdout.write(`updateTime: ${targetVal[0].updateTime}\n`);
process.stdout.write('blogList:\n');
targetVal[0].blogList.forEach(item => {
process.stdout.write(`Title: ${item.title}\n`);
process.stdout.write(`Url: ${item.url}\n`);
});
console.log('-------------------------------------');
});
};
if (command === 'exit') process.exit();
}
});
我這邊的判斷方法是
先判斷 user 輸入的字有沒有包含 get
,且中間以空白為區隔,區隔出來長度必須是2 ,如果成立就拿區隔出來的第二個值當作是 keyID,再把 拿到的資料透過filter將keyID做篩選,最後列出來。
當然你也可以將這程式改成你要的,可能你想要輸入名字之類的,就依照您自己的需求做改良。(,,・ω・,,)
以上。
有問題都可以留言問我
感謝各位讀者的觀看
參考文獻
Node doc
process.stdin
Node.js 基于流将日志、错误分别写入不同文件
安裝
直接可以使用 npm 下載
建議安裝在 global 的環境下,
畢竟這是用來給你建立專案的,除非你有特殊需求...
npm install -g express-generator
安裝完之後可以打開你的 terminal 輸入以下來確認你是否有裝成功唷
express -h
使用 Express-generator 快速建立符合Express的骨架
- 它幫你做了什麼
安裝完成後,利用 Express-generator 我們可以快速地建立出 Express 的應用程式架構。
以我想弄一個關於麥當勞的專案來說我只需要打以下指令
express --view=pug mcdonald
建立一個 view engine 是 pug
的一個麥當勞專案。
附註: pug 是什麼可以參考這篇大大寫的
因為我個人這次專案比較偏向撰寫後端 API 所以 view engine 的選擇變得也不是那麼重要甚至可以不用選擇。
輸出結果~
接著我們看一下他幫我們生產了哪些東西
他幫我們生成一個資料夾為mcdonald
的一個專案資料夾,並且很貼心的提醒你
先進入該專案
cd mcdonald
安裝該專案需要的套件
npm install
執行專案
DEBUG=mcdonald:* npm start
執行完之後你就可以打開你的瀏覽器輸入以下網址
http://localhost:3000/
就可以快速地看到你架設好的網站
看到以上畫面代表你也成功囉
這篇文章到此
之後會以 Express 這個框架撰寫一個很邪惡的專案
至於是什麼~ 繼續看就可以知道囉
參考文獻
Express官方
Npm express-generator
文章 Banner圖片來源
簡介 File System
顧名思義就是操作作業系統上操作檔案所需的所有功能,
然後 fs 模組有提供同步版本以及非同步版本,這邊不談論同步好還是非同步好,讀者都可以自由選擇。
差別在哪?
同步會在錯誤發生時直接噴錯~
非同步會以優先的 callback 作為最後一個參數。
此篇會以非同步串流的方式介紹。
之後有機會,會額外介紹檔案串流的部分。
我在研究這檔案系統時發現我真的是見的世面很淺,
也從沒想過原來一個語言的檔案系統可以跨平台的原因。
都是 煙捲 搞的鬼。
Node 中的 file system 可以跨平台是因為是一組 POXIS 函式的包裝,
aka 可移植作業系統介面
總而言之他就是一個統一介面,
使得他可以讓 Node 的 file system 支援多個作業系統上的運作,
例如 Linux , OS X, Windows , 樹莓派, Android ... 等等作業系統環境。
使用非串流的方式讀寫檔案
讀檔
先看一下官方文件,看一下使用方式~
fs.readFile(path[, options], callback)
先建立一個 readme.txt
檔案
內容如下
I'm Robin
寫一個名為 fsRead.js
的檔案
const fs = require('fs')
fs.readFile('readme.txt', (err, data) => {
if (err) return console.error(err);
console.log(data)
});
執行結果...
他會回傳一個 Buffer
物件,只要加上 .toString()
就可以如預期顯示你要的文字囉
const fs = require('fs')
fs.readFile('writeme.txt', (err, data) => {
if (err) return console.error(err);
console.log(data.toString())
});
寫檔
一樣參考官方文件用法
fs.writeFile(file, data[, options], callback)
寫一個名為 fsWrite.js
的檔案,
寫完之後使用上面讀檔的方式印出寫入好的資料。
const fs = require('fs')
fs.writeFile('./writeme.txt', '鼠年全馬 gogogo', (err) => {
if (err) return console.error(err);
fs.readFile('writeme.txt', (err, data) => {
if (err) return console.error(err);
console.log(data.toString())
});
});
執行結果
然後如果目錄中沒有該檔案他也會幫你建立並寫入。
存取檔案狀態資料
在 Node 中我們可以使用 fs.stat(), fs.lstat(), fs.fstat() 來回傳 fs.Stats 物件,來幫助你檢查檔案是否存在或是該檔案的詳細資訊。
一樣參照官方文件
fs.stat(path[, options], callback)
const fs = require('fs')
fs.stat('./readme.txt', function(err, stats) {
if (err) return console.error(err)
console.log(stats)
})
執行結果。
裡面有一個東西很特別是 mode,是關於檔案的權限,
問題是看不懂啊!XDD
可以搭配輔助函式 stat-mode
const fs = require('fs')
const Mode = require('stat-mode');
fs.stat('./readme.txt', function(err, stats) {
if (err) return console.error(err)
const mode = new Mode(stats)
console.log(mode.toString())
console.log(`Group execute ${mode.group.execute}`)
console.log(`Others write ${mode.others.write}`)
console.log(`Owner read ${mode.owner.read}`)
})
這樣就可以用比較人道的方式看這些資訊了xDD
檔案異動監聽器
不知道為什麼每次看到監聽器都有一種莫名的興奮感xD
可以做一些壞壞的事的感覺。
這部分簡單來說就是...
你做什麼我都知道哦!
書上說 fs.FSWatcher
據開發者所示,在跨平台不一致且不實用。
但是有一個第三方的模組每個月有兩百萬次的下載量,好像很猛很厲害
那就是 chokidar
這邊不多作介紹,我覺得主要是知道說官方的用法在跨平台上有問題就好。
這邊還是講一下官方的 fs.watcher
和使用方法。
建立一個檔案 watchme
,內容為空也沒關係。
因為我們等等就要監聽他~
建立一個js fsWatcher.js
const fs = require('fs')
fs.watchFile('watchme.txt', (curr, prev) => {
console.log(`the current mtime is: ${curr.mtime}`);
console.log(`the previous mtime was: ${prev.mtime}`);
});
執行之後 開始改動watchme.txt
,可以看到他是如何運作的~
使用串流的方式讀寫檔案
讀檔
這邊會用到之前學過的一個東西是EventEmiter(https://ithelp.ithome.com.tw/articles/10230520) 還不知道的可以先看一下~
- 建立一個檔案 readme.txt 內容就隨意
- 建立一個檔案叫做 fsReadStream.js
const fs = require('fs');
let result = '';
const reader = fs.createReadStream('readme.txt');
reader.setEncoding('UTF8');
reader.on('data', function(data) {
console.log(data);
result += data;
});
reader.on('end', function() {
console.log(result);
});
- 執行它~
寫檔
建立一個檔案叫做 fsWriteStream.js
const fs = require('fs');
const outputdata = 'Robin~~~~~~~~~~~~~~~~~~~~~~';
const writerStream = fs.createWriteStream('output.txt');
writerStream.write(outputdata, 'UTF8');
writerStream.end();
writerStream.on('finish', function() {
console.log('');
});
writerStream.on('error', function(err) {
console.log(err.stack);
});
就可以在目錄看到產生的 output.txt
內容就是剛剛寫的 Robin~~~
。
我想要我有一支程式可以每天幫我領麥當勞報報的優惠券!
我的一小步,是肥宅的一大步 - Robin
會有這想法的故事是這樣的,幾乎每週我都會與同事前往麥當勞,當然身為專業吃麥當勞的我一定會使用優惠券 Combo 連擊,而這個 Combo 券連擊一定要搭配麥當勞報報的優惠券。但是優惠券不是說拿就拿,必須每天領取,但是有時候過於忙碌會忘記領取,有一天去吃麥當勞的時候我發覺... 我發覺我無法使出我的優惠券 Combo 連擊。
身為一個肥宅...
慚愧阿!慚愧~
於是心生歹念想寫這個~
此篇學習目標 ◑ω◐ :
這篇的目標只有一個,就是...
- 使用 Express 打造一支 API 領取麥當勞優惠券
- 部署至 Heroku
下篇會再講肥宅的第二步,每天自動領取。
因為我還沒做... Orz
注意須知:
這篇原本在很早很早很早就開始寫(約莫兩週前),
但是一直都覺得哪裡寫不好哪裡不ok的(到現在也是xD)。
此篇主要不是
分享技術,
但是還是會有原始碼和該檔案的主要目的,
而是希望版上能有更多的這種為了更快學習一個語言或框架而發想寫一個專案~
感覺會讓寫程式在茫然的讀者能夠藉由寫專案獲得成就感。
進而讓自己的程式能力因為遇到問題而進步 !
就是一個寫不好沒關係!
滿足個人需求並且獲得成就感才是快樂學習的動力來源啊啊啊!
再進階一點就是滿足大眾需求~ 前提是要滿足的了自己...
使用 Express 打造一支 API 領取麥當勞優惠券
先看成果
看一下目前優惠券的狀態
- 主要需求:
- 獲取沒過期的優惠券
- 取得沒過期的歡樂貼數量
拿每日優惠券
- 主要需求:
- 獲取今日的優惠券
- 告訴我今日是領到優惠券 or 歡樂貼
登入拿 Token
- 主要需求:
- 拿取上述兩個 API 所需的 Token
先使用上篇使用的 Express-generator,建造一個骨架。
還沒看的可以先回去看~
專案結構
結構大部分都是使用 Express-generator 所建制的,
除了 Controller
的部分是參照這位大大寫的自訂 MVC
的概念,想說可以順便熟悉 MVC
,如果內容觀念有誤再請多多見諒和留言告知我。
Node.js-Backend見聞錄(10):關於後端觀念(六)-關於MVC
.
├── LICENSE
├── README.md
├── app.js
├── bin
│ └── www
├── controllers
│ ├── lotteryController.js
│ └── userController.js
├── models
│ ├── lottery.js
│ ├── request.js
│ └── user.js
├── package-lock.json
├── package.json
├── public
│ └── stylesheets
│ └── style.css
├── routes
│ ├── lotteryRouter.js
│ └── usersRouter.js
├── test
│ ├── lottery.test.js
│ ├── schema
│ │ ├── lottery.json
│ │ └── user.json
│ └── user.test.js
└── views
├── error.pug
├── index.pug
└── layout.pug
app.js
const usersRouter = require('./routes/usersRouter');
const lotteryRouter = require('./routes/lotteryRouter');
app.use('/api/users', usersRouter);
app.use('/api/lottery', lotteryRouter);
Routes
這邊會有兩個
- user -> 用來拿取 userToken
- lottery -> 拿取每日優惠券或查詢
lotteryRouter.js
const express = require('express');
const lotteryController = require('../controllers/lotteryController');
const router = express.Router();
router.get('/', lotteryController.getLotteryStatus);
router.post('/', lotteryController.getLottery);
module.exports = router;
usersRouter.js
const express = require('express');
const lotteryController = require('../controllers/lotteryController');
const router = express.Router();
router.get('/', lotteryController.getLotteryStatus);
router.post('/', lotteryController.getLottery);
module.exports = router;
Controllers
lotteryController.js
這邊分成兩部分
- getLottery
- 目的:獲得優惠券並且取得當日的優惠券資訊
- 注意:過期日為當日得隔兩天所以日期 +2 即可獲得當天的優惠券,否則則是歡樂貼。
- getLotteryStatus
- 目的:獲取未過期的優惠券以及歡樂貼清單
const lottery = require('../models/lottery');
async function getLottery(req, res) {
const nowDate = new Date();
nowDate.setDate(nowDate.getDate() + 2); // The lottery expire at the day after tomorrow.
const getLotteryResp = await lottery.getLottery(req.body.accessToken);
const getLotteryListResp = await lottery.getLotteryList(req.body.accessToken);
const stickerListResp = await lottery.getStickerList(req.body.accessToken);
const lotteryToday = await getLotteryListResp.body.results.coupons.filter(
(lotteryTarget) => lotteryTarget.object_info.redeem_end_datetime === nowDate.format('yyyy/mm/dd 23:59:59'),
);
const stickerToday = await stickerListResp.body.results.stickers.filter(
(stickerTarget) => stickerTarget.obtain_datetime > new Date().format('yyyy/mm/dd 00:00:00'),
);
const todayGet = (lotteryToday.length !== 0 && stickerToday.length === 0) ? lotteryToday[0].object_info.title : '歡樂貼QQ';
if (getLotteryResp.body.rc !== 1) {
res.status(getLotteryResp.statusCode);
res.json({
errorMessage: getLotteryResp.body.rm,
});
return;
}
res.status(getLotteryResp.statusCode);
res.json({
lottery: getLotteryResp.body.results.coupon.object_info.title,
todayLottery: todayGet,
});
}
async function getLotteryStatus(req, res) {
const lotteryList = [];
const getLotteryListResp = await lottery.getLotteryList(req.body.accessToken);
const stickerListResp = await lottery.getStickerList(req.body.accessToken);
if (getLotteryListResp.body.rc !== 1 && stickerListResp.body.rc !== 1) {
await res.status(400);
await res.json({
errorMessage: 'sticker or lottery have some problem ...',
});
return;
}
const lotteryNotExpire = await getLotteryListResp.body.results.coupons.filter((lotteryTarget) => lotteryTarget.object_info.redeem_end_datetime > new Date().format('yyyy/mm/dd HH:MM:ss'));
for (let i = 0; i < lotteryNotExpire.length; i += 1) {
lotteryList.push(lotteryNotExpire[i].object_info.title);
}
await res.status(200);
await res.json({
lottery: lotteryList,
totalStickersAmount: stickerListResp.body.results.stickers.length,
});
}
module.exports = {
getLottery,
getLotteryStatus,
};
userController.js
- 目的:獲得該 User 的 Token
const user = require('../models/user.js');
module.exports = async (req, res) => {
const resp = await user.getToken(req.body.account, req.body.password);
if (resp.body.rc !== '1') {
await res.status(resp.statusCode);
await res.json({
errorMessage: resp.body.rm,
});
return;
}
await res.status(resp.statusCode);
await res.json({ token: resp.body.results.member_info.access_token });
};
Models
lottery.js
這邊分成三部分
- getLottery 打每日優惠券的 API
- getLotteryList 打每日優惠券清單的 API
- getStickersList 打歡樂貼清單的 API
const request = require('./request');
require('dotenv').config();
require('date.format');
const deviceTime = new Date().format('yyyy/mm/dd HH:MM:ss');
const source = {
app_version: process.env.APP_VERSION,
device_time: deviceTime,
device_uuid: process.env.DEVICE_UUID,
model_id: process.env.MODEL_ID,
os_version: process.env.OS_VERSION,
platform: process.env.PLATFORM,
};
async function getLottery(accessToken, sourceInfo = source) {
const option = {
url: `${process.env.MC_HOST}/lottery/get_item`,
json: {
access_token: accessToken,
source_info: sourceInfo,
},
};
const response = await request.postRequest(option);
return response;
}
async function getLotteryList(accessToken, sourceInfo = source) {
const option = {
url: `${process.env.MC_HOST}/coupon/get_list`,
json: {
access_token: accessToken,
source_info: sourceInfo,
},
};
const response = await request.postRequest(option);
return response;
}
async function getStickerList(accessToken, sourceInfo = source) {
const option = {
url: `${process.env.MC_HOST}/sticker/get_list`,
json: {
access_token: accessToken,
source_info: sourceInfo,
},
};
const response = await request.postRequest(option);
return response;
}
module.exports = {
getLottery,
getLotteryList,
getStickerList,
};
user.js
- 打 麥當勞報報 Login 的 API
require('date.format');
const md5 = require('md5');
const request = require('./request');
async function getToken(userAccount, userPassword) {
const deviceTime = new Date().format('yyyy/mm/dd HH:MM:ss');
const appVersion = process.env.APP_VERSION;
const callTime = new Date().format('yyyymmddHHMMss');
const paramString = `${userAccount}${userPassword}`;
const modelId = process.env.MODEL_ID;
const osVersion = process.env.OS_VERSION;
const platform = process.env.PLATFORM;
const deviceUuid = process.env.DEVICE_UUID;
const orderNo = `${process.env.DEVICE_UUID}${callTime}`;
const maskMd5 = md5(`Mc${orderNo}${platform}${osVersion}${modelId}${deviceUuid}${deviceTime}${appVersion}${paramString}Donalds`);
const parm = {
account: userAccount,
password: userPassword,
OrderNo: orderNo,
mask: maskMd5,
source_info: {
app_version: appVersion,
device_time: deviceTime,
device_uuid: deviceUuid,
model_id: modelId,
os_version: osVersion,
Platform: process.env.PLATFORM,
},
};
const option = {
url: 'https://api.mcddaily.com.tw/login_by_mobile',
json: parm,
};
const response = await request.postRequest(option);
return response;
}
module.exports = {
getToken,
};
Setting
建立一個 .env 檔(注意這個設定檔如果有敏感資訊通常是不會上傳到 repo 的)
主要用於設定一些 device information
ACCESS_TOKEN,USER_ACCOUNT 和 USER_PASSWORD 主要是我用來寫測試的預設帳號,可以不必理會。
主要會被存取方式就是以 process.env.XXX
MC_HOST = https://api1.mcddailyapp.com
APP_VERSION = '2.2.0'
MODEL_ID = 'MIX 3'
OS_VERSION = '9'
PLATFORM = 'Android'
DEVICE_UUID = 'device_uuid'
USER_ACCOUNT = ''
USER_PASSWORD = ''
ACCESS_TOKEN = ''
部署至 Heroku
這部分我選用最方便且最懶得方式
連結GitHub repositories 自動部署
aka 我推你就 deployee
前面的註冊登入就不教了連結給你~點我
建立你的 app
輸入 app 名稱(注意只能小寫) ,以及地區(只有美國和歐洲)。
選擇部署方式
連結並搜尋你的 repositories 名稱
設定自動部署(還可以選擇 branch)
打開你的 app 看一下 url 是什麼
打了發現...
在 Heroku 設定剛剛的設定檔
就是 Key value 的輸入這樣~
接著就...
可以正常運作了~
我如何知道 APP 是呼叫哪支 API?
可以參閱此大大此篇詳細的 Charles 工具教學
mobile http request 攔截器攔起來-Charles
還可以偷看參考其他人用其他語言寫的麥當勞報報 xDD
McDaily
這邊這位大大還有貼心提醒
Please note that this app might stop working in the near future, as they have changed the hashing algorithm from simple MD5 to AES encryption + Base64 hashing using a differently formatted string.
專案目前情況
專案持續更新,直到我做到完全滿足我的需求為止。
GitHub Link: mcdonaldLottery
目前窘境:
- 後續自動領取的設計要自肥還是開放大眾使用
(牽涉到 Token 代管的問題就會牽扯到資安的問題感覺很尷尬... - Heroku 打麥當勞報報的 API response time 超久...
插Log 看時間差約莫一支在3-5秒左右,上述的一支 API 可能需要打 2-3 支左右的麥當勞報報 API ,造成 API 回覆時間過長。
(初步判定應該不是程式問題,可能是機器或是網路等原因) - 免錢仔使用 Heroku 機器會休眠,如果使用 UptimeRobot 會造成我在 Heroku 的當月免費時數不夠。
關於 API response time 目前的想法:
- Linode 每個月用最低費用 $150 自行架設
- AWS ec2 用最低的免錢方案
- Google GCE 新會員一年免費
以上三種雲端伺服器都要實際跑過才會知道能否解決。
今天要介紹 child process。
這篇有牽涉到以下幾篇的內容如果還沒看得或是有不了解的可以自行斟酌看看。
【我可以你也可以的Node.js】第四篇 - 進程、行程、過程、行程、程序、處理程序、Process
【我可以你也可以的Node.js】第七篇 - 第一次用EventEmiter就用來做壞壞的事ヽ(́◕◞౪◟◕‵)ノ
【我可以你也可以的Node.js】第十一篇 - Process 的標準串流 (stdin、stdout、stderr)
發現以專案類型發文,耗時非常巨大xDD
所以會以穿插學習文章,希望可以兼具學習和興趣。 (聽起來很有夢想)
此篇學習目標 ◑ω◐ :
- 什麼是子行程
- 子行程種類
- 子行程個別使用方式
什麼是子行程
維基百科是這樣說的
在電腦領域中,子行程為由另外一個行程(對應稱之為父行程)所建立的行程。子行程繼承了父行程的大部分屬性,例如檔案描述符。
簡單來說...
夠簡單吧?
這時的你...
以上純屬幹話,但是某方面來說沒有說錯xD
引用一下這位大大文章中提的
在Node.js中,提供了一個 child_process 模組,通過它可以開啟多個子程序,在多個子程序之間可以共享記憶體空間,可以通過子程序的互相通訊來實現資訊的交換。
也就是虎父無犬子,你從哪個行程創建的子行程他就繼承了該行程的大部分屬性。
所以他能幹嘛?
我個人感覺應用可以滿廣泛的我就列舉兩點我看到的~
- 在我讀的這本書提到
OS
中有許多功能只能透過命令列存取,而子行程
能夠做到這件事。 - 如果沒有
子行程
的存在,假使是一個執行緒執行所有操作,某個操作需要消耗大量地CPU
資源的情況下,後續操作都需要等待,那就會出大事,而子行程就可以幫助你解決這問題。
子行程種類
以下介紹四種方法建構子行程
- child_process.spawn
- child_process.exec
- child_process.execFile
- child_process.fork
我來簡單歸納一下這四種的基本介紹
方法 | 概述 |
---|---|
spawn | 執行命令,預設為不會 新建 shell ,執行一個命令,不會回傳結果。 |
exec | 建立 shell 並在 shell 中執行命令,可通過 callback 來獲得執行結果。 |
execFile | 執行檔案,預設情況下不會 新建 shell |
fork | 執行檔案,建立與父執行緒的神秘溝通管道,可以在這之中進行信息的傳送,也不會回傳結果。 |
子行程個別使用方式
child_process.spawn
這個是最常看到的方法。
用法就參照官方用法child_process.spawn(command[, args][, options])
就是啟動命令傳送參數,然後建立 stdin
、stdout
、stderr
。
這邊藉由 pwd 來輸出目前目錄作為示範。
const { spawn } = require('child_process');
const pwd = spawn('pwd');
pwd.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
pwd.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
pwd.on('close', (code) => {
console.log(`child process exit code: ${code}`);
});
執行結果:
這邊看到成功就會藉由 stdout
做輸出
反之如果失敗就是從 stderr
做輸出
最後那個 0
代表最終結果
如果是 0
代表沒有錯誤發生
如果是 1
代表出大事啦
故意讓他從 stderr
作為輸出const pwd = spawn('pwd');
改為const pwd = spawn('pwd',['-g']);
執行結果:
除此之外你可能還會想說
依照書上寫的是說(完全不附帶任何責任的言論xDD)
官方文件有說到將資料導向 stdin 的範例,讓一個命令的結果直接作為另一個命令的輸入。
找到另一個官方的範例
const { spawn } = require('child_process');
const ps = spawn('ps', ['ax']);
const grep = spawn('grep', ['ssh']);
ps.stdout.on('data', (data) => {
grep.stdin.write(data);
});
ps.stderr.on('data', (data) => {
console.error(`ps 的 stderr: ${data}`);
});
ps.on('close', (code) => {
if (code !== 0) {
console.log(`ps 进程退出,退出码 ${code}`);
}
grep.stdin.end();
});
grep.stdout.on('data', (data) => {
console.log(data.toString());
});
grep.stderr.on('data', (data) => {
console.error(`grep 的 stderr: ${data}`);
});
grep.on('close', (code) => {
if (code !== 0) {
console.log(`grep 进程退出,退出码 ${code}`);
}
});
我個人感覺這個的應用就是再次透過 eventEmiter 再延伸其他的動作。
補充:
child_process.spawn
還有出一個同步的版本,叫做child_process.spawnSync
就依照個人需求去使用囉~
child_process.exec
官方文件用法child_process.exec(command[, options][, callback])
這個也可以拿來執行命令使用方法就類似上面那個 spawn
,
但是最大的差異是他可以藉由 callback
拿到執行結果,
這邊就單純用 ls
列出目錄中所有的檔案當範例。
const { exec } = require('child_process');
exec('ls', (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
執行結果:
child_process.execFile
官方文件用法child_process.execFile(file[, args][, options][, callback])
這邊要注意的是跟上面不的 child_process.exec
不同的是一個是檔案路徑
,一個是命令
。
這邊就讓它執行我剛寫了一個檔案名稱叫 hello.js
,
且內容只有一行 console.log('hello world')
const { execFile } = require('child_process');
execFile('node', ['hello.js'], (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
執行結果:
另外補充一點在官方文件看到的
child_process.exec() 和 child_process.execFile() 之間區別的重要性可能因平台而異。 在 Unix 類型的操作系统(Unix、Linux、macOS)上,child_process.execFile() 可以更高效,因為預設情况下它不会產生 shell。
child_process.fork
官方文件用法child_process.fork(modulePath[, args][, options])
這個是滿特別的,最主要是在於它會對行程建構通訊的通道。
建立兩個檔案
// fork.js
const { fork } = require('child_process')
const path = require('path')
let child = fork(path.join(__dirname, './fork1.js'))
child.on('message', (data) => {
console.log(`父行程接收到訊息 -> ${data}`)
})
child.send('hello world')
child.on('error', (err) => {
console.error(err)
})
//fork1.js
process.on('message', (msg, setHandle) => {
console.log(`子行程接收到訊息 -> ${msg}`)
process.send(msg)
})
執行結果:
此篇到此~ 有問題都可以交流討論
感謝您
參考來源
node的process以及child_process模組學習筆記
官方文件
今天來分享 Forever~
我們平常開發幾乎都是使用 Nodemon , 而 Forever,簡單來說與他的差異就是當你的應用程式拋出例外時,他能夠自動偵測並且重啟讓你的應用程式永遠的跑下去,也包含著檔案更新也可以立即幫妳重啟。
如果哪天看到你的程式還是停止了請檢查以下項目
- 您的電費是否繳清?
- 是否有因為個人因素造成另一半的不適,導致另一半拔掉電源幫您省電。
- 有沒有拜拜?
此篇學習目標 ◑ω◐ :
- Install Forever
- 基本指令介紹
Install Forever
Use npm
sudo npm install forever -g
Use yarn
yarn add global forever
基本指令介紹
查看可用的選項
forever --help
使用 Forever 執行程式
forever start <檔案路徑>
使用範例 :
forever start app.js
Note:
我個人滿常在這邊加上 -w
就是可以讓他偵測有新的code 就會自動重啟。
列出目前正在跑的程式
forever list
停止正在執行的程式
停止單一運行中的程式
forever stop <檔案路徑 or pid>
停止全部正在運行的程式
forever stopall
以上是這篇內容。
最近在整理許多其實算是寫一半的專案 Orz
不知道各位讀者會不會有很多跟我一樣專案其實沒開發完整,
就跑去做其他專案xDD
這篇要分享上次肥宅的第一步後續,
還沒看的可以先回去看看,裡面有這次小專案的故事背景動機,
來了各位!
經過我內心非常積極但實際行動緩慢的這一兩週時間,
沒日沒夜的研究,終於先寫出一個過渡品,
至少能滿足現在我的需求。
實際上其實這兩週睡得很好xDD,然後前幾天同事約我去吃麥當勞,
我還是沒有優惠券用 TAT
於是在這兩天努力寫出來xDD
更新一下現況
- 我把專案移動至 aws 的 ec2 了
一次解決了上篇的打 API 延遲和 Heroku 的機器休眠。然後我是用 ec2 免費套餐所以管控好使用方式基本上是不會造成費用的,像我就是設定 如果超過 0.5 美金就寄信提醒我。
(超級客家做法
- 目前先自肥和身邊週遭的朋友沒有要造福大眾
就因為時不時要與同事增進感情吃麥當勞,沒有優惠券整個就不對了,所以先行實施自肥計畫,長遠的就持續更新囉。
此篇學習目標 ◑ω◐ :
這篇目標也只有一個,每天自動領取並且通知我領到什麼。
- 增加一個 API 並使用排程達到每天自動執行的目的。
- 使用 LINE Notify 超級簡單方便的服務來通知我抽到什麼 。
注意須知:
這目前只是我個人想做的專案,如果我構思上不夠完整或是有更好的方式,可以在下面留言跟我交流,身為小菜雞的我一定會努力盡我所能的學習,感恩。
如果你也是小菜雞,也可以跟我提出你疑問 :+1:
如果我回答不出來太艱深的問題是很正常的~
因為~~~
增加一個 API 並使用排程達到每天自動執行的目的
先看成果
打 API 開始自動領取
Line Notify 主動通知
申請 Line Notify Token
至官方登入 點我連結
申請權杖(Token)
申請傳送至1對1聊天室,並設定名稱(我這邊就亂取叫油光降臨)
拿取 Token
測試 token 能否發送訊息
curl --request POST \
--url https://notify-api.line.me/api/notify \
--header 'authorization: Bearer <yourToken>'
--form 'message=Test'
開始撰寫自動領取的 API
一樣我不會一一解釋,但是我會說明寫的時候的目的和想法。
如果有疑問可以底下留言(搞不好只是我單純犯蠢 xD。
Router
目的只有一個,就是增加一個 Endpont 目的就是自動領取。
app.js
增加以下內容
const subscriptionRouter = require('./routes/subscriptionRouter');
app.use('/api/subscription', subscriptionRouter);
subscriptionRouter.js
增加以下內容,進來之後到 subscriptionController
const express = require('express');
const subscriptionController = require('../controllers/subscriptionController');
const router = express.Router();
router.post('/', subscriptionController);
module.exports = router;
Controller
這邊目的就是接收 request
的內容並且傳遞給呼叫寫好的排程 model ,並回傳結果和排程的名稱。
subscriptionController.js
const subscription = require('../models/subscription.js');
module.exports = async (req, res) => {
const resp = await subscription(
req.body.accessToken,
req.body.lineNotifiyToken,
req.body.cronFormat,
);
res.status(200);
await res.json({
name: resp.name,
message: 'success',
});
};
Model
這部分會有兩個
- 寄送 line notify 訊息
- 排程執行自動領取
sendLineNotify.js
這邊就是單純寫一個你給我 token 和訊息內容我就幫你打出去~
const { postRequest } = require('./request');
module.exports = async (messageContent, lineNotifiyToken) => {
const options = {
url: 'https://notify-api.line.me/api/notify',
headers: {
'content-type': 'multipart/form-data',
authorization: `Bearer ${lineNotifiyToken}`,
},
formData: { message: messageContent },
};
const resp = await postRequest(options);
return resp;
};
subscription.js
這邊主要是使用人家寫好的 module -> node-schedule
基本上這邊邏輯就是打進來開始設定排程,時間是依照 cron format,
我這邊預設的 .env
檔案會設定以下,預設是每天的 00:01:00
。
CRON_FORMAT = '1 0 * * *'
如果拿優惠券 status code
或是 rc
有問題就送出領取失敗的訊息。
否則就直接告知我領取結果。
檔案內容:
const { scheduleJob } = require('node-schedule');
const sendLineNotify = require('./sendLineNotify.js');
const { getLottery } = require('./lottery');
require('dotenv').config();
module.exports = (accessToken, lineNotifiyToken, cronFormat = process.env.CRON_FORMAT) => {
const resp = scheduleJob(cronFormat, async () => {
const getLotteryResp = await getLottery(accessToken);
if (getLotteryResp.statusCode !== 200) return sendLineNotify(`領取失敗,因為 statusCode 拿到 ${getLotteryResp.statusCode}`, lineNotifiyToken);
if (getLotteryResp.body.rc !== 1) return sendLineNotify(`領取失敗,因為麥當勞報報 API RC 拿到 ${getLotteryResp.body.rc}`, lineNotifiyToken);
return sendLineNotify(`\n${getLotteryResp.body.results.coupon.object_info.title}`, lineNotifiyToken);
});
return resp;
};
專案目前情況
專案持續更新,直到我做到完全滿足我的需求為止。
GitHub Link: mcdonaldLottery
這兩天實作過程中發現還有很多需要改進的地方...
目前窘境:
如果要分享給不會打 API 的人使用整個就是不可能,
(除非他願意給出帳號密碼,這樣又不太妥。
所以可能需要一個介面讓他輸入帳號密碼拿取 Token
-> 輸入 Line Notify 的 Token
-> 介面按下送出自動領取並傳送至他給的 Line Notify。
看了一下文件發現 node-schedule 要在事後取消單一 job 好像有點困難,所以我如果要取消就是把所有取消,然後在伺服器重開之後所有 job 都會無效需要重新再打一次 API。
目前想法是要用 DB 紀錄每個打進來的 user 狀態,重啟的時候依照狀態去決定要不要重新自動排程。
傳送進來的 request 格式其實我都沒做檢查,例外處理也沒做,整個不及格xDDD
沒什想法,就是要花時間做。QQ
應該做一個可以查詢自己目前有沒有在自動領取的排程中,然後可以回傳說下次領取時間為何。
蠢事分享
我寫完之後 local 測試都正常,找了很久才發現是 ec2 時區跟我不一樣,我以為架設在日本就是日本,結果最後發現他還是 utc+0
...
所以記得要設定時區,不要跟我一樣犯蠢。
sudo timedatectl set-timezone Asia/Taipei
- 使用 ZLib 壓縮檔案
- 使用 ZLib 解壓縮檔案
使用 ZLib 壓縮檔案
先放一張非常帥氣的圖在目錄裡
我取名叫做 handsome.png
然後撰寫一個 js 內容為以下
// photoZlib.js
const zlib = require('zlib');
const fs = require('fs');
const gzip = zlib.createGzip();
const inp = fs.createReadStream('handsome.png');
const out = fs.createWriteStream('handsome.png.gz');
inp.pipe(gzip).pipe(out);
執行它
使用 ZLib 解壓縮檔案
留著剛剛壓縮好的檔案接續來試試看解壓縮
撰寫一個 js 內容為以下
// photoUnZlib.js
const zlib = require('zlib')
const fs = require('fs')
const gunzip = zlib.createGunzip()
const inp = fs.createReadStream('./handsome.png.gz')
const out = fs.createWriteStream('unzipHandsome.png')
inp.pipe(gunzip).pipe(out)
執行它
另外附上比較壓縮過後的檔案容量大小差異
相差了 1897 byte
,
如果壓縮較大的圖片或影片可能就會相差更大了。
以上
是不是滿簡單的 xDD
我個人覺得這寫成 cli 之類的工具方便而且非常實用,特別像是 windows 之類的環境沒有原生的壓縮功能。
我們都知道 Node
是一個 single thread
並且非同步的語言,
那...假使面臨排山倒海的使用者請求時你能想像那一個 thread
孤軍奮戰的感受嗎?
沒有 ! 你只想到你自己。(好久以前的梗)
所以後來官方提出了這個模組,好讓你可以開啟更多的 process
去減輕那個可憐 Thread
的負擔~
避免以下經典動圖的發生。
這讓我想到我上一支手機 Sony Z5P 擁有當年的旗艦 CPU ,在遊戲效能上根本爛到炸,標榜八核心但是實際上只有 4 顆小核心在跑 ,只有在跑分的時候另外四顆大核心才會拋頭露面刷存在感... (後來以 Root 自行改設定才解決)
此篇學習目標 ◑ω◐ :
Cluster
他怎麼運作的- 實戰在 Hello World
- 使用
pm2
讓管理cluster
更容易
Cluster
他怎麼運作的?
他主要是使用 child_process.fork()
產生新的 proccess 藉此與父進程溝通。
而 Cluster 由兩種分配方式決定如何分配進來的請求。
而這兩種都是經由 master process 傾聽一個 port
然後分配到不同的 process
。
如圖所示
第一種配發方式
The first one (and the default one on all platforms except Windows), is the round-robin approach, where the master process listens on a port, accepts new connections and distributes them across the workers in a round-robin fashion, with some built-in smarts to avoid overloading a worker process.
是經由 round-robin approach 來決定 ,簡單來說就有點像是老師分派值日生擦黑板,前提是一天只有一件事就是擦黑板且一天只需要擦一次就可以換下一個人。
所以就是當一個請求進來的時候進到 master process
然後由他去輪流指派 一個 process
的概念。(有錯或是舉例錯誤請糾正我)
Note:
除了Windows
之外其餘都是預設第一種配發方式。
就你最特別這樣。
第二種配發方式
The second approach is where the master process creates the listen socket and sends it to interested workers. The workers then accept incoming connections directly.
簡單來說一樣是由 master process
監聽接口,然後直接配發到有興趣的 process
,就是誰有興趣就配給誰。
在現實生活中感覺可以徵求意見,但是在這邊其實我不知道這個機制要如何知道誰有興趣誰沒興趣,如果有人知道的可以跟我分享一下實際案例,感恩。
如果你也不知道也沒關係我看完也是一個...
官方還特地說明了第二種配發方式
The second approach should, in theory, give the best performance. In practice however, distribution tends to be very unbalanced due to operating system scheduler vagaries. Loads have been observed where over 70% of all connections ended up in just two processes, out of a total of eight.
理論上,第二種方式效能會是最好的,但是實際上會造成其實負載平衡很不平衡的狀況,假使在高併發的情況下總共有八個 process
但卻會有 70%
的連線都只會落在兩個 proccess
在處理。
讓我感覺到能者過勞死
的悲痛。
俗話說的好...
你強,你來。
實戰在 Hello World
我們用之前 第二篇 寫的 Hello world 和官方給的範例來實測看看差別。
我這邊使用 loadtest 這工具來簡單測試一下。
single thread
實測結果: 平均 22 ms
的處理時間
cluster multi-core systems
官方 Hello world 範例
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers.
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
實測結果: 平均 26.9 ms
的處理時間
嗯...
跟我想像中的結果不一樣啊啊啊啊啊! 目前我還在尋找這未知的謎題...
目前我的猜測是因為其實他一下就做完了,然後我開了 cluster 導致他要花時間去分配,造成處理時間更長。
另外看了 stack overflow 的這個討論串
Node.js clustering is slower than single threaded mode
但是還是有點違背我的想像 ~~
如果你知道為什麼拜託告訴我。
使用 pm2
讓管理 cluster
更容易
我另外看了一下好像大多數人不會直接使用原生的 cluster 而是使用這個 pm2 因為設定簡單,然後...好像弄的很不錯(?)
用法很簡單就是pm2 start -i <數量> --name <tagName> <path>
然後我再次的挑戰上面那個謎題
詳細用法我這邊就不再贅述有興趣可以自行看官方文件唷~
實測結果: 平均 13.4 ms
的處理時間
這才是我要的結果xDDD
至於原因嘛...可能是還有更多設定方面和處理其實不太一樣,導致效能受影響(?)
所以我覺得...嗯...我還是直接用 pm2 好了 (X
如果有興趣研究的可以跟我說...我可以聽你的研究成果 xD
結語
另外看到滿多人提說其實很不建議使用這種方式來做 load balance
,原因是如上面所講的這個機制是透過 master process
來傾聽一個 port 再去分配 worker ,那其實透過 Nginx 來做 LB 然後還有各自的 port 效能來得更優異。
我發現之後開開心心的想說可以用在我前幾週發的麥當勞報報自動領取的 server,殊不知我一看我 aws 的目前使用配置...
沒錯...就是
1
配個...毛? XDDD
感謝收看。
你有想過為什麼有些網站的資料,
你明明沒有重新整理資料那些資料卻會自己更新嗎?
(例如一些虛擬貨幣交易所的匯率或是一些股票市場的價格走勢)
答案是他們直接用 Javascript 做在前端直接 Random 或是寫死啊!沒有在跟後端要資料的啦!
開玩笑的
(不過大部分詐騙網站或是一些主要以飢餓行銷手法的網站都是這樣做)
比方說...
網頁上顯示超值特價限時 10 分鐘,然後時間正在一分一秒流逝,
當你腦波一微弱單子就出去了,
但是當你下單完成回頭重新整理一次 10 分鐘又重新計算了,
想必你的心情會是...
基本上 Socket.io
他就是基於 EventEmitter
來提供給 Client 端來使用 Websocket
。
如果還不知道 EventEmitter
的同學可以參考我之前寫的文章,
讓你輕鬆且快樂的學習~
【我可以你也可以的Node.js】第七篇 - 第一次用EventEmiter就用來做壞壞的事ヽ(́◕◞౪◟◕‵)ノ
其實操作方面主要也只有兩個
就跟 EventEmitter 一樣幾乎都圍繞在 on
和 emit
,
先看一下成果~
基本上我就只是後端建立完連線之後,
每秒從後端送出一筆亂數讓前端 show 出來。
那我們幹話繼續說直接看 Code 吧
建立一個 socket.js
const http = require("http");
const url = require('url');
const fs = require('fs');
const io = require('socket.io');
const server = http.createServer(function(request, response) {
console.log('Connection');
const path = url.parse(request.url).pathname;
switch (path) {
case '/':
response.writeHead(200, { 'Content-Type': 'text/html' });
response.write('I don\'t need to reload~');
response.end(); d
break;
case '/socket.html':
fs.readFile(__dirname + path, function(error, data) {
if (error) {
response.writeHead(404);
response.write("opps this doesn't exist - 404");
} else {
response.writeHead(200, { "Content-Type": "text/html" });
response.write(data, "utf8");
}
response.end();
});
break;
default:
response.writeHead(404);
response.write("opps this doesn't exist - 404");
response.end();
break;
}
});
server.listen(8080);
const serv_io = io.listen(server);
serv_io.sockets.on('connection', function(socket) {
setInterval(() => {
socket.emit('randomNum', { 'message': Math.random() * 100 });
}, 1000)
});
這邊主要的重點在這段
const serv_io = io.listen(server);
serv_io.sockets.on('connection', function(socket) {
setInterval(() => {
socket.emit('randomNum', { 'message': Math.random() * 100 });
}, 1000)
當連線連上之後設定定時器一秒鐘送出一個 randomNum
的事件出去。
接著,建立一個 socket.html
<html>
<head>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<script>
var socket = io.connect();
socket.on('randomNum', function(data) {
console.log(data.message);
document.getElementById('viewText').innerText = `I don't need to reload~\n${data.message}`;
});
</script>
<p id=viewText> I don't need to reload~
</p>
</body>
</html>
這邊主要的重點是在於前端透過 socket.io 接收到 randomNum 這個事件然後更新 tag p
的內容。
所以由此可知,這年頭景氣不好啊賣東西都要搞心理戰了(重點錯誤)
希望以上範例可以讓正在讀的你能更好理解其實要實作 websocket 讓網頁動態呈現資料不是那麼難的一件事。
但也許是我還碰得不夠深xD
感覺這部分可以有很多的應用,
像我第一個想法就是可以透過這個來做個聊天室,
如果正在讀的你不知道他能幹嘛,
不仿想想看如何用利用這個實作一個專案,
讓你不僅能更理解還能獲得成就感。
也許你就能...
升職加薪
當上總經理
出任ceo
迎娶白富美
走上人生巔峰
先來簡單講講,為何需要?
因為是秘密所以不想讓人知道。
而在如何做到這點?
你不要說不就沒事了 (X)
在生活中我們有很多一定要接受的事,
例如:
我沒辦法控制我得體重啊
我隔壁同事交不到女朋友啊!
我隔壁同事交不到女朋友啊!
我隔壁同事交不到女朋友啊!
一定要藉由網路傳遞得一些敏感訊息 ( token
或 密碼
之類的 )
...
那我們從那麼多的生活例子拿最後一個來說,我隔壁同事交不到女朋友已經不是秘密了 (硬要嘴)
就是當我們藉由網路上,
必須要透過帳號密碼來註冊某網站的會員
又或是藉由登入透過 token
,來授權某些行為時,
那不管是 密碼
還是 token
都可以存取我們一些個人資料,
我相信你不會想要被做壞壞的事吧!
那這時你就需要透過加密來把明文變成密文,
目的就是為了不讓他人能夠輕易的被取得或是猜到。
而加密技術百百種我們這邊就不多做解釋,
我剛好逛到一篇我覺得整理的非常詳細的有興趣可以看一下
[CH18]加密技術(Cryptography)
注意:
這篇是很簡單的使用而已,沒有太多專業的分析研究,請斟酌觀賞感謝~
以串接 BitoPro API 為例
申請 API Key
用 Node 串接
結果:
恭喜老爺賀喜夫人成功啦
今天文章到此結束
感謝大家
.
.
.
.
.
.
.
.
上面是受到這張圖的啟發 xDD
其實官方恰恰好有 Node 的範例
那我們為了不跑題這篇主要來關注的是如何透過 crypto
模組來加密獲取 API
所需要的 headers
headers: {
'X-BITOPRO-APIKEY': apiKey,
'X-BITOPRO-PAYLOAD': payload, // For the authenticated APIs using DELETE method, you don't need the payload field.
'X-BITOPRO-SIGNATURE': signature
},
- apiKey - 就是直接拿官方的使用就可以了
- payload - 將 body JSON 轉成 base64 就可以囉
const payload = Buffer.from(JSON.stringify(body)).toString('base64')
- signature - 將 api secret 和 payload 加密成 16進制的 SHA-384 的 Hash
const signature = const crypto = require('crypto')
crypto
.createHmac('sha384', apiSecret)
.update(payload)
.digest('hex')
使用方式看起來是不是很簡單!
接著你就可以把這些塞到 headers ,來使用你的 API 囉
(這邊以拿 User Balance 為例)
完整程式碼參照官方詳細使用您可以自己調整我只是懶惰
const request = require('request')
const crypto = require('crypto')
const apiKey = '[Your API key here]'
const apiSecret = '[Your API secret here]'
const baseUrl = 'https://api.bitopro.com/v2'
const url = '/accounts/balance'
const nonce = Date.now()
const completeURL = baseUrl + url
// This is the default body for the authenticated APIs using GET method
const body = { identity: '[Your account (email)]', nonce }
const payload = Buffer.from(JSON.stringify(body)).toString('base64')
const signature = crypto
.createHmac('sha384', apiSecret)
.update(payload)
.digest('hex')
const options = {
url: completeURL,
headers: {
'X-BITOPRO-APIKEY': apiKey,
'X-BITOPRO-PAYLOAD': payload, // For the authenticated APIs using DELETE method, you don't need the payload field.
'X-BITOPRO-SIGNATURE': signature
},
body: JSON.stringify(body) // For the authenticated APIs using GET method, you don't need the body field.
}
// GET
return request.get(
options,
function(error, response, body) {
console.log('response:', JSON.stringify(body, 0, 2))
}
)
參考資料
且戰且走HTML5(3) 使用Socket.io
使用 Node.js 與 Socket.IO 建立即時性(Realtime)網頁應用程式 App
最近工作為了寫測試想要敵不動我不動,敵欲動我先跑。
想要藉由抓 Google Sheet 資料來檢查目前系統參數是否正常,
但是又不想要每次如果系統一有改動我都還要...
開Ticket -> 改 Code -> Code Review -> Merge 。
那如果使用 Google Sheet,我就只要改上面的參數,旁邊紀錄一下就完事了~
頂多規定旁邊註記更動人,並且 Google 還幫你版控。
是不是方便很多!?
最初其實我本來就有串過 Google 官方提供的 Sheet API,不難...但是說實話什麼都要自己來滿累的,於是乎我去 npm 找了一下相關套件。
於是乎找到了這個
google-spreadsheet
先來看成果
範例資料
執行結果
目前個人使用體驗滿不錯的,今天也手把手教大家怎麼串。
基本上步驟只有三個~
- 申請憑證
- 開權限
- 串起來
申請一組憑證
- 先建立專案 (Google Developers Console連結)
- 第一次使用的可能會看到這個,同意就好(X
- 第一次使用的可能會看到這個,同意就好(X
- 點選左上方選取專案
- 建立一個專案
- 確認切換專案成功
- 啟用 Google Sheet API
- 點選 ENABLE APPS AND SERVICES
- 點選 ENABLE APPS AND SERVICES
- 找一下 google sheet
- 啟用
- 新增憑證
- 切換到憑證的頁面點選建立憑證的服務帳戶
- 切換到憑證的頁面點選建立憑證的服務帳戶
- 沒有特殊需求就可以像我一樣直接按繼續(X
- 這邊也是,沒有特殊需求就可以像我一樣直接按繼續(X
- 建立完成後應該會有多一個服務帳戶點擊那個服務帳戶
- 新增金鑰
- 選擇 JSON
- 他會自動幫你下載下來放進你專案的目錄裡
開啟 Google Sheet 權限
- 點選共用
- 輸入你剛剛申請的服務帳號信箱地址
串起來!
先來安裝一下剛剛講的套件,
老司機的你一定知道怎麼做。
npm install google-spreadsheet
直接開寫
// googleSheet.js
const { GoogleSpreadsheet } = require('google-spreadsheet');
/**
* @param {String} docID the document ID
* @param {String} sheetID the google sheet table ID
* @param {String} credentialsPath the credentials path defalt is './credentials.json'
*/
async function getData(docID, sheetID, credentialsPath = './credentials.json') {
const result = [];
const doc = new GoogleSpreadsheet(docID);
const creds = require(credentialsPath);
await doc.useServiceAccountAuth(creds);
await doc.loadInfo();
const sheet = doc.sheetsById[sheetID];
const rows = await sheet.getRows();
for (row of rows) {
result.push(row._rawData);
}
return result;
};
module.exports = {
getData,
};
Sheet 的 docID 和 sheetID 請參照以下網址規則。
https://docs.google.com/spreadsheets/d/<docID>/edit#gid=<sheetID>
而 ./credentials.json
就是你剛剛下載的憑證,記得路徑和名字。
執行它
// test.js
const { getData } = require('./googleSheet.js');
(async () => {
const resp = await getData('<docID>', '<sheetID>');
console.log(resp);
})();
此篇到此結束~
希望對你有幫助~
另外這官方也有滿詳細的文件可供參考,可點擊以下連結看一下,如果是英文真的看不懂也可以與我私下討論或底下留言。
google-spreadsheet
【這些年我似是非懂的 Javascript】
之後還有滿多語言和技術想研究與大家分享,
不會再侷限在 40 週這些規則了,我已經突破極限了。
我一定會讀完這本書
目前我可以說我已經算是讀完了,但是有些我覺得我不到非常能夠像以往畫虎爛的方式跟大家聊聊,等我有靈感時會與大家分享 xD
我要變瘦
沒有。我體重大概 +-5 kg 又瘦又胖的,
都是頂呱呱惹的禍。
心路歷程
從一開使打開書滿滿的黑人問號
到後來能夠理解書上要傳遞的是什麼,理解後又怕講了又不專業又不好笑,沒人會看,導致花很多時間在思考如何簡單的傳遞。
中間有一段時間可能是怠惰期吧(X),
讓我覺得產出的文章質量不是那麼的好,但是點閱率卻異常的高,讓我覺得是不是我搞錯了些什麼xD,
也有讀者在下面留言表示喜歡,甚至我以個人興趣寫的麥當勞報報系列文章,還有人特地肉搜我 Facebook 跑來跟我聊聊,真的是滿特別的經驗 xD,
看著文章一點一滴的累積,自己對於 Node.js
的掌握相比以前進步了許多,滿高興的,也推薦看文章的你不仿也一起下來寫寫看。
不要怕被罵,就怕沒人在乎你寫什麼。
寫錯被糾正只會是賺到。
曾經我也是一直看著大神們寫著文章分享自己的經驗,現在換我也一起貢獻我微薄的心力,雖然不一定能夠帶給你們真的技術,但是也許能夠讓你開心一下,原來寫程式可以那麼的歡樂 (X)。
後續
應該會以專案的形式類似麥當勞報報系列文章 或是最近發的 蛤!原來串接 Google Sheet API 那麼簡單? 這類型的文章去分享給大家。
而在下週開始會分享我讀 You don't know JS
系列書的心得和自身分享。
之後也許會分享
- SQL 語言學習
- Go 語言學習
- Ruby 語言學習 (目前公司主要在用的,看著看著也滿有興趣的)
需求
- 自動撈 591 的資料
- 自動推播 (Line Notify)
- 可設定查找頻率
Rent House
透過自動偵查讓 591 租屋網,
讓你租房不再慢人一步
自動推 Line Notify 讓你馬上知道第一手租屋消息
先看結果~
Install
npm install
Configuration
打開開發人員工具
找到 network
找到 XHR
去 591 租屋網設定你想要關注的設定
抓到我們要的資料
找出 Header 中的 URL
、Cookie
、X-CSRF-TOKEN
- URL
- Cookie
- X_CSRF_TOKEN
申請 Line Notify Token 連結
設定 .env
LINE_NOTIFY_TOKEN = <LINE_NOTIFY_TOKEN>
X_CSRF_TOKEN = <X_CSRF_TOKEN>
COOKIE = <COOKIE>
TARGET_URL = <TARGET_URL>
REQUEST_FREQUENCY = <REQUEST_FREQUENCY>
REQUEST_FREQUENCY 單位為毫秒,所以一秒就是
1000
,建議不要調太低,這邊就是就是你要撈的頻率。
Run
npm run start
or
node app.js
我自己放在 aws 的 ec2 並且是透過 forever 去 run 下去之後,再改 .env 的內容改成其他我可能也會想知道的其他條件並且再 run ,就可以用多條件同時運行。
最後!
Enjoy it !
以上是今天分享的內容,
如果你也有用什麼方式解決租房困擾,
也歡迎跟我分享,我現在急需 xDDD
感謝你的觀看,
我是 Robin
悲傷的蛞蝓~
paypal.me/twcctz50
http://blog.sina.com.tw/window/feed.php?ver=rss&type=entry&blog_id=48997
https://paypal.me/twcctz50?country.x=TW&locale.x=zh_TW
使用 PayPal.Me 連結付款給我: https://paypal.me/twcctz50?country.x=TW&locale.x=zh_TW
健康是最好的禮物蛋黃油https://www.facebook.com/eggsoil
在生寶妹之前,寶妹媽,就食用蛋黃油調養車禍後造成的心律不整等後遺症狀將近兩年,配合復健、整復治療和游泳運動等,逐漸恢復正常的心律。
直到懷寶妹後至今,感謝蛋黃油讓寶妹媽能恢復健康,給這意外來的祝福一個健康的生長環境。寶妹雖是家中最小的孩子,但也是最健康、幸福的孩子!惟有她是媽媽有豐富的母乳可供親餵。
至今,恭喜寶妹已經滿兩歲了!每天還是喜歡找媽媽喝餒餒睡覺,出生至今,身體健康,沒有感冒和生病的紀錄。祝福寶妹,能一直健康、快樂的成長、學習,成為眾人的祝福!
代工生產製造需一千斤
需用者請提早預約安排
每天補充蛋黃油可降低罹癌風險?!是的!!
昨晚,十多年老案主的弟弟周大哥說,二哥鼻咽癌治療恢復後的後遺症,又發作了!需配合醫師開的抗生素和補充細胞再生的營養素,又訂了10瓶50ml,補充瓶。
據了解個案案主,罹癌前的工作是從事印刷業。坦白說,有點醫學常識的人多半知道化學油墨對身體的傷害為何?
本科所學為設計的我,也曾在相關產業待過十幾年的時間,等到研究所和醫院合作完成論文後,才知道自己的工作:設計和教職,其實,都隱含著很高的罹癌風險!
我們都曾因此賠上過健康,但慶幸自己和案主們,都是有福之人!
感謝蛋黃油豐富的卵磷脂營養成份,除了維生素C之外,幾乎涵蓋了所有!在103學年間,我曾因超鐘點一週上課時數33小時,忙碌於家庭和工作間,哪時老二剛出生半年,做好月子後,馬上就恢復忙碌的教職工作,不出一個月,就為卵巢炎、併發盆腔炎,抗生素吃了半年,還是不見恢復的病痛所苦!
期間,幾乎每兩週跑醫院兩三趟,署基的婦科主任醫師,也建議我要跟校長請辭,調養身體為重!感謝校長體諒,讓我減課到16堂,撐到合約到期,沒有違約金的困擾,離職後,就回家照顧老二和調養自己的身體。
卵巢炎超痛的!併發盆腔炎更痛!從早痛到晚!醫師說,抗生素吃半年了,就不能再吃了!感謝醫師沒讓我繼續吃下去!
而是勸我調整工作、生活和飲食!於是,我又開始大量食用蛋黃油和自己料理三餐,就這樣子,吃了半年,某天,突然驚覺:卵巢不痛了耶!
感謝神!在恢復前的每一時刻,我被疼痛纏身,影響情緒和睡眠,每天都不知要多久,才會好?!只是一直做該做的事,好好吃飯、休息,調養身心!直到恢復時,才發覺:哇!幸好,半年就好了!
這是蛋黃油在我近十年的健康危機中,第二次救了我!感謝神創造各樣美好食物,保守、祝福我們能有機會恢復祂起初創造我們的美好!
感謝近十幾年來,因著每一次的健康危機,即時食用蛋黃油排毒、調養身體、恢復正常的心律,讓我能在身體得滋養後再懷孕生下健康的孩子們,也讓我們的生命得到延續。
為此,我才投入後來的時間、金錢、精神在服務和我一樣有需要的案主們身上。感謝大家一起陪我攜手走過已過的十幾年。祝福每個人都能有機會認知到維護健康的身體,其奧秘就在於養成正確的日常飲食習慣!
筆者:蛋黃油男
電話:02-24978169
手機:0989-422508
為資深個案自主健康管理設計師,
目前服務於品蔚養生設計事務所。
https://liker.social/invite/pRwYGraC
https://www.facebook.com/eggsoil
https://www.facebook.com/nectw721
https://www.instagram.com/0989422508mdc
https://www.pinterest.com/twcctz500
https://www.linkedin.com/in/twcctz500
https://www.twitter.com/twcctz500
https://currents.google.com/117454133619976175486
https://www.youtube.com/shorts/o8Jaud7px4w
https://www.youtube.com/channel/UC5E4_uNgGl_omnUVfL7fUyA
https://fitness-center-727.business.site/
https://g.page/r/CUt-sGCWmpP2EBM/review
https://matters.news/@twcctz500
https://pastebin.com/NgugYMZP 來自 @pastebin
https://hardbin.com/ipfs/QmSmgLoGrr4dcJ4EFmszDXJAGbXW2oZU5nNgksdjG7bb3P/
https://zerobin.net/?b4e43b1d09b3dcbe#C4rH4SwtqM4v4BsehLtwVf2DSEhto1JRx8PzKIsmrYo=
https://zerobin.net/?4fdb0ea46d78f4a6#z7E38he/vzywjsvzxBwTBm5dvCaKc5Pei8nDkUflgOs=
https://privatebin.net/?8abe23c5b0253b66#7Vq3VyzThWp2ga7tvVBQ4om6aiYdqvma4bpfWYNKMCgG
https://medium.com/@twcctz50
https://about.me/twcctz500
paypal.me/twcctz50
<?xml version="1.0" encoding="UTF-8" ?> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"> <channel> <title>ASP.NET 伺服器控制項開發 :: 2008 iT 邦幫忙鐵人賽</title> <link>https://ithelp.ithome.com.tw/users/20007956/ironman</link> <description><![CDATA[ASP.NET 是目前相當熱門的網站開發程式語言,市面上也有一大卡車的書籍在介紹 ASP.NET,不過卻非常少介紹「ASP.NET 伺服器控制項」方面的書籍。在此將透過一系列...]]></description> <atom:link href="https://ithelp.ithome.com.tw/users/20007956/ironman" rel="self"></atom:link> <language>zh-TW</language> <lastBuildDate>Mon, 06 Jun 2022 20:19:06 +0800</lastBuildDate> <item> <title>[ASP.NET 控制項實作 Day29] 解決 DropDownList 成員 Value 值相同產生的問題(續)</title> <link>https://ithelp.ithome.com.tw/articles/10013458?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013458?sc=rss.iron</guid> <description><![CDATA[<p>接續上一文<br /> 接下來還要覆寫 LoadPostData 方法,取得 __EVENTARGUMENT 這個 HiddenField 的值,並判斷與原 SelectedIndex 屬性值是...]]></description> <content:encoded><![CDATA[<p>接續上一文<br /> 接下來還要覆寫 LoadPostData 方法,取得 __EVENTARGUMENT 這個 HiddenField 的值,並判斷與原 SelectedIndex 屬性值是否不同,不同的話傳回 True,使其產生 SelectedIndexChanged 事件。</p> <pre><code> Protected Overrides Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As NameValueCollection) As Boolean Dim values As String() Dim iSelectedIndex As Integer Me.EnsureDataBound() values = postCollection.GetValues(postDataKey) If (Not values Is Nothing) Then iSelectedIndex = CInt(Me.Page.Request.Form("__EVENTARGUMENT")) If (Me.SelectedIndex <> iSelectedIndex) Then MyBase.SetPostDataSelection(iSelectedIndex) Return True End If End If Return False End Function </code></pre> <p><strong>四、測試程式</strong><br /> 在 TBDropDownList 的 SelectedIndexChanged 事件撰寫如下測試程式碼。</p> <pre><code> Protected Sub DropDownList2_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DropDownList2.SelectedIndexChanged Dim sText As String sText = String.Format("TBDropDownList: Index={0} Value={1}", DropDownList2.SelectedIndex, DropDownList2.SelectedValue) Me.Response.Write(sText) End Sub </code></pre> <p>執行程式,在 TBDropDownList 選取 "王五" 這個選項時,會正常顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay29DropDownListValue_112DF/image_thumb_10.png" alt="" /></p> <p>接下選取 Value 值相同的 "陳六" 這個選項,也會正常引發 SelectedIndexChanged ,並顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay29DropDownListValue_112DF/image_thumb_11.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/30/5830.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/30/5830.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-30 21:23:12</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day29] 解決 DropDownList 成員 Value 值相同產生的問題</title> <link>https://ithelp.ithome.com.tw/articles/10013457?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013457?sc=rss.iron</guid> <description><![CDATA[<p>DropDownList 控制頁的成員清單中,若有 ListItem 的 Value 值是相同的情形時,會造成 DropDownList 無法取得正確的 SelectedIndex 屬性值、且無...]]></description> <content:encoded><![CDATA[<p>DropDownList 控制頁的成員清單中,若有 ListItem 的 Value 值是相同的情形時,會造成 DropDownList 無法取得正確的 SelectedIndex 屬性值、且無法正確引發 SelectedIndexChanged 事件的問題;今天剛好在網路上看到有人在詢問此問題,所以本文將說明這個問題的源由,並修改 DropDownList 控制項來解決這個問題。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008103021737321.rar" target="_blank">ASP.NET Server Control - Day29.rar</a></p> <p><strong>一、DropDownList 的成員 Value 值相同產生的問題</strong><br /> 我們先寫個測試程式來描述問題,在頁面上放置一個 DropDownList 控制項,設定 AutoPostBack=True,並加入四個 ListItem,其中 "王五" 及 "陳六" 二個 ListItem 的 Value 值相同。</p> <pre><code> <asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True"> <asp:ListItem Value="0">張三</asp:ListItem> <asp:ListItem Value="1">李四</asp:ListItem> <asp:ListItem Value="2">王五</asp:ListItem> <asp:ListItem Value="2">陳六</asp:ListItem> </asp:DropDownList> </code></pre> <p>在 DropDownList 的 SelectedIndexChanged 事件,輸出 DropDownList 的 SelectedIndex 及 SelectedValue 屬性值。</p> <pre><code> Protected Sub DropDownList1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DropDownList1.SelectedIndexChanged Dim sText As String sText = String.Format("DropDownList: Index={0} Value={1}", DropDownList1.SelectedIndex, DropDownList1.SelectedValue) Me.Response.Write(sText) End Sub </code></pre> <p>執行程式,在 DropDownList 選取 "李四" 這個選項時,會正常顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay29DropDownListValue_112DF/image_thumb.png" alt="" /></p> <p>接下來選取 "陳六" 這個選項時,竟然發生奇怪的現象,DorpDownList 竟然顯示相同 Value 值的 "王五" 這個成員的 SelectedIndex 及 SelectedValue 屬性值。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay29DropDownListValue_112DF/image_thumb_6.png" alt="" /><br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay29DropDownListValue_112DF/image_thumb_7.png" alt="" /></p> <p><strong>二、問題發生的原因</strong><br /> 我們先看一下 DropDownList 輸出到用戶端的 HTML 原始碼。</p> <pre><code><select name="DropDownList1" onchange="javascript:setTimeout('__doPostBack(\'DropDownList1\',\'\')', 0)" id="DropDownList1"> <option selected="selected" value="0">張三</option> <option value="1">李四</option> <option value="2">王五</option> <option value="2">陳六</option> </select> </code></pre> <p>DropDownList 是呼叫 __doPostBack 函式,只傳入 eventTarget參數 (對應到 __EVENTTARGET 這個 HiddenField) 為 DropDownList 的 ClientID;當 PostBack 回伺服端時,在 DropDownList 的 LoadPostData 方法中,會取得用戶端選取的 SelectedValue 值,並去尋找對應的成員的 SelectedIndex 值。可是問題來了,因為 "王五" 與 "陳六" 的 Value 是相同的值,當在尋找符合 Value 值的成員時,前面的選項 "王五" 會先符合條件而傳回該 Index 值,所以先造成取得錯誤的 SelectedIndex 。</p> <pre><code>Protected Overridable Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As NameValueCollection) As Boolean Dim values As String() = postCollection.GetValues(postDataKey) Me.EnsureDataBound If (Not values Is Nothing) Then MyBase.ValidateEvent(postDataKey, values(0)) Dim selectedIndex As Integer = Me.Items.FindByValueInternal(values(0), False) If (Me.SelectedIndex <> selectedIndex) Then MyBase.SetPostDataSelection(selectedIndex) Return True End If End If Return False End Function </code></pre> <p><strong>三、修改 DropDownList 控制項來解決問題</strong><br /> 要解決這個問題最好的方式就是直接修改 DropDownList 控制項,自行處理前端呼叫 __doPostBack 的動作,將用戶端 DropDownList 選擇 SelectedIndex 一併傳回伺服端。所以我們繼承 DropDownList 命名為 TBDropDownList,覆寫 AddAttributesToRender 來自行輸出 PostBack 的用戶端指令碼,我們會用一個變數記錄 AutoPostBack 屬性,並強制將 AutoPostBack 屬性值設為 False,這是為了不要 MyBase 產生 PostBack 的指令碼;然後再自行輸出 AutoPostBack 用戶端指令碼,其中 __doPostBack 的 eventArgument 參數 (對應到 __EVENTARGUMENT 這個 HiddenField) 傳入 this.selectedIndex。</p> <pre><code> Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter) Dim bAutoPostBack As Boolean Dim sScript As String '記錄 AutoPostBack 值,並將 AutoPostBack 設為 False,不要讓 MyBase 產生 PostBack 的指令碼 bAutoPostBack = Me.AutoPostBack Me.AutoPostBack = False MyBase.AddAttributesToRender(writer) If bAutoPostBack Then MyBase.Attributes.Remove("onchange") sScript = String.Format("__doPostBack('{0}',{1})", Me.ClientID, "this.selectedIndex") writer.AddAttribute(HtmlTextWriterAttribute.Onchange, sScript) Me.AutoPostBack = True End If End Sub </code></pre> <p>在頁面上放置一個 TBDropDownList 控制項,設定與上述案例相同的成員清單。</p> <pre><code> <bee:TBDropDownList ID="DropDownList2" runat="server" AutoPostBack="True"> <asp:ListItem Value="0">張三</asp:ListItem> <asp:ListItem Value="1">李四</asp:ListItem> <asp:ListItem Value="2">王五</asp:ListItem> <asp:ListItem Value="2">陳六</asp:ListItem> </bee:TBDropDownList> </code></pre> <p>執行程式查看 TBDropDownList 控制項的 HTML 原始碼,呼叫 __doPostBack 函式的參數已經被修改,eventArgument 參數會傳入該控制項的 selectedIndex。</p> <pre><code><select name="DropDownList2" id="DropDownList2" onchange="__doPostBack('DropDownList2',this.selectedIndex)"> <option selected="selected" value="0">張三</option> <option value="1">李四</option> <option value="2">王五</option> <option value="2">陳六</option> </select> </code></pre> <p>[超過字數限制,下一篇接續本文]</p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-30 21:15:23</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day28] 圖形驗證碼控制項(續)</title> <link>https://ithelp.ithome.com.tw/articles/10013365?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013365?sc=rss.iron</guid> <description><![CDATA[<p>接續一上文<br /> <strong>二、實作圖形驗證碼控制項</strong><br /> 雖然我們可以使用 Image 控制項來呈現 ValidateCode.aspx 頁面產生的驗證碼圖...]]></description> <content:encoded><![CDATA[<p>接續一上文<br /> <strong>二、實作圖形驗證碼控制項</strong><br /> 雖然我們可以使用 Image 控制項來呈現 ValidateCode.aspx 頁面產生的驗證碼圖形,可是這樣只處理一半的動作,因為沒有處理「使用者輸入的驗證碼」是否與「圖形驗證碼」相符,所以我們將實作一個圖形驗證碼控制項,來處理掉所有相關動作。<br /> 即然上面的示範使用 Image 控制項來呈現驗證碼,所以圖形驗證碼控制項就繼承 Image 命名為 TBValidateCode。</p> <pre><code> < _ Description("圖形驗證碼控制項"), _ ToolboxData("<{0}:TBValidateCode runat=server></{0}:TBValidateCode>") _ > _ Public Class TBValidateCode Inherits System.Web.UI.WebControls.Image End </code></pre> <p>新增 ValidateCodeUrl 屬性,設定圖形驗證碼產生頁面的網址。</p> <pre><code> ''' <summary> ''' 圖形驗證碼產生頁面網址。 ''' </summary> < _ Description("圖形驗證碼產生頁面網址"), _ DefaultValue("") _ > _ Public Property ValidateCodeUrl() As String Get Return FValidateCodeUrl End Get Set(ByVal value As String) FValidateCodeUrl = value End Set End Property </code></pre> <p>覆寫 Render 方法,若未設定 ValidateCodeUrl 屬性,則預設為 ~/Page/ValidateCode.aspx 這個頁面。另外我們在圖形的 ondbclick 加上一段用戶端指令碼,其作用是讓用戶可以滑鼠二下來重新產生一個驗證碼圖形。</p> <pre><code> Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Dim sUrl As String Dim sScript As String sUrl = Me.ValidateCodeUrl If String.IsNullOrEmpty(sUrl) Then sUrl = "~/Page/ValidateCode.aspx" End If If Me.BorderWidth = Unit.Empty Then Me.BorderWidth = Unit.Pixel(1) End If If Me.AlternateText = String.Empty Then Me.AlternateText = "圖形驗證碼" End If Me.ToolTip = "滑鼠點二下可重新產生驗證碼" Me.ImageUrl = sUrl If Not Me.DesignMode Then sScript = String.Format("this.src='{0}?flag='+Math.random();", Me.Page.ResolveClientUrl(sUrl)) Me.Attributes("ondblclick") = sScript End If Me.Style(HtmlTextWriterStyle.Cursor) = "pointer" MyBase.Render(writer) End Sub </code></pre> <p>另外新增一個 ValidateCode 方法,用來檢查輸入驗證碼是否正確。還記得我們在產生驗證碼圖形時,同時把該驗證碼的值寫入 Session("_ValidateCode") 中吧,所以這個方法只是把用戶輸入的值與 Seesion 中的值做比對。</p> <pre><code> ''' <summary> ''' 檢查輸入驗證碼是否正確。 ''' </summary> ''' <param name="Code">輸入驗證碼。</param> ''' <returns>驗證成功傳回 True,反之傳回 False。</returns> Public Function ValidateCode(ByVal Code As String) As Boolean If Me.Page.Session(SessionKey) Is Nothing Then Return False If SameText(CCStr(Me.Page.Session(SessionKey)), Code) Then Return True Else Return False End If End Function </code></pre> <p><strong>三、測試程式</strong><br /> 在頁面放置一個 TBValidateCode 控制項,另外加一個文字框及按鈕,供使用者輸入驗證碼後按下「確定」鈕後到伺服端做輸入值比對的動作。</p> <pre><code> <bee:TBValidateCode ID="TBValidateCode1" runat="server" /> <bee:TBTextBox ID="txtCode" runat="server"></bee:TBTextBox> <bee:TBButton ID="TBButton1" runat="server" Text="確定" /> </code></pre> <p>在「確定」鈕的 Click 事件中,我們使用 TBValidateCode 控制項的 ValidateCode 方法判斷驗證碼輸入的正確性。</p> <pre><code> Protected Sub TBButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles TBButton1.Click If TBValidateCode1.ValidateCode(txtCode.Text) Then Me.Response.Write("驗證碼輸入正確") Else Me.Response.Write("驗證碼輸入錯誤!") End If End Sub </code></pre> <p>執行程式,頁面就會隨機產生一個驗證碼圖形。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay28_1B7F/image_thumb.png" alt="" /></p> <p>輸入正確的值按「確定」鈕,就會顯示「驗證碼輸入正確」的訊息。因為我們在同一頁面測試的關係,你會發現 PostBack 後驗證碼圖形又會重新產生,一般正常的做法是驗證正確後就導向另一個頁面。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay28_1B7F/image_thumb_1.png" alt="" /></p> <p>當我們輸入錯誤的值,就會顯示「驗證碼輸入錯誤!」的訊息。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay28_1B7F/image_thumb_2.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/29/5818.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/29/5818.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-29 20:34:22</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day28] 圖形驗證碼控制項</title> <link>https://ithelp.ithome.com.tw/articles/10013361?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013361?sc=rss.iron</guid> <description><![CDATA[<p>在網頁上常把圖形驗證碼應用在登入或貼文的頁面中,因為圖形驗證碼具有機器不易識別的特性,可以防止機器人程式惡意的存取網頁。在本文中將實作一個圖形驗證碼的伺服器控制項,透過簡單的屬性設定就可以輕易地...]]></description> <content:encoded><![CDATA[<p>在網頁上常把圖形驗證碼應用在登入或貼文的頁面中,因為圖形驗證碼具有機器不易識別的特性,可以防止機器人程式惡意的存取網頁。在本文中將實作一個圖形驗證碼的伺服器控制項,透過簡單的屬性設定就可以輕易地在網頁上套用圖形驗證碼。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/20081029202355938.rar" target="_blank">ASP.NET Server Control - Day28.rar</a></p> <p><strong>一、產生圖形驗證碼</strong><br /> 我們先準備一個產生圖形驗證碼的頁面 (ValidateCode.aspx),這個頁面主要是繪製驗證碼圖形,並將其寫入記憶體資料流,最後使用 Response.BinaryWrite 將圖形輸出傳遞到用戶端。當我們輸出此驗證碼圖形的同時,會使用 Session("_ValidateCode") 來記錄驗證碼的值,以便後續與使用者輸入驗證碼做比對之用。</p> <pre><code>Partial Class ValidateCode Inherits System.Web.UI.Page ''' <summary> ''' 產生圖形驗證碼。 ''' </summary> Public Function CreateValidateCodeImage(ByRef Code As String, ByVal CodeLength As Integer, _ ByVal Width As Integer, ByVal Height As Integer, ByVal FontSize As Integer) As Bitmap Dim sCode As String = String.Empty '顏色列表,用於驗證碼、噪線、噪點 Dim oColors As Color() = { _ Drawing.Color.Black, Drawing.Color.Red, Drawing.Color.Blue, Drawing.Color.Green, _ Drawing.Color.Orange, Drawing.Color.Brown, Drawing.Color.Brown, Drawing.Color.DarkBlue} '字體列表,用於驗證碼 Dim oFontNames As String() = {"Times New Roman", "MS Mincho", "Book Antiqua", _ "Gungsuh", "PMingLiU", "Impact"} '驗證碼的字元集,去掉了一些容易混淆的字元 Dim oCharacter As Char() = {"2"c, "3"c, "4"c, "5"c, "6"c, "8"c, _ "9"c, "A"c, "B"c, "C"c, "D"c, "E"c, _ "F"c, "G"c, "H"c, "J"c, "K"c, "L"c, _ "M"c, "N"c, "P"c, "R"c, "S"c, "T"c, _ "W"c, "X"c, "Y"c} Dim oRnd As New Random() Dim oBmp As Bitmap Dim oGraphics As Graphics Dim N1 As Integer Dim oPoint1 As Drawing.Point Dim oPoint2 As Drawing.Point Dim sFontName As String Dim oFont As Font Dim oColor As Color '生成驗證碼字串 For N1 = 0 To CodeLength - 1 sCode += oCharacter(oRnd.Next(oCharacter.Length)) Next oBmp = New Bitmap(Width, Height) oGraphics = Graphics.FromImage(oBmp) oGraphics.Clear(Drawing.Color.White) Try For N1 = 0 To 4 '畫噪線 oPoint1.X = oRnd.Next(Width) oPoint1.Y = oRnd.Next(Height) oPoint2.X = oRnd.Next(Width) oPoint2.Y = oRnd.Next(Height) oColor = oColors(oRnd.Next(oColors.Length)) oGraphics.DrawLine(New Pen(oColor), oPoint1, oPoint2) Next For N1 = 0 To sCode.Length - 1 '畫驗證碼字串 sFontName = oFontNames(oRnd.Next(oFontNames.Length)) oFont = New Font(sFontName, FontSize, FontStyle.Italic) oColor = oColors(oRnd.Next(oColors.Length)) oGraphics.DrawString(sCode(N1).ToString(), oFont, New SolidBrush(oColor), CSng(N1) * FontSize + 10, CSng(8)) Next For i As Integer = 0 To 30 '畫噪點 Dim x As Integer = oRnd.Next(oBmp.Width) Dim y As Integer = oRnd.Next(oBmp.Height) Dim clr As Color = oColors(oRnd.Next(oColors.Length)) oBmp.SetPixel(x, y, clr) Next Code = sCode Return oBmp Finally oGraphics.Dispose() End Try End Function ''' <summary> ''' 產生圖形驗證碼。 ''' </summary> Public Sub CreateValidateCodeImage(ByRef MemoryStream As MemoryStream, _ ByRef Code As String, ByVal CodeLength As Integer, _ ByVal Width As Integer, ByVal Height As Integer, ByVal FontSize As Integer) Dim oBmp As Bitmap oBmp = CreateValidateCodeImage(Code, CodeLength, Width, Height, FontSize) Try oBmp.Save(MemoryStream, ImageFormat.Png) Finally oBmp.Dispose() End Try End Sub Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Dim sCode As String = String.Empty '清除該頁輸出緩存,設置該頁無緩存 Response.Buffer = True Response.ExpiresAbsolute = System.DateTime.Now.AddMilliseconds(0) Response.Expires = 0 Response.CacheControl = "no-cache" Response.AppendHeader("Pragma", "No-Cache") '將驗證碼圖片寫入記憶體流,並將其以 "image/Png" 格式輸出 Dim oStream As New MemoryStream() Try CreateValidateCodeImage(oStream, sCode, 4, 100, 40, 18) Me.Session("_ValidateCode") = sCode Response.ClearContent() Response.ContentType = "image/Png" Response.BinaryWrite(oStream.ToArray()) Finally '釋放資源 oStream.Dispose() End Try End Sub End Class </code></pre> <p>我們將此頁面置於 ~/Page/ValidateCode.aspx,當要使用此頁面的圖形驗證碼,只需要在使用 Image 控制項,設定 ImageUrl 為此頁面即可。</p> <pre><code><asp:Image ID="imgValidateCode" runat="server" ImageUrl="~/Page/ValidateCode.aspx" /> </code></pre> <p>[超過字數限制,下一篇接續本文]</p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-29 20:31:45</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day27] 控制項依 FormView CurrentMode 自行設定狀態(續2)</title> <link>https://ithelp.ithome.com.tw/articles/10013241?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013241?sc=rss.iron</guid> <description><![CDATA[<p>接續上一文<br /> 接下來設定做為新增、編輯使用的 TBFormView 控制項,我們只使用 EditItemTemplate 來同時處理新增、刪除,所以 EditItemTemplate ...]]></description> <content:encoded><![CDATA[<p>接續上一文<br /> 接下來設定做為新增、編輯使用的 TBFormView 控制項,我們只使用 EditItemTemplate 來同時處理新增、刪除,所以 EditItemTemplate 需要同時具有「新增」、「更新」、「取消」三個按鈕。其中 ProductID 為主索引欄位,所以我們使用 TBTextBox 來繫結 ProductID 欄位,設定 FormViewModeState.InsertMode="Enable" 使控制項在新增模式時為可編輯,設定 FormViewModeState.EditMode="Disable" 使控制項在修改模式是唯讀的。</p> <pre><code> <bee:TBFormView ID="TBFormView1" runat="server" DataKeyNames="ProductID" DataSourceID="SqlDataSource1" DefaultMode="Edit" SingleTemplate="EditItemTemplate" BackColor="White" BorderColor="#CCCCCC" BorderStyle="None" BorderWidth="1px" CellPadding="3" GridLines="Both" Visible="False"> <FooterStyle BackColor="White" ForeColor="#000066" /> <RowStyle ForeColor="#000066" /> <EditItemTemplate> ProductID: <bee:TBTextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductID") %>'> <FormViewModeState EditMode="Disable" InsertMode="Enable"> </FormViewModeState> </bee:TBTextBox> '省略 <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="True" CommandName="Insert" Text="新增" /> <asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update" Text="更新" /> <asp:LinkButton ID="UpdateCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="取消" /> </EditItemTemplate> </bee:TBFormView> </code></pre> <p><strong>2. 測試新增模式</strong><br /> 接下來執行程式,一開始為瀏覽模式,以 TBGridView 來呈現資料。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay27FormViewCurrentMode_E5A/image_thumb_2.png" alt="" /></p> <p>按下 Header 的「新增」鈕,就會隱藏 TBGridView,而切換到 TBFormView 的新增模式。其中繫結 ProductID 欄位的 TBTextBox 為可編輯模式,而下方的按鈕只會顯示「新增」及「取消」鈕。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay27FormViewCurrentMode_E5A/image_thumb_3.png" alt="" /></p> <p>在新增模式輸入完畢後,按下「新增」鈕,資料錄就會被寫入資料庫。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay27FormViewCurrentMode_E5A/image_thumb_4.png" alt="" /></p> <p><strong>3. 測試修改模式</strong><br /> 接下來測試修改模式,按下「編輯」鈕,就會隱藏 TBGridView,而切換到 TBFormView 的修改模式。其中繫結 ProductID 欄位的 TBTextBox 為唯讀模式,而下方的按鈕只會顯示「更新」及「取消」鈕。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay27FormViewCurrentMode_E5A/image_thumb_5.png" alt="" /></p> <p>在修改模式輸入完畢後,按下「更新」鈕,資料錄就會被寫入資料庫。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay27FormViewCurrentMode_E5A/image_thumb_6.png" alt="" /></p> <p><strong>4. 頁面程式碼</strong><br /> 示範了上述的操作後,接下來我們回頭看一下頁面的程式碼。你沒看錯,筆者也沒貼錯,真的是一行程式碼都沒有,因為所有相關動作都由控制項處理掉了。</p> <pre><code>Partial Class Day27 Inherits System.Web.UI.Page End Class </code></pre> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-28 13:57:23</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day27] 控制項依 FormView CurrentMode 自行設定狀態(續1)</title> <link>https://ithelp.ithome.com.tw/articles/10013239?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013239?sc=rss.iron</guid> <description><![CDATA[<p>接續上一文<br /> <strong>二、讓 TextBox 控制項可自行維護狀態</strong><br /> 接下來擴展 TextBox 控制項,繼承 TextBox 命名為 TBText...]]></description> <content:encoded><![CDATA[<p>接續上一文<br /> <strong>二、讓 TextBox 控制項可自行維護狀態</strong><br /> 接下來擴展 TextBox 控制項,繼承 TextBox 命名為 TBTextBox。新增 FormViewModeState 屬性 (TBFormViewModeState 型別),依 FormView Mode 來設定控制項狀。並覆寫 PreRender 方法,在此方法中呼叫 DoFormViewModeStatus 私有方法,依 FormView 的模式來處理控制項狀態。</p> <pre><code> ''' <summary> ''' 文字框控制項。 ''' </summary> < _ Description("文字框控制項。"), _ ToolboxData("<{0}:TBTextBox runat=server></{0}:TBTextBox>") _ > _ Public Class TBTextBox Inherits TextBox Private FFormViewModeState As TBFormViewModeState ''' <summary> ''' 依 FormViewMode 來設定控制項狀態。 ''' </summary> < _ Category(WebCommon.Category.Behavior), _ NotifyParentProperty(True), _ DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _ PersistenceMode(PersistenceMode.InnerProperty), _ DefaultValue("") _ > _ Public ReadOnly Property FormViewModeState() As TBFormViewModeState Get If FFormViewModeState Is Nothing Then FFormViewModeState = New TBFormViewModeState End If Return FFormViewModeState End Get End Property ''' <summary> ''' 處理控制項狀態。 ''' </summary> Private Sub DoControlStatus(ByVal ControlStatus As EControlState) Select Case ControlStatus Case EControlState.Enable Me.Enabled = True Case EControlState.Disable Me.Enabled = False Case EControlState.Hide Me.Visible = False End Select End Sub ''' <summary> ''' 依 FormView 的模式來處理控制項狀態。 ''' </summary> Private Sub DoFormViewModeStatus() Dim oFormView As FormView '若控制項置於 FormView 中,則依 FormView 的模式來處理控制項狀態 If TypeOf Me.BindingContainer Is FormView Then oFormView = DirectCast(Me.BindingContainer, FormView) Select Case oFormView.CurrentMode Case FormViewMode.Insert DoControlStatus(Me.FormViewModeState.InsertMode) Case FormViewMode.Edit DoControlStatus(Me.FormViewModeState.EditMode) Case FormViewMode.ReadOnly DoControlStatus(Me.FormViewModeState.BrowseMode) End Select End If End Sub ''' <summary> ''' 覆寫。引發 PreRender 事件。 ''' </summary> Protected Overrides Sub OnPreRender(ByVal e As EventArgs) MyBase.OnPreRender(e) '依 FormView 的模式來處理控制項狀態 DoFormViewModeStatus() End Sub End Class </code></pre> <p><strong>三、測試程式</strong><br /> <strong>1. 設定控制項相關屬性</strong><br /> 我們使用 Northwnd 資料庫的 Products資料表為例,以 GridView+FormView 示範資料「新增/修改/刪除」的操作。在頁面拖曳 SqlDataSource 控制項後,在頁面上的使用 TBGridView 來顯示瀏覽資料。TBGridView 的 FormViewID 設為關連的 TBFormVIew 控制項;另外有使用到 TBCommandField,設定 ShowHeaderNewButton=True,讓命令列具有「新增」鈕。</p> <pre><code> <bee:TBGridView ID="TBGridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="SqlDataSource1" FormViewID="TBFormView1"> <Columns> <bee:TBCommandField ShowDeleteButton="True" ShowEditButton="True" ShowHeaderNewButton="True" > </bee:TBCommandField> '省略 </Columns> </bee:TBGridView> </code></pre> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-28 13:53:32</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day27] 控制項依 FormView CurrentMode 自行設定狀態</title> <link>https://ithelp.ithome.com.tw/articles/10013233?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013233?sc=rss.iron</guid> <description><![CDATA[<p>在 <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/05/12/3936.aspx" target="_blank">GridV...]]></description> <content:encoded><![CDATA[<p>在 <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/05/12/3936.aspx" target="_blank">GridView+FormView 示範資料 新增/修改/刪除(進階篇:伺服器控制項)</a> 一文中,示範了擴展 GridView 及 FormView 控制項,讓 GridView 可以透過屬性與 FormView 做關連來處理資料的「新增/修改/刪除」的動作。因為在該案例中,只使用 FormView 的 EditTemplate 同時處理「新增」及「修改」的動作,所以還需要自行撰寫部分程式碼去判斷控制項在新增或修改的啟用狀態,例如編號欄位在新增時為啟用,修改時就不啟用。在該文最後也提及其實有辨法讓這個案例達到零程式碼的目標,那就是讓控制項 (如 TextBox) 自行判斷所在的 FormView 的 CurrentMode,自行決定本身是否要「啟用/不啟用」、「顯示/隱藏」等狀態。本文以 TextBox 為例,說明如何修改 TextBox 讓它可以達到上述的需求。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/20081028133154164.rar" target="_blank">ASP.NET Server Control - Day27.rar</a><br /> Northwnd 資料庫下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008102364857537.rar" target="_blank">NORTHWND.rar</a></p> <p><strong>一、TBFormViewModeState 類別</strong><br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay27FormViewCurrentMode_E5A/image_thumb.png" alt="" /><br /> 我們先定義 EControlState (控制項狀態) 列舉,描述控制項在特定模式的狀態為何。</p> <pre><code> ''' <summary> ''' 控制項狀態列舉。 ''' </summary> Public Enum EControlState ''' <summary> ''' 不設定。 ''' </summary> NotSet = 0 ''' <summary> ''' 啟用。 ''' </summary> Enable = 1 ''' <summary> ''' 不啟用。 ''' </summary> Disable = 2 ''' <summary> ''' 隱藏。 ''' </summary> Hide = 3 End Enum </code></pre> <p>再來定義 TBFormViewModeState 類別,用來設定控制項在各種 FormView 模式 (瀏覽、新增、修改) 中的控制項狀態。</p> <pre><code>''' <summary> ''' 依 FormViewMode 來設定控制項狀態。 ''' </summary> < _ Serializable(), _ TypeConverter(GetType(ExpandableObjectConverter)) _ > _ Public Class TBFormViewModeState Private FInsertMode As EControlState = EControlState.NotSet Private FEditMode As EControlState = EControlState.NotSet Private FBrowseMode As EControlState = EControlState.NotSet ''' <summary> ''' 在新增模式(FormViewMode=Insert)的控制項狀態。 ''' </summary> < _ NotifyParentProperty(True), _ DefaultValue(GetType(EControlState), "NotSet") _ > _ Public Property InsertMode() As EControlState Get Return FInsertMode End Get Set(ByVal value As EControlState) FInsertMode = value End Set End Property ''' <summary> ''' 在編輯模式(FormViewMode=Edit)的控制項狀態。 ''' </summary> < _ NotifyParentProperty(True), _ DefaultValue(GetType(EControlState), "NotSet") _ > _ Public Property EditMode() As EControlState Get Return FEditMode End Get Set(ByVal value As EControlState) FEditMode = value End Set End Property ''' <summary> ''' 在瀏覽模式(FormViewMode=ReadOnly)的控制項狀態。 ''' </summary> < _ NotifyParentProperty(True), _ DefaultValue(GetType(EControlState), "NotSet") _ > _ Public Property BrowseMode() As EControlState Get Return FBrowseMode End Get Set(ByVal value As EControlState) FBrowseMode = value End Set End Property End Class </code></pre> <p>定義為 TBFormViewModeState 型別的屬性是屬於複雜屬性,要套用 TypeConverter(GetType(ExpandableObjectConverter)),讓該屬性可在屬性視窗 (PropertyGrid) 擴展以便設定屬性值,如下圖所示。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay27FormViewCurrentMode_E5A/image_thumb_1.png" alt="" /></p> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-28 13:45:43</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day26] 讓你的 GridView 與眾不同</title> <link>https://ithelp.ithome.com.tw/articles/10013209?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013209?sc=rss.iron</guid> <description><![CDATA[<p>在網路上可以找到相當多擴展 GridView 控制項功能的文章,在筆者的部落格中也有多篇提及擴展 GridView、DataControlField、BoundFIeld 功能的相關文章,在本文...]]></description> <content:encoded><![CDATA[<p>在網路上可以找到相當多擴展 GridView 控制項功能的文章,在筆者的部落格中也有多篇提及擴展 GridView、DataControlField、BoundFIeld 功能的相關文章,在本文將這些關於擴展 GridView 控制項功能及欄位類別的相關文章做一整理簡介,若需要擴展 GridView 相關功能時可以做為參考。<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/05/22/4105.aspx" target="_blank"><strong>1. 擴展 GridView 控制項 - 無資料時顯示標題列</strong></a><br /> 摘要:當 GridView 繫結的 DataSource 資料筆數為 0 時,會依 EmptyDataTemplate 及 EmptyDataText 的設定來顯示無資料的狀態。若我們希望 GridView 在無資料時,可以顯示欄位標題,有一種作法是在 EmptyDataTemplate 中手動在設定一個標題列,不過這種作法很麻煩。本文擴展 GridView 控制項,直接透過屬性設定就可以在無資料顯示欄位標題。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0805/GridView_DAA6/image_thumb.png" alt="" /></p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/05/17/4028.aspx" target="_blank"><strong>2. 擴展 GridView 控制項 - 支援 Excel 及 Word 匯出</strong></a><br /> 摘要:GridView 匯出 Excel 及 Word 文件是蠻常使用的需求,此篇文章將擴展 GridView 控制項提供匯出 Excel 及 Word 文件的方法。一般在 GridView 匯出的常見下列問題也會在此一併被解決。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0805/GridViewExcelWord_14C22/image_thumb_1.png" alt="" /><br /> <img src="http://files.dotblogs.com.tw/jeff377/0805/GridViewExcelWord_14C22/image_thumb_2.png" alt="" /></p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/05/12/3936.aspx" target="_blank"><strong>3. GridView+FormView 示範資料 新增/修改/刪除(進階篇:伺服器控制項)</strong></a><br /> 摘要:擴展 GridView 及 FormView 控制項,在 GridView 控制項中新增 FormViewID 屬性,關連至指定的 FormView 控制項 ID,就可以讓 GridView 結合 FormView 來做資料異動的動作。</p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1766.aspx" target="_blank"><strong>4. 擴展 CommandField 類別 - 刪除提示訊息</strong></a><br /> 摘要:新增 DeleteConfirmMessage 屬性,設定刪除提示確認訊息。<br /> <img src="http://blog.blueshop.com.tw/images/blog_blueshop_com_tw/jeff377/1525/r_Ex19.1.png" alt="" /></p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1767.aspx" target="_blank"><strong>5. 擴展 CommandField 類別 - 刪除提示訊息含欄位值</strong></a><br /> 摘要:設定刪除提示確認訊息中可包含指定 DataField 欄位值,明確提示要刪除的資料列。<br /> <img src="http://blog.blueshop.com.tw/images/blog_blueshop_com_tw/jeff377/1525/r_Ex20.1.png" alt="" /></p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1710.aspx" target="_blank"><strong>6. 讓 CheckBoxField 繫結非布林值(0 或 1)欄位</strong></a><br /> 摘要:CheckBoxField 若繫結的欄位值為 0 或 1 時 (非布林值) 會發生錯誤,本文擴展 CheckBoxField 類別,讓 CheckBoxField 有辨法繫結 0 或 1 的欄位值。</p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/05/21/4093.aspx" target="_blank"><strong>7. 擴展 CheckBoxField 類別 - 支援非布林值的雙向繫結</strong></a><br /> 摘要:CheckBoxField 繫結的欄位值並無法直接使用 CBool 轉型為布林值,例如 "T/F"、"是/否" 之類的資料,若希望使用 CheckBoxField 來顯示就比較麻煩,一般的作法都是轉為 TemplateField,自行撰寫資料繫結的函式,而且只能支援單向繫結。在本文直接改寫 CheckBoxField 類別,讓 CheckBoxField 可以直接雙向繫結 "T/F" 或 "是/否" 之類的資料。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0805/CheckBoxField_1471C/image_thumb.png" alt="" /></p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/05/13/3969.aspx" target="_blank"><strong>8. 擴展 CommandField 類別 - Header 加入新增鈕</strong></a><br /> 摘要:支援在 CommandField 的 Header 的部分加入「新增」鈕,執行新增鈕會引發 RowCommand 事件。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0805/CommandFieldHeader_13B3E/image_4.png" alt="" /></p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/05/29/4169.aspx" target="_blank"><strong>9. GridView 自動編號欄位 - TBSerialNumberField</strong></a><br /> 摘要:繼承 DataControlField 來撰寫自動編號欄位,若 GridView 需要自動編號欄位時只需加入欄位即可。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0805/GridViewTBSerialNumberField_13347/image_2.png" alt="" /></p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx" target="_blank"><strong>10. 自訂 GridVie 欄位類別 - 實作 TBDropDownField 欄位類別</strong></a><br /> 摘要:支援在 GridView 中顯示下拉清單的欄位類別。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay23DataControlField_67E8/image_thumb_1.png" alt="" /></p> <p><a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/26/5777.aspx" target="_blank"><strong>11. 自訂 GridView 欄位 - 日期欄位</strong></a><br /> 摘要:支援在 GridView 中顯示日期下拉選單編輯的欄位類別。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay25TBDropDownFieldItems_6B94/image_thumb_2.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/27/5793.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/27/5793.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-27 22:37:14</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day25] 自訂 GridView 欄位 - 日期欄位(續)</title> <link>https://ithelp.ithome.com.tw/articles/10013091?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013091?sc=rss.iron</guid> <description><![CDATA[<p>接續上一文<br /> <strong>四、覆寫 ExtractValuesFromCell 方法 - 擷取儲存格的欄位值</strong><br /> 當用戶端使用 GridView 編輯後執...]]></description> <content:encoded><![CDATA[<p>接續上一文<br /> <strong>四、覆寫 ExtractValuesFromCell 方法 - 擷取儲存格的欄位值</strong><br /> 當用戶端使用 GridView 編輯後執行更新動作時,會呼叫 ExtractValuesFromCell 方法,來取得儲存格的欄位值,以便寫入資料來源。所以我們要覆寫 ExtractValuesFromCell 方法,將 Cell 或 TDateEdit 控制項的值取出填入具 IOrderedDictionary 介面的物件。</p> <pre><code> ''' <summary> ''' 使用指定 DataControlFieldCell 的值填入指定的 IDictionary 物件。 ''' </summary> ''' <param name="Dictionary">用於儲存指定儲存格的值。</param> ''' <param name="Cell">包含要擷取值的儲存格。</param> ''' <param name="RowState">資料列的狀態。</param> ''' <param name="IncludeReadOnly">true 表示包含唯讀欄位的值,否則為 false。</param> Public Overrides Sub ExtractValuesFromCell( _ ByVal Dictionary As IOrderedDictionary, _ ByVal Cell As DataControlFieldCell, _ ByVal RowState As DataControlRowState, _ ByVal IncludeReadOnly As Boolean) Dim oControl As Control = Nothing Dim sDataField As String = Me.DataField Dim oValue As Object = Nothing Dim sNullDisplayText As String = Me.NullDisplayText Dim oDateEdit As TBDateEdit If (((RowState And DataControlRowState.Insert) = DataControlRowState.Normal) OrElse Me.InsertVisible) Then If (Cell.Controls.Count > 0) Then oControl = Cell.Controls.Item(0) oDateEdit = TryCast(oControl, TBDateEdit) If (Not oDateEdit Is Nothing) Then oValue = oDateEdit.Text End If ElseIf IncludeReadOnly Then Dim s As String = Cell.Text If (s = " ") Then oValue = String.Empty ElseIf (Me.SupportsHtmlEncode AndAlso Me.HtmlEncode) Then oValue = HttpUtility.HtmlDecode(s) Else oValue = s End If End If If (Not oValue Is Nothing) Then If TypeOf oValue Is String Then If (CStr(oValue).Length = 0) AndAlso Me.ConvertEmptyStringToNull Then oValue = Nothing ElseIf (CStr(oValue) = sNullDisplayText) AndAlso (sNullDisplayText.Length > 0) Then oValue = Nothing End If End If If Dictionary.Contains(sDataField) Then Dictionary.Item(sDataField) = oValue Else Dictionary.Add(sDataField, oValue) End If End If End If End Sub </code></pre> <p><strong>五、測試程式</strong><br /> 我們使用 Northwnd 資料庫的 Employees 資料表為例,在 GridView 加入自訂的 TBDateField 欄位繫結 BirthDate 欄位,另外加入另一個 BoundField 的唯讀欄位,也同樣繫結 BirthDate 欄位來做比較。</p> <pre><code> <bee:TBDateField DataField="BirthDate" HeaderText="BirthDate" SortExpression="BirthDate" DataFormatString="{0:d}" ApplyFormatInEditMode="True" CalendarStyle="Winter" /> <asp:BoundField DataField="BirthDate" HeaderText="BirthDate" SortExpression="BirthDate" DataFormatString="{0:d}" ApplyFormatInEditMode="True" ReadOnly="true" /> </code></pre> <p>執行程式,在編輯資料列時,TBDateField 就會以 TDateEdit 控制項來進行編輯。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay25TBDropDownFieldItems_6B94/image_thumb_2.png" alt="" /></p> <p>使用 TDateEdit 編輯欄位值後,按「更新」鈕,資料就會被寫回資料庫。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay25TBDropDownFieldItems_6B94/image_thumb_3.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/26/5777.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/26/5777.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-26 17:04:50</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day25] 自訂 GridView 欄位 - 日期欄位</title> <link>https://ithelp.ithome.com.tw/articles/10013083?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013083?sc=rss.iron</guid> <description><![CDATA[<p>前二篇文章介紹了自訂 GridView 使用的下拉清單欄位 (TBDropDownField),對如何繼承 BoundField 類別下來改寫自訂欄位應該有進一步的了解。在 GridView 中...]]></description> <content:encoded><![CDATA[<p>前二篇文章介紹了自訂 GridView 使用的下拉清單欄位 (TBDropDownField),對如何繼承 BoundField 類別下來改寫自訂欄位應該有進一步的了解。在 GridView 中輸入日期也常蠻常見的需求,在本文將再實作一個 GridView 使用的日期欄位,在欄位儲存格使用 TBDateEdit 控制項來編輯資料。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/20081026164615771.rar" target="_blank">ASP.NET Server Control - Day25.rar</a><br /> Northwnd 資料庫下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008102364857537.rar" target="_blank">NORTHWND.rar</a></p> <p><strong>一、繼承 TBBaseBoundField 實作 TDateField</strong><br /> GridView 的日期欄位需要繫結資料,一般的作法是由 BoundField 繼承下來改寫;不過我們之前已經有繼承 BoundField 製作一個 TBBaseBoundField 的自訂欄位基底類別 (詳見「 <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx" target="_blank">[ASP.NET 控制項實作 Day23] 自訂 GridVie 欄位類別 - 實作 TBDropDownField 欄位類別</a>」 一文),所以我們要實作的日期欄位直接繼承 TBBaseBoundField 命名為 TDateField,並覆寫 CreateField 方法,傳回 TDateField 物件。</p> <pre><code> ''' <summary> ''' 日期欄位。 ''' </summary> Public Class TBDateField Inherits TBBaseBoundField Protected Overrides Function CreateField() As DataControlField Return New TBDateField() End Function End Class </code></pre> <p>自訂欄位類別主要是要覆寫 InitializeDataCell 方法做資料儲存格初始化、覆寫 OnDataBindField 方法將欄位值繫結至 BoundField 物件、覆寫 ExtractValuesFromCell 方法來擷取儲存格的欄位值,下面我們將針對這幾個需要覆寫的方法做一說明。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay25TBDropDownFieldItems_6B94/image_thumb.png" alt="" /></p> <p><strong>二、覆寫 InitializeDataCell 方法 - 資料儲存格初始化</strong><br /> 首先覆寫 InitializeDataCell 方法處理資料儲存格初始化,當唯讀狀態時使用 Cell 來呈現資料;若為編輯狀態時,則在 Cell 中加入 TBDateEdit 控制項,並將 TBDateField 的屬性設定給 TBDateEdit 控制項的相關屬性。然後將儲存格 (DataControlFieldCell) 或日期控制項 (TDateEdit) 的 DataBinding 事件導向 OnDataBindField 事件處理方法。</p> <pre><code> ''' <summary> ''' 資料儲存格初始化。 ''' </summary> ''' <param name="Cell">要初始化的儲存格。</param> ''' <param name="RowState">資料列狀態。</param> Protected Overrides Sub InitializeDataCell(ByVal Cell As DataControlFieldCell, ByVal RowState As DataControlRowState) Dim oDateEdit As TBDateEdit Dim oControl As Control If Me.CellIsEdit(RowState) Then '編輯狀態在儲存格加入 TBDateEdit 控制項 oDateEdit = New TBDateEdit() oDateEdit.FirstDayOfWeek = Me.FirstDayOfWeek oDateEdit.ShowWeekNumbers = Me.ShowWeekNumbers oDateEdit.CalendarStyle = Me.CalendarStyle oDateEdit.Lang = Me.Lang oDateEdit.ShowTime = Me.ShowTime oControl = oDateEdit Cell.Controls.Add(oControl) Else oControl = Cell End If If (oControl IsNot Nothing) AndAlso MyBase.Visible Then AddHandler oControl.DataBinding, New EventHandler(AddressOf Me.OnDataBindField) End If End Sub </code></pre> <p>TDateEdit 控制項為筆者自行撰寫的日期控制項,TDateEdit 控制項的相關細節可以參考筆者部落格下面幾篇文章有進一步說明。<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1742.aspx" target="_blank">日期控制項實作教學(1) - 結合 JavaScript</a><br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1743.aspx" target="_blank">日期控制項實作教學(2) - PostBack 與 事件</a><br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1746.aspx" target="_blank">TBDateEdit 日期控制項 - 1.0.0.0 版 (Open Source)</a><br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay21_6429/image_thumb_1.png" alt="" /></p> <p><strong>三、覆寫 OnDataBindField 方法 - 將欄位值繫結至 BoundField 物件</strong><br /> 當 GridView 執行 DataBind 時,每個儲存格的 DataBinding 事件都會被導向 OnDataBindField 方法,此方法中我們會由資料來源取得指定欄位值,處理此欄位值的格式化時,將欄位值呈現在 Cell 或 TDateEdit 控制項上。</p> <pre><code> ''' <summary> ''' 將欄位值繫結至 BoundField 物件。 ''' </summary> Protected Overrides Sub OnDataBindField(ByVal sender As Object, ByVal e As EventArgs) Dim oControl As Control Dim oDateEdit As TBDateEdit Dim oNamingContainer As Control Dim oDataValue As Object '欄位值 Dim bEncode As Boolean '是否編碼 Dim sText As String '格式化字串 oControl = DirectCast(sender, Control) oNamingContainer = oControl.NamingContainer oDataValue = Me.GetValue(oNamingContainer) bEncode = ((Me.SupportsHtmlEncode AndAlso Me.HtmlEncode) AndAlso TypeOf oControl Is TableCell) sText = Me.FormatDataValue(oDataValue, bEncode) If TypeOf oControl Is TableCell Then If (sText.Length = 0) Then sText = " " End If DirectCast(oControl, TableCell).Text = sText Else If Not TypeOf oControl Is TBDateEdit Then Throw New HttpException(String.Format("{0}: Wrong Control Type", Me.DataField)) End If oDateEdit = DirectCast(oControl, TBDateEdit) If Me.ApplyFormatInEditMode Then oDateEdit.Text = sText ElseIf (Not oDataValue Is Nothing) Then oDateEdit.Text = oDataValue.ToString End If End If End Sub </code></pre> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-26 16:56:36</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結(續)</title> <link>https://ithelp.ithome.com.tw/articles/10013047?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013047?sc=rss.iron</guid> <description><![CDATA[<p>接續上一文<br /> <strong>三、由關連的資料來源擷取資料</strong><br /> 再來就是重點就是要處理 PerformSelecrt 私有方法,來取得 Items 屬性的成員...]]></description> <content:encoded><![CDATA[<p>接續上一文<br /> <strong>三、由關連的資料來源擷取資料</strong><br /> 再來就是重點就是要處理 PerformSelecrt 私有方法,來取得 Items 屬性的成員清單內容。PerformSelect 方法的作用是去尋找頁面上的具 IDataSource 介面的控制項,並執行此資料來源的 Select 方法,以取得資料來設定 Items 的清單內容。<br /> <strong>step1. 尋找資料來源控制項</strong><br /> PerformSelect 方法中有使用 FindControlEx 方法,它是自訂援尋控制項的多載方法,是取代 FindControl 進階方法。程式碼中使用 FindControlEx 去是頁面中以遞迴方式尋找具有 IDataSource 介面的控制項,且 ID 屬性值為 TBDropDownList.ID 的屬性值。<br /> <strong>step2. 執行資料來源控制項的 Select 方法</strong><br /> 當找到資料來源控制項後 (如 SqlDataSource、ObjectDataSource ...等等),執行其 DataSourceView.Select 方法,此方法需入一個 DataSourceViewSelectCallback 函式當作參數,當資料來源控制項取得資料後回呼我們指定的 OnDataSourceViewSelectCallback 函式中做後序處理。<br /> <strong>step3. 將取得的資料來設定生 Items 的清單內容</strong><br /> 在 OnDataSourceViewSelectCallback 函式中接到回傳的具 IEnumerable 介面的資料,有可能是 DataView、DataTable ...等型別的資料。利用 DataBinder.GetPropertyValue 來取得 DataTextField 及 DataValueField 設定的欄位值,逐一建立 ListItem 項目,並加入 Items 集合屬性中。</p> <pre><code> ''' <summary> ''' 從關聯的資料來源擷取資料。 ''' </summary> Private Sub PerformSelect() Dim oControl As Control Dim oDataSource As IDataSource Dim oDataSourceView As DataSourceView '若未設定 DataSourceID 屬性則離開 If StrIsEmpty(Me.DataSourceID) Then Exit Sub '找到具 IDataSource 介面的控制項 oControl = FindControlEx(Me.Control.Page, GetType(IDataSource), "ID", Me.DataSourceID) If oControl Is Nothing Then Exit Sub oDataSource = DirectCast(oControl, IDataSource) oDataSourceView = oDataSource.GetView(String.Empty) oDataSourceView.Select(DataSourceSelectArguments.Empty, _ New DataSourceViewSelectCallback(AddressOf Me.OnDataSourceViewSelectCallback)) End Sub ''' <summary> ''' 擷取資料的回呼函式。 ''' </summary> ''' <param name="data">取得的資料。</param> Private Sub OnDataSourceViewSelectCallback(ByVal data As IEnumerable) Dim oCollection As ICollection Dim oValue As Object Dim oItem As ListItem Me.Items.Clear() If data Is Nothing Then Exit Sub oCollection = TryCast(data, ICollection) Me.Items.Capacity = oCollection.Count For Each oValue In data oItem = New ListItem() If StrIsNotEmpty(Me.DataTextField) Then oItem.Text = DataBinder.GetPropertyValue(oValue, DataTextField, Nothing) End If If StrIsNotEmpty(Me.DataValueField) Then oItem.Value = DataBinder.GetPropertyValue(oValue, DataValueField, Nothing) End If Me.Items.Add(oItem) Next End Sub </code></pre> <p><strong>四、測試程式</strong><br /> 使用上篇中同一個案例做測試,同樣以 Northwnd 資料庫的 Products 資料表為例。在 GridView 加入自訂的 TBDropDownField 欄位繫結 CategoryID 欄位,並設定 DataSourceID、DataTextField、DataValueField 屬性;另外加入另一個 BoundField 的唯讀欄位,也同樣繫結 CategoryID 欄位來做比較。</p> <pre><code> <bee:TBDropDownField HeaderText="CategoryID" SortExpression="CategoryID" DataField="CategoryID" DataTextField="CategoryName" DataValueField="CategoryID" DataSourceID="SqlDataSource2"> </bee:TBDropDownField> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" ReadOnly="true" /> </code></pre> <p>執行程式,在 GridView 瀏覽的模式時,TBDropDownField 的儲存格已經會呈現 Items 對應成員的顯示文字。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay24TBDropDownField_73B8/image_thumb_1.png" alt="" /></p> <p>執行資料列編輯時,也可以正常顯示下拉清單的內容。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay24TBDropDownField_73B8/image_thumb_2.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-25 18:11:28</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結</title> <link>https://ithelp.ithome.com.tw/articles/10013041?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10013041?sc=rss.iron</guid> <description><![CDATA[<p>上篇中我們實作了 GridView 的 TBDropDownField 欄位類別,不過眼尖的讀者不知有沒有發覺我們並處理 Items 屬性取得成員清單的動作,而是直接設定儲存格內含的 TBDro...]]></description> <content:encoded><![CDATA[<p>上篇中我們實作了 GridView 的 TBDropDownField 欄位類別,不過眼尖的讀者不知有沒有發覺我們並處理 Items 屬性取得成員清單的動作,而是直接設定儲存格內含的 TBDropDownList 控制項相關屬性 (DataSourceID、DataTextField、DataValueField 屬性) 後,就由 TDropDownList 控制項自行處理 Items 屬性的資料繫結。當 GridView 的資料列是編輯狀態時,下拉清單會顯示出 Items 的文字內容;可是瀏覽狀態的資料列,卻是顯示欄位原始值,無法呈現 Items 的文字內容。本文將說明如何自行處理 TBDropDownField 的 Items 屬性的資料繫結動作,並使唯讀狀態的資料列也可以呈現 Items 的文字內容。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay24TBDropDownField_73B8/image_thumb.png" alt="" /><br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/20081025163920497.rar" target="_blank">ASP.NET Server Control - Day24.rar</a><br /> Northwnd 資料庫下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008102364857537.rar" target="_blank">NORTHWND.rar</a></p> <p><strong>一、Items 屬性的問題</strong><br /> 我們重新看一次原本 TBDropDownField 類別在處理 Items 屬性的資料繫結取得清單內容的程式碼,在覆寫 InitializeDataCell 方法中,當儲存格為編輯模式時,會呈現 TBDropDownList 控制項並設定取得 Items 清單內容的相關屬性,讓 TBDropDownList 自行去處理它的 Items 屬性的清單內容。</p> <pre><code>'由資料來源控制項取得清單項目 oDropDownList.DataSourceID = Me.DataSourceID oDropDownList.DataTextField = Me.DataTextField oDropDownList.DataValueField = Me.DataValueField </code></pre> <p>不知你有沒有發覺,我們無論在 InitializeDataCell 及 OnDataBindField 方法中,都沒有針對 TBDropDownList 控制項做任何 DataBind 動作,那它是怎麼從 DataSourceID 關聯的資料來源擷取資料呢?因為 GridView 在執行 DataBind 時,就會要求所有的子控制項做 DataBind,所以我們只要設定好 BDropDownList 控制項相關屬性後,當 TBDropDownList 自動被要求資料繫結時就會取得 Items 的清單內容。<br /> 當然使用 TBDropDownList 控制項去處理 Items 的資料繫結動作最簡單,可是這樣唯讀的儲存格只能顯示原始欄位值,無法呈現 Items 中對應成員的文字;除非無論唯讀或編輯狀態,都要建立 TBDropDownList 控制項去取得 Items 清單內容,而唯讀欄位使用 TBDropDownList.Items 去找到對應成員的顯示文字,不過這樣的作法會怪怪的,而且沒有執行效能率。所以比較好的辨法,就是由 TBDropDownField 類別自行處理 Items 的資料繫結,同時提供給唯讀狀態的<br /> DataControlFieldCell 及編輯狀態的 TBDropDownList 使用。</p> <p><strong>二、由 TBDropDownField 類別處理 Items 屬性的資料繫結</strong><br /> 我們要自行處理 Items 屬性來取得成員清單,在 InitializeDataCell 方法中無須處理 Items 屬性,只需產生儲存格需要的子控制項,未來在執行子控制項的 DataBinding 時的 OnDataBindField 方法中再來處理 Items 屬性。</p> <pre><code> Protected Overrides Sub InitializeDataCell( _ ByVal Cell As DataControlFieldCell, _ ByVal RowState As DataControlRowState) Dim oDropDownList As TBDropDownList Dim oControl As Control If Me.CellIsEdit(RowState) Then oDropDownList = New TBDropDownList() oControl = oDropDownList Cell.Controls.Add(oControl) Else oControl = Cell End If If (oControl IsNot Nothing) AndAlso MyBase.Visible Then AddHandler oControl.DataBinding, New EventHandler(AddressOf Me.OnDataBindField) End If End Sub </code></pre> <p>在 OnDataBindField 方法中,我們加上一段處理 Items 屬性的程式碼如下,會利用 PerformSelecrt 私有方法,由關聯的資料來源 (即 DataSrouceID 指定的資料來源控制項) 擷取資料並產生 Items 的成員清單,在後面會詳細講解 PerformSelecrt 方法處理擷取資料的細節。因為 TBDropDownField 每個資料儲存格都會執行 OnDataBindField 方法,但 Items 取得成員清單的動作只需做一次即可,所以會以 FIsPerformSelect 區域變數來判斷是否已取得 Items 的成員清單,若已取過就不重新取得,這樣比較有執行效能。</p> <pre><code> If Not Me.DesignMode Then If Not FIsPerformSelect Then '從關聯的資料來源擷取資料 PerformSelect() FIsPerformSelect = True End If End If </code></pre> <p>當取得儲存儲的對應的欄位值時,依此欄位值由 Items 集合去取得對應的 ListItem 成員,並以此 ListItem.Text 的文字內容來做顯示。</p> <pre><code> '由 Items 去取得對應成員的顯示內容 oListItem = Me.Items.FindByValue(CCStr(sText)) If oListItem IsNot Nothing Then sText = oListItem.Text End If </code></pre> <p>若是由 TBDropDownList 所引發的 OnDataBindField 方法時,使用 SetItems 私有方法將 TBDropDownField.Items 屬性複製給 TBDropDownList.Item 屬性。</p> <pre><code> ODropDownList = DirectCast(oControl, TBDropDownList) SetItems(ODropDownList) </code></pre> <p>SetItems 私有方法的程式碼如下。</p> <pre><code> Private Sub SetItems(ByVal DropDownList As TBDropDownList) Dim oItems() As ListItem If Not Me.DesignMode Then ReDim oItems(Me.Items.Count - 1) Me.Items.CopyTo(oItems, 0) DropDownList.Items.AddRange(oItems) End If End Sub </code></pre> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-25 18:09:12</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day23] 自訂GridVie欄位-實作TBDropDownField欄位(續3)</title> <link>https://ithelp.ithome.com.tw/articles/10012977?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012977?sc=rss.iron</guid> <description><![CDATA[<p>接續上一文<br /> <strong>四、測試程式</strong><br /> 辛苦寫好 TBDropDownField 欄位類別時,接下來就是驗收成果的時候。我們以 Northwnd 資料...]]></description> <content:encoded><![CDATA[<p>接續上一文<br /> <strong>四、測試程式</strong><br /> 辛苦寫好 TBDropDownField 欄位類別時,接下來就是驗收成果的時候。我們以 Northwnd 資料庫的 Products 資料表為例,將 TBDropDownList .DataField 設為 CategoryID 欄位來做測試。首先我們測試沒有 DataSoruceID 的情況,在 GridView 加入自訂的 TBDropDownField 欄位繫結 CategoryID 欄位,另外加入另一個 BoundField 的唯讀欄位,也同樣繫結 CategoryID 欄位來做比較。</p> <pre><code> <bee:TBDropDownField HeaderText="CategoryID" SortExpression="CategoryID" DataField="CategoryID" > <Items> <asp:ListItem Value="">未對應</asp:ListItem> <asp:ListItem Value="2">Condiments</asp:ListItem> <asp:ListItem Value="3">Confections</asp:ListItem> </Items> </bee:TBDropDownField> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" ReadOnly="true" /> </code></pre> <p>執行程式,在 GridView 在唯讀模式,TBDropDownFIeld 可以正確的繫結 CategoryID 欄位值。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay23DataControlField_67E8/image_thumb.png" alt="" /></p> <p>編輯某筆資料列進入編輯狀態,就會顯示 TBDropDownList 控制項,清單成員為我們在 Items 設定的內容。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay23DataControlField_67E8/image_thumb_1.png" alt="" /></p> <p>使用 TBDropDownList 來做編輯欄位值,按下更新鈕,這時會執行 TBDropDownField.ExtractValuesFromCell 方法,取得儲存格中的值;最後由資料來源控制項將欄位值寫回資料庫。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay23DataControlField_67E8/image_thumb_2.png" alt="" /></p> <p>接下來測試設定 TBDropDownField.DataSourceID 的情況,把 DataSourcID 指向含 Categories 資料表內容的 SqlDataSoruce 控制項。</p> <pre><code> <bee:TBDropDownField HeaderText="CategoryID" SortExpression="CategoryID" DataField="CategoryID" DataTextField="CategoryName" DataValueField="CategoryID" DataSourceID="SqlDataSource2"> </bee:TBDropDownField> </code></pre> <p>執行程式查看結果,可以發現 TBDropDownList 控制項的清單內容也可以正常顯示 SqlDataSoruce 控制項取得資料。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay23DataControlField_67E8/image_thumb_3.png" alt="" /></p> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-24 00:32:30</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day23] 自訂GridVie欄位-實作TBDropDownField欄位(續2)</title> <link>https://ithelp.ithome.com.tw/articles/10012973?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012973?sc=rss.iron</guid> <description><![CDATA[<p>接續上一文<br /> <strong>step4. 處理資料繫結</strong><br /> 當 GridView 控制項在執行資料繫結時,儲存格的控制項就會引發 DataBinding 事...]]></description> <content:encoded><![CDATA[<p>接續上一文<br /> <strong>step4. 處理資料繫結</strong><br /> 當 GridView 控制項在執行資料繫結時,儲存格的控制項就會引發 DataBinding 事件,而這些事件會被導向 OnDataBindField 方法來統一處理儲存格中控制項的繫結動作。</p> <pre><code> ''' <summary> ''' 將欄位值繫結至 BoundField 物件。 ''' </summary> ''' <param name="sender">控制項。</param> ''' <param name="e">事件引數。</param> Protected Overrides Sub OnDataBindField(ByVal sender As Object, ByVal e As EventArgs) Dim oControl As Control Dim ODropDownList As TBDropDownList Dim oNamingContainer As Control Dim oDataValue As Object '欄位值 Dim bEncode As Boolean '是否編碼 Dim sText As String '格式化字串 oControl = DirectCast(sender, Control) oNamingContainer = oControl.NamingContainer oDataValue = Me.GetValue(oNamingContainer) bEncode = ((Me.SupportsHtmlEncode AndAlso Me.HtmlEncode) AndAlso TypeOf oControl Is TableCell) sText = Me.FormatDataValue(oDataValue, bEncode) If TypeOf oControl Is TableCell Then If (sText.Length = 0) Then sText = " " End If DirectCast(oControl, TableCell).Text = sText Else If Not TypeOf oControl Is TBDropDownList Then Throw New HttpException(String.Format("{0}: Wrong Control Type", Me.DataField)) End If ODropDownList = DirectCast(oControl, TBDropDownList) If Me.ApplyFormatInEditMode Then ODropDownList.Text = sText ElseIf (Not oDataValue Is Nothing) Then ODropDownList.Text = oDataValue.ToString End If End If End Sub </code></pre> <p><strong>step5. 取得儲存格中的值</strong><br /> 另外我們還需要覆寫 ExtractValuesFromCell 方法,取得儲存格中的值。這個方法是當 GridView 的編輯資料要準備寫入資料庫時,會經由 ExtractValuesFromCell 方法此來取得每個儲存格的值,並將這些欄位值加入 Dictionary 參數中,這個準備寫入的欄位值集合,可以在 DataSource 控制項的寫入資料庫的相關方法中取得使用。</p> <pre><code> ''' <summary> ''' 使用指定 DataControlFieldCell 物件的值填入指定的 System.Collections.IDictionary 物件。 ''' </summary> ''' <param name="Dictionary">用於儲存指定儲存格的值。</param> ''' <param name="Cell">包含要擷取值的儲存格。</param> ''' <param name="RowState">資料列的狀態。</param> ''' <param name="IncludeReadOnly">true 表示包含唯讀欄位的值,否則為 false。</param> Public Overrides Sub ExtractValuesFromCell( _ ByVal Dictionary As IOrderedDictionary, _ ByVal Cell As DataControlFieldCell, _ ByVal RowState As DataControlRowState, _ ByVal IncludeReadOnly As Boolean) Dim oControl As Control = Nothing Dim sDataField As String = Me.DataField Dim oValue As Object = Nothing Dim sNullDisplayText As String = Me.NullDisplayText Dim oDropDownList As TBDropDownList If (((RowState And DataControlRowState.Insert) = DataControlRowState.Normal) OrElse Me.InsertVisible) Then If (Cell.Controls.Count > 0) Then oControl = Cell.Controls.Item(0) oDropDownList = TryCast(oControl, TBDropDownList) If (Not oDropDownList Is Nothing) Then oValue = oDropDownList.Text End If ElseIf IncludeReadOnly Then Dim s As String = Cell.Text If (s = " ") Then oValue = String.Empty ElseIf (Me.SupportsHtmlEncode AndAlso Me.HtmlEncode) Then oValue = HttpUtility.HtmlDecode(s) Else oValue = s End If End If If (Not oValue Is Nothing) Then If TypeOf oValue Is String Then If (CStr(oValue).Length = 0) AndAlso Me.ConvertEmptyStringToNull Then oValue = Nothing ElseIf (CStr(oValue) = sNullDisplayText) AndAlso (sNullDisplayText.Length > 0) Then oValue = Nothing End If End If If Dictionary.Contains(sDataField) Then Dictionary.Item(sDataField) = oValue Else Dictionary.Add(sDataField, oValue) End If End If End If End Sub </code></pre> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-24 00:31:32</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day23] 自訂GridVie欄位-實作TBDropDownField欄位(續1)</title> <link>https://ithelp.ithome.com.tw/articles/10012971?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012971?sc=rss.iron</guid> <description><![CDATA[<p>接續上一文<br /> <strong>step2. 加入 TBBaseBoundField 的屬性</strong><br /> TBBaseBoundField 類別會內含 DropDown...]]></description> <content:encoded><![CDATA[<p>接續上一文<br /> <strong>step2. 加入 TBBaseBoundField 的屬性</strong><br /> TBBaseBoundField 類別會內含 DropDownList 控制項,所以加入設定 DropDownList 控制項的對應屬性;我們在 TBBaseBoundField 類別加入了 Items 、DataSourceID、DataTextField、DataValueField 屬性。其中 Items 屬性的型別與 DropDownList.Items 屬性相同,都是 ListItemCollection 集合類別,且 Items 屬性會儲存於 ViewState 中。</p> <pre><code> ''' <summary> ''' 清單項目集合。 ''' </summary> < _ Description("清單項目集合。"), _ DefaultValue(CStr(Nothing)), _ PersistenceMode(PersistenceMode.InnerProperty), _ DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _ Editor(GetType(ListItemsCollectionEditor), GetType(UITypeEditor)), _ MergableProperty(False), _ Category("Default")> _ Public Overridable ReadOnly Property Items() As ListItemCollection Get If (FItems Is Nothing) Then FItems = New ListItemCollection() If MyBase.IsTrackingViewState Then CType(FItems, IStateManager).TrackViewState() End If End If Return FItems End Get End Property ''' <summary> ''' 資料來源控制項的 ID 屬性。 ''' </summary> Public Property DataSourceID() As String Get Return FDataSourceID End Get Set(ByVal value As String) FDataSourceID = value End Set End Property ''' <summary> ''' 提供清單項目文字內容的資料來源的欄位。 ''' </summary> < _ Description("提供清單項目文字內容的資料來源的欄位。"), _ DefaultValue("") _ > _ Public Property DataTextField() As String Get Return FDataTextField End Get Set(ByVal value As String) FDataTextField = value End Set End Property ''' <summary> ''' 提供清單項目值的資料來源的欄位。 ''' </summary> Public Property DataValueField() As String Get Return FDataValueField End Get Set(ByVal value As String) FDataValueField = value End Set End Property </code></pre> <p><strong>step3.建立儲存格內含的控制項</strong><br /> GridView 是以儲存格 (DataControlFieldCell) 為單位,我們要覆寫 InitializeDataCell 方法來建立儲存格中的控制項;當儲存格為可編輯狀態時,就建立 DropDownList 控制項並加入儲存格中,在此使用上篇文章提及的 TBDropDownList 控制項來取代,以解決清單成員不存在造成錯誤的問題。若未設定 DataSourceID 屬性時,則由 Items 屬性取得自訂的清單項目;若有設定 DataSourceID 屬性,則由資料來源控制項 (如 SqlDataSource、ObjectDataSource 控制項) 來取得清單項目。<br /> 當建立儲存格中的控制項後,需要以 AddHeadler 的方法,將此控制項的 DataBinding 事件導向 OnDataBindField 這個事件處理方法,我們要在 OnDataBindField 處理資料繫結的動作。</p> <pre><code> ''' <summary> ''' 資料儲存格初始化。 ''' </summary> ''' <param name="Cell">要初始化的儲存格。</param> ''' <param name="RowState">資料列狀態。</param> Protected Overrides Sub InitializeDataCell( _ ByVal Cell As DataControlFieldCell, _ ByVal RowState As DataControlRowState) Dim oDropDownList As TBDropDownList Dim oItems() As ListItem Dim oControl As Control If Me.CellIsEdit(RowState) Then oDropDownList = New TBDropDownList() oControl = oDropDownList Cell.Controls.Add(oControl) If Not Me.DesignMode Then If StrIsEmpty(Me.DataSourceID) Then '自訂清單項目 ReDim oItems(Me.Items.Count - 1) Me.Items.CopyTo(oItems, 0) oDropDownList.Items.AddRange(oItems) Else '由資料來源控制項取得清單項目 oDropDownList.DataSourceID = Me.DataSourceID oDropDownList.DataTextField = Me.DataTextField oDropDownList.DataValueField = Me.DataValueField End If End If Else oControl = Cell End If If (oControl IsNot Nothing) AndAlso MyBase.Visible Then AddHandler oControl.DataBinding, New EventHandler(AddressOf Me.OnDataBindField) End If End Sub </code></pre> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-24 00:25:02</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day23] 自訂GridVie欄位-實作TBDropDownField欄位</title> <link>https://ithelp.ithome.com.tw/articles/10012965?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012965?sc=rss.iron</guid> <description><![CDATA[<p>GridView 是 ASP.NET 中一個相當常用的控制項,在 GridView 可加入 BoundField、CheckBoxField、CommandField、TemplateField...]]></description> <content:encoded><![CDATA[<p>GridView 是 ASP.NET 中一個相當常用的控制項,在 GridView 可加入 BoundField、CheckBoxField、CommandField、TemplateField ... 等不同型別的欄位,可是偏偏沒有提供在 GridView 中可呈現 DropDownList 的欄位型別;遇到這類需求時,一般的作法都是使用 TemplateField 來處理。雖然 TemplateField 具有相當好的設計彈性。可是在當 GridView 需要動態產生欄位的需求時,TemplateField 就相當麻煩,要寫一堆程式碼自行去處理資料繫結的動作。相互比較起來,BoundField、CheckBoxField ...等這類事先定義類型的欄位,在 GridView 要動態產生這些欄位就相當方便。如果我們可以把一些常用的 GridView 的欄位,都做成類似 BoundField 一樣,只要設定欄位的屬性就好,這樣使用上就會方便許多,所以在本文將以實作 DropDownList 欄位為例,讓大家了解如何去自訂 GridView 的欄位類別。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008102321355642.rar" target="_blank">ASP.NET Server Control - Day23.rar </a><br /> Northwnd 資料庫下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008102364857537.rar" target="_blank">NORTHWND.rar</a></p> <p><strong>一、選擇合適的父類別</strong><br /> 一般自訂 GridView 的欄位類別時,大都是由 DataControlField 或 BoundField 繼承下來改寫。若是欄位不需繫結資料(如 CommandFIeld),可以由 DataControlFIeld 繼承下來,若是欄位需要做資料繫結時(如 CheckBoxFIld,可以直接由 BoundField 繼承下來改寫比較方便。<br /> DataControlField 類別是所有類型欄位的基底類別,BoundField 類別也是由 DataControlField 類別繼承下來擴展了資料繫結部分的功能,所以我們要實作含 DropDownList 的欄位,也是由 BoundField 繼承下來改寫。</p> <p><strong>二、自訂欄位基底類別</strong><br /> 在此我們不直接繼承 BoundFIeld,而是先撰寫一個繼承 BoundField 命名為 TBBaseBoundField 的基底類別,此類別提供一些通用的屬性及方法,使我們更方便去撰寫自訂的欄位類別。</p> <pre><code> ''' <summary> ''' 資料欄位基礎類別。 ''' </summary> Public MustInherit Class TBBaseBoundField Inherits BoundField Private FRowIndex As Integer = 0 ''' <summary> ''' 資料列是否為編輯模式。 ''' </summary> ''' <param name="RowState">資料列狀態。</param> Public Function RowStateIsEdit(ByVal RowState As DataControlRowState) As Boolean Return (RowState And DataControlRowState.Edit) <> DataControlRowState.Normal End Function ''' <summary> ''' 資料列是否為新增模式。 ''' </summary> ''' <param name="RowState">資料列狀態。</param> Public Function RowStateIsInsert(ByVal RowState As DataControlRowState) As Boolean Return (RowState And DataControlRowState.Insert) <> DataControlRowState.Normal End Function ''' <summary> ''' 資料列是否為編輯或新增模式。 ''' </summary> ''' <param name="RowState">資料列狀態。</param> Public Function RowStateIsEditOrInsert(ByVal RowState As DataControlRowState) As Boolean Return RowStateIsEdit(RowState) OrElse RowStateIsInsert(RowState) End Function ''' <summary> ''' 判斷儲存格是否可編輯(新增/修改)。 ''' </summary> ''' <param name="RowState">資料列狀態。</param> Friend Function CellIsEdit(ByVal RowState As DataControlRowState) As Boolean Return (Not Me.ReadOnly) AndAlso RowStateIsEditOrInsert(RowState) End Function ''' <summary> ''' 資料列索引。 ''' </summary> Friend ReadOnly Property RowIndex() As Integer Get Return FRowIndex End Get End Property ''' <summary> ''' 儲存格初始化。 ''' </summary> ''' <param name="Cell">要初始化的儲存格。</param> ''' <param name="CellType">儲存格類型。</param> ''' <param name="RowState">資料列狀態。</param> ''' <param name="RowIndex">資料列之以零起始的索引。</param> Public Overrides Sub InitializeCell(ByVal Cell As DataControlFieldCell, ByVal CellType As DataControlCellType, _ ByVal RowState As DataControlRowState, ByVal RowIndex As Integer) FRowIndex = RowIndex MyBase.InitializeCell(Cell, CellType, RowState, RowIndex) End Sub ''' <summary> ''' 是否需要執行資料繫結。 ''' </summary> ''' <param name="RowState">資料列狀態。</param> Friend Function RequiresDataBinding(ByVal RowState As DataControlRowState) As Boolean If MyBase.Visible AndAlso StrIsNotEmpty(MyBase.DataField) AndAlso RowStateIsEdit(RowState) Then Return True Else Return False End If End Function End Class </code></pre> <p><strong>三、實作 TBDropDownField 欄位類別</strong><br /> <strong>step1. 繼承 TBBaseBoundField 類別</strong><br /> 首先新增一個類別,繼承 TBBaseBoundField 命名為 TBDropDownFIeld 類別,覆寫 CreateField 方法,傳回 TBDropDownFIeld 物件。</p> <pre><code> Public Class TBDropDownField Inherits TBBaseBoundField Protected Overrides Function CreateField() As System.Web.UI.WebControls.DataControlField Return New TBDropDownField() End Function End Class </code></pre> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-24 00:19:12</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day22] 讓 DropDownList 不再因項目清單不存在而造成錯誤(續)</title> <link>https://ithelp.ithome.com.tw/articles/10012915?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012915?sc=rss.iron</guid> <description><![CDATA[<p>接續上篇文章內容<br /> <strong>三、解決 TBDropDownList 設定 DataSourceID 造成資料無法繫結的問題</strong><br /> 要解決上述 TBDro...]]></description> <content:encoded><![CDATA[<p>接續上篇文章內容<br /> <strong>三、解決 TBDropDownList 設定 DataSourceID 造成資料無法繫結的問題</strong><br /> 要解決上述 TBDropDownList 設定 DataSourceID 問題,需在設定 SelectedValue 屬性時,若 Items.Count=0 先用一個 FCachedSelectedValue 變數將正確的值先暫存下來,然後覆寫 PerformDataBinding 方法,當 DorpDownList 取得 DataSoruceID 所對應的項目清單內容後,因為這時 Items 的內容才會完整取回,再去設定一次 SelectedValue 屬性就可以正確的繫結資料。</p> <pre><code> Public Class TBDropDownList Inherits DropDownList Private FCachedSelectedValue As String ''' <summary> ''' 覆寫 SelectedValue 屬性。 ''' </summary> Public Overrides Property SelectedValue() As String Get Return MyBase.SelectedValue End Get Set(ByVal value As String) If Me.Items.Count <> 0 Then Dim oItem As ListItem = Me.Items.FindByValue(value) If (oItem Is Nothing) Then Me.SelectedIndex = -1 '當 Items 不存在時 Else MyBase.SelectedValue = value End If Else FCachedSelectedValue = value End If End Set End Property Protected Overrides Sub PerformDataBinding(ByVal data As System.Collections.IEnumerable) MyBase.PerformDataBinding(data) 'DataSoruceID 資料繫結後再設定 SelectedValue 屬性值 If (Not FCachedSelectedValue Is Nothing) Then Me.SelectedValue = FCachedSelectedValue End If End Sub End Class </code></pre> <p>重新執行程式,切換到編輯模式時,TBDropDownList 就可以正確的繫結欄位值了。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay22DropDownList_14F78/image_thumb_5.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-23 07:03:54</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day22] 讓 DropDownList 不再因項目清單不存在而造成錯誤</title> <link>https://ithelp.ithome.com.tw/articles/10012909?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012909?sc=rss.iron</guid> <description><![CDATA[<p>DropDownList 控制項常常會因為項目清單中不存在繫結的欄位,而發生以下的錯誤訊息。因為繫結資料的不完整或異常就會造成這樣的異常錯誤,在設計上實在是相當困擾,而且最麻煩的是這個錯誤在頁面...]]></description> <content:encoded><![CDATA[<p>DropDownList 控制項常常會因為項目清單中不存在繫結的欄位,而發生以下的錯誤訊息。因為繫結資料的不完整或異常就會造成這樣的異常錯誤,在設計上實在是相當困擾,而且最麻煩的是這個錯誤在頁面的程式碼也無法使用 Try ... Catch 方式來略過錯誤。其實最簡單的方式就去直接去修改 DropDownList 控制項,讓 DropDownList 控制項繫結資料時,就算欄位值不存在清單項目中也不要釋出錯誤,本文就要說明如何繼承 DorpDownList 下來修改,來有效解決這個問題。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay22DropDownList_14F78/image_thumb.png" alt="" /><br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008102365119295.rar" target="_blank">ASP.NET Server Control - Day22.rar</a><br /> Northwnd 資料庫下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008102364857537.rar" target="_blank">NORTHWND.rar</a></p> <p><strong>一、覆寫 SelectedValue 屬性解決資料繫結的問題</strong><br /> DropDownList 控制項繫結錯誤的原因,可以由上圖的錯誤訊息可以大概得知是寫入 SelectedValue 屬性時發生的錯誤;所以我們繼承 DorpDownList 下來命名為 TBDropDownList,並覆寫 SelectedValue 屬性來解決這個問題。解決方式是在寫入 SelectedValue 屬性時,先判斷準備寫入的值是否存在項目清單中,存在的話才寫入 SelectedValue 屬性,若不存在則直接設定 SelectedIndex 屬性為 -1。</p> <pre><code> Public Class TBDropDownList Inherits DropDownList ''' <summary> ''' 覆寫 SelectedValue 屬性。 ''' </summary> Public Overrides Property SelectedValue() As String Get Return MyBase.SelectedValue End Get Set(ByVal value As String) Dim oItem As ListItem = Me.Items.FindByValue(value) If (oItem Is Nothing) Then Me.SelectedIndex = -1 '當 Items 不存在時 Exit Property Else MyBase.SelectedValue = value End If End Set End Property End Class </code></pre> <p>我們以 Northwnd 資料庫的 Products 資料表做為測試資料,事先定義 DropDownList 的 Items 內容,其中第一個加入 "未對應" 的項目,將 SelectedValue 屬性繫結至 CategoryID 欄位。</p> <pre><code> <bee:TBDropDownList ID="DropDownList1" runat="server" SelectedValue='<%# Bind("CategoryID") %>'> <asp:ListItem Value="">未對應</asp:ListItem> <asp:ListItem Value="2">Condiments</asp:ListItem> <asp:ListItem Value="3">Confections</asp:ListItem> </bee:TBDropDownList> </code></pre> <p>當資料的 CategoryID 欄位值不存在於 DropDownList 的 Items 集合屬性中時,就會顯示第一個 "未對應" 的項目。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay22DropDownList_14F78/image_thumb_1.png" alt="" /></p> <p><strong>二、TBDropDownList 設定 DataSoruceID 產生的問題</strong><br /> 上述的解決方法在筆者的「讓 DropDownList DataBind 不再發生錯誤」一文中已經有提及,不過有讀者發現另一個問題,就是當 DropDownList 設定 DataSourceID 時卻會發生資料無法正常繫結,以下就來解決這個問題。<br /> 我們設定 TBDropDownList 的 DataSoruceID 來取得項目清單的內容,將 DataSourceID 設定為另一個取得 Categories 資料表內容的 SqlDataSource 控制項。</p> <pre><code> <bee:TBDropDownList ID="DropDownList1" runat="server" SelectedValue='<%# Bind("CategoryID") %>' DataSourceID="SqlDataSource2" DataTextField="CategoryName" DataValueField="CategoryID"> </bee:TBDropDownList> <asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:Northwnd %>" SelectCommand="SELECT CategoryID, CategoryName, Description, Picture FROM Categories" ProviderName="<%$ ConnectionStrings:Northwnd.ProviderName %>" > </asp:SqlDataSource> </code></pre> <p>當執行程式時,FormView 原本在瀏覽模式時的 CategoryID 欄位值為 7 (CategoryName 應為 Product)。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay22DropDownList_14F78/image_thumb_3.png" alt="" /><br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay22DropDownList_14F78/image_thumb_2.png" alt="" /></p> <p>當按下「編輯」時切換到 EditItemTemplate 時,改用 TBDropDownList 繫結 CategoryID 欄位值,可以這時欲無法繫結正確的值。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay22DropDownList_14F78/image_thumb_4.png" alt="" /></p> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-23 06:59:56</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day21] 實作控制項智慧標籤(續)</title> <link>https://ithelp.ithome.com.tw/articles/10012897?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012897?sc=rss.iron</guid> <description><![CDATA[<p>接續 [ASP.NET 控制項實作 Day21] 實作控制項智慧標籤 一文<br /> <strong>step2. 在智慧標籤面板加入屬性項目</strong><br /> DesignerA...]]></description> <content:encoded><![CDATA[<p>接續 [ASP.NET 控制項實作 Day21] 實作控制項智慧標籤 一文<br /> <strong>step2. 在智慧標籤面板加入屬性項目</strong><br /> DesignerActionPropertyItem 類別是設定智慧標籤面上的屬性項目,DesignerActionPropertyItem 建構函式的第一個參數(memberName) 為屬性名稱,這個屬性指的是 TBDateEditActionList 類別中的屬性,所以要在 TBDateEditActionList 新增一個對應的屬性。<br /> 例如在智慧標籤中加入 AutoPostBack 屬性項目,則在 TBDateEditActionList 類別需有一個對應 AutoPostBack 屬性。</p> <pre><code> oItems.Add(New DesignerActionPropertyItem("AutoPostBack", _ "AutoPostBack", "Behavior", "是否引發 PostBack 動作。")) </code></pre> <p>TBDateEditActionList.AutoPostBack 屬性如下,其中 Me.Component 指的是目前的 TDateEdit 控制項,透過 GetPropertyValue 及 SetPropertyValue 方法來存取控制項的指定屬性。</p> <pre><code> ''' <summary> ''' 是否引發 PostBack 動作。 ''' </summary> Public Property AutoPostBack() As Boolean Get Return CType(GetPropertyValue(Me.Component, "AutoPostBack"), Boolean) End Get Set(ByVal value As Boolean) SetPropertyValue(Me.Component, "AutoPostBack", value) End Set End Property ''' <summary> ''' 設定物件的屬性值。 ''' </summary> ''' <param name="Component">屬性值將要設定的物件。</param> ''' <param name="PropertyName">屬性名稱。</param> ''' <param name="Value">新值。</param> Public Shared Sub SetPropertyValue(ByVal Component As Object, ByVal PropertyName As String, ByVal Value As Object) Dim Prop As PropertyDescriptor = TypeDescriptor.GetProperties(Component).Item(PropertyName) Prop.SetValue(Component, Value) End Sub ''' <summary> ''' 取得物件的屬性值。 ''' </summary> ''' <param name="Component">具有要擷取屬性的物件。</param> ''' <param name="PropertyName">屬性名稱。</param> Public Shared Function GetPropertyValue(ByVal Component As Object, ByVal PropertyName As String) As Object Dim Prop As PropertyDescriptor = TypeDescriptor.GetProperties(Component).Item(PropertyName) Return Prop.GetValue(Component) End Function </code></pre> <p><strong>step3. 在智慧標籤面板加入方法項目</strong><br /> DesignerActionMethodItem 類別是設定智慧標籤面上的方法項目,DesignerActionPropertyItem 建構函式的第二個參數(memberName) 為方法名稱,這個方法指的是 TBDateEditActionList 類別中的方法,所以要在 TBDateEditActionList 新增一個對應的方法。<br /> 例如在智慧標籤中加入 About 方法項目,則在 TBDateEditActionList 類別需有一個對應 About 方法。</p> <pre><code> oItems.Add(New DesignerActionMethodItem(Me, "About", _ "關於 TDateEdit 控制項", "About", _ "關於 TDateEdit 控制項。", True)) </code></pre> <p>TBDateEditActionList 的 About 方法只是單純顯示一個訊息視窗,一般你可以在這方法加入任何想在設計階段處理的動作。例如自動產生 GridView 的欄位、在 FormView 加入控制項並自動排版,這些都可以在此實現的。</p> <pre><code> Public Sub About() MsgBox("TDateEdit 是結合 The Coolest DHTML Calendar 日期選擇器實作的控制項") End Sub </code></pre> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-22 18:02:28</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day21] 實作控制項智慧標籤</title> <link>https://ithelp.ithome.com.tw/articles/10012896?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012896?sc=rss.iron</guid> <description><![CDATA[<p>控制項通常會把常用屬性或功能顯示在智慧標籤中,提供使用者更簡便的快速設定,例如下圖為 GridView 的智慧。若要製作控制項的智慧標籤,需實作控制項的 ActionList 加入智慧標籤中要顯...]]></description> <content:encoded><![CDATA[<p>控制項通常會把常用屬性或功能顯示在智慧標籤中,提供使用者更簡便的快速設定,例如下圖為 GridView 的智慧。若要製作控制項的智慧標籤,需實作控制項的 ActionList 加入智慧標籤中要顯示的項目,在本文將以 TDateEdit 控制項為例,進一步說明控制項的智慧標籤的實作方式。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay21_6429/image_thumb.png" alt="" /><br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008102217453969.rar" target="_blank">ASP.NET Server Control - Day21.rar</a></p> <p><strong>一、TDateEdit 控制項介紹</strong><br /> TDateEdit 控制項是筆者之前在部落格中實作的一個日期控制項,如下圖所示。它是結合 JavaScript 的 The Coolest DHTML Calendar 日期選擇器實作的控制項,我已將 TDateEdit 控制項的相關程式碼含入 Bee.Web.dll 組件中。TDateEdit 控制項的相關細節可以參考筆者部落格下面幾篇文章有進一步說明,本文將以 TDateEdit 控制項為例,只針對實作智慧標籤的部分做進一步說明。<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1742.aspx" target="_blank">日期控制項實作教學(1) - 結合 JavaScript</a><br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1743.aspx" target="_blank">日期控制項實作教學(2) - PostBack 與 事件</a><br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1746.aspx" target="_blank">TBDateEdit 日期控制項 - 1.0.0.0 版 (Open Source)</a><br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay21_6429/image_thumb_1.png" alt="" /></p> <p><strong>二、控制項加入智慧標籤</strong><br /> 控制項要加入智慧標籤要實作控制項的 Designer,我們繼承 ControlDesigner 命名為 TBDateEditDesigner,然後覆寫 ActionLists 屬性,此屬性即是傳回智慧標籤中所包含的項目清單集合。在 ActionLists 屬性中一般會先加入父類別的 ActionLists 屬性,再加入自訂的 ActionList 類別,這樣才可以保留原父類別中智慧標籤的項目清單。</p> <pre><code> ''' <summary> ''' TBDateEdit 控制項的設計模式行為。 ''' </summary> Public Class TBDateEditDesigner Inherits System.Web.UI.Design.ControlDesigner ''' <summary> ''' 取得控制項設計工具的動作清單集合。 ''' </summary> Public Overrides ReadOnly Property ActionLists() As DesignerActionListCollection Get Dim oActionLists As New DesignerActionListCollection() oActionLists.AddRange(MyBase.ActionLists) oActionLists.Add(New TBDateEditActionList(Me)) Return oActionLists End Get End Property End Class </code></pre> <p>我們自訂的 ActionList 為 TBDateEditActionList 類別,它在智慧標籤呈現的項目清單如下圖所示,接下去我們會說明 TBDateEditActionList 類別的內容。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay21_6429/image_thumb_2.png" alt="" /></p> <p><strong>三、自訂智慧標籤面板的項目清單集合</strong><br /> DesignerActionList 類別定義用於建立智慧標籤面板的項目清單的基底類別,所以我們首先繼承 DesignerActionList 命名為 TBDateEditActionList。</p> <pre><code> ''' <summary> ''' 定義 TBDateEdit 控制項智慧標籤面板的項目清單集合。 ''' </summary> Public Class TBDateEditActionList Inherits DesignerActionList ''' <summary> ''' 建構函式。 ''' </summary> Public Sub New(ByVal owner As ControlDesigner) MyBase.New(owner.Component) End Sub End Class </code></pre> <p>接下來要覆寫 GetSortedActionItems 方法,它會回傳 DesignerActionItemCollection 集合型別,此集合中會傳回要顯示在智慧標籤面板的項目清單集合,所以我們要在 DesignerActionItemCollection 集合中加入我們要呈現的項目清單內容。</p> <pre><code> ''' <summary> ''' 傳回要顯示在智慧標籤面板的項目清單集合。 ''' </summary> Public Overrides Function GetSortedActionItems() As System.ComponentModel.Design.DesignerActionItemCollection Dim oItems As New DesignerActionItemCollection() '在此加入智慧標籤面板的項目清單 Return oItems End Function </code></pre> <p><strong>step1. 在智慧標籤面板加入靜態標題項目</strong><br /> 首先介紹 DesignerActionHeaderItem 類別,它是設定靜態標題項目,例如我們在 TDateEdit 的智慧標籤中加入「行為」、「外觀」二個標題項目,其中 DesignerActionHeaderItem 建構函式的 category 參數是群組名稱,我們可以將相關的項目歸類到同一個群組。</p> <pre><code>Dim oItems As New DesignerActionItemCollection() oItems.Add(New DesignerActionHeaderItem("行為", "Behavior")) oItems.Add(New DesignerActionHeaderItem("外觀", "Appearance")) </code></pre> <p>[超過字數限制,下一篇接續本文]</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-22 18:01:29</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day20] 偵錯設計階段的程式碼</title> <link>https://ithelp.ithome.com.tw/articles/10012807?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012807?sc=rss.iron</guid> <description><![CDATA[<p>上篇我們介紹了自訂 Designer 來輸出控制項設計階段的 HTML 碼,可是若你去對針 Designer 的程式碼下中斷點,你會發覺根本無法偵錯。因為程式在執行階段時期,根本不會執行 Des...]]></description> <content:encoded><![CDATA[<p>上篇我們介紹了自訂 Designer 來輸出控制項設計階段的 HTML 碼,可是若你去對針 Designer 的程式碼下中斷點,你會發覺根本無法偵錯。因為程式在執行階段時期,根本不會執行 Designer 相關類別,所以你在 Designer 類別中下的中斷點完全無效;當然不可能這樣寫程式碼而用感覺去偵錯,本文將告訴你如何去偵錯設計階段的程式碼。<br /> <strong>一、設計階段程式碼的錯誤</strong><br /> 如果撰寫 Designer、Editor、ActionList 等設計階段的程式碼,當這些設計階段的程式碼發生錯誤,可能會發生設計頁面中控制項的錯誤情形,如下圖所示。因為控制項專案本身非啟動專案,在測試網站的設計頁面若控制項發生異常時會直接釋出錯誤,無法偵錯設計階段的程式碼;若真得要偵錯誤設計階段的問題,就要使用另一個 VS2008 來偵錯。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay20_E7AA/image_thumb_1.png" alt="" /></p> <p><strong>二、設定起始外部程式</strong><br /> 要偵錯控制項設計階段的程式碼,要先將控制項專案(Bee.Web)設定為啟時專案。然後設定控制項專案的「屬性」,在「偵錯」頁籤中的起始動作選擇「起始外部程式」,選擇 VS2008 的執行檔位置,預設為 C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay20_E7AA/image_thumb.png" alt="" /></p> <p><strong>三、開始偵錯設計階段程式碼</strong><br /> <strong>step1. 控制項專案開始偵錯</strong><br /> 在設計階要偵錯的程式碼下中斷點,在控制項專案按下 F5 開始偵錯,這時會啟動另一個新的 VS2008 執行檔。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay20_E7AA/image_thumb_2.png" alt="" /></p> <p><strong>step2. 在新的 VS2008 的工具箱加入控制項</strong><br /> 在新的 VS2008 中新增一個測試網站,在工具箱按右鍵執行「選擇項目」開啟「選擇工具箱項目」視窗,然後按「瀏覽」鈕按選擇控制項組件(Bee.Web.dll),將要偵錯的控制項加入工具箱中。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay20_E7AA/image_thumb_4.png" alt="" /><br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay20_E7AA/image_thumb_5.png" alt="" /><br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay20_E7AA/image_thumb_6.png" alt="" /></p> <p><strong>step3. 將控制項拖曳至頁面做設計動作</strong><br /> 在新的 VS2008 中,將控制項拖曳至頁面,就會開始執行設計階段的程式碼,特定的設計動作就會執行到相對的設計階段程式碼,當執行到之前下的中斷點時就可以開始偵錯了。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay20_E7AA/image_thumb_7.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/21/5741.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/21/5741.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-21 00:28:45</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day19] 控制項設計階段的外觀</title> <link>https://ithelp.ithome.com.tw/articles/10012682?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012682?sc=rss.iron</guid> <description><![CDATA[<p>有一些控制項在執行階段是不會呈現,也就是說控制項本身在執行階段不會 Render 出 HTML 碼,例如 SqlDataSoruce、ScriptManager 這類控制項;那它們在設計階段的頁...]]></description> <content:encoded><![CDATA[<p>有一些控制項在執行階段是不會呈現,也就是說控制項本身在執行階段不會 Render 出 HTML 碼,例如 SqlDataSoruce、ScriptManager 這類控制項;那它們在設計階段的頁面是如何呈現出來呢?本文將針對控制項設計階段的外觀做進一步的說明。<br /> 程式碼下載:ASP.NET Server Control - Day19.rar<br /> <strong>一、控制項設計階段的 HTML 碼</strong><br /> Web 伺服器控制項的設計模式行為都是透過 ControlDesigner 來處理,連設計階段時控制項的外觀也是如此;控制項在設計階段與執行執行時呈現的外觀不一定相同,當然大部分會儘量一致,使其能所見即所得。<br /> 控制項在設計階段的 HTML 碼是透 ControlDesigner.GetDesignTimeHtml 方法來處理,在 ControlDesigner.GetDesignTimeHtml 預設會執行控制項的 RenderControl 方法,所以大部分的情況下設計階段與執行階段輸出的 HTML 碼會相同。當控制項的 Visible=False 時,執行階段是完全不會輸出 HTML 碼,可是在設計階段時會特別將控制項設定 Visible=True,使控制項能完整呈現。</p> <p>ControlDesigner.GetDesignTimeHtml 方法</p> <pre><code>Public Overridable Function GetDesignTimeHtml() As String Dim writer As New StringWriter(CultureInfo.InvariantCulture) Dim writer2 As New DesignTimeHtmlTextWriter(writer) Dim errorDesignTimeHtml As String = Nothing Dim flag As Boolean = False Dim visible As Boolean = True Dim viewControl As Control = Nothing Try viewControl = Me.ViewControl visible = viewControl.Visible If Not visible Then viewControl.Visible = True flag = Not Me.UsePreviewControl End If viewControl.RenderControl(writer2) errorDesignTimeHtml = writer.ToString Catch exception As Exception errorDesignTimeHtml = Me.GetErrorDesignTimeHtml(exception) Finally If flag Then viewControl.Visible = visible End If End Try If ((Not errorDesignTimeHtml Is Nothing) AndAlso (errorDesignTimeHtml.Length <> 0)) Then Return errorDesignTimeHtml End If Return Me.GetEmptyDesignTimeHtml End Function </code></pre> <p><strong>二、自訂控制項的 Designer</strong><br /> 以 TBToolbar 為例,若我們在 RenderContents 方法未針對 Items.Count=0 做輸出 HTML 的處理,會發現未設定 Items 屬性時,在設計頁面上完全看不到 TBToolbar 控制項;像這種控制項設計階段的 HTML 碼,就可以自訂控制項的 Designer 來處理。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay19_1ACC/image_thumb.png" alt="" /></p> <p>繼承 ControlDesigner 命名為 TBToolbarDesigner,這個類別是用來擴充 TBToolbar 控制項的設計模式行為。我們可以覆寫 GetDesignTimeHtml 方法,處理設計階段表示控制項的 HTML 標記,此方法回傳的 HTML 原始碼就是控制項呈現在設計頁面的外觀。所以我們可以在 TBToolbar.Items.Count=0 時,輸出一段提示的 HTML 碼,這樣當 TBToolbar 未設定 Items 屬性時一樣可以在設計頁面上呈現控制項。</p> <pre><code> ''' <summary> ''' 擴充 TBToolbar 控制項的設計模式行為。 ''' </summary> Public Class TBToolbarDesigner Inherits System.Web.UI.Design.ControlDesigner ''' <summary> ''' 用來在設計階段表示控制項的 HTML 標記。 ''' </summary> Public Overrides Function GetDesignTimeHtml() As String Dim sHTML As String Dim oControl As TBToolbar oControl = CType(ViewControl, TBToolbar) If oControl.Items.Count = 0 Then sHTML = "<div style=""background-color: #C0C0C0; border:solid 1px; width:200px"">請設定 Items 屬性</div>" Else sHTML = MyBase.GetDesignTimeHtml() End If Return sHTML End Function End Class </code></pre> <p>在 TBToolbar 控制項套用 DesignerAttribute 設定自訂的 TBToolbarDesigner 類別。</p> <pre><code> <Designer(GetType(TBToolbarDesigner))> _ Public Class TBToolbar Inherits WebControl End Class </code></pre> <p>重建控制項組件,切換到設計頁面上的看 TBToolbar 控制項未設定 Items 屬性時的外觀,就是我們在 TBToolbarDesigner.GetDesignTimeHtml 方法回傳的 HTML 碼。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay19_1ACC/image_thumb_1.png" alt="" /></p> <p>如果你覺得上述設計階段的控制項有點太陽春,我們也可以輸出類似 SqlDataSource 控制項的外觀,將未設定 Items 屬性時輸出 HTML 改呼叫 CreatePlaceHolderDesignTimeHtml 方法。</p> <pre><code> If oControl.Items.Count = 0 Then sHTML = MyBase.CreatePlaceHolderDesignTimeHtml("請設定 Items 屬性") Else sHTML = MyBase.GetDesignTimeHtml() End If </code></pre> <p>來看一下這樣修改後的結果,是不是比較專業一點了呢。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay19_1ACC/image_thumb_2.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/20/5726.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/20/5726.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-20 02:25:29</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day18] 修改集合屬性編輯器</title> <link>https://ithelp.ithome.com.tw/articles/10012636?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012636?sc=rss.iron</guid> <description><![CDATA[<p>上篇我們實作了「集合屬性包含不同型別的成員」,不過若有去使用屬性視窗編輯 TBToolbar 的 Items 屬性,你會發覺這個集合屬性編輯器無法加入我們定義不同型別的成員,只能加入最原始的集合...]]></description> <content:encoded><![CDATA[<p>上篇我們實作了「集合屬性包含不同型別的成員」,不過若有去使用屬性視窗編輯 TBToolbar 的 Items 屬性,你會發覺這個集合屬性編輯器無法加入我們定義不同型別的成員,只能加入最原始的集合成員。是不是只能在 aspx 程式碼中手動去輸入呢?當然不需要這樣人工作業,只要改掉集合屬性編輯器就可以達到我們的需求,本文將介紹修改集合屬性編輯器的相關作法。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/200810190138285.rar" target="_blank">ASP.NET Server Control - Day18.rar</a></p> <p><strong>一、自訂集合屬性編輯器</strong><br /> 我們先看一下 TBToolbar.Items 屬性套用的 EditorAttribute,它是使用 CollectionEditor 類別來當作屬性編輯器,所以我們就是要繼承 CollectionEditor 類別下來修改成自訂的屬性編輯器。</p> <pre><code>< _ Editor(GetType(CollectionEditor), GetType(UITypeEditor)) _ > _ Public ReadOnly Property Items() As TBToolbarItemCollection </code></pre> <p>新增一個繼承 CollectionEditor 的 TBToolbarItemCollectionEditor 類別,並加入建構函式。此類別屬於 Bee.WebControls.Design 命名空間,通常我們會把設計階段使用的類別歸類到特別的命名空間便於管理及使用。</p> <pre><code>Namespace WebControls.Design Public Class TBToolbarItemCollectionEditor Inherits CollectionEditor ''' <summary> ''' 建構函式。 ''' </summary> ''' <param name="Type">型別。</param> Public Sub New(ByVal Type As Type) MyBase.New(Type) End Sub End Class End Namespace </code></pre> <p>我們可以先修改 Items 屬性的 EditorAttribute,看看我們自訂的 TBToolbarItemCollectionEditor 是否能正常運作。不過這個屬性編輯器跟原本的沒什麼差異,因為我們只是單純繼承下來沒做任何異動,接下去我們就要開始來修改這個屬性編輯器。</p> <pre><code>< _ Editor(GetType(TBToolbarItemCollectionEditor), GetType(UITypeEditor)) _ > _ Public ReadOnly Property Items() As TBToolbarItemCollection </code></pre> <p><strong>二、加入不同型別的集合成員</strong><br /> 再來我們就要著手修改集合屬性編輯器,讓它可以加入不同型別的集合成員。覆寫 CollectionEditor 的 CanSelectMultipleInstances 方法傳回 True,這個方法是設定 CollectionEditor 是否允許加入多種不同型別的集合成員。</p> <pre><code> Protected Overrides Function CanSelectMultipleInstances() As Boolean Return True End Function </code></pre> <p>再來覆寫 CreateNewItemTypes 方法,這個方法是取得這個集合編輯器可包含的資料型別,將集合可包含的資料型別以陣列傳回。</p> <pre><code> ''' <summary> ''' 取得這個集合編輯器可包含的資料型別。 ''' </summary> ''' <returns>這個集合可包含的資料型別陣列。</returns> Protected Overrides Function CreateNewItemTypes() As System.Type() Dim ItemTypes(2) As System.Type ItemTypes(0) = GetType(TBToolbarButton) ItemTypes(1) = GetType(TBToolbarTextbox) ItemTypes(2) = GetType(TBToolbarLabel) Return ItemTypes End Function </code></pre> <p>重建控制項組件,使用 Items 的集合屬性編輯器,就可以發現「加入」鈕的下拉清單就會出現我們所定義的三種型別的集合成員,如此可以加入不同型別的成員了。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay18_6E6C/image_2.png" alt="" /></p> <p><strong>三、設定清單項目的顯示文字</strong><br /> 在成員清單項目中預設會顯示成員含命名空間的型別,若我們要修改成比較有識別的顯示文字,例如 TBToolbarButton(Key=Add) 可以顯示「按鈕-Add」,這時可以覆寫 GetDisplayText 方法來設定清單項目的顯示文字。</p> <pre><code> ''' <summary> ''' 取出指定清單項目的顯示文字。 ''' </summary> Protected Overrides Function GetDisplayText(ByVal value As Object) As String If TypeOf value Is TBToolbarButton Then Return String.Format("按鈕 - {0}", CType(value, TBToolbarButton).Key) ElseIf TypeOf value Is TBToolbarTextbox Then Return "文字框" ElseIf TypeOf value Is TBToolbarLabel Then Return String.Format("標籤 - {0}", CType(value, TBToolbarLabel).Text) Else Return value.GetType.Name End If End Function </code></pre> <p><img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay18_6E6C/image_4.png" alt="" /></p> <p><strong>四、集合編輯器的屬性視窗的屬性描述</strong><br /> 一般屬性視窗下面都會有屬性描述,可以集合屬性編輯器中的屬性視窗下面竟沒有屬性描述。若我們要讓它的屬性描述可以顯示,可以覆寫 CreateCollectionForm 方法,取得集合屬性編輯表單,再去設定表單上的 PropertyGrid.HelpVisible<br /> = True 即可。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay18_6E6C/image_6.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/19/5721.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/19/5721.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-19 00:13:21</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day17] 集合屬性包含不同型別的成員</title> <link>https://ithelp.ithome.com.tw/articles/10012600?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012600?sc=rss.iron</guid> <description><![CDATA[<p>我們知道在 GridView 的 Columns 集合屬性中,可以包含不同型別的欄位,如 BoundFIeld、CheckBoxField、HyperLinkField ...等不同型別的欄位。...]]></description> <content:encoded><![CDATA[<p>我們知道在 GridView 的 Columns 集合屬性中,可以包含不同型別的欄位,如 BoundFIeld、CheckBoxField、HyperLinkField ...等不同型別的欄位。如果我們希望工具列中不只包含按鈕,可以包含其他不同類型的子控制項,那該怎麼做呢?本文就以上篇中的 TBToolbar 控制項為案例,讓 Items 集合屬性可以加入 Button、TextBox、Label ...等不同的子控制項。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/20081017235034673.rar" target="_blank">ASP.NET Server Control - Day17.rar</a><br /> <strong>一、不同型別的集合成員</strong><br /> 我們的需求是讓工具列可以加入 Button、TextBox、Label 三種子控制項,所以繼承原來的 TBToolbarItem (只保留 Enabled 屬性),新增了 TBToolbarButton、TBToolbarTextbox、TBToolbarLabel 三個類別。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay17_1430A/image_6.png" alt="" /></p> <p>這些新增的成員類別都是繼承至 TBToolbarItem,所以在 aspx 程式碼中,手動輸入 Items 的成員時,就會列出這幾種定義的成員型別。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay17_1430A/image_2.png" alt="" /></p> <p><strong>二、建立不同型別集合成員的子控制項</strong><br /> 因為 Items 屬性的成員具不同型別,所以我們要改寫 RenderContents 方法,判斷成員型別來建立對應類型的子控制項。若為 TBToolbarButton 型別建立 Button 控制項、若為 TBToolbarTextbox 型別則建立 TextBox 控制項、若為 TBToolbarLabel 型別則建立 Label 控制項。其中 TBToolbarButton 建立的控制項為 TBButton,這個控制項是我們在「 <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/04/5578.aspx" target="_blank">[ASP.NET 控制項實作 Day3] 擴展現有伺服器控制項功能</a>」一文中實作的具詢問訊息的按鈕控制項。</p> <pre><code> ''' <summary> ''' 覆寫 RenderContents 方法。 ''' </summary> Protected Overrides Sub RenderContents(ByVal writer As System.Web.UI.HtmlTextWriter) Dim oItem As TBToolbarItem Dim oControl As Control For Each oItem In Me.Items If TypeOf oItem Is TBToolbarButton Then '建立 Button 控制項 oControl = CreateToolbarButton(CType(oItem, TBToolbarButton)) ElseIf TypeOf oItem Is TBToolbarTextbox Then '建立 Textbox 控制項 oControl = CreateToolbarTextbox(CType(oItem, TBToolbarTextbox)) Else '建立 Label 控制項 oControl = CreateToolbarLabel(CType(oItem, TBToolbarLabel)) End If Me.Controls.Add(oControl) Next MyBase.RenderContents(writer) End Sub ''' <summary> ''' 建立工具列按鈕。 ''' </summary> Private Function CreateToolbarButton(ByVal Item As TBToolbarButton) As Control Dim oButton As TBButton Dim sScript As String oButton = New TBButton() oButton.Text = Item.Text oButton.Enabled = Item.Enabled oButton.ID = Item.Key oButton.ConfirmMessage = Item.ConfirmMessage sScript = Me.Page.ClientScript.GetPostBackEventReference(Me, Item.Key) oButton.OnClientClick = sScript Return oButton End Function ''' <summary> ''' 建立工具列文字框。 ''' </summary> Private Function CreateToolbarTextbox(ByVal Item As TBToolbarTextbox) As Control Dim oTextBox As TextBox oTextBox = New TextBox Return oTextBox End Function ''' <summary> ''' 建立工具列標籤。 ''' </summary> Private Function CreateToolbarLabel(ByVal Item As TBToolbarLabel) As Control Dim oLabel As Label oLabel = New Label() oLabel.Text = Item.Text Return oLabel End Function </code></pre> <p>我們手動在 aspx 程式碼中輸入不同型別的成員,TBToolbar 控制項就會呈現對應的子控制項。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay17_1430A/image_8.png" alt="" /></p> <p><strong>三、執行程式</strong><br /> 執行程式,就可以在瀏覽器看到呈現的工具列,當按下「刪除」時也會出現我們定義的詢問訊息。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay17_1430A/image_4.png" alt="" /></p> <p>輸出的 HTML 碼如下</p> <pre><code><span id="TBToolbar1"> <input type="submit" name="TBToolbar1$Add" value="新增" onclick="__doPostBack('TBToolbar1','Add');" id="TBToolbar1_Add" /> <input type="submit" name="TBToolbar1$Edit" value="修改" onclick="__doPostBack('TBToolbar1','Edit');" id="TBToolbar1_Edit" /> <input type="submit" name="TBToolbar1$Delete" value="刪除" onclick="if (confirm('確定刪除嗎?')==false) {return false;}__doPostBack('TBToolbar1','Delete');" id="TBToolbar1_Delete" /> <span>關鍵字</span> <input name="TBToolbar1$ctl01" type="text" /> <input type="submit" name="TBToolbar1$Search" value="搜尋" onclick="__doPostBack('TBToolbar1','Search');" id="TBToolbar1_Search" /> </span> </code></pre> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/18/5718.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/18/5718.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-18 00:05:57</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day16] 繼承 WebControl 實作 Toolbar 控制項</title> <link>https://ithelp.ithome.com.tw/articles/10012507?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012507?sc=rss.iron</guid> <description><![CDATA[<p>前面我們討論過「繼承 CompositeControl 實作 Toolbar 控制項」,本文將繼承 WebControl 來實作同樣功能的 Toolbar 控制項,用不同的方式來實作同一個控制項...]]></description> <content:encoded><![CDATA[<p>前面我們討論過「繼承 CompositeControl 實作 Toolbar 控制項」,本文將繼承 WebControl 來實作同樣功能的 Toolbar 控制項,用不同的方式來實作同一個控制項,進而比較二者之間的差異。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/20081016225639603.rar" target="_blank">ASP.NET Server Control - Day16.rar</a></p> <p><strong>一、繼承 WebControl 實作 TBToolbar 控制項</strong><br /> <strong>step1. 新增繼承 WebControl 的 TBToolbar 控制項</strong><br /> 新增繼承 WebControl 的 TBToolbar 控制項,你也可以直接原修改原 TBToolbar 控制項,繼承對象由 CompositeControl 更改為 WebControl即可。跟之前一樣在 TBToolbar 控制項加入 Items 屬性及 Click 事件。<br /> 另外 TBToolbar 控制項需實作 INamingContainer 界面,此界面很特殊沒有任何屬性或方法,INamingContainer 界面的作用是子控制項的 ClientID 會在前面加上父控制項的 ClickID,使每個子控制項有唯一的 ClientID。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay15WebControlToolbar_12C6D/image_thumb_3.png" alt="" /></p> <p><strong>step2. 建立工具列按鈕集合</strong><br /> 覆寫 RenderContents 方法,將原本 TBToolbar (複合控制項) 的 CreateChildControls 方法中建立工具列按鈕程式碼,搬移至 RenderContents 方法即可。</p> <pre><code> Private Sub ButtonClickEventHandler(ByVal sender As Object, ByVal e As EventArgs) Dim oButton As Button Dim oEventArgs As ClickEventArgs oButton = CType(sender, Button) oEventArgs = New ClickEventArgs() oEventArgs.Key = oButton.ID OnClick(oEventArgs) End Sub ''' <summary> ''' 覆寫 RenderContents 方法。 ''' </summary> Protected Overrides Sub RenderContents(ByVal writer As System.Web.UI.HtmlTextWriter) Dim oItem As TBToolbarItem Dim oButton As Button For Each oItem In Me.Items oButton = New Button() oButton.Text = oItem.Text oButton.Enabled = oItem.Enabled oButton.ID = oItem.Key AddHandler oButton.Click, AddressOf ButtonClickEventHandler Me.Controls.Add(oButton) Next If Me.Items.Count = 0 AndAlso Me.DesignMode Then oButton = New Button() oButton.Text = "請設定 Items 屬性。" Me.Controls.Add(oButton) End If MyBase.RenderContents(writer) End Sub </code></pre> <p>上述的直接搬移過來的程式碼還有個問題,就是原來的使用 AddHandler 來處理按鈕事件的方式變成沒有作用了?因為現在不是複合式控制項,當前端的按鈕 PostBack 傳回伺服端時,TBToolbar 不會事先建立子控制槓,所以機制會找不到原來產生的按鈕,也就無法使用 AddHandler 來處理事件了。</p> <pre><code>AddHandler oButton.Click, AddressOf ButtonClickEventHandler </code></pre> <p><strong>step3. 處理 Click 事件</strong><br /> 因為不能使用 AddHandler 來處理按鈕事件,所以我們就自行使用 Page.ClientScript.GetPostBackEventReference 方法來產生 PostBack 動作的用戶端指令碼,按鈕的 OnClientClick 去執行 PostBack 的動作。</p> <pre><code> For Each oItem In Me.Items oButton = New Button() oButton.Text = oItem.Text oButton.Enabled = oItem.Enabled oButton.ID = oItem.Key sScript = Me.Page.ClientScript.GetPostBackEventReference(Me, oItem.Key) oButton.OnClientClick = sScript Me.Controls.Add(oButton) Next </code></pre> <p>TBToolar 控制項輸出的 HTML 碼如下</p> <pre><code><span id="TBToolbar1"> <input type="submit" name="TBToolbar1$Add" value="新增" onclick="__doPostBack('TBToolbar1','Add');" id="TBToolbar1_Add" /> <input type="submit" name="TBToolbar1$Edit" value="修改" onclick="__doPostBack('TBToolbar1','Edit');" id="TBToolbar1_Edit" /> <input type="submit" name="TBToolbar1$Delete" value="刪除" onclick="__doPostBack('TBToolbar1','Delete');" id="TBToolbar1_Delete" /> </span> </code></pre> <p>要自行處理 PostBack 的事件,需實作 IPostBackEventHandler 介面,在 RaisePostBackEvent 方法來引發 TBToolbar 的 Click 事件。</p> <pre><code> Public Class TBToolbar Inherits WebControl Implements INamingContainer Implements IPostBackEventHandler Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent Dim oEventArgs As ClickEventArgs oEventArgs = New ClickEventArgs() oEventArgs.Key = eventArgument Me.OnClick(oEventArgs) End Sub End Class </code></pre> <p><strong>二、測試程式</strong><br /> 在測試頁面上放置 TBToolbar 控制項,在 Click 事件撰寫測試程式碼。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay15WebControlToolbar_12C6D/image_thumb.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/17/5706.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/17/5706.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-17 00:05:40</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day15] 複合控制項隱藏的問題</title> <link>https://ithelp.ithome.com.tw/articles/10012425?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012425?sc=rss.iron</guid> <description><![CDATA[<p>上一篇我們使用複合控制項(繼承 CompositeControl)的方式來實作 TBToolbar 控制項,本文將針對複合控制項做一些測試,說明在使用複合控制項要注意的一些問題。<br /> 程...]]></description> <content:encoded><![CDATA[<p>上一篇我們使用複合控制項(繼承 CompositeControl)的方式來實作 TBToolbar 控制項,本文將針對複合控制項做一些測試,說明在使用複合控制項要注意的一些問題。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008101612954661.rar" target="_blank">ASP.NET Server Control - Day15.rar</a><br /> <strong>一、複合控制項建立子控制項的時機</strong><br /> 還記得我們之前介紹複合控制項時有談到 CompositeControl 類別會確保我們存取子控制項時,它的子控制項一定會事先建立;也就是當我們使用 Controls 屬性去存取子控制項時,一定會執行 CreateChildControls 方法,以確保子控制項事先被建立。我們看一下 CompositeControl 類別的 Controls 屬性的寫法就可以了解其中的原由,在存取 CompositeControl.Controls 屬性時,它會先執行 Control.EnsureChildControls 方法;而 EnsureChildControls 方法會去判斷子控制項是否已建立,若未建立會去執行 CreateChildControls 方法,這也就是為什麼 CompositeControl 有辨法確保子控制項事先被建立的原因。</p> <p>CompositeControl.Controls 屬性如下</p> <pre><code>Public Overrides ReadOnly Property Controls As ControlCollection Get Me.EnsureChildControls Return MyBase.Controls End Get End Property </code></pre> <p>Control.EnsureChildControls 方法如下</p> <pre><code>Protected Overridable Sub EnsureChildControls() If (Not Me.ChildControlsCreated AndAlso Not Me.flags.Item(&H100)) Then Me.flags.Set(&H100) Try Me.ResolveAdapter If (Not Me._adapter Is Nothing) Then Me._adapter.CreateChildControls Else Me.CreateChildControls End If Me.ChildControlsCreated = True Finally Me.flags.Clear(&H100) End Try End If End Sub </code></pre> <p><strong>二、複合控制項隱藏的問題</strong><br /> 我們以上篇的 TBToolbar 控制項為例,撰寫一些測試案例來說明複合控制項的問題。在撰寫測試案例之前,我們先修改一下 TBToolbar 控制項,覆寫 LoadViewState 及 SaveViewState 方法,將 Items 屬性儲存於 ViewState 中以維持狀態。</p> <p>在測試頁面上放置「測試一」、「測試二」、「PostBack」三個按鈕,這三個按鈕的動作如下。<br /> 「測試一」按鈕:在工具列直接新增一個按鈕。<br /> 「測試二」按鈕:先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。<br /> 「PostBack」按鈕:單純執行 PostBack,不撰寫程式碼。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay15_E8C/image_thumb.png" alt="" /></p> <p>三個按鈕的程式碼如下所示。</p> <pre><code> Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click Dim oItem As TBToolbarItem '加入新按鈕 oItem = New TBToolbarItem() oItem.Text = "新按鈕" oItem.Key = "NewButton" TBToolbar1.Items.Add(oItem) Me.Response.Write("「測試一」按鈕") End Sub Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click Dim oItem As TBToolbarItem Dim oButton As Button '先執行 FindControl 去取得 ID="Add" 的按鈕 oButton = TBToolbar1.FindControl("Add") '再加入新按鈕 oItem = New TBToolbarItem() oItem.Text = "新按鈕" oItem.Key = "NewButton" TBToolbar1.Items.Add(oItem) Me.Response.Write("「測試二」按鈕") End Sub Protected Sub Button3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button3.Click '單純 PostBack,無程式碼 Me.Response.Write("「PostBack」按鈕") End Sub </code></pre> <p>案例一:執行「測試一」按鈕,在工具列直接新增一個按鈕。<br /> 當按下「測試一」按鈕時,工具列可以正常加入我們新增的按鈕。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay15_E8C/image_thumb_1.png" alt="" /></p> <p>案例二:執行「測試二」按鈕,先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。<br /> 重新執行程式,當按下「測試二」按鈕時,你會發現奇怪的現象,工具列竟然沒有加入我們新增的按鈕?<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay15_E8C/image_thumb_2.png" alt="" /></p> <p>此時再按下「PostBack」按鈕,工具列才會出現我們剛剛加入的按鈕。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay15_E8C/image_thumb_3.png" alt="" /></p> <p>為什麼會發生這種怪現象呢?其實原因很簡單,因為 FindControl 時會去存取 Controls 屬性,而這時子控制項已經被建立了;而之前再用 Items 屬性加入新按鈕,它已經不會在重建子控制項,導致第一時間沒有加入新按鈕。不過 Items 屬性會被存在 ViewState 中,所以當執行「PostBack」按鈕時,就會出現我們剛剛新增的按鈕。</p> <p><strong>三、解決方式</strong><br /> 要解決上述「測試二」的問題,只要覆寫 TBToolbar 控制項的 Render 方法,在 Render 前執行 RecreateChildControls 方法,強制重建子控制項。</p> <pre><code> ''' <summary> ''' 覆寫 Render 方法。 ''' </summary> Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Me.RecreateChildControls() MyBase.Render(writer) End Sub </code></pre> <p>再一次執行「測試二」的動作,就會發現執行結果就會正常了。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay15_E8C/image_thumb_5.png" alt="" /></p> <p><strong>四、結語</strong><br /> 在複合控制項的 Render 前執行 RecreateChildControls 方法可以強制重建子控制項,可是這樣又會引發另一個問題,那就是當直接存取子控制項去修改子控制項的屬性後,一旦在 Render 又重建子控制項,那之前設定子控制項狀態又被全部重建了,所以需特別注意有這樣的情形。另外複合控制項有可能重覆執行建立子控制的動作,在執行效能上也比較不佳。</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/16/5695.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/16/5695.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-16 00:14:12</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day14] 繼承 CompositeControl 實作 Toolbar 控制項</title> <link>https://ithelp.ithome.com.tw/articles/10012339?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012339?sc=rss.iron</guid> <description><![CDATA[<p>之前我們簡單介紹過繼承 CompositeControl 來實作複合控制項,在本文我們將以 Toolbar 控制項為例,以複合控制項的作法(繼承 CompositeControl )來實作 To...]]></description> <content:encoded><![CDATA[<p>之前我們簡單介紹過繼承 CompositeControl 來實作複合控制項,在本文我們將以 Toolbar 控制項為例,以複合控制項的作法(繼承 CompositeControl )來實作 Toolbar 控制項,此工具列控制項包含 Items 屬性來描述工具列項目集合,依 Items 屬性的設定來建立工具列按鈕,另外包含 Click 事件可以得知使用按了那個按鈕。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/20081014234924792.rar" target="_blank">ASP.NET Server Control - Day14.rar</a><br /> <strong>一、工具列項目集合類別</strong><br /> 工具列包含多個按鈕,新增 TBToolbarItem 類別來描述工具列項目,TBToolbarItem 類別包含 Key、Text、Enabled 三個屬性;而 TBToolbarItemCollection 為 TBToolbarItem 的集合類別來描述工具列按鈕集合。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay14CompositeControlToolbar_12481/image_thumb.png" alt="" /></p> <p><strong>二、實作 TBToolbar 控制項</strong><br /> <strong>step1. 新增繼承 CompositeControl 的 TBToolbar 控制項</strong></p> <pre><code> < _ Description("工具列控制項。"), _ ParseChildren(True, "Items"), _ ToolboxData("<{0}:TBToolbar runat=server ></{0}:TBToolbar>") _ > _ Public Class TBToolbar Inherits CompositeControl End Class </code></pre> <p><strong>step2. 新增 Items 屬性,描述工具列項目集合</strong></p> <pre><code> ''' <summary> ''' 工具列項目集合。 ''' </summary> < _ Description("工具列項目集合。"), _ PersistenceMode(PersistenceMode.InnerProperty), _ DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _ Editor(GetType(CollectionEditor), GetType(UITypeEditor)) _ > _ Public ReadOnly Property Items() As TBToolbarItemCollection Get If FItems Is Nothing Then FItems = New TBToolbarItemCollection() End If Return FItems End Get End Property </code></pre> <p><strong>step3. 新增 Click 事件</strong><br /> TBToolbar 類別新增 Click 事件,當按下按鈕時會引發 Click 事件,由 Click 的事件引數 e.Key 可以得知使用者按了那個按鈕。</p> <pre><code> ''' <summary> ''' Click 事件引數。 ''' </summary> Public Class ClickEventArgs Inherits System.EventArgs Private FKey As String = String.Empty ''' <summary> ''' 項目鍵值。 ''' </summary> Public Property Key() As String Get Return FKey End Get Set(ByVal value As String) FKey = value End Set End Property End Class ''' <summary> ''' 按下工具列按鈕所引發的事件。 ''' </summary> < _ Description("按下工具列按鈕所引發的事件。") _ > _ Public Event Click(ByVal sender As Object, ByVal e As ClickEventArgs) ''' <summary> ''' 引發 Click 事件。 ''' </summary> Protected Overridable Sub OnClick(ByVal e As ClickEventArgs) RaiseEvent Click(Me, e) End Sub </code></pre> <p><strong>step4. 建立工具列按鈕集合</strong><br /> 覆寫 CreateChildControls 方法,依 Items 屬性的設定,來建立工具列中的按鈕集合。每個按鈕的 Click 事件都導向 ButtonClickEventHandler 方法,來處理所有按鈕的 Click 動作,並引發 TBToolbar 的 Click 事件。</p> <pre><code> Private Sub ButtonClickEventHandler(ByVal sender As Object, ByVal e As EventArgs) Dim oButton As Button Dim oEventArgs As ClickEventArgs oButton = CType(sender, Button) oEventArgs = New ClickEventArgs() oEventArgs.Key = oButton.ID OnClick(oEventArgs) End Sub ''' <summary> ''' 建立子控制項。 ''' </summary> Protected Overrides Sub CreateChildControls() Dim oItem As TBToolbarItem Dim oButton As Button For Each oItem In Me.Items oButton = New Button() oButton.Text = oItem.Text oButton.Enabled = oItem.Enabled oButton.ID = oItem.Key AddHandler oButton.Click, AddressOf ButtonClickEventHandler Me.Controls.Add(oButton) Next MyBase.CreateChildControls() End Sub </code></pre> <p><strong>三、測試程式</strong><br /> 在頁面拖曳 TBToolbar 控制項,並設定 Items 屬性,如入新增、修改、刪除三個按鈕。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay14CompositeControlToolbar_12481/image_thumb_1.png" alt="" /></p> <p>在 TBToolbar 控制項的 Click 事件加入測試程式碼,輸出引發 Click 事件的 e.Key。</p> <pre><code> Protected Sub TBToolbar1_Click(ByVal sender As Object, ByVal e As Bee.Web.WebControls.TBToolbar.ClickEventArgs) Handles TBToolbar1.Click Me.Response.Write(String.Format("您按了 {0}", e.Key)) End Sub </code></pre> <p>執行程式,當按了工具列上的按鈕時,就會引發 Click 事件,並輸出該按鈕對應的 Key。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay14CompositeControlToolbar_12481/image_thumb_2.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/15/5687.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/15/5687.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-15 00:13:50</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day13] Flash 控制項</title> <link>https://ithelp.ithome.com.tw/articles/10012267?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012267?sc=rss.iron</guid> <description><![CDATA[<p>Flash 也是網頁常用的 ActiveX 插件,在本文中將繼承 TBActiveX 下來撰寫 TBFlash 控制項,用來輸出網頁套用 Flash 的相關 HTML 碼。<br /> 程式碼下...]]></description> <content:encoded><![CDATA[<p>Flash 也是網頁常用的 ActiveX 插件,在本文中將繼承 TBActiveX 下來撰寫 TBFlash 控制項,用來輸出網頁套用 Flash 的相關 HTML 碼。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/2008101323519608.rar" target="_blank">ASP.NET Server Control - Day13.rar</a></p> <p><strong>一、網頁 Flash 的原始 HTML 碼</strong><br /> 我們先觀查在網頁中套用 Flash 插件的原始 HTML 碼,以點部落首頁抬頭的 Flash 原始碼為例如下,其中 <object> tag 的 codebase attribute 是指 Flash 插件的下載位置及版本。</p> <pre><code><object id="ShockwaveFlash2" height="90" width="728" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"> <param value="http://files.dotblogs.com.tw/dotjum/ad/debug.swf" name="movie"/> <param value="high" name="quality"/> <param value="#000000" name="bgcolor"/> <embed height="90" width="728" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" quality="high" src="http://files.dotblogs.com.tw/dotjum/ad/debug.swf"/> </object> </code></pre> <p>在 <object> tag 中必要的 attribute 為 classid、codebase、movie、width、height,而 <embed> tag 的必要 attribute 為 src、pluginspage、width、height,其他選擇性的 attribute 可參閱以下網頁。</p> <p>Flash OBJECT and EMBED tag attributes<br /> <a href="http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_12701" target="_blank"><a href="http://kb.adobe.com/selfservice/viewContent.do?externalId=tn%5C_12701" target="_blank">http://kb.adobe.com/selfservice/viewContent.do?externalId=tn\_12701</a></a></p> <p><strong>二、實作 TFlash 控制項</strong><br /> 了解 Flash 的原始 HTML 碼後,我們就可以開始著手撰寫 TBFlash 控制項,想辨法來輸出所需要的 HTML 碼。</p> <p><strong>step1. 新增 TBFlash 控制項繼承至 TBActiveX</strong><br /> 我們先在 TBActiveX 控制項新增一個 CodeBase 屬性,用來設定 ActiveX 插入的下載位置及版本,然後新增 TBFlash 控制項繼承至 TBActiveX,並在建構函式中設定 MyBase.ClassId 及 MyBase.CodeBase 屬性。</p> <pre><code> Public Class TBFlash Inherits TBActiveX ''' <summary> ''' 建構函式。 ''' </summary> Sub New() MyBase.ClassId = "D27CDB6E-AE6D-11CF-96B8-444553540000" MyBase.CodeBase = "http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0" End Sub End Class </code></pre> <p><strong>step2. 加入相關屬性</strong><br /> 在 TBFlash 加入 MovieUrl 及 Quality 屬性,MovieUrl 為 Flash 檔案來源,Quality 為影音品質。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay13Flash_13AEC/image_2.png" alt="" /></p> <p><strong>step3. 輸出 Flash 相關參數</strong><br /> 覆寫 CreateChildControls 方法,輸出 MovieUrl 及 Quality 屬性對應的參數,以及在 Params 集合屬性設定的參數。</p> <pre><code> ''' <summary> ''' 加入 MediaPlayer 參數。 ''' </summary> ''' <param name="Name">參數名稱。</param> ''' <param name="Value">參數值。</param> Private Sub AddParam(ByVal Name As String, ByVal Value As String) Dim oParam As TBActiveXParam oParam = New TBActiveXParam(Name, Value) Me.Params.Add(oParam) End Sub ''' <summary> ''' 建立 Embed 標記。 ''' </summary> Private Function CreateEmbed() As HtmlControls.HtmlGenericControl Dim oEmbed As HtmlControls.HtmlGenericControl Dim oParam As TBActiveXParam oEmbed = New HtmlControls.HtmlGenericControl() oEmbed.TagName = "embed" oEmbed.Attributes("src") = Me.ResolveClientUrl(Me.MovieUrl) oEmbed.Attributes("pluginspage") = "http://www.macromedia.com/go/getflashplayer" oEmbed.Attributes("height") = Me.Height.ToString oEmbed.Attributes("width") = Me.Width.ToString 'Embed 的 Attributes 加入 Params 集合屬性的設定 For Each oParam In Me.Params If oParam.Name <> "movie" Then oEmbed.Attributes(oParam.Name) = oParam.Value End If Next Return oEmbed End Function ''' <summary> ''' 建立子控制項。 ''' </summary> Protected Overrides Sub CreateChildControls() Dim oEmbed As HtmlControls.HtmlGenericControl '加入 movie 參數 AddParam("movie", Me.ResolveClientUrl(Me.MovieUrl)) '加入 quality 參數 If Me.Quality <> EQuality.NotSet Then AddParam("quality", Me.Quality.ToString.ToLower) End If MyBase.CreateChildControls() oEmbed = CreateEmbed() Me.Controls.Add(oEmbed) End Sub </code></pre> <p><strong>三、測試程式</strong><br /> 在頁面拖曳 TBFlash 控制項,設定 MovieUrl 及 Quality 屬性,若有需要加入其他參數,可自行設定 Params 集合屬性。執行程式就可以在頁面上看到呈現出來的 Flash。</p> <pre><code> <bee:TBFlash ID="TBFlash1" runat="server" Height="90px" MovieUrl="http://files.dotblogs.com.tw/dotjum/ad/debug.swf" Quality="High" Width="728px"> </bee:TBFlash> </code></pre> <p><img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay13Flash_13AEC/image_4.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/14/5674.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/14/5674.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-14 00:16:30</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day12] 繼承 TBActiveX 重新改寫 TBMediaPlayer 控制項</title> <link>https://ithelp.ithome.com.tw/articles/10012196?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012196?sc=rss.iron</guid> <description><![CDATA[<p>上篇介紹的 TBActiveX 控制項,它可以支援網頁 Media Player 的設定,這跟前面提及的 TBMediaPlayer 功能相同。TBActiveX 具有網頁設定 ActiveX ...]]></description> <content:encoded><![CDATA[<p>上篇介紹的 TBActiveX 控制項,它可以支援網頁 Media Player 的設定,這跟前面提及的 TBMediaPlayer 功能相同。TBActiveX 具有網頁設定 ActiveX 通用屬性,所以 TBMediaPlayer 基本上是可以由 TBActiveX 繼承下來,再加入 Media Player 特有的屬性即可。本文將原來的 TBMediaPlayer 控制項,繼承的父類別由 WebControl 改為 TBActiveX 類別,重新改寫 TBMediaPlayer 控制項。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/200810130325962.rar" target="_blank">ASP.NET Server Control - Day12.rar</a></p> <p><strong>一、改寫 TBMediaPlayer 控制項</strong><br /> TBMediaPlayer 控制項原本是繼承 WebControl,現改繼承對象為 TBActiveX,來重新改寫 TBMediaPlayer 控制項。</p> <p><strong>step1. TBMediaPlayer 繼承至 TBActiveX</strong><br /> 新增 TBMediaPlayer 控制項,繼承至 TBActiveX,並在建構函式設定 Media Player ActiveX 的 ClassId。</p> <pre><code> Public Class TBMediaPlayer Inherits TBActiveX ''' <summary> ''' 建構函式。 ''' </summary> Sub New() MyBase.ClassId = "6BF52A52-394A-11D3-B153-00C04F79FAA6" End Sub End Class </code></pre> <p><strong>step2. 加入相關屬性</strong><br /> 跟原來的 TBMediaPlayer 控制項一樣,加入 Url、AutoStart、UIMode 三個屬性,可視情形加入需要設定的屬性。</p> <p><strong>step3. 加入 Media Player 參數</strong><br /> 覆寫 CreateChildControls 方法,動態依屬性設定在 Params 集合屬性加入參數。雖然 TBMediaPlayer 控制項目前只有 Url、AutoStart、UIMode 三個屬性,但是父類別 TBActiveX 具有 Params 集合屬性,所以開發人員可以視需求加入其他未定義的參數。</p> <pre><code> ''' <summary> ''' 加入 MediaPlayer 參數。 ''' </summary> ''' <param name="Name">參數名稱。</param> ''' <param name="Value">參數值。</param> Private Sub AddParam(ByVal Name As String, ByVal Value As String) Dim oParam As TBActiveXParam oParam = New TBActiveXParam(Name, Value) Me.Params.Add(oParam) End Sub ''' <summary> ''' 覆寫 CreateChildControls 方法。 ''' </summary> Protected Overrides Sub CreateChildControls() '加入 Url 參數 If Me.Url <> String.Empty Then AddParam("URL", Me.ResolveClientUrl(Me.Url)) End If '加入 autoStart 參數 If Me.AutoStart Then AddParam("autoStart", "true") End If '加入 uiMode 參數 If Me.UIMode <> EUIMode.NotSet Then AddParam("uiMode", Me.UIMode.ToString) End If MyBase.CreateChildControls() End Sub </code></pre> <p><strong>二、執行程式</strong><br /> 在頁面拖曳 TBMediaPlayer 控制項,設定 Url、AutoStart、UIMode 屬性,若有需要加入其他參數,可自行設定 Params 集合屬性。執行程式就可以在頁面上看到呈現出來的 Media Player。</p> <pre><code> <bee:TBMediaPlayer ID="TBMediaPlayer1" runat="server" AutoStart="True" Height="249px" Url="D:\Movie_01.wmv" Width="250px"> </bee:TBMediaPlayer> </code></pre> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/13/5663.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/13/5663.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-13 00:13:29</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day11] ActiveX 伺服器控制項</title> <link>https://ithelp.ithome.com.tw/articles/10012159?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012159?sc=rss.iron</guid> <description><![CDATA[<p>Media Player 與 Flash 之類在網頁上執行的外掛控制項,都是屬於 ActiveX 控制項,它們套用在 HTML 碼中的方式差不多,除了要指定 ClassID 以外,ActiveX...]]></description> <content:encoded><![CDATA[<p>Media Player 與 Flash 之類在網頁上執行的外掛控制項,都是屬於 ActiveX 控制項,它們套用在 HTML 碼中的方式差不多,除了要指定 ClassID 以外,ActiveX 使用的參數(相當於 ActiveX 控制項的屬性)以 Param Tag 來表示。本文標題命名為「ActiveX 伺服器控制項」就是避免誤解為 ActiveX 控制項,而是在 ASP.NET 中輸出 ActiveX 相關 HTML 碼的伺服器控制項;我們可透過 ActiveX 伺服器控制項可以用來輸出網頁上引用 ActiveX 的通用 HTML 碼,另外 ActiveX 的參數會以集合屬性來呈現,所以也會一併學習到集合屬性的撰寫方式。<br /> 程式碼下載:<a href="http://files.dotblogs.com.tw/jeff377/0810/200810124846794.rar" target="_blank">ASP.NET Server Control - Day11.rar</a></p> <p><strong>一、集合屬性</strong><br /> ActiveX 的 Param 參數是集合屬性,所以我們定義了 TBActiveParam 類別描述 ActiveX 參數,包含 Name 及 Value 屬性;而 TBActiveXParamCollection 為 TBActiveParam 的集合類別,用來描述 ActiveX 參數集合。TBActiveXParamCollection 繼承 CollectionBase,加入操作集合的 Add、Insert、Remove、IndexOf、Contains 等方法,關於集合屬性的用法可以參閱筆者在部落格的「<a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1747.aspx" target="_blank">撰寫伺服器控制項的集合屬性 (CollectionBase)</a>」一文中有詳細說明。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay11ActiveX_166/image_thumb_1.png" alt="" /></p> <p><strong>二、實作 ActiveX 伺服器控制項</strong><br /> <strong>step1. 新增繼承 WebControl 的 TBActiveX</strong></p> <p><strong>step2. 覆寫 TagKey 屬性,傳回 object 的 Tag</strong></p> <pre><code> Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag Get Return HtmlTextWriterTag.Object End Get End Property </code></pre> <p><strong>step3. 新增 ClassId 屬性,描述 ActiveX 的 ClassId</strong><br /> 定義 ClassId 屬性,並覆寫 AddAttributesToRender 來輸出此屬性。</p> <pre><code> ''' <summary> ''' 覆寫 AddAttributesToRender 方法。 ''' </summary> Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter) '加入 MediaPlayer ActiveX 元件的 classid writer.AddAttribute("classid", String.Format("clsid:{0}", Me.ClassId)) MyBase.AddAttributesToRender(writer) End Sub </code></pre> <p><strong>step4. 新增 Params 屬性,描述 ActiveX 的參數集合</strong><br /> 定義 Params 屬性,型別為 TBActiveXParamCollection 類別,套用 EditorAttribute 設定 CollectionEditor 為集合編輯器。</p> <pre><code> ''' <summary> ''' ActiveX 控制項參數集合。 ''' </summary> < _ Description("控制項參數集合。"), _ PersistenceMode(PersistenceMode.InnerProperty), _ DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _ Editor(GetType(CollectionEditor), GetType(UITypeEditor)) _ > _ Public ReadOnly Property Params() As TBActiveXParamCollection Get If FParams Is Nothing Then FParams = New TBActiveXParamCollection() End If Return FParams End Get End Property </code></pre> <p>當編輯 Params 時,會使用的 CollectionEditor 集合編輯器。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay11ActiveX_166/image_thumb_2.png" alt="" /></p> <p><strong>step5. 輸出 ActiveX 參數</strong><br /> 覆寫 CreateChildControls 方法,在此方法依 Params 集合屬性定義依序來輸出 ActiveX 的參數集合。</p> <pre><code> Private Sub AddParam(ByVal Name As String, ByVal Value As String) Dim oParam As HtmlControls.HtmlGenericControl oParam = New HtmlControls.HtmlGenericControl("param") oParam.Attributes.Add("name", Name) oParam.Attributes.Add("value", Value) Me.Controls.Add(oParam) End Sub ''' <summary> ''' 建立子控制項。 ''' </summary> Protected Overrides Sub CreateChildControls() Dim oParam As TBActiveXParam '加入 ActiveX 參數集合 For Each oParam In Me.Params AddParam(oParam.Name, oParam.Value) Next MyBase.CreateChildControls() End Sub </code></pre> <p><strong>三、執行程式</strong><br /> 上一篇我們使用 TBMediaPlayer 控制項來設定 Media Player,在此我們改用 TBActiveX 控制項來設定 Media Player,一樣可以呈現相同的結果。</p> <pre><code> <bee:TBActiveX ID="TBActiveX1" runat="server" ClassId="6BF52A52-394A-11D3-B153-00C04F79FAA6" Height="250px" Width="250px"> <Params> <bee:TBActiveXParam Name="URL" Value="d:/Movie_01.wmv" /> <bee:TBActiveXParam Name="autoStart" Value="true" /> </Params> </bee:TBActiveX> </code></pre> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/12/5659.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/12/5659.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-12 04:20:27</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day10] Media Player 控制項</title> <link>https://ithelp.ithome.com.tw/articles/10012142?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012142?sc=rss.iron</guid> <description><![CDATA[<p>我們在前面幾篇文章中,已經簡要的對伺服器控制項做了基本介紹,接下來的幾篇文章中我們要開始實作伺服器控制項。在網頁上常使用 Media Player 來撥放影片,在 ASP.NET 中沒有現成的控...]]></description> <content:encoded><![CDATA[<p>我們在前面幾篇文章中,已經簡要的對伺服器控制項做了基本介紹,接下來的幾篇文章中我們要開始實作伺服器控制項。在網頁上常使用 Media Player 來撥放影片,在 ASP.NET 中沒有現成的控制項來處理 Media Player,只能在 aspx 中加入 Media Player 相關的程式碼;本文將示範如何製作一個 Media Player 控制項,讓我們在 ASP.NET 中更方便的使用 Media Player。<br /> 程式碼下載:<a href="http://Files.Dotblogs.com.tw/jeff377%5C0810%5C2008101122230798.rar" target="_blank">ASP.NET Server Control - Day10.rar</a></p> <p><strong>一、Media Player 原始 HTML 碼</strong><br /> 在製作 Media Player 控制項之前,你需要先了解 Media Player 原本的 HTML 碼,控制項的作用就是想辨法把這些寫在 aspx 中的 HTML 碼交由控制項來輸出而已,以下為網頁中加入 Media Player 的 HTML 碼範例。</p> <pre><code><OBJECT id="VIDEO" width="320" height="240" style="position:absolute; left:0;top:0;" CLASSID="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6" type="application/x-oleobject"> <PARAM NAME="URL" VALUE="your file or url"> <PARAM NAME="SendPlayStateChangeEvents" VALUE="True"> <PARAM NAME="AutoStart" VALUE="True"> <PARAM name="uiMode" value="none"> <PARAM name="PlayCount" value="9999"> </OBJECT> </code></pre> <p>在下面的網頁有 Media Player 相關參數說明。<br /> <a href="http://www.mioplanet.com/rsc/embed_mediaplayer.htm" target="_blank"><a href="http://www.mioplanet.com/rsc/embed%5C_mediaplayer.htm" target="_blank">http://www.mioplanet.com/rsc/embed\_mediaplayer.htm</a></a></p> <p><strong>二、實作 Media Player 控制項</strong><br /> <strong>step1.首先新增由 WebControl 繼承下來的 TBMediaPlayer 類別。</strong></p> <pre><code> Public Class TBMediaPlayer Inherits WebControl End Class </code></pre> <p><strong>step2.覆寫 TagKey 屬性,傳回 object 的 Tag。</strong></p> <pre><code> Protected Overrides ReadOnly Property TagKey() As System.Web.UI.HtmlTextWriterTag Get Return HtmlTextWriterTag.Object End Get End Property </code></pre> <p><strong>step3.輸出 HTML Tag 的 Attribute</strong><br /> 在 object Tag 中包含 style、classid、type 二個 Attribute,WebControl 本身會處理 style,所以我們只需覆寫 AddAttributesToRender 方法,處理 classid 及 type 二個 Attribute,記得覆寫 AddAttributesToRender 方法時最後要加入 MyBase.AddAttributesToRender(writer),才會執行父類別的 AddAttributesToRender 方法。</p> <pre><code> ''' <summary> ''' 覆寫 AddAttributesToRender 方法。 ''' </summary> Protected Overrides Sub AddAttributesToRender(ByVal writer As System.Web.UI.HtmlTextWriter) '加入 MediaPlayer ActiveX 元件的 classid writer.AddAttribute("classid", "clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6") writer.AddAttribute("type", "application/x-oleobject") MyBase.AddAttributesToRender(writer) End Sub </code></pre> <p><strong>step4.加入 Url 屬性</strong><br /> 加入指定播放檔案來源的 Url 屬性,其中套用 EditorAttribute 設定 UrlEditor,使用 Url 專用的編輯器來設定屬性。</p> <pre><code> ''' <summary> ''' 播放檔案來源。 ''' </summary> < _ Description("播放檔案來源"), _ Bindable(True), _ Category("Appearance"), _ Editor(GetType(UrlEditor), GetType(UITypeEditor)), _ UrlProperty(), _ DefaultValue("") _ > _ Public Property Url() As String Get Return FUrl End Get Set(ByVal value As String) FUrl = value End Set End Property </code></pre> <p><strong>step5.輸出 Url 參數</strong><br /> 接下來覆寫 CreateChildControls 方法,輸出 Url 參數。</p> <pre><code> ''' <summary> ''' 加入參數。 ''' </summary> ''' <param name="Name">參數名稱。</param> ''' <param name="Value">參數值。</param> Private Sub AddParam(ByVal Name As String, ByVal Value As String) Dim oParam As HtmlControls.HtmlGenericControl oParam = New HtmlControls.HtmlGenericControl("param") oParam.Attributes.Add("name", Name) oParam.Attributes.Add("value", Value) Me.Controls.Add(oParam) End Sub Protected Overrides Sub CreateChildControls() '加入 Url 參數 AddParam("url", Me.ResolveClientUrl(Me.Url)) MyBase.CreateChildControls() End Sub </code></pre> <p><strong>step6.輸出 Media Player 其他參數</strong><br /> 你可以將 Media Player 的參數設定皆使用相對應的屬性來設定,然後使用 step5 的方式來輸出所有設定的參數值。</p> <p><strong>三、Media Player 控制項程式碼</strong><br /> Media Player 控制項的完整程式碼如下,此控制項只加入 URL、AutoStart、UIMode 三個參數,你可以視需求情形將使用到的參數定義為屬性來做設定即可。<br /> 因為此處有字元數限制,完整的程式碼請參閱筆者部落格相同文章<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/11/5655.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/11/5655.aspx</a></p> <p><strong>四、執行程式</strong><br /> 把 TBMediaPlayer 控制項拖曳至頁面,設定好屬性後,執行程式就可以在頁面上看到呈現出來的 Media Player。</p> <pre><code> <bee:TBMediaPlayer ID="TBMediaPlayer1" runat="server" Height="250px" Width="250px" Url="~/test.wmv" /> </code></pre> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/11/5655.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/11/5655.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-11 19:08:27</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day9] 控制項常用 Attribute 介紹(2)</title> <link>https://ithelp.ithome.com.tw/articles/10012060?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012060?sc=rss.iron</guid> <description><![CDATA[<p>接續上篇 Attribute 的介紹,本文將再介紹一些伺服器控制項常用的 Attribute。<br /> <strong>六、ToolboxDataAttribute 類別</strong><...]]></description> <content:encoded><![CDATA[<p>接續上篇 Attribute 的介紹,本文將再介紹一些伺服器控制項常用的 Attribute。<br /> <strong>六、ToolboxDataAttribute 類別</strong><br /> 作用:指定當自訂控制項從工具箱拖曳到頁面時,為此自訂控制項產生的預設標記。<br /> 當我們新增一個伺服器控制項,它就會預設在控制項類別套用 ToolboxDataAttribute,定義在控制項在 aspx 程式碼中的標記。</p> <pre><code><ToolboxData("<{0}:TBButton runat=server ></{0}:TBButton>")> _ Public Class TBButton Inherits System.Web.UI.WebControls.Button End Class </code></pre> <p><strong>七、DefaultPropertyAttribute 類別</strong><br /> 作用:指定類別的預設屬性。<br /> 下面的範例中,MyTextbox 類別套用 DefaultPropertyAttribute,設定 Text 屬性為預設屬性。</p> <pre><code><DefaultProperty("Text")> _ Public Class MyTextbox Inherits WebControl Public Property Text() As String Get Return CType(Me.ViewState("Text"), String) End Get Set(ByVal value As String) Me.ViewState("Text") = value End Set End Property End Class </code></pre> <p><strong>八、DefaultEventAttribute 類別</strong><br /> 作用:指定控制項的預設事件。<br /> 下面的範例中,MyTextbox 類別套用 DefaultEventAttribute,設定 TextChanged 為預設屬性。</p> <pre><code><DefaultEvent("TextChanged")> _ Public Class MyTextbox Inherits WebControl ''' <summary> ''' TextChanged 事件。 ''' </summary> Public Event TextChanged As EventHandler End Class </code></pre> <p>當設計階段在頁面上的 MyTextbox 控制項點二下時,就會產生預設事件的處理函式。</p> <pre><code> Protected Sub MyTextbox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyTextbox3.TextChanged End Sub </code></pre> <p><strong>九、LocalizableAttribute 類別</strong><br /> 作用:指定屬性是否應該當地語系化。<br /> 當屬性套用設為為 true 的 LocalizableAttribute 時,其屬性值會儲存在資源檔中,未來不需修改程式碼就可以將這些資源檔當地語系化。</p> <pre><code> <Localizable(True)> _ Public Property Text() As String Get Return CType(Me.ViewState("Text"), String) End Get Set(ByVal value As String) Me.ViewState("Text") = value End Set End Property </code></pre> <p><strong>十、DesignerAttribute 類別</strong><br /> 作用:設定控制項在設計階段服務的類別。<br /> 指定一個設計階段的服務類別,來管理控制項在設計階段的行為,例如控制項的設計階段外觀、智慧標籤內容。例如下面範例的 TBGridView 控制項就定義了 TBGridViewDesigner 來實作設計階段的行為,未來的章節中也會介紹如何實作控制項的 Designer。</p> <pre><code> < Designer(GetType(TBGridViewDesigner)) > _ Public Class TBGridView Inherits GridView End Class </code></pre> <p><strong>十一、EditorAttribute 類別</strong><br /> 作用:指定在屬性視窗中編輯屬性值的編輯器。<br /> 例如 ListBox 控制項的 Items 屬性,在屬性視窗編輯 Items 屬性時,會彈出 Items 集合屬性的編輯器。以下範例就是定義 Items 屬性的編輯器類別為 TBListItemsCollectionEditor,未來的章節中也會介紹如何實作屬性的 Editor。</p> <pre><code> <Editor(GetType(TBListItemsCollectionEditor), GetType(System.Drawing.Design.UITypeEditor))> _ Public Overrides ReadOnly Property Items() As ListItemCollection </code></pre> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/10/5653.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/10/5653.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-10 11:27:02</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day8] 控制項常用 Attribute 介紹(1)</title> <link>https://ithelp.ithome.com.tw/articles/10012016?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10012016?sc=rss.iron</guid> <description><![CDATA[<p>Property 與 Attribute 二個術語一般都是翻譯成「屬性」,例如類別的屬性,是使用英文的 Property,而 HTML/XML 的元素屬性,使用的英文則是 Attribute。在...]]></description> <content:encoded><![CDATA[<p>Property 與 Attribute 二個術語一般都是翻譯成「屬性」,例如類別的屬性,是使用英文的 Property,而 HTML/XML 的元素屬性,使用的英文則是 Attribute。在 .NET 中 Property 與 Attribute 的意義及用法不同,不過微軟線上文件也將它翻譯為「屬性」,這可能讓人發生困擾及誤解;筆者比較喜歡的方式就是 Property 是屬性,Attribute 就維持原文。在 .NET 中類別或屬性上可以套用上不同的 Attribute,使類別或屬性具有不同的特性,本文將介紹一些在伺服器控制項常使用到的 Attribute。<br /> <strong>一、DescriptionAttribute 類別</strong><br /> 作用:指定控制項或屬性的描述。<br /> 當 DescriptionAttribute 套用至控制項的類別時,設定的描述內容就會出現在工具箱中控制項的提示。</p> <pre><code><Description("按鈕控制項")> _ Public Class TBButton Inherits System.Web.UI.WebControls.Button End Class </code></pre> <p><img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay7Attribute_D3/image_thumb.png" alt="" /><br /> 當 DescriptionAttribute 套用至控制項的屬性時,在屬性視窗下面就會出現設定的屬性描述內容。</p> <pre><code><Description("詢問訊息")> _ Public Property ConfirmMessage() As String </code></pre> <p><img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay7Attribute_D3/image_thumb_1.png" alt="" /></p> <p><strong>二、DefaultValueAttribute 類別</strong><br /> 作用:指定屬性的預設值。<br /> 使用 DefaultValueAttribute 設定屬性的預設值,若設定的屬性值與預設值相同時,此屬性值就不會出現在 aspx 程式碼中;筆者強烈建議屬性一定套用 DefaultValueAttribute,一來在 aspx 中的程式碼會比較少,另外一個重點是若因為某些因素需要修改屬性的預設值時,所有已開發頁面的控制項屬性值會一併變更;因為當初屬性值是預設值,沒有被寫入 aspx 程式碼中,所以一但控制項的屬性預設值變更,頁面已佈屬的控制項的屬性值就會全面適用。</p> <pre><code> Private FConfirmMessage As String = String.Empty <DefaultValue("")> _ Public Property ConfirmMessage() As String Get Return FConfirmMessage End Get Set(ByVal value As String) FConfirmMessage = value End Set End Property </code></pre> <p><strong>三、CategoryAttribute 類別</strong><br /> 作用:指定屬性或事件的分類名稱,當屬性視窗設定為 [分類] 模式時,以群組方式來顯示屬性或事件。<br /> 例如設定 ConfirmMessage 屬性在 "Behavior" 分類,則 ConfirmMessage 屬性會被歸類到「行為」分類。</p> <pre><code> <Category("Behavior")> _ Public Property ConfirmMessage() As String </code></pre> <p><img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay7Attribute_D3/image_thumb_2.png" alt="" /></p> <p><strong>四、BindableAttribute 類別</strong><br /> 作用:指定成員是否通常使用於繫結。<br /> 在資料繫結設定視窗中中,指定屬性是否預設會出現在屬性清單中。</p> <pre><code><Bindable(True)> _ Public Property ConfirmMessage() As String </code></pre> <p><img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay7Attribute_D3/image_thumb_3.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/09/5647.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/09/5647.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-09 21:11:43</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day7] 設定工具箱的控制項圖示</title> <link>https://ithelp.ithome.com.tw/articles/10011933?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10011933?sc=rss.iron</guid> <description><![CDATA[<p>當我們把自訂控制項加入到工具箱中時,你會發現所有的控制項預設都是同樣的圖示,雖然控制項的圖示不變更不會有什麼影響,不過我們還是希望為自訂控制項加上合適的外衣,本文將介紹如何設定工具箱控制項圖示。...]]></description> <content:encoded><![CDATA[<p>當我們把自訂控制項加入到工具箱中時,你會發現所有的控制項預設都是同樣的圖示,雖然控制項的圖示不變更不會有什麼影響,不過我們還是希望為自訂控制項加上合適的外衣,本文將介紹如何設定工具箱控制項圖示。<br /> <strong>一、加入控制項圖示檔</strong><br /> 首先要準備一個 16 x 16 的點陣圖(bmp),如下所示。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay6_BA88/image_thumb_1.png" alt="" /><br /> 將此圖檔加入至「伺服器控制項專案」中,可以如下圖所示,用一個特定的資料夾來儲存所有工具箱的圖示。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay6_BA88/image_thumb_2.png" alt="" /><br /> 然後在圖檔的屬性視窗中,設定建置動作為「內嵌資源」。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay6_BA88/image_thumb_3.png" alt="" /><br /> <strong>二、設定控制項的圖示</strong><br /> 首先定義一個 TBResource 類別,此為一個空的類別,其命名空間需與根命名空間相同,做為引用資源檔時使用。並加上控制項圖示的 WebResource 定義,因為根命名空間是 Bee.Web,而圖檔名稱為 TBButton.bmp,所以定義如下所示。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay6_BA88/image_thumb_5.png" alt="" /><br /> 假設我們要設定 TBButton 的工具箱圖示,則在 TBButton 類別套用 ToolboxBitmapAttribute 如下,其中第一個參數為 GetType(TBResource),第二個參數為圖檔檔名。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay6_BA88/image_thumb_7.png" alt="" /><br /> 重新編輯伺服器控制項專案,再將 Bee.Web.dll 組件的控制項加入工具箱中,你就可以發現 TBButton 的圖示已經變成設定的圖示了。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay6_BA88/image_thumb_8.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/08/5624.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/08/5624.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-08 22:26:13</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day6] 事件與 PostBack</title> <link>https://ithelp.ithome.com.tw/articles/10011861?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10011861?sc=rss.iron</guid> <description><![CDATA[<p>一般類別的事件撰寫很單純,不過在 ASP.NET 中與前端使用者互動產生的事件就不是那麼簡單了;在以往的 ASP 年代是沒有事件這回事的,而在 ASP.NET 把網頁程式撰寫真正的物件導向化,用...]]></description> <content:encoded><![CDATA[<p>一般類別的事件撰寫很單純,不過在 ASP.NET 中與前端使用者互動產生的事件就不是那麼簡單了;在以往的 ASP 年代是沒有事件這回事的,而在 ASP.NET 把網頁程式撰寫真正的物件導向化,用戶端使用者的操作透過 PostBack 來產生相對應的事件。例如前端使用者按鈕後會引發伺服端 Button 的 Click 事件,當前端使用者輸入文字框完畢後離開後會引發伺服端 TextBox 的 TextChanged 事件,在本文中就是要說明如何透過 PostBack 來產生與使用者互動的事件。<br /> <strong>一、IPostBackEventHandler 與 IPostBackDataHandler 介面</strong><br /> 控制項要處理 PostBack 產生的事件,必須實作 IPostBackEventHandler 或 IPostBackDataHandler 介面,這二個介面有什麼差別呢?例如 Button 是實作IPostBackEventHandler 介面,由控制項產生的 PostBack 直接引發事件,如 Button 的 Click 事件。例如 TextBox 是實作 IPostBackDataHandler 介面,當頁面產生 PostBack 時,會傳用戶端輸入的新值給控制項,由控制項本身去決定是否引發該事件;以 TextBox 舉例來說,它會判斷新值與舊值不同時才會引發 TextChanged 事件。</p> <p><strong>二、IPostBackEventHandler 介面實作</strong><br /> 首先介紹 IPostBackEventHandler 介面,它包含 RaisePostBackEvent 方法,控制項在此方法中需處理要引發那些事件。我們繼承 WebControl 撰寫 MyButton 類別來說明 IPostBackEventHandler 介面,我們簡化控制項程式碼直接在 Render 方法輸入按鈕的 HTML 原始碼,並定義一個 Click 事件。實作 IPostBackEventHandler 介面的 RaisePostBackEvent 方法,在此方法中直接引發 Click 事件。</p> <pre><code>Public Class MyButton Inherits WebControl Implements IPostBackEventHandler ''' <summary> ''' Click 事件。 ''' </summary> Public Event Click As EventHandler ''' <summary> ''' 引發 Click 事件。 ''' </summary> Private Sub OnClick(ByVal e As EventArgs) RaiseEvent Click(Me, e) End Sub Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent Dim e As New EventArgs() OnClick(e) '引發 Click 事件 End Sub ''' <summary> ''' 覆寫 Render 方法。 ''' </summary> Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Dim sHTML As String sHTML = String.Format("<INPUT TYPE=Submit Name={0} Value = '按鈕'/>", Me.UniqueID) writer.Write(sHTML) End Sub End Class </code></pre> <p>在頁面上拖曳 MyButton 控制項,在屬性視窗找到 Click 事件,點二下產生 Click 事件處理函式,並撰寫測試程式碼如下。</p> <pre><code> Protected Sub MyButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyButton1.Click Me.Page.Response.Write("MyButton Click 事件") End Sub </code></pre> <p>執行程式,當按下 MyButton 按鈕時,就會執行它的 RaisePostBackEvent 方法,進而引發 Click 事件,也就會執行 Click 事件處理函式的程式碼。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay5PostBack_143C0/image_2.png" alt="" /></p> <p><strong>三、IPostBackDataHandler 介面實作</strong><br /> IPostBackDataHandler 介面包含 LoadPostData 及 RaisePostDataChangedEvent 方法,當頁面 PostBack 時,會尋找具 IPostBackDataHandler 介面的控制項,先執行LoadPostData 方法,控制項在 LoadPostData 方法中會判斷用戶端傳入值決定是否引發事件,若 LoadPostData 傳回 True 表示要引發事件,此時會執行RaisePostDataChangedEvent 方法去決定要引發那些事件,反之傳回 False 表示不引發事件。</p> <p>我們繼承 WebControl 撰寫 MyText 類別來說明 IPostBackDataHandler 介面,我們簡化控制項程式碼直接在 Render 方法輸入文字框的 HTML 原始碼,並定義一個 TextChanged 事件。在 LoadPostData 方法中我們會判斷用戶端傳入值與目前的值是否不相同,不相同時才會傳回 True,此時才會執行 RaisePostDataChangedEvent 方法,進而引發 TextChanged 事件。</p> <pre><code>Public Class MyTextbox Inherits WebControl Implements IPostBackDataHandler Public Property Text() As String Get Return CType(Me.ViewState("Text"), String) End Get Set(ByVal value As String) Me.ViewState("Text") = value End Set End Property ''' <summary> ''' TextChanged 事件。 ''' </summary> Public Event TextChanged As EventHandler ''' <summary> ''' 引發 TextChanged 事件。 ''' </summary> Private Sub OnTextChanged(ByVal e As EventArgs) RaiseEvent TextChanged(Me, e) End Sub Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData '前端使用者輸入值 Dim oNewValue As String = postCollection.Item(postDataKey) If oNewValue <> Me.Text Then Me.Text = oNewValue Return True Else Return False End If End Function Public Sub RaisePostDataChangedEvent() Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent Dim e As New EventArgs() '引發 TextChanged 事件 OnTextChanged(e) End Sub ''' <summary> ''' 覆寫 Render 方法。 ''' </summary> Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Dim sHTML As String sHTML = String.Format("<INPUT Type=text Name={0} Value={1} >", Me.UniqueID, Me.Text) writer.Write(sHTML) End Sub End Class </code></pre> <p>在頁面上拖曳 MyTextbox 及 MyButton 控制項,在 MyButton 的 Click 及 MyTextbox 的 TextChanged 事件撰寫如下測試程式碼。</p> <pre><code> Protected Sub MyButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyButton1.Click Me.Page.Response.Write("MyButton Click 事件") Me.Page.Response.Write("<br/>") End Sub Protected Sub MyTextbox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyTextbox1.TextChanged Me.Page.Response.Write("MyTextbox TextChanged 事件") Me.Page.Response.Write("<br/>") End Sub </code></pre> <p>執行程式,在 MyTextbox 輸入 "A",再按下 MyButton,因為 MyTextbox 的值「空字串->"A"」,所以會引發 MyTextbox 的 TextChanged 事件及 MyButton 的 Click 事件。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay5PostBack_143C0/image_6.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格</p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-07 23:30:19</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day5] 屬性與 ViewState</title> <link>https://ithelp.ithome.com.tw/articles/10011745?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10011745?sc=rss.iron</guid> <description><![CDATA[<p>在 ASP.NET 中,控制項的屬性與 ViewState 有著密不可分的關係,透過 ViewState 才有辨法維護控制項的屬性值。在本文中將介紹屬性與 ViewState 的關係,並說明屬性...]]></description> <content:encoded><![CDATA[<p>在 ASP.NET 中,控制項的屬性與 ViewState 有著密不可分的關係,透過 ViewState 才有辨法維護控制項的屬性值。在本文中將介紹屬性與 ViewState 的關係,並說明屬性如何存取 ViewState 是比較有效率的方式。<br /> 當你加入一個「ASP.NET 伺服器控制項」時,類別中預設會有一個 Text 屬性寫法的範例如下所示,屬性的讀寫都是直接存取 ViewState,這是一般常見的控制項屬性寫法。可是這種屬性的寫法是沒有效率的,因為 ViewState 的成員是 Object 型別,每次讀取屬性時都是由 ViewState 取出指定鍵值的成員再轉型為屬性的型別,寫入屬性的動作也是直接寫入 ViewState 中。</p> <pre><code> Property Text() As String Get Dim s As String = CStr(ViewState("Text")) If s Is Nothing Then Return String.Empty Else Return s End If End Get Set(ByVal Value As String) ViewState("Text") = Value End Set End Property </code></pre> <p>比較好的方式應該是讀取 ViewState 成員只做一次型別轉換的動作,而寫入 ViewState 的動作可以在 Render 前做批次寫入的動作即可。為了達到這樣的需求,我們可以覆寫 LoadViewState 及 SaveViewState 方法來處理屬性與 ViewState 的存取動作;當控制項初始化後會執行 LoadViewState 方法,來載入 ViewState 還原的控制項狀態,當控制項 Render 之前,會執行 SaveViewState 方法,將控制項的最終狀態存入 ViewState 中,也就是在此方法之後對控制項所做的任何變更都將會被忽略。</p> <p>我們改寫屬性的寫法,不直接用 ViewState 來存取屬性,而是改用「屬性區域變數」來存取屬性,在 LoadViewState 時載入 ViewState 到屬性區域變數,而 SaveViewState 時再將屬性區域變數寫入 ViewState 中。我們依此方式將 Text 屬性改寫如下。</p> <pre><code> Private FText As String Property Text() As String Get Return FText End Get Set(ByVal Value As String) FText = Value End Set End Property ''' <summary> ''' 載入 ViewState 還原控制項狀態。 ''' </summary> Protected Overrides Sub LoadViewState(ByVal savedState As Object) If Not (savedState Is Nothing) Then ' Load State from the array of objects that was saved at vedViewState. Dim myState As Object() = CType(savedState, Object()) If Not (myState(0) Is Nothing) Then MyBase.LoadViewState(myState(0)) End If If Not (myState(1) Is Nothing) Then FText = CType(myState(1), String) End If End If End Sub ''' <summary> ''' 將控制項狀態寫入 ViewState 中。 ''' </summary> Protected Overrides Function SaveViewState() As Object Dim baseState As Object = MyBase.SaveViewState() Dim myState(1) As Object myState(0) = baseState myState(1) = FText Return myState End Function </code></pre> <p>利用上述的方式,我們可以在 LoadViewState 批次載入所有屬性值,而在 SaveViewState 批次寫入屬性值,如此在讀取屬性就不用一直做型別轉換的動作以改善效率。</p> <p><strong>結語</strong><br /> 雖然屬性一般都是儲存於 ViewState 中,不過若是一些無關緊要的屬性或是完全不會執行階段就變更的屬性,可以考慮不需要將這些屬性儲存於 ViewState 中;因為 ViewState 是個兩面刃,ViewState 可以很輕易幫我們維護屬性值,不過相對的也增加了面頁的傳輸量,所以可以視實際情形來決定屬性是否要儲存於 ViewState 中。</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/07/5601.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/07/5601.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-06 21:17:20</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day4] 複合控制項</title> <link>https://ithelp.ithome.com.tw/articles/10011633?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10011633?sc=rss.iron</guid> <description><![CDATA[<p>複合控制項就是控制項可包含其他子控制項,複合控制項繼承至 System.Web.UI.WebControls.CompositeControl,例如 Login 及 Wizard 等控制項就是屬...]]></description> <content:encoded><![CDATA[<p>複合控制項就是控制項可包含其他子控制項,複合控制項繼承至 System.Web.UI.WebControls.CompositeControl,例如 Login 及 Wizard 等控制項就是屬於複合控制項。我們常在網頁上常看到一種輸入日期的方式是年月日三個下拉清單,本文將利用複合控制項來實作這個年月日下拉清單控制項,示範如何實作複合控制項。<br /> <strong>一、CompositeControl 類別的特性</strong><br /> CompositeControl 類別是抽象類別,它會實作 INamingContaner 介面,INamingContaner 介面會在子控制項的 ClinetID 加上父控制項的 ID,以確保頁面上控制項的 ClientID 是唯一的。繼承 CompositeControl 類別一般都是覆寫 CreateChildControls 方法,在此方法中將建立子控制項並加入 Controls 集合屬性中;當存取其子控制項時,若子控制項未建立,會執行 CreateChildControls 方法,以會確保所有子控制項皆已在存取 ControlCollection 之前建立。<br /> <strong>二、日期下拉清單輸入器</strong><br /> 我們繼承 CompositeControl 類別,命名為 TBDropDownDate。這個控制項會包含年月日三個下拉清單(DropDownList),所以我們只要在 CreateChildControls 方法中依序建立年月日的 DropDownList 子控制項,並加入 Controls 集合屬性中即可。</p> <pre><code>''' <summary> ''' 日期下拉清單輸入器。 ''' </summary> < _ ToolboxData("<{0}:TBDropDownDate runat=server></{0}:TBDropDownDate>") _ > _ Public Class TBDropDownDate Inherits System.Web.UI.WebControls.CompositeControl Protected Overrides Sub CreateChildControls() Dim oYear As DropDownList Dim oMonth As DropDownList Dim oDay As DropDownList Dim N1 As Integer '年下拉清單區間為 1950-2010 (年區間可以用屬性來設定) oYear = New DropDownList oYear.ID = "Year" For N1 = 1950 To 2010 oYear.Items.Add(N1.ToString) Next Me.Controls.Add(oYear) '加入子控制項 Me.Controls.Add(New LiteralControl("年")) '月下拉清單區間為 1-12 oMonth = New DropDownList oMonth.ID = "Month" For N1 = 1 To 12 oMonth.Items.Add(N1.ToString) Next Me.Controls.Add(oMonth) '加入子控制項 Me.Controls.Add(New LiteralControl("月")) '日下拉清單區為為 1-31 oDay = New DropDownList oDay.ID = "Day" For N1 = 1 To 12 oDay.Items.Add(N1.ToString) Next Me.Controls.Add(oDay) '加入子控制項 Me.Controls.Add(New LiteralControl("日")) End Sub End Class </code></pre> <p>在設定階段拖曳 TBDropDownDate 到頁面上,就可以看到我們在 CreateChildControls 方法中所加入的子控制項。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay4_9C76/image_2.png" alt="" /></p> <p>執行程式,檢視它的 HTML 原始碼,會發現年月日的子控制項的 ClientID 都會在原 ID 前加上父控制項的 ID,這樣命名規則可以確保所有的控制項的 ClinetID 都是唯一值。</p> <pre><code><span id="TBDropDownDate1"> <select name="TBDropDownDate1$Year" id="TBDropDownDate1_Year"> ....省略 <select name="TBDropDownDate1$Month" id="TBDropDownDate1_Month"> ....省略 <select name="TBDropDownDate1$Day" id="TBDropDownDate1_Day"> </span> </code></pre> <p><strong>三、結語</strong><br /> 我們已經看過三類伺服器控制項的簡單案例,不過這三個案例都只是簡單說明控制項 UI 的部分,一個完整的控制項需具備屬性、方法、事件、設計階段支援...等,在後面的文章中,我們將陸續針對這些部分做詳細的介紹。</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/05/5583.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/05/5583.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-05 22:22:25</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day3] 擴展現有伺服器控制項功能</title> <link>https://ithelp.ithome.com.tw/articles/10011562?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10011562?sc=rss.iron</guid> <description><![CDATA[<p>相對於由無到有開發控制項,繼承現有現伺服器控制項是比較簡單且實用的方式;若希望在現有的控制項增加某些屬性或功能,直接繼承該控制項下來擴展功能是最快的方式,例如「按下 Button 會彈出詢問訊息...]]></description> <content:encoded><![CDATA[<p>相對於由無到有開發控制項,繼承現有現伺服器控制項是比較簡單且實用的方式;若希望在現有的控制項增加某些屬性或功能,直接繼承該控制項下來擴展功能是最快的方式,例如「按下 Button 會彈出詢問訊息」、「TextBox 設為 ReadOnly 時,可以取得前端傳回的 Text 屬性」這類需求,都可以直接繼承原控制項下來,加上我們需要的功能即可。以下我們就以一個簡單的案例來說明如何繼承現有伺服器下來擴展功能。<br /> <strong>一、擴展 Button 控制項:按鈕加上詢問訊息</strong><br /> 按下按鈕執行某些動作前,有時會詢問使用者是否執行該動作;例如按下刪除鈕,會詢問使用者是否確定要執行刪除的動作。當然這只需要簡單的 JavaScript 就可以完成,不過相對於 .NET 的程式語言,JavaScript 是非常不易維護的用戶端指令碼,如果能讓開發人員完全用不到 JavaScript,那何樂不為呢? 那就由 Button 控制項本身提供加上詢問訊息的功能就可以,相關的 JavaScript 由控制項去處理。<br /> 一般要在 Button 加上詢問訊息,只要在 OnClientClick 屬性設定如下的 JavaScript 即可。我們的目的只是讓開發人員連設定 OnClientClick 屬性的 JavaScript 都省略,直接設定要詢問的訊息即可,接下來我們就要開始實作這個控制項。</p> <pre><code><asp:Button ID="Button1" runat="server" Text="Button" OnClientClick="if (confirm('確定執行嗎?')==false) {return false;}" /> </code></pre> <p>在 Bee.Web 專案中,加入「ASP.NET 伺服器控制項」,此控制項繼承 Button 下來命名為 TBButton (命名空間為 Bee.Web.WebControls)。在 TBButton 類別中加入 ConfirmMessage 屬性,用來設定詢問訊息的內容。然後在 Render 方法將詢問詢息的 JavaScript 設定到 OnClientClick 屬性即可。</p> <pre><code>Namespace WebControls < _ Description("按鈕控制項"), _ ToolboxData("<{0}:TBButton runat=server></{0}:TBButton>") _ > _ Public Class TBButton Inherits System.Web.UI.WebControls.Button <Description("詢問訊息")> _ Public Property ConfirmMessage() As String Get Dim sConfirmMessage As String sConfirmMessage = CStr(ViewState("ConfirmMessage")) If sConfirmMessage Is Nothing Then Return String.Empty Else Return sConfirmMessage End If End Get Set(ByVal value As String) ViewState("ConfirmMessage") = value End Set End Property ''' <summary> ''' 覆寫 Render 方法。 ''' </summary> Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Dim sScript As String Dim sConfirm As String '若有設定 ConfirmMessage 屬性,則在 OnClientClick 加入詢問訊息的 JavaScript If Me.ConfirmMessage <> String.Empty Then sScript = Me.OnClientClick '詢問訊息的 JavaScript sConfirm = String.Format("if (confirm('{0}')==false) {{return false;}}", Me.ConfirmMessage) If sScript = String.Empty Then Me.OnClientClick = sConfirm Else Me.OnClientClick = sConfirm & sScript End If End If MyBase.Render(writer) End Sub End Class End Namespace </code></pre> <p>將 TBButton 拖曳到測試頁面,設定 ConfirmMessage 屬性。</p> <pre><code><bee:TBButton ID="TBButton1" runat="server" ConfirmMessage="確定刪除此筆資料嗎?" Text="刪除" /> </code></pre> <p>執行結果如下。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/8cea6e0221af_B473/image_2.png" alt="" /></p> <p><strong>二、結語</strong><br /> 筆者在開發 ASP.NET 的應用程式過程中,通常會習慣把所有現有控制項繼承下來,無論目前需不需要擴展控制項功能。這種方式對於開發大型系統是相當有幫助的,因為無法預期在系統開發的過程中會不會因為某些狀況,而臨時需要擴展控制項的功能,所以就先全部繼承下來以備不時之需,也為未來保留修改的彈性。</p> <p><strong>三、相關連結</strong><br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1748.aspx" target="_blank">擴展 CommandField 類別 - 刪除提示訊息</a><br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1697.aspx" target="_blank">按鈕加上詢問訊息</a></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/04/5578.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/04/5578.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-04 21:24:49</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day2] 建立第一個伺服器控制項</title> <link>https://ithelp.ithome.com.tw/articles/10011523?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10011523?sc=rss.iron</guid> <description><![CDATA[<p>上一篇中已經建立「ASP.NET 伺服器控制項」專案,接下來我們將學習來撰寫第一個伺服器控制項。<br /> 撰寫伺服器控制項大致分為下列三種方式<br /> 1.由無到有建立全新的控制項,一般...]]></description> <content:encoded><![CDATA[<p>上一篇中已經建立「ASP.NET 伺服器控制項」專案,接下來我們將學習來撰寫第一個伺服器控制項。<br /> 撰寫伺服器控制項大致分為下列三種方式<br /> 1.由無到有建立全新的控制項,一般會繼承至 System.Web.UI.Control 或 System.Web.UI.WebControls.WebControl 類別。<br /> 2.繼承現有控制項,擴展原有控制項的功能,如繼承原有 TextBox 來擴展功能。<br /> 3.複合式控制項,將多個現有的控制項組合成為一個新的控制項,例如 TextBox 右邊加個 Button 整合成一個控制項,一般會繼承至 System.Web.UI.WebControls.CompositeControl 類別。</p> <p>本文將先介紹第1種方式,由無到有來建立控制項,後面的文章中會陸續介紹第2、3種方式的控制項。要建立全新的控制項會繼承至 Control 或 WebControl,沒有 UI 的控制項可由 Control 繼承下來 (如 SqlDataSource),具 UI 的控制項會由 WebControl 繼承下來。接下來的範例中,我們將繼承 WebControl 來建立第一個 MyTextBox 控制項。</p> <p><strong>一、新增 MyTextBox 控制項</strong><br /> 在 Bee.Web 專案按右鍵選單,執行「加入\新增項目」,選擇「ASP.NET 伺服器控制項」,在名稱文字框中輸入 MyTextbox,按下「確定」鈕,就會在專案中加入 MyTextbox 控制項類別。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/0e8fe1ad2977_14385/image_thumb.png" alt="" /><br /> 新加入的控制項預設有一個 Text 屬性,以及覆寫 Render 方法。Render 方法是「將控制項呈現在指定的 HTML 寫入器中」,簡單的說就是在 Render 方法會將控制項對應的 HTML 碼輸出,用來呈現在用戶端的瀏覽器上。假設我們要撰寫一個網頁上的文字框,那就先去看一下文字框在網頁中對應的 HTML 碼,然後在 Render 方法中想辨法輸出這些 HTML 碼即可。</p> <p><strong>二、輸出控制項的 HTML 碼</strong><br /> 你可以使用 FrontPage 之類的 HTML 編輯器,先編輯出控制項的呈現方式,進而去觀查它的 HTML 碼,再回頭去思考如何去撰寫這個伺服器控制項。假設 MyTextbox 控制項包含一個文字框及一個按鈕,那最終輸出的 HTML 碼應該如下。</p> <pre><code><input id="Text1" type="text" /> <input id="Button1" type="button" value="button" /> </code></pre> <p>我們在 MyTextbox 的 RenderContents 方法中輸出上述的 HTML 碼。</p> <pre><code> Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) Dim sHTML As String sHTML = "<input id=""Text1"" type=""text"" />" & _ "<input id=""Button1"" type=""button"" value=""button"" />" writer.Write(sHTML) End Sub </code></pre> <p>建置控制項專案,然後拖曳 MyTextbox 在測試頁面上,設計階段就會呈現出我們期望的結果。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/0e8fe1ad2977_14385/image_4.png" alt="" /></p> <p>執行程式,在瀏覽器看一下 MyTextbox 控制項輸出的結果,是不是跟我們預期的一樣呢。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/0e8fe1ad2977_14385/image_6.png" alt="" /></p> <p><strong>三、屬性套用到控制項 HTML 碼</strong><br /> 控制項不可能單純這樣輸出 HTML 碼而已,控制項的相關屬性設定,一般都影響到輸出的 HTML 碼。假設 MyTextbox 有 Text 及 ButtonText 二個屬性,分別對應到 文字框的內容及按鈕的文字,MyTextbox 本來就有 Text 屬性,依像畫蘆葫新增 ButtonText 屬性。</p> <pre><code> < _ Bindable(True), _ Category("Appearance"), _ DefaultValue(""), _ Localizable(True)> _ Property ButtonText() As String Get Dim s As String = CStr(ViewState("ButtonText")) If s Is Nothing Then Return String.Empty Else Return s End If End Get Set(ByVal Value As String) ViewState("ButtonText") = Value End Set End Property </code></pre> <p>RenderContents 方法改寫如下。</p> <pre><code> Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) Dim sHTML As String sHTML = "<input id=""Text1"" type=""text"" value=""{0}""/>" & _ "<input id=""Button1"" type=""button"" value=""{1}"" />" sHTML = String.Format(sHTML, Me.Text, Me.ButtonText) writer.Write(sHTML) End Sub </code></pre> <p>重新建置控制項專案,在頁面上測試 MyTextbox 的 Text 及 ButtonText 屬性。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/0e8fe1ad2977_14385/image_8.png" alt="" /></p> <p><strong>四、使 ClientID (HTML 原始碼控制項的 ID) 是唯一值</strong><br /> 在頁面上放置二個 MyTextbox 控制項,執行程式,在瀏覽器中檢查 MyTextbox 的 HTML 原始碼。你會發現 MyTextbox 會以一個 span 包住控制項的內容,而每個控制項的輸出的 ClientID 是唯一的。不過 MyTextbox 內含的文字框及按鈕卻會重覆,所以一般子控制項的 ClientID 會在前面包含父控制項的 ID。</p> <pre><code><span id="MyTextbox1"> <input id="Text1" type="text" value="這是文字"/> <input id="Button1" type="button" value="這是按鈕" /> </span> <br /> <span id="MyTextbox2"> <input id="Text1" type="text" value="這是文字"/> <input id="Button1" type="button" value="這是按鈕" /> </span> </code></pre> <p>所以我們再次修改 RenderContents 方法的程式碼</p> <pre><code> Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) Dim sHTML As String sHTML = "<input id=""{0}_Text"" type=""text"" value=""{1}""/>" & _ "<input id=""{0}_Button"" type=""button"" value=""{2}"" />" sHTML = String.Format(sHTML, Me.ID, Me.Text, Me.ButtonText) writer.Write(sHTML) End Sub </code></pre> <p>執行程式,再次檢視 HTML 原始碼,所有的 ClinetID 都會是唯一的。</p> <pre><code><span id="MyTextbox1"> <input id="MyTextbox1_Text" type="text" value="這是文字"/> <input id="MyTextbox1_Button" type="button" value="這是按鈕" /> </span> <br /> <span id="MyTextbox2"> <input id="MyTextbox2_Text" type="text" value="這是文字"/> <input id="MyTextbox2_Button" type="button" value="這是按鈕" /> </span> </code></pre> <p><strong>五、控制項前置詞</strong><br /> 自訂控制項的預設前置詞是 cc1,不過這是可以修改的,在專案中的 AssemblyInfo.vb 檔案中,加入如下定義即可。詳細的作法請參考筆者部落格中的「<a href="http://www.dotblogs.com.tw/jeff377/archive/2008/03/17/1744.aspx" target="_blank">自訂伺服器控制項前置詞</a>」一本有詳細介紹,在此不再累述。</p> <pre><code>'設定控制項的標記前置詞 <Assembly: TagPrefix("Bee.Web.WebControls", "bee")> </code></pre> <p><strong>六、結語</strong><br /> 本文中是用土法鍊鋼的方法在撰寫伺服器控制項,一般在實作控制項時會有更好的方式、更易維護的寫法,後續的文章中會陸續介紹相關作法。</p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/03/5573.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/03/5573.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-03 23:51:35</pubDate> </item> <item> <title>[ASP.NET 控制項實作 Day1] 建立 ASP.NET 伺服器控制項專案</title> <link>https://ithelp.ithome.com.tw/articles/10011408?sc=rss.iron</link> <guid isPermaLink="true">https://ithelp.ithome.com.tw/articles/10011408?sc=rss.iron</guid> <description><![CDATA[<p>在 ASP.NET 開發環境中,我們常使用現成的控制項直接拖曳至頁面中使用,有沒有想過我們也可以開發自用的控制項呢?本文將本文以 VS2008 為開發工具,VB.NET 為開發程式語言,來說明如...]]></description> <content:encoded><![CDATA[<p>在 ASP.NET 開發環境中,我們常使用現成的控制項直接拖曳至頁面中使用,有沒有想過我們也可以開發自用的控制項呢?本文將本文以 VS2008 為開發工具,VB.NET 為開發程式語言,來說明如何建立「伺服器控制項」專案,以及如何測試開發階段的的伺服器控制項。<br /> <strong>一、建立「ASP.NET 伺服器控制項」專案</strong><br /> 首先執行功能表「檔案\新增專案」,在專案類型中選擇 Visual Basic -> Web,選取「ASP.NET 伺服器控制項」範本,在名稱文字框中輸入專案名稱,也就是組件的檔案名稱,我們輸入 Bee.Web 為專案名稱,組件檔案為 Bee.Web.dll,按下「確定」鈕即會建立新的「ASP.NET 伺服器控制項」專案。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/166fb54653d6_12877/image_thumb_1.png" alt="" /><br /> 在新建立「ASP.NET 伺服器控制項」專案中,會預設加入一個伺服器控制項類別(ServerControl1.vb),這個伺服器控制項已經事件幫我們加入一些控制項的程式碼。目前暫不做任何修改,直接使用此控制項來做測試說明。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/166fb54653d6_12877/image_thumb_2.png" alt="" /><br /> 接下來執行功能表「專案\Bee.Web 屬性」,設定此組件的根命名空間,一般慣用的根命名空間都會與組件名稱相同,以方便加入參考時可以快速找到相關組件。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/166fb54653d6_12877/image_thumb_10.png" alt="" /><br /> 我們先儲存這個「ASP.NET 伺服器控制項」專案,指定儲存位置,按下「儲存」鈕。整個專案相關檔案,會儲存在以專案名稱的資料夾中。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/166fb54653d6_12877/image_thumb_3.png" alt="" /></p> <p><strong>二、加入測試網站</strong><br /> 不要關閉目前「ASP.NET 伺服器控制項」專案,執行功能表「檔案\加入\新網站」,選擇「ASP.NET 網站」,會在方案中加入一個網站,來測試開發階段的伺服器控制項使用。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/166fb54653d6_12877/image_thumb_5.png" alt="" /><br /> 在測試網站加入參考,選擇「專案」頁籤,此頁籤中會列出該方案中其他可加入參考的專案,選取 Bee.Web 專案,按下「確定」鈕。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/166fb54653d6_12877/image_thumb_6.png" alt="" /><br /> 先在 Bee.Web 專案中執行「建置」動作,然後切換到測試網站的頁面設計,工具箱中就會出現 ServerControl1 伺服器控制項。這個控制項就可以直接拖曳至頁面中使用,這個控制項只是單純 Render 出 Text 屬性值,你可以在控制項屬性視窗中,更改 Text 屬性值為 "測試文字",就會看到這個控制項顯示 "測試文字"。將測試網站設為啟動專案,按下「F5」執行程式,就會看到該控制在執行階段的結果。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/166fb54653d6_12877/image_thumb_12.png" alt="" /></p> <p>備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格<br /> <a href="http://www.dotblogs.com.tw/jeff377/archive/2008/10/02/5562.aspx" target="_blank">http://www.dotblogs.com.tw/jeff377/archive/2008/10/02/5562.aspx</a></p> ]]></content:encoded> <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">jeff377</dc:creator> <pubDate>2008-10-02 23:16:29</pubDate> </item> </channel> </rss>