這些年我似是非懂的 Javascript

  • 749
  • 0
  • 2022-07-04

這些年我似是非懂的 Javascript

【這些年我似是非懂的 Javascript】 :: 第 12 屆 iThome 鐵人賽 https://ithelp.ithome.com.tw/users/20110242/ironman https://ithelp.ithome.com.tw/static/2020-12th-ironman/images/12thfb.jpg 【這些年我似是非懂的 Javascript】 :: 第 12 屆 iThome 鐵人賽 https://ithelp.ithome.com.tw/users/20110242/ironman zh-TW Wed, 08 Jun 2022 20:02:03 +0800 【這些年我似是非懂的 Javascript】Prototype #建構器 https://ithelp.ithome.com.tw/articles/10254599?sc=rss.iron https://ithelp.ithome.com.tw/articles/10254599?sc=rss.iron

今天要來分享一點關於 prototype 和建構器的部分。

今天要來分享一點關於 prototype 和建構器的部分。

建構器所 new 的不是你想的那樣

我之前在文中也提過因為 Javascript 並不會有複製的行為只會有連結的行為,
所以當你使用 new 的時候並不會有你想要的那種結果,
(你想要的應該是獨立的一個 instance 對吧!?

我們來看看以下這個範例
它叫做類別函式

function Foo(){
    // ...
}

const a = new Foo();

Foo.prototype; // {constructor: ƒ}
Object.getPrototypeOf(a) == Foo.prototype; //true

注意: 上面用的是 Foo 不是 foo 的原因是因為在 Javascript 世界中的慣例一個類別會以第 一個字母為大寫來命名。

這類別函式預設都會拿到一個特性叫做 prototype
而當上面 anew Foo() 被建立出來後他會拿到一個內部的 [[Prototype]] 連結,
它會連到 Foo.prototype 所指的物件。

所以你並沒有 new 出你要的 instances ,你只會建立出它[[Prototype]]] 的連結,
所以你不會建立出分離的東西而是緊緊連在一起的。

除此之外你會注意到 Foo.prototype 裡面還有一個東西是 constructor
它主要是用來指回該物件所關聯的函式的一個參考。
所以假如說有兩個物件一個是Foo 另一個是 Bar ,並且 Bar 繼承 Foo那他們的關係就有點像是這樣

而這種看起來像是繼承,但是不是我們知道繼承的那種精神,
它叫做原型式繼承
書中提到他在繼承前面加上原型式扭轉了它原本的意義,
這本書的作者甚至形容他~

就像是左手拿柳橙,右手拿蘋果,然後你堅持要叫他"紅色的柳橙"。

這聽起來非常怪沒錯 xD
說到底它就... 不是繼承。


以上是今天的內容
下篇應該會講一下 prototype 串鏈到底是怎麼回事~
感謝您的收看
我們下次見


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]>
Robin 2020-12-06 23:51:45
【這些年我似是非懂的 Javascript】那些年我睡掉的物件導向 #淺談 #Part 3 https://ithelp.ithome.com.tw/articles/10254524?sc=rss.iron https://ithelp.ithome.com.tw/articles/10254524?sc=rss.iron

嗨各位~
...]]>

嗨各位~
今天要來分享一下關於上次提到 Javascript 沒有類別可以實體化只有物件,
那他到底缺乏甚麼?
答案是他不會複製物件到其他物件中,只會被連結在一起。
而這個類別複製行為就叫做 mixins 並且分為兩種:

  • 明確的
  • 隱含的

明確的 Mixins

因為 JS 不會自動複製,
所以我們可以自己做一個複製的行為,也有人稱他為 extend
如果以 CarVehicle 來舉例大概會長以下這樣

function mixin(sourceObj, targetObj){
    for(const key in sourceObj){
        // 沒有存在該 key 時再複製
        if(!(key in targetObj) targetObj[key] = sourceObj[key])
    }
}


const Vehicele = {
    engined = true,
    ignition: function(){
        console.log("Turning on the engine.");
    },
    drive: function(){
        this.ignittion();
        console.log("Steering and moving forward")
    },
}


const Car = mixin(Vehicele, {
    wheels: 4,
    drive: function(){
        Vehicle.drive.call(this);
        console.log(`Rolling on all ${this.wheels} wheels~`);
    };
});

依照上面來看 Car 有了 Vehicel 的特性和函式,實際上函式並沒有確實被拷貝而是單純參考,所以實際上只是複製了 ignition() 的一份參考的拷貝和 engined 的一個特性。(Drive 沒有被複製是因為原本 Car 就有了注意上面範例中的 if)

所以這部分你以為是你想的那樣嗎? xDD

Javascript: 想不到吧!?

重訪多型

剛剛在上面範例看到的 Vehicle.drive.call(this) 就是所謂的明確的虛擬多型,至於為何不直接用 Vehicle.drive() 那他就會被 this 綁定變成式呼叫 Vehicle 而不是 Car ,所以要確保Drive() 在 Car 中被呼叫就要使用 .call(this)
書中提到

因為 JS 這種特殊性,明確的虛擬多型會需要這種虛擬多型參考的每個函式中建立出脆弱的手動明確連結,導致會造成大量的成本產生雖然說他的確可以達到我們要的效果不過成本大於優點,所以不建議使用。

混合複製 (Mixing copies)

還記得剛剛那段 mixin 的程式碼嗎?

function mixin(sourceObj, targetObj){
    for(const key in sourceObj){
        // 沒有存在該 key 時再複製
        if(!(key in targetObj) targetObj[key] = sourceObj[key])
    }
}

這邊我們需要在迭代過程中檢查一下他有沒有相同名的特性再決定要不要複製,
那有沒有方式可以不要這樣?
答案是有的,
我們先指定 Car 特定的內容之前我們先做拷貝這樣我們就可以省略那一個判斷的步驟了!

來看看實際這樣做的結果吧

function mixin(sourceObj, targetObj){
    for(const key in sourceObj){
        targetObj[key] = sourceObj[key])
    }
}

const Car = mixin(Vehicel,{});
mixin({
    wheels: 4,
    drive: function(){
      // ...
    }
}, Car)

痾... 這樣看起來是不是造成了看起來更沒效率因為你要再次把東西又再次迭代進去xDD
所以正常不會選這種方式。

但是不管哪種方式其實都可以做到我們要的目的。

那到底的痛點還是一個就是...
Javascript 的函式沒辦法真正的被複製(目前沒有標準和可靠的方式),
所以阿...
當你修改了其中一個共用的函式物件像是增加一個特性或是做修改
那...

小結論

明確的 mixins 在 JS 中其實是不錯的機制,他可以解決很多個物件混進目標物件中,達成多重繼承的行為,但是實際上他不僅會造成一些隱含的危險比方說函式共用,還有可讀性的問題。

如果你覺得痛苦,那你就應該停止使用因為她所帶給你的利益已經遠遠不及造成的傷害。

James Harden 曾經說過...

就是告訴我們不要假會甚麼都不要用就好了!
複製貼上讓你的程式碼變成義大利麵再交給別人維護,
想要不維護別人的 legacy code 就要努力創造難以維護的 code 給別人維護。

(以上幹話可以不用看xD)

隱含的 Mixins

讓我們先來直接看範例

const ObjA = {
    foo: function(){
        this.bar = "Hey guys";
        this.boo = this.boo? this.boo + 1: 1;
    }
}

ObjA.foo();
ObjA.bar; // "Hey guys"
ObjA.boo; // 1

const ObjB={
    foo: function(){
        // ObjA 和 ObjB 有隱含的 mixins
        ObjA.foo.call(this);
    }
}

ObjB.cool();
ObjB.bar; // "Hey guys"
ObjB.boo; // 1 (不是 2 與 ObjA 共有

從上面可以看到藉由

ObjA.foo.call(this);

借用了 ObjA.foo() 並且在 ObjB 的環境中呼叫他並且結果是套用在 ObjB 物件上而不是原本的物件。

他實際上其實也是一個滿母湯的作法,
並且如果以可讀性來說就已經是要避免的寫法。

感謝您的收看
今天文章到此結束


題外話

今天一早去考多益去測試我自己菜英文的實力,果真是猜猜樂呢 xD
我朋友和女友都說

: 很浪費錢欸,你也沒什麼準備

但是對我來說其實這不是浪費錢,先墊墊自己實力再訂定目標去進步是不是一種策略?
比起這不敢那不敢,先不要這不要的
不如直接行動讓現實打臉你,你才知道妳自己有多麼不足需要去補 xD

如果我考很爛然後努力向上之後有大幅進步之後再來分享xD
另外有自身英文進步的方式或撇步可以跟我分享,
我目前的想法是
美劇看爆!

感謝您我們下次見

]]> Robin 2020-11-29 23:55:44
【這些年我似是非懂的 Javascript】那些年我睡掉的物件導向 #淺談 #Part 2 https://ithelp.ithome.com.tw/articles/10254428?sc=rss.iron https://ithelp.ithome.com.tw/articles/10254428?sc=rss.iron

嗨各位好久不見,

嗨各位好久不見,
今天要來分享上次的續集 第 2 part ,
上篇講到建構器
今天要來分享關於繼承和多重繼承的部分。

繼承

上次有稍微提到這部分

例如一台車子就可以先定義更通用的類別叫做載具(Vehicle),
而載具裡面定義了各種不同類型的功能,比方說上面所說的載人、推進、飛天,
當我們想要定義其他載具,比方說火車,我們就可以透過繼承或是擴充,就可以達到我們要的。

今天再來分享一個例子來解釋一下甚麼是繼承

一對夫妻生小孩,父母的基因會複製在小孩身上,但是小孩是與父母分離的個體,但是特徵會被父母影響。以物件導向也就是說父母的 DNA 就是父類別,小孩的 DNA 就是子類別,子類別會擁有父類別複製過來的最初的行為,並且子類別可以做複寫任何繼承過來的行為,或是定義新的行為。
也就是小孩長大了也可以長江後浪推前浪或是一代不如一代XDD 又或是創造新的屬於自己的天地。

多重繼承

甚麼? 難道是影分身?

還是如果是以父母的 DNA 來說...
咳咳嗯...
誰是我爸爸
如果以父母的 DNA 來比喻感覺很怪異,但是你想是卡片合成之類的可能比較好想像 xD
某些物件導向語言可以指定一個以上的父類別來繼承,這就叫做多重繼承,也就是說拔拔"們"的類別定義都會複製到子類別中。
就像是如果我爸是巴菲特 + Lebron James + 畢卡索 + 李白,那我感覺我就是神了 (各種亂尬)
這聽起來非常棒對吧?
是不是想說...
貴圈真亂 xDD

對的!除了貴圈真亂之外他還有可能會造成一些複雜的問題出現。
但是他也不是完全沒缺點,如果其中兩個父類別都提供了相同的方法,那子類別會取用哪個呢?
就像是如果我爸爸是李白加上杜甫那我該選擇要選用誰的造詩功力?
那在假使可以指定選用哪個那是不是又跟原本多重繼承的概念有點衝突,好像不是那麼優雅又聰明了。

除了這個還有另一個變體叫做 鑽石問題(diamond problem)

如果像上面圖這樣,
B 和 C 繼承父類別 A,並且各自都覆寫那個方法接著 D 又多重繼承 B 和 C ,那他該使用 B 還是 C 的方法呢?

我們這主題是 Javascript ,所以回到 JS 本身他會怎麼處理呢?
答案是...
不用處理xDDD
因為 JS 本身沒有提供原生的機制去進行多重繼承

哈哈哈
哈哈

嗚嗚嗚嗚嗚 QQ

(有人認為這是好事,畢竟他可以減少複雜度,但是現實中沒辦法避免開發人員自己去用各種方式偽造多重繼承的效果


以上是今天的內容
下篇會講關於最後提到的偽造多重繼承

感謝你的收看
我們下次見

]]> Robin 2020-11-22 23:44:38
【這些年我似是非懂的 Javascript】那些年我睡掉的物件導向 #淺談 #Part 1 https://ithelp.ithome.com.tw/articles/10253928?sc=rss.iron https://ithelp.ithome.com.tw/articles/10253928?sc=rss.iron

嗨~各位好久不見,
最近幾乎都在寫一些自己喜歡的專案,

嗨~各位好久不見,
最近幾乎都在寫一些自己喜歡的專案,
不知不覺已經兩週了呢 (歡樂的時光總是過得那麼快),
今天要來繼續學習啦~

如果你也跟我一樣在大學時期物件導向課程都在唱歌睡覺的,你值得與我一起惡補學習 xDD

到底什麼是類別?

其實類別就是個像是一種分類的抽象的設計模式
將任何給定的結構想成更通用的基礎定義的一個變體。

以交通工具來說,交通工具百百種,
有些會推進,有些會飛又或是可以載人,
假使一個交通工具我就給它定義他可以飛的功能,或是他可以推進,那一百種交通工具就必須每次都重新再次定義他會飛或是他可不可以可推進。
聽起來就很不合理!對吧?

那我們該怎麼做?
例如一台車子就可以先定義更通用的類別叫做載具(Vehicle)
而載具裡面定義了各種不同類型的功能,比方說上面所說的載人、推進、飛天,
當我們想要定義其他載具,比方說火車,我們就可以透過繼承或是擴充,就可以達到我們要的。
是不是聽起來比一開始的方法聰明多了!?

除此之外,類別還有一個概念是多型,
就是他可以藉由來自父類別的某項通用行為在一個子類別中被覆寫,並且賦予他更特殊的行為。

在 Javascript 的類別

講這麼多, Javascript 具備一些很像類別的語法元素,例如 new 或是 instanceof ,甚至是 ES6 新加入的 class 關鍵字,所以 Javascript 實際上還是具備了類別這東西嘛!
...
...
其實沒有 xD

不是啊!類別如果是種設計模式,那還有什麼問題!?
問題在於,對於 JS 來說類別只是一個選擇性的模式,他不像是 Java 一樣直接硬著來你根本沒有選擇的餘地,可以說在 JS 中使用你偽造出來的類別和其他語言是不同的,至於為什麼...?
讓我們繼續看下去~ (書上說的 xD)

建構器

建構器是類別的實體是由該類別的一種特殊方法所建構的,通常會與他的類別同名。
直接看個範例。

class Player{
    specialTrick = nothing
    
    Player(trick){
        specialTrick = trick
    }
    
    showTime(){
        console.log(`My killer moves is ${specialTrick}`)
    }
}

假使要製作一個球員的實體,我們就可以這樣做。

Iverson = new Player('killer crossover');
Iverson.showTime(); // 'My killer moves is killer crossover'

所以看起來就是 Player 類別有一個 Player() 建構器,他就是我們使用 new Player() 時所呼叫的東東,而當我們加上 new 的同時讓語言引擎知道你想要建立一個新的類別實體。

以上是今天的內容


啊~~ 最近忙著租屋和工作的事,
下篇繼續說 xD

感謝您的收看
我們下集見

到底誰說鐵人賽完之後會感覺很閒 xDD

]]> Robin 2020-11-01 22:43:05
【這些年我似是非懂的 Javascript】Day 30 - 完賽心得 https://ithelp.ithome.com.tw/articles/10253242?sc=rss.iron https://ithelp.ithome.com.tw/articles/10253242?sc=rss.iron

今天是最後一天鐵人賽,
好像沒有完賽心得就不完美了一樣,

今天是最後一天鐵人賽,
好像沒有完賽心得就不完美了一樣,
原本想說不應該在三十天時寫心得,
畢竟這系列不只有這三十天~

後來想想...
最後一天不騙一下怎麼行

不是!沒你們想的那麼膚淺,

我覺得是覺得不管做什麼事情,
做了一段落來回頭看看自己這段時間有哪些需要改進的,
或是和原本預想有什麼落差是非常必要的。

我們先來看看原本第一天所說的xD

不求讀完只求盡力和對 JS 的進步,
剩餘沒讀完的部分還是會以文章的方式撰寫分享。

我原本以為我第一天就會誇下海口說我要讀完這四本 xDD
結果我發現原來我第一天就這麼沒骨氣。

不過看這三十天的文章成長趨勢...
似乎後段的文章內容篇幅已經縮減為原本的三分之一上下,
但是講真的!
這本書已經寫的非常平易近人了,
但是越到後面我其實自己本身需要消化的時間也越來越長 QQ

我相信其實沒什麼人想看這類型的心得xD
之後我還是會陸續發關於這系列的文章,
但是不會像這三十天一樣都那麽倉促 Orz

另外特別感謝三十天有在持續觀看的你 QQ

我們~
沒有明天見~ 下篇見!

]]> Robin 2020-10-14 21:57:11
【這些年我似是非懂的 Javascript】Day 29 - 物件 # Part 5 # 特性存取的秘密 https://ithelp.ithome.com.tw/articles/10252957?sc=rss.iron https://ithelp.ithome.com.tw/articles/10252957?sc=rss.iron

今天要來分享特性存取的秘密~

[[GET]]

<...]]>

今天要來分享特性存取的秘密~

[[GET]]

你知道當你在存取一個物件裡面的特性時會發生什麼事情嗎?

const obj ={
    a: 16
}

obj.a; // 16

obj.a 是一個特性存取的動作,但是他不只是單純只是找 obj 內有無 a 這個特性而已...
難道...
他會往外找!?

...

沒有! 你想太多了xD
其實他會對他執行一個 [[GET]] 的作業,基本上他會先看你這個物件有沒有你要找的特性,有的話他就回傳該值,沒有的話...

就會發生很可怕的事...
就是書上說在第十章的 [[Prototype]]串鏈 會提到xDD 所以現在我也不知道

反正他有一個重要的結果是他如果找不到任何方法找到該值他就會回傳 undefined

看起來就是找有的東西比找沒有的東西來的麻煩許多~
你說是吧!?

[[Put]]

既然有 Get 那有個 Put 也不稀奇(?

基本上這邊就是當你要 assign 值給一個特性時他會做些啥?

  1. 該特性是一個存取器描述器嗎? 如果是就呼叫他的設值器
  2. 該特性是 writable 為 false 的一個資料描述器嗎? 在 嚴格模式就噴爆你,不是就不噴你。
  3. 如果是writable 為 true 的話,就照正常方式把值給那個現存的特性。

所以我說 存取器描述器資料描述器 那麼多專業名詞到底是三...?

  • 存取器描述器
    當他有取值器或是設值器,他就是一個"存取器描述器"。 簡單來說就是 getset 啦。
  • 資料描述器
    定義一個屬性時,他沒有取值器或是設值器時他就是資料描述器。 簡單來說看起來就是純資料啦。

那如果跟上面的 [[Get]]一樣找不到相關的特性,會怎麼做?
這部分也是屬於之後第十章的 [[Prototype]]的範疇xDD
我們之後來看看~

存在 (Existence)

前面有說假使像是存取該不存在的特性時,他會回傳 undefined,但是有一種可能像是這樣。

const obj={
    a: undefined
}

obj.a; // undefined

嗯...
他就是把 undefined 丟進存在的特性當值,那我們到底要怎麼區分他到底存不存在呢?

可以使用以下兩種方式

const obj={
    a: undefined
}

('a' in obj); // true
('b' in obj); // true

obj.hasOwnProperty(a); // true 
obj.hasOwnProperty(b); // false 

以上是今天的文章
感謝收看

最近越來越忙,鐵人賽也到了尾聲,
可惜今天還是來不及寫到迭代 Orz


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]>
Robin 2020-10-13 22:12:08
【這些年我似是非懂的 Javascript】Day 28 - 物件 # Part 4 # 特性描述器 Combo https://ithelp.ithome.com.tw/articles/10252550?sc=rss.iron https://ithelp.ithome.com.tw/articles/10252550?sc=rss.iron

昨天分享了特性組合的一般單獨的使用方法,
今天要來分享一下他們...]]>

昨天分享了特性組合的一般單獨的使用方法,
今天要來分享一下他們的 Combo 連技和相關用到的東西。

物件常數 (Object constant)

writableconfigurable 都設定成 false,就可以建立出一個不能修改也不能被變更的物件常數。

const obj = {}; // 給個空物件

Object.defineProperty(obj,"a",{
    value: 17,
    writable: false,
    enumerable: true,
    configurable: false
});

obj.a; // 17  出現啦~
obj.a = 123;
obj.a; // 17 // 改不了啊!!!!

Object.defineProperty(obj,"a",{
    value: 17,
    writable: true,
    enumerable: true,
    configurable: true
}); // TypeError 改不了啊!!!!

避免擴充 (Prevent extensions)

如果你想要避免這個 Object 被新增額外的特性怎麼辦?
登登登登登~
你可以使用 Object.preventExtensions(..)

const obj ={
    a:17
};

Object.preventExtensions(obj);

obj.b = 123;
obj.b; // undefined

假使是非嚴格模式它就默默地失敗了,但如果是嚴格模式他就會噴你 TypeError

密封 (Seal)

如何建立一個密封過的物件呢?
什麼意思!? 就是你不准新增任何的特性也不可以刪除既有的特性,但是你唯一能做的事就是修改他的值。
Object.seal(..),基本上這個就是你丟進去一個Object ,他就幫你呼叫剛剛提的 Object.preventExtensions(..) 和設定configurablefalse

const obj = {
    a:17
};

Object.seal(obj);

obj.b = 123;
obj.b; // undefined 不能增加特性

Object.defineProperty(obj,"a",{
    value: 16,
    writable: true,
    enumerable: true,
    configurable: true
}); // TypeError 不給改啊!!!

obj.a = 123;
obj.a; // 123 可以改~

凍結 (Freeze)

不准新增刪除和重新配置該特性,也不能更改值。
基本上就是 Object.seal + writable: false

const obj = {
    a:17,
};

Object.freeze(obj);

obj.b = 123;
obj.b; // undefined 不能增加特性

Object.defineProperty(obj,"a",{
    value: 16,
    writable: true,
    enumerable: true,
    configurable: true
}); // TypeError 不給改啊!!!

obj.a = 777;
obj.a; // 17 也不給改啊!!!

你剩下能做的大概就只剩讀取他和對他迭代。

以上是今天的內容

明天倒數兩天啦啦啦!
看起來要讀完這本是涼了xDD
不過物件這章節應該可以結束。
明天主要會講特性的存取一些眉眉角角~
"可能"還有迭代 xD~

感謝您的收看
我們明天見


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-10-12 21:44:38
【這些年我似是非懂的 Javascript】Day 27 - 物件 # Part 3 # 特性描述器 https://ithelp.ithome.com.tw/articles/10251791?sc=rss.iron https://ithelp.ithome.com.tw/articles/10251791?sc=rss.iron

今天要主要來分享一下特性描述器。

特性描述器 (prope...]]>

今天要主要來分享一下特性描述器。

特性描述器 (property descriptor)

在 ES5 之前 JS 無法讓你判定該物件特性的"特徵",
意思是說你沒辦法知道他是不是 read-only 之類的。
至於以前怎麼知道他是不是...
書中沒有特別提道,
感覺只能用猜的或是直接嘗試(我自己猜的)。

但是在 ES5 增加了一個功能叫做特性描述器 (property descriptor)。

const obj = {
    a: 17
};

Object.getOwnPropertyDescriptor(obj,"a");
/*
{
    value: 17,
    writable: true,
    enumerable: true,
    configurable: true
}
*/

由上述的範例可以看到他除了 value 之外,他還有三個特徵。

  • 可寫入的 (Writable)
  • 可列舉的 (Enumerable)
  • 可配置的 (Configurable)

那我們該如何修改這特徵?
可以使用 Object.defineProperty(..) 新增或是修改現有的特性。
要注意的是如果是修改現有的特性的話他要是可配置的(configurable)。

const obj = {}; // 給個空物件

Object.defineProperty(obj,"a",{
    value: 17,
    writable: true,
    enumerable: true,
    configurable: true
});

obj.a; // 17  出現啦~

接著我們針對這三個特徵來一一說明。

可寫入的 (Writable)

這部分很簡單,這邊就是單純你可以決定你能否變更一個特性的值。

const obj = {}; // 給個空物件

Object.defineProperty(obj,"a",{
    value: 17,
    writable: false,
    enumerable: true,
    configurable: true
});

obj.a = 123;
obj.a; // 17  Ops 還是原本的 17

如果你不是在嚴格模式下,你的更改值這動作會直接被吃掉,他也不會跟你說更改失敗,
但是反之你如果在嚴格模式下,他會直接噴爆你給你個 TypeError 並且很貼心的跟你說這是個不可寫入的特性。

可列舉的 (Enumerable)

這個馬上之後會提到這邊先提一點點,這部分主要會影響到的是 for .. in 迴圈 等等之類的。

來個範例

const obj = {
    name: 'robin'
}

Object.defineProperty(obj,"age",{
    value: 16,
    writable: true,
    enumerable: true,
    configurable: true
});

for(let targetProp in obj){
    console.log(targetProp);
};
// name
// age

Object.defineProperty(obj,"age",{
    value: 16,
    writable: true,
    enumerable: false, // 把 age 改成不可列舉
    configurable: true
});


for(let targetProp in obj){
    console.log(targetProp);
};
// name
// 啊... 看不到 age 了

可配置的 (Configurable)

可配置就是剛剛提到的Object.defineProperty(..),能不能修改就是看這個啦。

const obj = {
    a: 17
}

Object.defineProperty(obj,"a",{
    value: 16,
    writable: true,
    enumerable: true,
    configurable: false // 設定不可改
});

Object.defineProperty(obj,"a",{
    value: 18,
    writable: true,
    enumerable: true,
    configurable: true // 設定可改
}); // TypeError 

這邊不管你是不是嚴格模式都會噴你 TypeError,那如果從 false 想把它改回來怎麼辦...?
答案是你沒有回頭路xDD
那如果我直接刪掉它這個特性呢?

const obj = {
    a: 17
}

delete obj.a;
obj.a; // undefined  不見了

Object.defineProperty(obj,"a",{
    value: 16,
    writable: true,
    enumerable: true,
    configurable: false // 設定不可改
});

obj.a; // 16
delete obj.a;
obj.a; // 16  砍不掉啊啊啊啊

以上是今天的內容,
也認識到了特性描述器這神奇的東東。
感謝您的收看我們明天見~


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]>
Robin 2020-10-11 18:53:36 【這些年我似是非懂的 Javascript】Day 26 - 物件 # Part 2 https://ithelp.ithome.com.tw/articles/10251706?sc=rss.iron https://ithelp.ithome.com.tw/articles/10251706?sc=rss.iron

今天繼續分享物件~
從陣列開始~
Let's go...]]>

今天繼續分享物件~
從陣列開始~
Let's go~


陣列

之前也有提過關於陣列的部分,
陣列也是使用 [..] 的存取型式,
但是他的組織結構不一樣的地方在於,
他是預設使用"數值索引",
例如以下範例

const arrayData = ['robin','labala','aruba'];

arrayData.length; // 3
arrayData[0]; // 'robin'
arrayData[1]; // 'labala'
arrayData[2]; // 'aruba'

因為陣列本身還是一個"物件"的關係,
所以你還是可以對他增加特性到陣列上

const arrayData = ['robin','labala','aruba'];

arrayData.foo = 'foo';

arrayData.length; // 3
arrayData[0]; // 'robin'
arrayData[1]; // 'labala'
arrayData[2]; // 'aruba'

arrayData['foo']; // 'foo'

注意你增加的特性不會影想到陣列的長度

你也可以完全不使用數值的索引,全部給他新增特性,但是這樣感覺很母湯,因為陣列本身的性為是經過最佳化處理,
如果你這樣做就像是你切肉用水果刀,
切水果用殺豬刀一樣。

複製物件

關於 JS 中如何複製一個物件是身為小菜雞一定會遇到的問題,那該是要淺層拷貝還是要深層拷貝呢?
差在哪?

  • 淺層拷貝:
    會有一個新的物件,除了純值外的都只會複製參考。
  • 深層拷貝
    我全都要,我全都複製。

聽起來滿好瞭解和釐清的啊!
但是... 下面這蛋疼的範例你先看看。

function foo(){/*..*/}

const bar = {
    a: true
};

const baz = [];

const obj ={
    b: 2,
    c: bar, // 參考
    d: baz, // 參考
    e: foo
};

baz.push(bar,obj)

所以我說 obj 的拷貝值是淺層還是深層 xD?
如果是淺層拷貝 b 會在新的物件裡新增一個複製,但是 cde 特性還是原本的參考,所以說不過去啊!

那如果是深層拷貝,他不只會複製 obj ,他還會複製 barbaz,但是 baz中又還是有對 objbar 的參考,這樣無限的循環複製的問題稱之為循環參考。

看完是不是覺得...

貴圈真亂。

書中提道在 JS 中的框架都選擇了各自的解決方法,並沒有統一的標準...

所以我們該如何去解決他?
可以使用
JSON-safe 物件你就可以輕易的被複製起來了。
(簡單來說就是先變成字串再變回物件)

const newObj = JSON.parse(JSON.stringify(obj));

ES6 定義了 Object.assign(..),而這個東西他可以接收一個物件當第一個參數,接著後續參數作為來源。
並且他會將來源的東西複製到那個目標上,並且回傳該目標。

const newObj = Object.assign({}, obj);

newObj.b; // 2
newObj.c === bar; // true
newObj.d === baz; // true
newObj.e === foo; // true

以上是今天的內容
(還真少 Orz)
連假是魔鬼QQ,
今天原本的內容會併在明天所以理論上來說明天內容會比較多一點xD
"希望"可以在三十天內至少看完這本。


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-10-10 21:58:22
【這些年我似是非懂的 Javascript】Day 25 - 物件 # Part 1 https://ithelp.ithome.com.tw/articles/10251488?sc=rss.iron https://ithelp.ithome.com.tw/articles/10251488?sc=rss.iron

今天開始我們要一起來看看物件到底是什麼?
目前我的想法是......]]>

今天開始我們要一起來看看物件到底是什麼?
目前我的想法是...

一包東西..裡面好像有一些功能或是資料可以用(超膚淺xDD)


物件的形式

物件有兩種形式

  1. 宣告形式
    const obj = {
        key: value
    }
    
  2. 建構形式
    const obj = new Object();
    obj.key = value;
    

這兩者主要的差異就是宣告形式的話你可以一次新增多個,但是建構形式只能一個一個建立。

都是物件...嗎?

Javascript 的一切都是物件 (everything in Javascript is an object)。

那... string, boolean, number, null, undefined, 本身不是 object 怎麼解釋?
(null 是 object 這件事是一個 bug
所以剛剛提的那句話顯然不成立。

另外之前也提過 function 是物件的一個子型別,可以說是一個可以呼叫的物件。

還有 Array 也是,但它帶有額外的行為,組織方式也滿特別的(我覺得像是一個裝藥的盒子xD。

內容 (contents)

一個物件的內容是由值所構成的,而且這些值是存在命名過的位置中,我們叫它"特性"。

而這些特性名稱的行為就像是指標,讓你可以指向你想要的資料,之前有提過兩種方式可以存取物件,
一種是我們常用的 . 稱之為特性存取。

const obj = {
    a: 17
};

console.log(obj.a) // 17

另一種則是叫鍵值存取。

const obj = {
    a: 17
};

console.log(obj['a']) // 17

要注意的是雖然特性存取這種方式最常見,但是鍵值存取好用的地方在於他可以接受相容於 UTF-8 或是 Unitcode的字串作為特性名稱,但是特性存取不行。
例如以下範例

const obj = {}

obj['Hi!Robin~']= 'robin'; // work
obj.Hi!Robin~ = 'robin'; // SyntaxError Unexpected token '!'

注意!無論如何,特性名稱永遠都是字串,之前也提過就算你給他個數字,他也會自動轉成一個字串。

const obj = {};

obj[1] = '1';
obj['1'] = '11';  // 直接覆蓋掉

console.log(obj[1]);  // 11
console.log(obj['1']);  // 11

計算得出的特性名稱 ( Computed property names )

在 ES6 中新增了這個功能叫做 " 計算得出的特性名稱 ( Computed property names ) "

基本上就是你可以在物件字面值宣告的鍵值名稱位置做計算像是這樣。

const prefix = 'robin'
const obj = {
    [prefix + 'Name']: "robin",
    [prefix + 'Age']: "18",
    [prefix + 'Phone']: "0912345678",
}

obj['robinName'];  "robin"
obj['robinAge'];  // "18"
obj['robinPhone'];  // "0912345678"

我之前有用過幾次,但是我完全不知道是 ES6 才新增的

特性 vs. 方法

其實有兩個名詞在我記憶中是分不清楚差異的那就是函式和方法。

其實在 JS 中 函式(Function)方法(method) 是可以互換的名詞。

稍微查了一下,
我覺得如果是硬要分我應該會用下面兩點來區分。

  • 函式可以獨立存在跟物件無關。
  • 方法跟物件有關比方說是某類別的子函式之類的。

如果觀念上有錯麻煩在指正。

那假使當物件中有一個函式被參考取用,
就可以說他是方法嗎?
感覺有點怪xD
實際上不能說這個函式"屬於"某個物件,他只是一個"參考"。


以上是今天的內容
感謝您的收看
明天繼續分享物件~


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-10-09 23:45:41
【這些年我似是非懂的 Javascript】Day 24 - 是這個不是那個的 this # Part 4. https://ithelp.ithome.com.tw/articles/10251020?sc=rss.iron https://ithelp.ithome.com.tw/articles/10251020?sc=rss.iron

今天來分享繫結的例外,
除了那四個規則外還是有例外。

...]]>

今天來分享繫結的例外,
除了那四個規則外還是有例外。

忽略 this

當你使用 bindcallapply 並且傳入 nullundefined當作參數,此時就會轉變成套用預設繫結也就是全域變數。

function foo(){
    console.log(this.a);
}

var a = 2;

foo.call(null); 

而主要使用的情境大概就是當你不在意 this 的是什麼時,但是你想要傳遞其他參數就用 null 來佔空間,
但這並不會是一個完美無缺的解決方式,他還是有可能會造成一些第三方的函式出現一些 bug 而且妳會很難抓到。

那... 斗洗爹 (我該怎麼辦呢)?

書中提到作者本身會偏好的實務做法是會使用電腦網路(或是軍方)的專業術語建立一個 DMZ( demilitarized zone ) 物件,主要就是一個完全空的,並且無委派的物件(書上說之後會講xD)。

Google 了一下維基百科

DMZ(全稱Demilitarized zone,中文為「非軍事區」,或稱Perimeter network,即「邊界網路」、周邊網路[1]或「對外網路」)為一種網路架構的布置方案,常用的架設方案是在不信任的外部網路和可信任的內部網路外,建立一個面向外部網路的物理或邏輯子網路,該子網路能設定用於對外部網路的伺服器主機。

該方案可以使用在防火牆、路由器等區隔內外網的網路裝置。在一般比較低階的網路裝置,DMZ的功能只能以軟體設定的介面去設定並實作,實體的網路層相接,會與一般的LAN PORT相接共同管理。不過在一般比較高階的網路裝置,如高階的防火牆裝置,DMZ的功能除了軟體的介面的設定外,在實體的連接PORT除了一般的WAN PORT、LAN PORT外,還會有另外獨立的DMZ PORT,這樣可以方便網路管理人員在管理網段時,除了軟體介面上去設定WAN、LAN、DMZ等網段外,在實體的纜線連接(通常採用的是RJ45)時,也可以直接區分網段,更方便管理。

...

我們還是看範例好了 xDD
ø: 小寫的空集合數學符號 (Option+O)
如果你不喜歡也可以找其他的~

function foo(a,b){
    console.log(`a: ${a}, b: ${b}`);
}

var ø = Object.create(null);

foo.apply(ø,[2,3]); // a: 2, b:3
var bar = foo.bind(ø, 2);
bar(3); // a: 2, b:3

基本上 DMZ 物件的名稱你喜歡就好,但是不要用一般會取的變數跟我說那是 DMZ 就好 xDD
除了功能上的好處,ø 在語義上還傳達了這個 this 是空的比 null 還清楚。

間接參考 ( indirect references )

基本上就是字面上的意思,當你建立出對函式得間接參考也會套用到預設繫結。
如下面的範例

function foo(){
    console.log(this.a);
}

var a = 2;
var x = {a: 3, foo:foo};
var y = {a:4};

x.foo(); // 3
(y.foo = x.foo)(); // 2 oh shit

另外如果是嚴格模式的話 this 就是 undefined

語彙的 this - 箭頭函式 ( Arrow-function )

ES6 引進的箭頭函式大家應該都不陌生,不過他不適用這幾天講的四種規則xD
讓我們一起來看看吧~

function foo(){
    return (a)=>{
        console.log(this.a);
    };
}

var obj = {
    a:0;
};

var obj1= {
    a:1;
};

var bar = foo.call(obj);

bar.call(obj1); // 0 

主要原因是箭頭函式被呼叫時會從語彙捕捉到 foo() 的 this,所以 foo 已經被繫結到 obj 時,他也會一起連結到 obj,而且他不會被覆寫。

以上是今天的內容
明天開始會分享物件~


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]>
Robin 2020-10-08 23:16:46
【這些年我似是非懂的 Javascript】Day 23 - 是這個不是那個的 this # Part 3. https://ithelp.ithome.com.tw/articles/10250476?sc=rss.iron https://ithelp.ithome.com.tw/articles/10250476?sc=rss.iron

今天主要分享的就是兩個部分,
一個是明確的繫結,
...]]>

今天主要分享的就是兩個部分,
一個是明確的繫結,
另一個是當有多個規則能夠套用時的優先順序。
那我們開始吧


明確的繫結 ( Explicit Binding )

所謂的明確的繫結就只是指出你想要的 this ,對 this 而言 你可以使用 call(..)apply(..) 或是 ES5 提出的 bind(..) 來明確的綁定給 this 的物件,雖然他們是對 this 來說是一樣的,但是他們的行為卻會不一樣。

call

call 第一個參數為 this 的值,用法如下

function foo(content){
    console.log(this.a);
    console.log(content);
}

var obj = {
    a:2
};

foo.call(obj,'Hellow world'); 
// 2
// 'Hellow world'

apply

與 call 使用方式比較大的差異是後面接收的參數是陣列。

function foo(content){
    console.log(this.a);
    console.log(content);

}

var obj = {
    a:2
};

foo.apply(obj,['Hellow world']); 
// 2
// 'Hellow world'

bind

主要用法差異在於說先綁定一個物件後,
再將後續的參數傳入。

這個其實在別人的 code 中我個人滿常見的
用法如下

function foo(content){
    console.log(this.a);
    console.log(content);

}

var obj = {
    a:2
};

var bar = foo.bind(obj);
console.log(bar('Hellow world'))
// 2
// 'Hellow world'

佛心函式

在 JS 有很多函式會提供給你一個 Option 的參數(通常叫做 context ),
可以讓你使用特定的 this ,不用你自己透過 call(..) 或是 apply(..) 來做明確的繫結。(貼心到爆
例如以下範例

function foo(el){
    console.log(el, this.id);
}

var obj={
    id: "nice"
};

[0,1,2].forEach(foo,obj);
// 0 nice 1 nice 2 nice

new 繫結

最後一個規則啦!
基本上這個就很簡單你是不是有看過使用 new 建構過一個新物件,那所謂的 "new 繫結" 就是 this 會指向你 new 出來的物件。
例如下面的範例

function foo(){
    this.a = a;
}

var bar = new foo(2);
console.log(bar.a) // 2

優先順序

當有多個規則能夠套用時時的優先順序是誰先誰後?

  1. new 綁定
  2. 明確綁定
  3. 隱含綁定
  4. 預設綁定

以上是今天的內容,感謝您的收看 ~

明天繼續分享感恩你的收看


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-10-07 21:59:14
【這些年我似是非懂的 Javascript】Day 22 - 是這個不是那個的 this # Part 2. https://ithelp.ithome.com.tw/articles/10249991?sc=rss.iron https://ithelp.ithome.com.tw/articles/10249991?sc=rss.iron

接續昨天的內容讓我們繼續看下去~

呼叫地點

接續昨天的內容讓我們繼續看下去~

呼叫地點

昨天提到

其實就是當一個函式被調用時會有一個"啟動紀錄"被創建出來,也被叫做"執行情境",他裡面就包含了很多資訊包含該函式式在哪裡被調用的傳入什麼參數等等的,

所以...
我們找是誰調用他的就可以找到該函式被調用的位置,
真簡單~

這世界...
沒有你想的那麼簡單,某些編成模式會遮蔽真正調用的位置。

呼叫堆疊

到底是誰叫了我?
他就是執行到目前為止已被呼叫的函式所產生的堆疊,
不用想太多就是一個堆疊然後一直紀錄誰叫了誰這樣。

function baz(){
    // 堆疊: baz 所以被呼叫的地點是在全域範疇中
    console.log("baz");
    bar();
}

function bar(){
    // 堆疊: baz -> bar 所以被呼叫的地點是在 baz
    console.log("bar");
    foo();
}

function foo(){
    // 堆疊: baz -> bar -> foo 所以被呼叫的地點是在 bar
    console.log("foo");
}

baz(); // 起始點

這樣看起來其實呼叫堆疊沒你想像的那麼難對吧!?
一片蛋糕!

就像是你高中學的程式追蹤一樣~

規則

在知道上面的被誰呼叫之後,接著就要開始判斷 this 中的四個規則。

  • 預設繫結
  • 隱含的繫結
  • 隱含地失去
  • new 繫結

預設繫結

這是第一個規則也是最常見的規則,
獨立函式的調用

function foo(){
    console.log(this.a);
}

var a = 2;
foo(); // 2

其實 foo 裡面的 this.a 就等於外面的 a,他並不是拷貝出來的而是真的他!

但是凡事都有例外
在嚴格模式(strict mode)中全域物件就不會是預設繫結this 就會變成 undefined

function foo(){
    "use strict"
    console.log(this.a);  
}

var a = 2;
foo(); // TypeError 因為 this 是 undefined

主要嚴格模式會影響的其實是該函式,跟調用的地點無關

function foo(){
    console.log(this.a); // 沒事兒
}

var a = 2;

(function(){
    "use strict"
    
    foo(); // 2
})();

書中溫馨提醒:
不要混合使用 strict mode 和不是 strict mode,是男人就整個都用或是都不要用,不然會讓人黑人問號。

隱含的繫結

呼叫地點如果是一個...
情境物件(context object)
擁有物件(owning object)
包含物件(containing object)
以上三個都是一樣的東西只是名詞不一樣

反正長得像這樣

function foo(){
    console.log(this.a); // 叫的是 obj 的 a ,不是全域的 a 
}

var obj = {
    a:2,
    foo:foo
};

var a = 6;

obj.foo(); //2

剛剛說的那三個名詞聽起來就是 obj "擁有"了這個函式,所以導致 this 指向該物件。
...

...

...

你只能說他在呼叫的"那時候"擁有或包含那個函式。

不在乎天長地久,只在乎曾經擁有

(不要理我,讓我尷尬

這部分很簡單的是假如包了又包包了又包,那到底是指誰!?
就是最後誰指誰就是誰!

function foo(){
    console.log(this.a);
}

var obj = {   // 最後的一根稻草
    a:2,
    foo:foo
};

var obj1 ={
    a: 12,
    obj:obj
}

var a = 6;

obj1.obj.foo(); //2

隱含地失去

隱含地繫結失去繫結被退回到全域變數也就是他只是一個參考,或是在嚴格模式下的 undefined,就是隱含地失去。

function foo(){
    console.log(this.a);
}

var obj ={
    a: 2,
    foo: foo
};

var bar = obj.foo; // reference
var a = "Welcome to global";

bar(); "Welcome to global"

還記得剛剛說過"重要的是呼叫的地點",而 bar 被叫的地點其實很單純就是 global 。

接著當傳遞一個 callback 時會花森什麼事?

function foo(){
    console.log(this.a);
}

function bar(cb){
    cb();
}

var obj = {
    a: 3,
    foo: foo
};

var a = "Welcome to global";

bar(obj.foo); "Welcome to global"

wow~ wow~ wow~

參數的傳遞只是一個隱含的指定,所以想當然爾就會跟剛剛提的結果一樣。

另外還有一個常見的情況是當 callback 把 this 指向觸發該事件的 DOM 元素就是被刻意的更改 this 那也是一個蛋疼的情況。

...

好!

以上是今天的內容
明天接著介紹的是今天沒講完的"明確的繫結"
還有當有多個規則能夠套用時的優先順序。

敬請期待~


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]>
Robin 2020-10-06 22:02:06
【這些年我似是非懂的 Javascript】Day 21 - 是這個不是那個的 this # Part 1. https://ithelp.ithome.com.tw/articles/10249579?sc=rss.iron https://ithelp.ithome.com.tw/articles/10249579?sc=rss.iron

這個 this 到底是什麼,對於使用 JS 的你知道 this 這關鍵字他...]]>

這個 this 到底是什麼,對於使用 JS 的你知道 this 這關鍵字他的參考到底是什麼你知道嗎?

反正我是不知道拉
如果你也不知道歡迎跟我一起學習

this 到底是什麼可以吃嗎?

答案很明顯是不行(廢話),
但是他是一個物件,然後裡面的值取決於你執行的函式。
先來談談他的好處

function foo(){
    return this.name.toUpperCase();
}

function boo(){
    const helloStr = `Hello, My name is ${foo.call(this)}`
    console.log(helloStr)
}

const me = {
    name: "Robin"
}

foo.call(me); // ROBIN
boo.call(me); // Hello, My name is ROBIN

那如果說不用這個方式會以什麼方式寫呢?
就是傳入一個 object

function foo(context){
    return context.name.toUpperCase();
}

function boo(context){
    const helloStr = `Hello, My name is ${foo(context)}`
    console.log(helloStr)
}

const me = {
    name: "Robin"
}

foo(me); // ROBIN
boo(me); // Hello, My name is ROBIN

這看起來沒什麼太大的問題,
但是使用 this 的話看起來明顯乾淨了許多,
如果使用的情境越複雜,差異越是明顯。

不負責的言論:書上說之後在物件與原型那章節會提到一群函式能夠自動參考適當的情境物件是一件非常美妙的事(聽起來是作者本身感同身受xDD

你知道嗎?

其實 this 是參考到函式本身,

你現在的想法如果是"真的假的?",
那恭喜你可以繼續看下去,
如果你現在的想法是"聽你在屁!",
請先放下你的怒火,

你想得沒錯!
其實 this 不是參考到函式本身,
在字面上或文法上合理的推論,
可是實際上想想...
什麼情況會想要參考到函式本身xDD?
大概只有想要自己 call 自己進行遞迴之類的,
那如果真的是這樣那以下的範例應該可以 work!

function foo(countFoo){
    console.log(`foo-> ${countFoo}`);
    this.count++;
}

foo.count = 0;

for(let i=0; i<5; i++){
    foo(i);
}
// foo-> 0
// foo-> 1
// foo-> 2
// foo-> 3
// foo-> 4


console.log(foo.count); // 0 , 說好的 5 呢?

實際上看起來就不是對吧!?
所以我們要拋開他字面上的意思...

其實它就是...
"this 執行時期的繫結",主要他會取決於函式調用的各種條件為基礎,this 的繫結與函式在何處被宣告無關,單純就是取決於函式被呼叫的方式。

那...所以他到底怎麼知道 this 被用什麼方式呼叫?

其實就是當一個函式被調用時會有一個"啟動紀錄"被創建出來,也被叫做"執行情境",他裡面就包含了很多資訊包含該函式式在哪裡被調用的傳入什麼參數等等的,

講那麼多啊上面那個我該如何用 this 給他大大的擁抱?

function foo(countFoo){
    console.log(`foo-> ${countFoo}`);
    this.count++;
}

foo.count = 0;

for(let i=0; i<5; i++){
    foo.call(foo,i);
}
// foo-> 0
// foo-> 1
// foo-> 2
// foo-> 3
// foo-> 4


console.log(foo.count); // 5

差異再哪?

主要是透過 call() 確保 this 指向該函式物件本身。

以上是今天的內容
如果看不懂也沒關係明天會學會如何找出一個函式的呼叫地點和他執行過程是如何繫結 this 的 (書上說的xD)


今天是鐵人賽第 21 天,越來越鐵人 xD
生活中滿多意外會造成原本的行程有變化,
俗話說的好"計劃趕不上變化",
例如我現在就只剩下兩個小時要寫文章加看書,
我都好奇我真的有時間內化嗎 Orz ?
我只能盡量先縮減今天要看的內容
我感到非常抱歉Orz

感謝你
我們明天見


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-10-05 23:28:09
【這些年我似是非懂的 Javascript】Day 20 - 閉包 (Closure) # Part 2 https://ithelp.ithome.com.tw/articles/10249086?sc=rss.iron https://ithelp.ithome.com.tw/articles/10249086?sc=rss.iron

今天要來分享一下一些 Module Pattern 使用閉包強大的功能。<...]]>

今天要來分享一下一些 Module Pattern 使用閉包強大的功能。

模組 (Module Pattern)

在 JS 中可以藉由使用閉包的特性,隱藏一些模組內資訊,並且選擇供外部的 API ,例如以下範例

function fooModule() {
  var something = 'hellow';
  var another = [1, 2, 3];

  function bar() {
    console.log(something);
  }

  function baz() {
    console.log(another.join(' ! '));
  }

  return {
    bar: bar,
    baz: baz,
  };
}

var someModule = fooModule();

someModule.bar(); // hellow
someModule.baz(); // 1 ! 2 ! 3

像是以上的範例
這種實作的方式也叫做 揭露模組
要使用模組模式以下兩個條件

  • 必須要有一個外層的包含函式,然後他必須至少被調用一次

    例如上範例的 var someModule = fooModule();

  • 函式內至少必須要回傳一個內層函式,這樣才能讓該函式可以透過閉包存取該模組私有的資料或狀態。

除了上述做法外,還可以把它變成一個 IIFE(即刻調用函式)。

const foo =(function fooModule() {
  var something = 'hellow';
  var another = [1, 2, 3];

  function bar() {
    console.log(something);
  }

  function baz() {
    console.log(another.join(' ! '));
  }

  return {
    bar: bar,
    baz: baz,
  };
})();

foo.bar(); // hellow
foo.baz(); // 1 ! 2 ! 3

ES6 之後的模組

ES6 之後在模組概念新增了一級的語法支援,主要是可以經過模組系統載入之後,ES6 會把一個檔案當作一個個別的模組,每個模組都能匯入其他模組或是特定 API 的成員 ,或是能夠匯出自己公開的 API 成員。

這部分我很常用到呢 xD 但是我不知道他是 ES6 後新增的。

範例

// foo.js
import sayHellow from "bar";

const name = "robin"

function printHellowName(){
    console.log(`${sayHellow()}, ${name}`)
}

export printHellowName;

// bar.js

function sayHellow(){
    return `Hellow`
}

export sayHellow;

以上是今天的內容


連假終於到了尾聲,
我也從台南回到了台北,
還記得去年當兵時有人跟我說,
在連假就可以明顯看得出來有錢跟沒有錢的人的區別,
就是有錢人搭高鐵,沒錢的人搭台鐵人擠人,
我現在不算是有錢但是至少還搭的起高鐵,
但是這次連假我去台南...
卻還是像沙丁魚一樣xDD
我卻想不起來是誰跟我說的 (氣到想不起來xD)

寫文章似乎已經成為了生活的一部分,
雖然感覺會有一點掃興的感覺,

我: 等等等等!!再給我半小時,我寫完就走 Orz

發完文章後有種特別踏實的感覺,
今天是鐵人賽的第 20 天
剩餘 10 天!
還在努力的你也堅持住啊!!!
感謝你的觀看
我們明天見


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-10-04 23:21:09
# 【這些年我似是非懂的 Javascript】Day 19 - 閉包 (Closure) # Part 1 https://ithelp.ithome.com.tw/articles/10248395?sc=rss.iron https://ithelp.ithome.com.tw/articles/10248395?sc=rss.iron

經過瞭解語彙範疇後,今天要來分享 JS 我好像似懂又非懂的"

經過瞭解語彙範疇後,今天要來分享 JS 我好像似懂又非懂的"閉包 (Closure)"。

什麼是閉包 ?

  • 我之前的想法 :
    Function 包 Function 就是閉包。
  • 實際上:
    其實是一個函式並可以記住並且可以存取語彙範疇,並且當函式在他的語彙範疇外執行時也可以的功能。

所以我之前的想法其實不算是非常正確的,
原因等等看一看就會明白了。

那我們平常在哪裡可以看得到?

真的沒有在唬爛,只要你有心人人都是閉包。

說這麼多先說說他的好處是什麼?
他可以不需要使用到全域變數,
在不污染環境的情況下讓外部可以修改該狀態。

有點像是你把它封裝好的包裹,然後你可以透過念能力在外部可以使用。 (最近獵人看太多xD )

聽不懂沒關係我們直接看看範例,

function foo(){
    var a = '17';
    
    function bar(){
        console.log(a);
    }
    bar(); // '17'
}

還記得之前學的嗎?
語彙範疇的查找功能 RHS,所以 boo 查找 a 會找到外層 foo 宣告的 a

所以他... 是閉包嗎?
如果依照我之前以為的

閉包就是 Function 包 Function

那他就是閉包,因為他的確是 Function 包 Function。

但是你反觀剛剛定義的閉包,

其實是一個函式並可以記住並且可以存取語彙範疇,並且當該函式在他的語彙範疇外執行時也可以的功能。

那看起來就不完全不是了啊,

到目前為止好像還是看不出來閉包是什麼...
那我們再改一下上面的範例

function foo(){
    var a = '17';
    
    function bar(){
        console.log(a);
    }
    return bar; // 注意這邊沒有執行 bar 本身
}

var baz = foo();

baz(); // 2 看到了閉包的蹤影

你會發現 baz 其實執行的就是 foo Function 內層的 bar 對吧!?
而且還可以讀取到原本在該語彙範疇所宣告的 a
那就達成了剛剛所說的。

一個函式並可以記住並且可以存取語彙範疇,並且當該函式在他的語彙範疇外執行時也可以的功能。

等等等...
之前不是有提過有垃圾收集器 (Garbage Collection),那這樣看 foo 在執行後理論上裡面的記憶體應該會被回收才是啊,
那為何在閉包中不適用呢?
原因是閉包中的內層 bar() 函式本身還是有一個參考指向到讓外層的 foo() 讓這範疇可以繼續留存,而那個參考就叫做閉包。

迴圈與閉包

之前我其實踩過這個坑就是我想要用一個 for 迴圈並且加上 setTiimeout 每秒執行一次迴圈就好,
實際寫出來長這樣。

for(var i=0; i<=5 ; i++){
    setTimeout(function timer(){
        console.log(i);
    },1000)
}

理論上看起來會是一秒執行一次對吧!?
第一圈等待一秒再接著下一圈,
但事實是...

6
6
6
6
6

Why?
看結果而言看起來就是他一路向西走,直接把迴圈執行到底然後統一都在一秒後一次 console 出來,6的由來是因為最後一次加完他 >=5 所以不執行第六次,所以才會拿到五次的 6
(這部分牽扯到 JS 的非同步,之後有機會再說)

所以我到底該如何實作這個?

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, j * 1000);
  })(i);
}

Nice ~ 透過閉包並且還應用到之前提的 IIFE 每次迭代來建立一個新的範疇...
每次迭代來建立一個新的範疇...
每次迭代來建立...
每次迭代...
那其實可以直接用 let 搞定對吧!?
我們只需要建立一個區塊範疇就搞定啦~

for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i);
  }, i * 1000);
}

是不是簡單多了!?


今天先分享到這,明天來講講剩餘的一部分,
邊旅遊還要空出時間寫文章真的不容易xDD
連假是魔鬼QQ
先這樣囉~

感謝各位

參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-10-03 14:51:14
【這些年我似是非懂的 Javascript】Day 18 - 拉升(Hoisting) https://ithelp.ithome.com.tw/articles/10247806?sc=rss.iron https://ithelp.ithome.com.tw/articles/10247806?sc=rss.iron

這篇來分享一下在之前有提過的"提升 (Hoist...]]>

這篇來分享一下在之前有提過的"提升 (Hoisting)",
就是還被抓到打錯字的這篇 xDD
【這些年我似是非懂的 Javascript】Day 4 - 你一定可以入的了門 #下篇

雖然上面提的那篇已經提過了,
但是我還是再分享一次,

console.log(a); // undefined
var a;

原本 console.log 那行預期應該是 ReferenceError ,卻得到 undefined
why !?
因為 JS 的 Engine 將變數宣告都提升至該範疇的最上面,這種現象就稱為提升( Hositing )

JS 到底是編譯式語言還是直譯式語言

網路上一查一堆都是 JS 是直譯式的,
但是如果他有 Hoisting 那又怎麼可能是直譯式,
再看一次上面的範例,
假使他真的是直譯式語言,
當他執行到第一行的 conosole.log 時就應該要直接噴錯了才不可能會顧慮到下面有一個 var 的宣告。

種種跡象指出,他有經過編譯!
也就是在編譯階段他就把所有的宣告的變數或函式找出來,
並且預先處理好。

函式優先

剛剛上面有提到 JS Engine 會將宣告的變數或函式找出來,
並且預先處理好。
那...誰優先?
答案是函式!
我們來看看下面的範例

foo(); // 函式搶先
var foo; 

function foo(){
    console.log('函式搶先');
}

foo = function foo(){
    console.log('變數優先');
}

如果同樣會提昇的話變數如果較為優先那應該答案會是 "變數優先",但實際結果卻是函式較為優先。

如果沒有 Hoisting

如果 JS 沒有 Hoisting 會造成什麼問題?

  • 必須要先宣告變數才能使用
  • 必須要先宣告函式才能使用
  • 沒辦法達成 function 互相使用

第一點還行,第二點就會滿不方便的,你必須要確保你要呼叫的函式在你使用的上方,那萬一要互相 call ...
就會造成第三點所說的,沒辦法達到 function 互 call。

目前我功力還沒有可以到解釋他是如何運作的,
只能大概描述他會發生並且避免,
如果想知道更詳細的運作方式,
我這邊還是再推一次這位大大寫的,
(雖然我目前還有些看不太懂)
但是寫的超讚,推推!
我知道你懂 hoisting,可是你了解到多深?

以上是今天的內容
感謝您的收看


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))
我知道你懂 hoisting,可是你了解到多深?

]]> Robin 2020-10-02 05:17:16
【這些年我似是非懂的 Javascript】Day 17 - 區塊的範疇 https://ithelp.ithome.com.tw/articles/10247620?sc=rss.iron https://ithelp.ithome.com.tw/articles/10247620?sc=rss.iron

還記得昨天說的那句話

...]]>

還記得昨天說的那句話

ES6 以前除了函式外沒有任何結構可以建立他們自己的範疇泡泡。

ES6 我們擁有了什麼造成這改變?

  • ES6 以前只能用 var 宣告變數,並只能透過函式來建造一個範疇泡泡。
    {
        var a = 3;
    }
    console.log(a); // 3  oh .. shit
    
  • ES6 之後,可以透過 constlet 來宣告以區塊為範疇的變數。
    {
        const a = 4;
        let b = 5;
    }
    console.log(a); // ReferenceError
    console.log(b); // ReferenceError
    

那以前沒有區塊作為範疇的變數會發生什麼事?
以滿常用的 for 來說

  • ES6 以前,區塊以外還是可以存取到,但是我只會在 for 裡頭用到,卻污染了整個範疇。
    for(var i = 1; i < 10; i++){
        console.log(i);
    } // 1,2,3 ... 9
    console.log(i); // 10  oh shit 
    
  • ES6 以後,再也不用擔心上面發生的事了。
    for(let i = 1; i < 10; i++){
        console.log(i);
    } // 1,2,3 ... 10
    console.log(i); // ReferenceError
    

Try/catch

JS 在 ES3 的規格有指出 try..catchcatch 子句會以那個 catch 區塊為範疇
如下面的範例

try{
    undefined();
}catch(err){
    console.log(err); // 抓得到
}
console.log(err); // ReferenceError

垃圾回收 (Garbage Collection)

區塊範疇除了上面之外還有另一個好處是可以將不需要一直留存的資料在處理後就直接回收該記憶體空間。
如下面範例

  • 佔著記憶體但是我只需要做一次
    function foo(data) {
      // do something...
    }
    
    var someReallyBigData = { .. }; // 霸佔著資源
    
    foo(someReallyBigData);  // 霸佔著資源
    
    var btn = document.getElementById('boo_button');
    
    btn.addEventListener('click'), function clickHandler(e){
      console.log('Clicked button');
    });
    
  • 解決上面的問題向 Engine 表示我不在需要他
    function foo(data) {
      // do something...
    }
    { 
    // 在這裡宣告的變數並且用完就丟掉資源
        let someReallyBigData = { .. }; 
    
        foo(someReallyBigData);  
    }
    var btn = document.getElementById('boo_button');
    
    btn.addEventListener('click'), function clickHandler(e){
      console.log('Clicked button');
    });
    

會有這樣的好處是有關於閉包(closure)的部分的範疇,這部分會在大概兩三天後應該會講到。

那~ 今天的分享就到此

感謝各位觀看
我想今天大家應該都在烤肉了吧 xDD
祝各位有個愉快的中秋節~


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-10-01 22:25:03 【這些年我似是非懂的 Javascript】Day 16 - 函式的範疇 https://ithelp.ithome.com.tw/articles/10247204?sc=rss.iron https://ithelp.ithome.com.tw/articles/10247204?sc=rss.iron

前一個章節有提到範疇泡泡,
那這些泡泡除了最外圈的全域變數外,...]]>

前一個章節有提到範疇泡泡,
那這些泡泡除了最外圈的全域變數外,
難道建立範疇泡泡只能透過函式嗎?

讓我們繼續看下去~


函式的範疇

在 JS 的世界中函式會為自己建立一個範疇泡泡,
ES6 以前除了函式外沒有任何結構可以建立他們自己的範疇泡泡。
那函式範疇所造成的影響是什麼?

所有的變數都是屬於函式內部的,
也就是說在全域範疇是無法使用的,如下面的範例。

function foo(){
    const a = 123;
    function boo(){
        const b = 321;
    }
    const c = 567;
}

foo(); // 可呼叫
console.log(boo()); // 呼叫失敗,拋出 ReferenceError
console.log(a,b,c); // 呼叫失敗,拋出 ReferenceError

隱身在普通範疇

使用函式範疇其實就有點像是把你的程式碼包裹起來,而當任何人使用該函式他就會被綁到新的包裹函式的範疇上,而不是之前他的範疇 (真繞口)。
這有什麼好處?
實際來看看

function foo(a){
    function boo(a){
        return a-1;
    }
    const b = a + boo(a*5);
    console.log(b);
}

foo(2); // 11

他可以將 b 以及 boo() 完全區隔開不受外界影響,讓私有的保持私有。
看起來很正常啊,如果不這麼做會發生什麼事
可以看一下下面的範例

function foo(a){
    b = a + boo(a*5);
    console.log(b);
}

function boo(a){
    return a-1;
}

var b;

foo(2); // 11

等等等等...
結果還是一樣啊!?
那感覺沒差吧?

才怪!

你這樣做的話 b 在全域範疇內,誰都有可能造成你無法預料的後果,萬一被存取加減一下,就會造成你內心原本預期的效果,這就可以應證上面所說的"讓私有的保持私有"的好處。

此外因為變數 b 和函式 boo() 被完全阻隔,也可以避免識別字的重複。

隱藏不等於沒污染

剛剛聽起來滿完美的,
但是其實就算是這樣還可能會造成範疇的污染,
例如以下範例

const a = 2;

function foo(){
    const a = 3;
    console.log(a);
}
foo(); // 用了 foo 這個識別字
console.log(a); // 2

foo 這個 function 明顯是要執行,
雖然也用隱藏的方式阻隔了 a 變數,
避免私有變數被存取,
但是本身的 foo 卻污染了該範疇,
那該怎麼做才好?
你可以透過 IIFE(即刻調用的函式運算式)

我怎麼記得我之前文章寫過 xDD
之後回頭再補上連結。

簡單來說他可以將函式透過() 包裹起來,變成一個運算式,並且在最尾段再加上 () 來執行該函式。
拿上面的例子改一下

const a = 2;

(function foo(){
    const a = 3;
    console.log(a);
})(); // 3 直接被調用
console.log(a); // 2
foo(); // ReferenceError 噴爆你

這就可以很明白的知道這樣連他本身都不會污染包含他的範疇了。

IIFE

剛剛在上面提到了 IIFE 書中也提了一些我不知道的事,
我覺得滿有趣的也跟你們分享,

形式

以前的 IIFE 形式某些人會使用以下的方式

(function (){
    console.log(17)
}()); // 17

我個人比較常看到和使用的是下面這種方式

(function (){
    console.log(17)
})(); // 17

這兩種的功能是一模一樣的,純粹只是個人偏好的風格不同。

誓死捍衛 undefined

你渴望爆炸嗎?

還記得之前提過 undefined 有可能會被當成變數被亂搞,當你使用 IIFE 就可以避免悲劇發生保證 undefined 是你認識的 undefined

undefined = true; // 弄爆你

(function foo(){
    let a;
    if (a === undefined){
        console.log('沒爆炸')
    }
})(); // 沒爆炸

傳入參數

他可以透過最後的 () 傳入參數,

(function (num){
    console.log(num)
})(123); // 123

反轉順序

要執行的函式不會先出現,而是在調用和傳入給他參數之後出現,很常被用在 UMD (Universal Module Definition) 專案中。 (完全沒聽過啊!我好菜 Orz)

var a = 2;

( function foo(func) {
  func(window);
})( function boo(global) {
  const a = 3;
  console.log(a); // 3
  console.log(global.a); // 2
}); 

簡單來說他就是透過參數的方式帶入整個 boo function,再傳入 windows 這全域變數作為 global 參數。

以上是今天的分享


啊... 明天已經是中秋連假的開始,
看來我要抱著筆電去台南玩了 QQ

越來越硬的感覺xDD

還記得開賽的前五天...

再看看現在的我...

然後今天準備要開始寫文章的時候發現,
已經有一批人已經完賽惹 Orz
在此敬佩那些完賽的鐵人們。

好啦~ 明天見~


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-09-30 23:40:26
【這些年我似是非懂的 Javascript】Day 15 - 語彙範疇 https://ithelp.ithome.com.tw/articles/10246368?sc=rss.iron https://ithelp.ithome.com.tw/articles/10246368?sc=rss.iron

範疇的運作方式主要有兩種模型,
第一種就是今天的主題

範疇的運作方式主要有兩種模型,
第一種就是今天的主題語彙範疇
是大多數的程式語言所用的,
第二種叫做動態範疇
它則是少數語言(Bash, Perl 的某些模式)使用的。
接著我們就來好好的跟他交朋友吧

語彙分析時期

昨天那篇有提到傳統編譯式語言通常的三步驟的第一步就是 lexing 也就是語彙分析
而語彙範疇就是在這個時期所定義的範疇(聽起來有點繞口),而這範疇其實就是你在寫 code 時所編寫的"變數"和"區塊",

範疇泡泡

我自己在高中的時候,計概老師都叫他 Block ,那年夏天老師溫柔的握著我的手把 Block 畫出來的那種感覺。
(做夢中)


如圖所示
Bubble 1 最外圈就是全域範疇
Bubble 2 包含 foo 的範疇
Bubble 3 包含 boo 的範疇

Bubble 1 包含 Bubble 2 + Bubble 3
Bubble 2 包含 Bubble 3
這樣應該就知道了吧!

查找

由上面的範疇泡泡可以知道結構與相對位置,而最內層的 console.log,就會一路從最內層開始找,找到最外層直到只要找到"第一個符合",就會停止查找,
不管該函式在哪邊""用,語彙範疇只會管你在哪被宣告的。

欺騙語彙範疇

JS 有兩個機制是會影響到語彙範疇的,而且在社群上已經被唾棄,幾乎是完全不建議使用。

  1. with
  2. eval (這大家應該都熟悉)

那我們就先從 with 來看看吧

with

我自己之前沒看過和用過,並且已經棄用了,他主要是讓我們可以對一個物件進行多次參考,而不用每次都一直參考該物件本身。

範例
正常情況下

obj.name = 'robin';
obj.age = '18';
obj.phone = '0912345678';

使用 with

with(obj){
    name = 'robin';
    age = '18';
    phone = '0912345678';
}

看起來不錯啊!?
真放便而且清楚明瞭。
但是他被唾棄且棄用...
why!?
因為...
他的霸氣會外漏

來看看下面的範例

function foo(){
    with(obj){
        a = 3;
    }
}

var obj1 = {
    a = 4;
}

var obj2 = {
    b = 4;
}

foo(obj1);
console.log(obj1.a); // 3

foo(obj2);
console.log(obj2.a); // undefined
console.log(a); // 3 shit 變成全域變數了

看到這你一定會想說...

原因是這樣~
第一個 obj1 本身就有 a 這個特性,
而第二個 obj2 沒有 a 這個特性...
然後...
它就直接略過 witha 做正常語彙參考一樣直接往外找,
找不到 a 直接幫他建了一個 a 並且塞 3 這個值給他。

不過這樣講起來,經過昨天的 LHSRHS 的講解後,其實你就已經有一個底直接開啟嚴格模式,其實就不會發生這類型的 side effect 了。

eval

我以前用過 Orz... 而且被糾正過。
但是其實我當初也不太知道為何不能用,那我們就一起來看看他有些什麼問題吧。

eval 的主要用途是在於他可以把一段 String 轉為程式碼,並且可以正常執行。

eval('console.log('hi')') // hi

那他為何被唾棄?
因為他在一開始的時候 Engine 不會知道他的內容造成他欺騙語彙範疇,就有點類似半路殺出程咬金的感覺xD
來看看範例吧

function foo(str, a){
    eval(str);
    console.log(a,b); // 8,6
}

var b = 7;

foo('var b = 6',7);

原本你以為的結果會是 8,7,結果突然因為 eval 導致結果變 8,6,與你預期的不符合。

效能

不管事 eval 還是 with,他們都是在執行的時候才修改或建立新的語彙範疇,
之前提過 JS 在編譯階段會做各種最佳化,那當他們遇到上述兩個的時候,他們會直接略過不管,不會去解析裡面的內容,導致最佳化的時候沒有被最佳化所以效能會變比較差...

以上是今天的內容
感謝您的收看
如果喜歡請按讚訂閱加分享
明天見~


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-09-29 19:04:05
【這些年我似是非懂的 Javascript】Day 14 - 範疇 https://ithelp.ithome.com.tw/articles/10245448?sc=rss.iron https://ithelp.ithome.com.tw/articles/10245448?sc=rss.iron

什麼是範疇?
你有想過當你將值存進變數時,那變數放哪嗎?

什麼是範疇?
你有想過當你將值存進變數時,那變數放哪嗎?
而所謂的範疇就是一組規則用來定義變數儲存的位置。

接著我們正式進入第二本書的內容囉!

編譯器在幹嘛?

沒聽過編譯器也看過編譯器走路 (?
網路上搜尋 JS 大概幾乎直接都是看到說 JS 是一個直譯式的語言,但是你知道...他其實是編譯式語言嗎?

JS 特別的是他並不是像許多傳統的編譯式語言一樣,預先就編譯好,而是在快要執行的時候才編譯。
(聽起來有點臨時抱佛腳的感覺 xD)

傳統編譯式語言到底做了哪些事?
通常在執行前會經歷的是三個步驟,而這三個步驟大致上就稱為編譯。
以下粗略分享一下。

  • Tokennizing(語法基本單元化) and Lexing(語彙分析)
    將一串字元拆分成有意義的組塊(chunks),譬如說var a = 17;,他就可能會拆成 vara=2; 之類的。

    空白有可能會有可能不會,會依照情境決定他是否有意義才決定。

  • Parsing(剖析)
    基本上就是將上面拆出來的變成一個 AST(抽象與法樹)。

  • Code-Generation
    接續上面的將拆出來的 AST 轉成可執行的一組機器指令,並實際建立出變數等等的再將值丟進變數。

而 JS 做得比上面說的三個單純的步驟來得複雜,在剖析過程中會有最佳化裡面可能包含了消除多餘元素(例如多餘的空白之類的)。

跟範疇交朋友

以範例來說

var a = 17;

這個述句在處理的方式會是如何?
主要有三個重要的角色

  1. Engine (引擎)
  2. Compiler (編譯器)
  3. Scope (範疇)

那我們就來看一下他們對於上面的述句負責的事~

  1. Compiler 遇到 var a ,他就會問 Scope,這個 a ,是否有在這特定的範疇集合內? 有的話就忽略宣告的動作繼續前進,沒有的話他就會要求 Scope 為那個範疇集合宣告 a 這個變數。
  2. Compiler 產生 Engine 看得懂的程式碼後會讓 Engine 去處理 a = 2 的指定式, Engine 就會跑去問 Scope 說目前範疇有沒有一個 a 的變數可以用? 如果有就拿來,沒有就會往外面的地方找,找到就恭喜老爺賀喜夫人,他就會把 2 塞給他,如果找不到的話 Engine 就會噴你錯誤。

LHS 和 RHS

剛剛上面講到第二步時 Engine 去查找 a 是否被宣告,而這個問 Scope 的動作種類有分 LHS (Lefthand side) 和 RHS (Righthand side),這個查找的方向不同結果自然也就不同。

當一個變數出現在一個指定作業的左手邊,進行的就是 LHS ,反之則是 RHS (但 RHS 不是說指定的在右手邊,而是說他不是左手邊)。

舉例來說

function foo(b){
    var a = b;
    return a + b;
}

var c = foo(2);

...

...

一起來找看看 LHSRHS 有多少個吧!

...

...

好了嗎?
公佈答案囉~

...

這邊的 LHS 查找就有 3 個!

  1. c = ..
  2. b = 2 (隱含的參數在 foo旁邊 xD)
  3. a = ..

RHS 查找則有 4 個!

  1. foo(2..
  2. = a;
  3. a..
  4. b..

不是啊!
所以我說幹嘛要區分 LHSRHS
我看不出來重點在哪?我哪裡需要?
主要是為了知道你在寫程式時,知道 JS 報的錯是為了什麼。
譬如說以下範例

function foo(a){
    console.log(a+b);
    b = a;
}

foo(2);

RHS 查找 b 這第一次出現的時候,他找不到所以稱之為為宣告的變數(undeclared),最終如果他在巢狀迴圈找不到,他就會拋出一個 ReferenceError
但是!!!
對於 LHS 而言的行為就很不一樣,
之前有介紹過嚴格模式對吧!? (沒有的話可以回去看~
LHS 的行為模式會依照有沒有開啟嚴格模式而改變

  • 有開啟嚴格模式的話
    會拋出 ReferenceError
  • 沒有開啟嚴格模式的話
    會在全域建立這個變數

巢狀範疇

剛剛有提到當 Engine 找不到該變數在指定的範疇集合時,他就會往外找直到找到最外層全域變數為止。
例如下面的範例

function foo(a){
    return a + b;
}

var b = 3;
foo(3);

foo 這 function 裡頭並沒有 b 這個變數,於是 Engine 就往外找到 b 這變數。

以上
感謝各位讀者讀到這~
明天繼續
最近公司各種誘惑啊~又是烤肉又是火鍋的 Orz
我會堅持住的 (吧 QQ


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))

]]> Robin 2020-09-28 00:43:12
【這些年我似是非懂的 Javascript】Day 13 - 文法 # 趴兔 https://ithelp.ithome.com.tw/articles/10244888?sc=rss.iron https://ithelp.ithome.com.tw/articles/10244888?sc=rss.iron

ASI 自動分號插入

你有想過其他的語言不加分號時他會直...]]>

ASI 自動分號插入

你有想過其他的語言不加分號時他會直接噴你錯,
但是在 JS 你好像有加或是沒加都沒事兒...

Why!?

因為 JS 有名為 ASI ( Automatic Semicolon Insertion ,自動分號插入) 的功能,
也就是你忘記加他就會幫你補上,
但是他並不是幫你所有需要的都加上,
最主要是在"換行"出現時他才會幫你,
他並不會幫你加在一段文字的中間。

主要要注意的兩點

  • 述句區塊(block)後本來就不需要,所以 ASI 也不會幫你加上去。
  • 會涉入的主要情境為使用 breakcontinuereturn 以及 yield

還記得我一開始工作時 Code review 時常被留言一堆的分號沒加上,
心裡想說這真的那麼重要嗎?
但是這樣講又好像不對,
Google 了一堆資訊兩方各有擁護者,直到後來...
我使用了 ESLint + Prettier
直接自動幫我加上一勞永逸!
我再也不會被留一堆留言是因為 Coding Style xDD

如果你還沒使用這兩個美妙的工具還不趕快手刀安裝 xD
但是要注意的一點是... 用過就回不去了。

TDZ 暫時死亡區域

JS 在 ES6 定義了一個新的概念,就叫做 TDZ,主要是在說程式碼的某個變數的參考動作還不能進行的地方,因為該變數還沒達成必要的初始化動作。
簡單來說就是你過早使用變數導致該變數直接噴錯誤如以下範例。

{
    a = 17; //Uncaught ReferenceError: Cannot access 'a' before initialization
    let a; 
}

看起來真蛋疼

try..finally

如果 try 子句內有一個 return 那 finally 還會執行嗎? 那如果執行是先執行還是後執行呢?
請作答!

...

...

...

答案是會執行,但是執行順序是 return 會馬上執行並設定 foo() 的完成值,接著馬上執行 finally 那時候 foo() 才是真的完成最猴才會回傳該完成值給那個 console.log
如下面的範例

function foo(){
    try {
        return 17;
    }
    finally{
        console.log('test')
    }
    
    console.log('never run')
}

console.log(foo()); 
// test
// 17

throw 與上述情況相同,但是當在 finally 子句中如果有 throw 他就會將原本 try 裡面的完成值取代掉變成 finally throw 的東西。

通常在函式中省略 return 就基本上是等於 return; 或是return undefined; ,但是在 finally 的區塊內部會因為你省略 return 而造成將完成值複寫的問題 (但是如果你故意在裡面 return 還是會被複寫就是了)

那...
如果 finally 裡面加上了 break 又會發生什麼事 ...?
先跟你說母湯安餒xD
你會發生的是 break 將會取消掉 return 的完成值,那維護你 code 人就會想要拿刀殺了你

Switch

之前提過這個的用法,基本上就是用來取代 if..else..if.. else 的串鏈這種寫法加的簡潔,
那你知道嗎?

  • switch 包裹的東西和 case 之間的比對動作是用 === 的也就是絕對的值,如果想要用 == 你可以嘗試用下面的寫法。
    const a = "17";
    
    switch(true){
        case a == 18:
            console.log("I\'m 18 or \'18\' ");
            break;
        case a == 17:
            console.log("I\'m 17 or \'17\' ");
            break;
        default:
            // 不會到這兒
    }
    // "I'm 17 or '17' "
    
  • default 是 option 的,而且他不一定要放在最後,如下面範例。
    const a = 10;
    
    switch(a){
        case 1:
        case 2:
            // 不會到這
        default:
            console.log("default");
        case 3:
            console.log("3");
            break;
        case 99:
            console.log("99");
            break;
    }
    // "default"
    // 3
    

    結果會是這樣是因為 switch 找不到相符的值,直接走到 default 但是又沒有 break 所以直接到了 case 3 然後遇到了 break 才停止。

以上


比我預期中讀的進度還要落後很多...
不過沒關係!我們細水長流 (是這樣說嗎xD)
明天開始就換下一本書囉!
範疇與 Closures / this 與物件原型

期待期待~


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-27 01:39:26
【這些年我似是非懂的 Javascript】Day 12 - 文法 # 趴萬 https://ithelp.ithome.com.tw/articles/10244246?sc=rss.iron https://ithelp.ithome.com.tw/articles/10244246?sc=rss.iron

今天要來分享一下 Javascript 的文法~
什麼是文法?...]]>

今天要來分享一下 Javascript 的文法~
什麼是文法?
對於 Javascript 來說文法就是描述他的語法,包括運算子、關鍵字等等的,如何讓他們結合在一起成為一個正確並且有效的程式的一個結構化方式。

我個人認為不用在這塊太糾結字面上的文法或是語法的詞彙爭奪戰xDD 我們一起來看看作者想傳遞的是什麼吧!


述句與運算式

以我們日常學的英語來說,述句就像是句子,運算式就像是片語,而運算子就像是連接詞或是標點符號。
直接來看一下範例

const a = 8*7
const b = a;
b;

對於 JS 來說這三個其實都是含有運算式的述句,
一二行叫做宣告述句,假使一二行把 const拿掉他們就叫做指定運算式。
那...第三行呢!?
第三行雖然只有一個運算式 b ,但他本身也可以當作一個述句被稱之為運算式述句。

述句都有完成值

你有發現...當你在使用 JS 的 REPL 時,你不管打了些什麼,當他完成該述句時他"x都會"回傳最近一次的完成值 ( 就算他是 undefined 也會)。

我自己其實有發現... 但是我不曾去查過為什麼會這樣 xDD

來看看範例就知道我在說些什麼了xDD

上面兩行的"宣告述句是回傳 undefined
而下面兩行的運算述句則是直接回傳該值,
那...如果去掉 const 的指定運算式呢?
答案跟運算述句一樣是回傳該值。

那有區塊 (block) 的完成值是什麼?
他會回傳該區塊最後一個述句或是運算式的值。

我有一個大膽的想法是將該區塊的完成值放進變數行不行!?
譬如說下面這個範例

const a = 123;
b = if(true){
        a + 123;
    }

這當你想著夢想很美好時,
然後你就會拿到一個...
Uncaught SyntaxError: Unexpected token 'if'

頓時感到現實很殘酷xDDD

不過這也不是唯一的方式去實現你想要的夢想,
你可以使用... 用了就會被撻罰和鄙視的 eval(..)

const a = 123;
b = eval("if(true){a + 123;}");
b; // 246

除了這個又醜又臭的方式沒了嗎?
還有!
ES7 提出了一個叫做 do expression 的運算式,大概長以下這樣。

const a = 123;
b = do{
        if(true){
            a + 123;
        };
    };
b; // 246

目的就是為了減少使用 eval 這東西的機會。

運算式的副作用

你知道嗎?運算式本身就有副作用!
像是下面的範例...

var a = 12;
var b = a + 5;

...你看出來了嗎?
我是看不出來啦
如果你現在憤怒,就代表你的觀念是正確的xD
運算式本身沒有副作用,主要會產生副作用的情況是在於說函式呼叫時產生的副作用,可以參考以下範例。

function foo(){
    a = a+1;
};

var a = 1; 
foo(); // 結果是 `undefined`,但是他卻直接改變了 a 這個值

除此之外還有下面這種情況可能會產生副作用

var a = 17;
var b = a++;

a; // 18
b; // 17

你可能預期原本 a 和 b 都是拿到 18 這個值,但是你忘了 ++ 他放在後綴使用會變成在該值會傳之後才會發生,反之如果放在前綴就會是你要的了~
a++ 後綴 -> 先回傳在運算
++a 前綴 -> 先運算在回傳
這樣應該會比較好理解

那...
如果我用 () 封住他行不行!?
答案是不行!
因為他沒辦法定義一個新的包裹運算式,
除了上面那個使用前綴的方式外,還有另一種方式叫做述句序列逗號運算子(聽起來真不好記xD),用法如下

var a = 18, b;
b = ( a++, a );
a; // 19
b; // 19

其實 else if 不存在

在 JS 中其實沒有 else..if 這樣的子句,像是以下範例。

if (x){
    // ...
}
else if(y){
    // ...
}
else if(z){
    // ...
}

這次沒有騙!
是說真的XD
用過 JS 的你一定知道...

if (a) {
    doSomething(a)
};

可以寫成下面這樣

if (a) doSomething(a);

or

if (a) {doSomething(a)};

那...回到剛剛的問題
所以剛剛的 else if 其實最後他是會這樣剖析...

if (x){
    // ...
}
else {
    if(y){
        // ...
    }
    else{
        if(z){
            // ...
        }
    } 
}

嗯... 我看到我也是為之驚嘆xDD

運算子的優先順序

之前提過其實 JS 的 &&|| 並不是產生 truefalse 的 boolean 值,而是選擇其中一個運算元對吧!?
沒看過回去看! (兇屁

var a = 18;
var b = 'foo';

a && b; // "foo"
a || b; // 18

兩個運算元沒爭議...
那...三個呢!?

var a = 18;
var b = 'foo';
var c = [1,2,3];

a && b || c; // ???
a || b && c; // ???

我們這一 part 就是要來了解一下運算子的優先序~
()> && > || > ?:(三元運算子)
然後 &&|| 是左結合的(也就是從左到右)
但是 ?: 這三元運算子是右結合的(也就是從右到左)
所以上面的會變成

(a && b) || c;  // 'foo';
a || (b && c); // 18;

why ?
第一個
a && b 得到 'foo',接著 'foo' || c 得到 'foo', 所以答案就是 'foo'
第二個
b && c 得到 [1,2,3],接著 a || [1,2,3] 得到 18,所以答案就是 18

這樣看起來是不是很簡單!?
來點簡單的小測驗吧
(先不要往下來看答案,自己試試看)

const a = 18;
const b = "foo";
const c = false;

const d = a && b || c ? c || b ? a : c && b : a ;

...

...

...

...

要公佈答案囉~
答案是...

18

基本上就是 && 全部先做完接著做 ||,慢慢做就海闊天空了xD
你答對了嗎?
歡迎底下留言 "我答對了",
就可以獲得我一個你好棒棒的回應。

短路

發生短路常見原因之一是電池的正極與負極被低電阻的導線連接在一起。 這時,較大的電流使得電源在短時間內提供大量的能量。 強大電流使熱量迅速的產生並大量積累,進而導致電池的爆炸或釋放氫氣和電解質。 ... 電弧是一種氣體放電現象,電流通過某些絕緣介質(例如空氣)所產生的瞬間火花。
-維基百科

開玩笑的xD
不是這種短路!
這邊的短路是對 &&|| 來說假使左手邊的運算元就可以決定結果時,右手邊的運算元其實就不會被估算或是執行,也就是走已經走捷徑出去了那後面再多也不會執行。


好!
今天分享到此結束~
下一篇還是文法~
這本書的最後一篇了!!!
啊啊啊啊!!!
寫到忘記丟垃圾 (超崩潰)

前幾天分享過我的時間規劃,
黃金讀書時間是把手機電腦相關,
會影響到你的都關靜音並且關閉,
但是我設定要倒垃圾的鬧種也隨之靜音了...
我們明天見 Orz


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-26 00:09:38
【這些年我似是非懂的 Javascript】Day 11 - 魔幻邪惡的強制轉型 #最終章 #隱含的強制轉型 https://ithelp.ithome.com.tw/articles/10243598?sc=rss.iron https://ithelp.ithome.com.tw/articles/10243598?sc=rss.iron

接續前幾篇的內容~

接續前幾篇的內容~
【這些年我似是非懂的 Javascript】Day 8 - 魔幻邪惡的強制轉型 #第一章 # 心情轉折
【這些年我似是非懂的 Javascript】Day 9 - 魔幻邪惡的強制轉型 #第二章 #明確的強制轉型
【這些年我似是非懂的 Javascript】Day 10 - 魔幻邪惡的強制轉型 #第三章 #隱含的強制轉型

運算子 || 與 &&

基本上我們在其他語言之類的幾乎是用過類似的邏輯運算子,但是在JS 有一些細部的差異,與其說他是邏輯的運算子,他更像運算元選擇器運算子。
怎麼說?
在 JS 中,他並不會產生 Boolean 值,他其實是在兩個運算元中做選擇,如下面的範例。

const a = 17;
const b = 'abcd';
const c = null;

a || b; // 17
a && b; // 'abcd'
c || b; // 'abcd'
c && b; // null

為什麼會產生以上的結果?
因為 ||&& 會在第一個運算元進行 Boolean 的強制轉換,
如果 || 轉換後第一個為 true 就是選擇第一個運算元,反之就是第二個。
&& 則是相反轉換後第一個為 true 就是選擇第二個運算元,反之就是第一個。

這種用法感覺很常見在以前沒有預設參數的時候,不過現在有預設參數可以用了。

Symbol 的強制轉型

Symbol 可以被明確的強制轉型成 string ,但是相同動作使用隱含的強制轉型會直接被拋出一個錯誤。
(我也不太能理解這設計概念)

還不止這樣!
Symbol 不能轉成 Number (明確的和隱含的都不行),
Symbol 可以轉成 Boolean (明確的和隱含的都可以)。
不過我自己個人倒是沒什麼感覺,畢竟我...
好像還沒實務上使用過 Symbol。(還太菜的緣故吧QQ)

寬鬆相等 vs. 嚴格相等

之前在前面幾天有提過這個差異,
就是允不允許強制轉型
再說一次哦!
差異不是在檢查型態與否。

相等性的效能

因為 === 要檢查型別,所以他做的事比 == 還多,所以效能來說當然是 === 比較慢啊。

...

我剛剛才說過! 他差異在於說允不允許強制轉型!
有在注意看應該會馬上想說...
: X!跟剛剛說好的不一樣啊!!!

所以如果轉成允不允許強制轉型,這就很明顯是 === 的效能會比 == 還好,書上寫說實測雖然只差百萬分之一秒,但是這部分要知道的應該是,其實差異不大,你該注意的是你是否需要強制轉型。

比較 Boolean 與其他值的地雷

先來個範例

const a = '17';
const b = true;

a == b; // false

why!?

a 不是 truthy 值嗎?
理論上會變成 true == true 吧!?

...

No No No~
如果 Type(x) 是 Boolean 就會變成 ToNumber(x) == y 這個結果
如果 Type(y) 是 Boolean 就會變成 x == ToNumber(y)這個結果。
所以上面那個範例就會變成 17 == 1 ,當然是 false,true 被強制轉型成 1

那...

const a = '17';
const b = false;

a == b; // false

所以他不是 false 也不是 true !?

不要被自己的大腦和表面的形象給欺騙了,他只是一樣的理論,從 false 轉 Number 變成 0, 0 當然不等於 17。

所以盡量不要使用 == true 或是 == false 來判斷該值,因為他看起來會與你表面上看的會有差異。

安全的使用隱含的強制轉型

  • 比較任一邊有 true 或 false 不要用 ==
  • 比較任一邊有可能會出現 []"" 或是 0 就要考慮一下是否要用 ==
    以上情境如果不確定,都用 === ,避免一些你沒有想到的例外情況。

強制轉型在這邊已經到了最終章,有些內容已經是前面不小心多提了,但是稍微重要一點的有在提一次,希望在讀的你也可以對於強制轉型有更多的認識。

明天來分享一下這本書的最後一個橋段~ "文法"。

我們明天見~


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-25 00:09:53
【這些年我似是非懂的 Javascript】Day 10 - 魔幻邪惡的強制轉型 #第三章 #隱含的強制轉型 https://ithelp.ithome.com.tw/articles/10242951?sc=rss.iron https://ithelp.ithome.com.tw/articles/10242951?sc=rss.iron

今天要來聊聊這個主題的主角 隱含的強制轉型

今天要來聊聊這個主題的主角 隱含的強制轉型
如果還沒看過前兩篇的可以斟酌觀看~
以下連結奉上~
【這些年我似是非懂的 Javascript】Day 8 - 魔幻邪惡的強制轉型 #第一章 # 心情轉折
【這些年我似是非懂的 Javascript】Day 9 - 魔幻邪惡的強制轉型 #第二章 #明確的強制轉型

那我們就開始今天的內容~


因為恐懼所以你憤怒

圖片來源

假使說你用的是一個強型別的語言,並且想將x要從 A type 轉到 C type 並存到 y ,但是這中間必須先轉成 B type,如以下範例。

CType x = CType(BType(y));

假使說那個語言可以簡化定義使用方式可以用以下的方法實作

CType x = CType(y);

簡化了了先轉換 B Type 這個動作,增加可讀性但卻省略了中間隱藏了那些轉換的細節,你會那麼在乎這中間省略的步驟嗎?

我個人感覺會偏向可以省略成下面的方案,
對我來說如果知情就沒什麼好怕的了。

我們可以選用他好的一面,並且熟悉他去避開他可能會發生的事情,我覺得這滿重要的,一蓋的去否定反而減少了該語言好的特性。

這章節我們就來好好與它做朋友

隱含的 String 與 Number 的強制轉型

昨天講過明確的強轉型關於 String 和 Number ,
這邊也是同個主題但是主角是"隱含的"。
先來看一下以下範例

const a = '16';
const b = '1';

const c = 16;
const d = 1;

a+b // 161
c+d // 17

為什麼會有像是以上的結果?
簡單來說就是運算元的部分是否有一個或是兩個 String ,而這邊的 + 就會以為要是 String 的串接。

看過前幾篇的你一定知道我接著要說什麼...

上面說的部分是錯的哦!

如果上面說的是對的話那該如何解釋下面這個範例

const a = [1,2];
const b = [3,4];

a+b; // "1,23,4"

這明顯已經將兩個都不是 String,但是卻將兩邊轉成 String 並且串接起來了。

依據 ES5 語言規格的 section 11.6.1,如果有其中一個運算元是 String 或是接下來的任一步驟會產生 String 的值時用 + 就會進行 String 的串接,那 + 所用的演算法又剛好對應到剛剛用的 Array ,當 + 遇到一個 Object (包含 Array),作為任一運算元時,他會先呼叫 ToPrimitive 抽象運算來先處理該值,然後接著呼叫 [[DefaultValue]],並依照上下文情境傳入 number 來作為 hint 偏好型別。 (這邊的上下文情境其實我也不太懂 Orz)
接著又依據 ToNumber 的抽象運算處理 object ,當他沒辦法在 array 進行 valueOf() 產生簡單的基值,它就會改使用 toString() 表示法,導致他變成以下的結果

"1,2"+"3+4"; // 1,23,4

看不懂?

大致上來說就是
今天 + 看到他左右手其中一個是 Object 他會想先把他轉成 Number 然後 ToNumber 看不懂...就給了 toString() 處理。

書中提到在使用 JS 日常中最常看到的是以下範例

const a = 19;

const c = a+"";
c // "19"

他強制轉型成 String,因為剛剛提到的第一點是他先看兩邊運算元其中有一個 String 他就會判定要進行字串相加。

那如果想要轉回來怎麼辦?

與他對立的 - 出現了xD
直接看範例

const a = "19";

const c = a-0;
c // 19

- 這個運算子只被定義用來做數值的相減,
所以會迫使 a 強迫轉型成 Number。

*/ 也有相同效果

那如果將剛剛的範例再一次並且使用 - 呢?
器跨買~

const a = [1,2];
const b = [3,4];

a-b; // NaN

他們會先將 array 的值都會先被強制轉型成 String 然後在變成 number,最後才會進行 - 的動作。

也就是你把他再變成那再簡單一點

const a = [1];
const b = [2];

a-b; // -1

登愣~
結果就如你所願。

我個人也覺得非常有趣書中提到的一點是,儘管社群裡在怎麼被說邪惡啊魔幻,但相比 a = b +"" 的用法卻比 String(..) 這種明確的用法來的常見且實用 xDD

那我就問你!
你還憤怒嗎?
如果是那就是你還害怕 (開玩笑的)

隱含的 Boolean 與 Number 的強制轉

看書上說 (完全撇除責任xD)

強制轉型能發光發熱的地方在於將某個複雜的 boolean 邏輯簡化成簡單的數值加法運算。
(這不是通用技巧,只是單純看情況的專用解法)

來看看下面的範例的情境

function onlyOne(a,b,c){
    return !!((a && !b && !c) || (!a && b && !c) || (!a && !b && c) )
};

const a = true;
const b = false;

onlyOne(a,b,b); // true
onlyOne(b,a,b); // true
onlyOne(a,b,a); // false

簡單來說上面的範例想要做的是參數中只有一個為 true 或 truthy 就回傳 true,反之如果有兩個以上就是 false,那如果這個範例改成處理很多很多很多個值怎麼辦?

這時候就可以展現真正的技術了(誤

function onlyOne(){
    let sum = 0;
    for (let v of arguments){
        if (v) sum+= v;
    }
    return sum == 1;
};

const a = true;
const b = false;

onlyOne(a,b,b); // true
onlyOne(b,a,b); // true
onlyOne(b,b,b,a,b); // true

onlyOne(a,b,a); // false
onlyOne(a,a,b,a,b,b,b,a); // false

簡單來說就是讓隱含的強制轉型將所有的 Boolean 變成 0 或是 1 ,並且將所有的參數都相加,如果最終結果等於 1 回傳 true,否則回傳 false,完全符合剛剛的需求。

隱含的從任何非 Boolean 值強制轉型成 Boolean

什麼時候的運算式會需要隱含的強制轉型?
其實超常見的

  1. if(..)
  2. for(..)
  3. while(..)
  4. ?: (三元運算式)
  5. || 、 &&

以上那些就算不是 Boolean 也會被強制轉型成 Boolean。


今天是第十天,
剩餘完賽還有二十天~
今天早上睡過頭了黃金讀書時間破滅xDD
但後續還是照著原本的行程走~

以上為今天分享的內容
希望對你有幫助


感謝您的收看
我們明天繼續分享"魔幻邪惡的強制轉型系列最終章!"

我們明天見~


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-24 00:02:48
【這些年我似是非懂的 Javascript】Day 9 - 魔幻邪惡的強制轉型 #第二章 #明確的強制轉型 https://ithelp.ithome.com.tw/articles/10242376?sc=rss.iron https://ithelp.ithome.com.tw/articles/10242376?sc=rss.iron

接著昨天那篇

接著昨天那篇
這些年我似是非懂的 Javascript】Day 8 - 魔幻邪惡的強制轉型 #上篇 # 心情轉折

今天來分享明確強制轉型
而這個明確的強制轉型是大家都認同的不邪惡也不魔幻,因為其他語言大部分轉型也是差不多的做法,會叫明確的強制轉型是因為他可以很明顯的看出來他最後的行為。

明確的 String 與 Number 的強制轉型

這個應該是最常見的在 string 與 number 之間互換,
通常是用內建的 String(..) 或是 Number(..),而且我們不用使用 new ,所以我們也不是在建立物件包裹器。
用法如下

const a = 18;
const b = String(a);
b; // "18"

const c = "19";
const d = Number(c);
d; // 19

ToString 的規則和 ToNumber 的規則在上一篇有提過~

除了上面範例的轉換方式還有以下的用法~

const a = 18;
const b = a.toString();
b; // "18"

const c = "19";
const d = +c;
d; // 19

toString() 這個是我個人滿常使用的,書中提到其實他看起來雖然是"明確的",但是他實際上有不容易看出來的隱含性,原因是 toString() 這個不能用在直接是基本型別的值上,因為 JS 會自動把他封裝所以他才能被呼叫,所以這又稱之為"明確地隱含"。

看到這是不是想說...貴圈真亂xD

還沒完呢~
+ 這個運算子的單元運算形式不會有任何運算的動作,但是他會把後面那個 c 強制轉為 number 值 ,而這個算隱含的強制轉型還是明確的強制轉型,書上是說看個人經驗和觀點,但是在 JS 的社群中普遍被接受的是他是"明確的"。

我個人是覺得他是隱含的,因為我沒看過這種用法,而且我不深入去查,我會不知道他是強制轉型成 number。

還是不太建議使用 +c 這種用法,畢竟就算我知道或是你知道,很難保證之後維護的同伴也知道,明確的強制轉型就應該要很明確不應該存在太多的不確定性降低混淆的風險,但書上編排是放在明確的轉型我想是因為社群主要認同(?。

日期轉為數字

剛剛提到的 + 這個單元運算子除了上述用法還有一個功能是將 Date 物件強制轉型成一個 number

我也是第一次知道xD

來看看以下範例

const a = new Date();

+a; // 1600876740123

另外 new Date();() 是 option 的所以你可以能會看到 new Date這種用法,但是看起來並不會提升可讀性(我個人認為),還是會造成混淆之類的副作用xDD。

如何使用非強制轉型也可以拿到時間戳的方法?
可以使用 getTime();

const a = new Date().getTime();

a // 1600876740123

除了上面那種兩個方式之外,還有一個方式是我個人滿常使用的拿時間戳的方法

const a = Date.now();

a // 1600876740123

小總結:

如果你想要拿"現在的"時間戳可以使用 Date.now();
反之你想要拿"非現在的"時間戳你可以使用 new Date(..).getTime();

明確的剖析數值字串

從一個 string 內容剖析(parse) 成數字可以達成像是強制轉型的行為,但是他看起來行為上有些不同,如以下範例。

const a = "17";
const b = "17px";

Number(a); // 17
Number(b); // NaN

parseInt(a); 17
parseInt(b); 17

why?
因為字串剖析可以"容忍"非數值字元,從左到右遇到就停下來,
而反之直接使用強制轉型就換轉換失敗所以最後產生NaN

所以他們兩個本質上要處理的事情其實是不同的,不應該互相替代使用。

例如我都亂用QQ

parseInt(..) 只能使用在 string 值,
其他的都會被自動轉成 string ,譬如說 function, number, object ... 等等

但幾咧!

所以他是隱含的強制轉型...嗎?
對!(難道還不夠明顯嗎xD)

所以這點要非常小心和注意(感覺是個坑)。

上面講的只是第一個坑,
在 ES5 之前還有一個坑是關於 parseInt(..) 當如果你沒有傳入第二個參數來說明你要解讀的值的基值(8 進位、10 進位、16 進位之類的),他就會自動幫你看開頭例如 0x or 0X 就是 16 進位,0 開頭就是 8 進位 ,
聽起來滿自動滿美好的啊!?
根本不是xDD
萬一是小時譬如 08 點,
整個就是
...

但是他真的在 ES5 之前就無解嗎?
有!
就是你要記得傳入第二個參數,如以下範例

const hour = parseInt('08',10);

你以為就只有兩個坑嗎?xDD
還有!

parseInt(1/0, 19) // 18

說好的無限呢!!!?
簡單來說他幹了一件事就是結果會變成這樣

parseInt(Infinity, 19) // 18

剛剛說過... 他會從左到右開始抓!
所以第一個字拿到 "I",第二個字拿到 "n"

I -> 18
n -> 看不懂,不在 19 的基數中

所以得到 18
真的是非常棒xD

總結來說就是不要亂用xDD
parseInt(..) 請使用在 string,避免意想不到的事發生。

明確的從任何非 Boolean 值強制轉型成 Boolean

前面提到 + 會將值強制轉型成 number,而 ! 這個非常常見的否定運算子,問題在於他可以反轉他的值,把 true 變 false ,所以常見的方式是他會明確的強制轉換成 Boolean 就是使用雙否定運算子(!!)
傳說中的...

我相反你的相反

翻過去轉過來的概念xD
直接看範例

const a = "0";
!!a; // true

const b = [];
!!b; // true

const c = {};
!!c; // true

const d = "";
!!d; // false

const e = 0;
!!e; // false

const f = null;
!!f; // false

let g;
!!g; // fasle

強制轉型 Boolean 的另一個用途是在 JSON 序列化的過程強制進行轉型,如下範例。

const a = [
    1,
    function(){/*..*/};
    2,
    function(){/*..*/};
]

JSON.stringify(a); // "[1,null,2,null]"

JSON.stringify(a,function(key,val){
    if(typeof val == "function"){
        return !!val; //強制轉型
    }
    else{
        return val;
    }
}); // "[1,true,2,true]"

三元運算子所隱含的秘密

const a = 17;
const b = a ? true:false;

上面是之前介紹過三元運算子範例,
但是他其實有一個隱含強制轉型的存在,就是...
a 的那個地方他會強制轉型為 boolean 才會開始做真假值的判斷,
書上寫說他可以叫做 "明確的隱含"...
我還是那句...

貴圈真亂xD

盡量避免使用這種用法 (不是指三元運算子,是單指這樣的強制轉型方式)。

以上是今天分享的內容
感謝收看。


分享一個掌控時間的撇步關於昨天提的"黃金讀書和寫文章",

就是...

  1. 打開電腦
  2. 將手機開勿擾丟到你摸不到的位置(最好不要有震動)
  3. 電腦的任何社交頁面和軟體全部關閉,最多只有音樂相關的應用程式
    (不要用 Youtube 聽請善用 Youtube music 或其他只有音樂的軟體)

個人經驗是打開 Youtube 會從找歌變成看影片,
很恐怖!
真的xD


感謝您的收看
我們明天持續分享"魔幻邪惡的強制轉型系列" 第三章-隱含的強制轉型

我們明天見~


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-23 00:59:37
【這些年我似是非懂的 Javascript】Day 8 - 魔幻邪惡的強制轉型 #第一章 # 心情轉折 https://ithelp.ithome.com.tw/articles/10241735?sc=rss.iron https://ithelp.ithome.com.tw/articles/10241735?sc=rss.iron

看到標題先不要罵我xD
我是說外面很多人對於強制轉型的既定印象...]]>

看到標題先不要罵我xD
我是說外面很多人對於強制轉型的既定印象。

黃渤說得好

因為恐懼所以你憤怒

圖片來源

我之前說過,當你了解過後也許你就不會認為他那麼的壞,
也許能夠善用它的好處且避開他的壞處。

最底下也會分享我這幾天在煩惱的事情和解決方式,
以及最後的心情轉變和決定。

幹話繼續說,
那我們就開始今天的分享吧~


轉換值

從某個型別到另一個型別,如果明確的進行就是"型別轉換",
如果是隱性的就稱之為"強制轉型",跟之前說過的明確的強制轉型隱含的強制轉型其實是一樣的意思。
請再看一次範例xD

const a = 123;
const b = a+'456'; // 123456 隱含的強制轉型

const c = String(a); // '123' 明確的強制轉型 

如果你是對於上面的範例覺得很清楚,其實對你來說就沒有所謂的隱含明確的區分,反之如果你對於這些都不熟悉,其實這些對你來說都是隱含的。

抽象的值運算

這邊主要要探討的是規範了的值如何變成一個 string, number 或是 boolean ,ES5 中的 section 9 定義了幾個抽象運算,主要就有值轉換的規則,接著我們會透過 toString, toNumbertoBoolean 來當主要的分享。

ToString

簡單來說就是任何不是 string 的值被強制轉換成 string,這過程是透過剛剛所說的 section 9.8 的 toString抽象運算處理的。

太大太小的值會被轉換成指數

const a = 1.01 *1000000000000000000*1000000000000000000
a.toString(); // "1.01e+36"

Array 字串話後會以 "," 隔開

const a = [1,2,3]
a.toString(); // '1,2,3'

JSON 的字串化

相信對於使用過 JS 的你一定對 JSON.stringify(..) 不陌生吧!?
不過你知道嗎他其實跟 toString(..)轉換的結果是相同的,
不管如何他都會回傳給你一個 string
那又如何?
啊...萬一他不是 JSON 呢?
例如以下範例

JSON.stringify(1) // "1"
JSON.stringify(null) // "null"
JSON.stringify(true) // "true"

另外 JSON.stringify(..)在遇到 undefined function symbol 會直接忽略。

ToNumber

基本上就是從非數字轉到數字一樣經由 ES5 所定義的抽象運算處理的。
例如 true 變成 1 或著是 false 變成 0
String to number 基本上運作方式就是照字面上如果轉換失敗就會是 NaN,需要注意的是以 0 為前綴的八進位數字不會被當成八進位會直接變成十進位,畢竟他還是沒那麼聰明不知道你是八進位還是十進位xD

ToBoolean

還記得之前提過 Falsy&Truthy 嗎?
不記得可以回去看一下XDD
這邊就不介紹了... 連結在這~
【這些年我似是非懂的 Javascript】Day 3 - 你一定可以入的了門 #上篇
...

...

...

開玩笑的~ xD

這邊主要要釐清一些我們可能誤解的觀念,
通常我們會覺得 0 就是 false1 就是 true ,在判斷上也許強制轉型會自動把它轉成你所想的那樣,
但是 number 就是 number
boolean 就是 boolean
我們不一樣~~~
每個人都有不同的境遇~ 的境遇~ 的境遇~~ (自己製造 echo)

圖片來源

聰明的你一定知道 Boolean 會有哪兩個東西~
沒錯!
就是 FalseTrue
接著我們就要討論除了這兩個東西之外的什麼東西會變成 Falsy值,什麼東西會變 Truthy值。

Falsy值

在 JS 中只有少數的值會被強制轉成 false 如下~

  • undefined
  • null
  • false
  • +0, -0 和 NaN
  • ""

記熟之外的以上其他全部都是 true
所以也就是其他都在 truthy清單....嗎?
不對!
JS 並沒有真正定義 truthy 清單,所以這應該保持保留的態度。
整個就是薛丁格的 Boolean (誤

Falsy 物件

看到這應該會覺得一個黑人問號,
說好只有上面那些,怎麼又會有 Falsy 物件?
還記得之前學過"物件包裹器"嗎?
簡單啦!
那就是包裹著 falsy 的值的物件包裹器 就是 Fasly 物件囉!
...
...
...

先來猜看看以下的結果是如何

const a = new Boolean(false);
const b = new Number(0);
const c = new String("");
const d = a && b && c;

const d = Boolean(a && b && c);

...
...
...

答案是

true

原因就跟之前提過的一樣他們還是物件包裹器所以是一個物件
他並不在 falsy 名單內當然是 true 啊!

所以由此可知...
犯人不是物件包裹器裡面包著 falsy 的值...
真相只有一個!

圖片來源

犯人就是 document.all
書上說是廣為人知的,
但是我根本不知道這東西是什麼...
(可能要之後實際遇到才會有那種"哦哦哦"的感覺!!!!)
只好在家默默地說:我就知道
簡單來說原因是瀏覽器會基於 JS 的正規語意讓他們創造出奇特的行為,它的外觀就是正常的物件但是轉完 boolean 就會變 false 。
然後他又是一個永久性的 bug 。 (怎麼感覺 JS bug 真多 xDDD)
但沒關係有愛就行(?)

Truthy 值

上面提到除了在上面的 falsy 值外和那個特殊的 document.all 物件之外,其他都是 True 嗎?

那你一定知道底下這個範例的結果是如何!

const a = "false";
const b = "0";
const c = "''";

const d = Boolean( a && b && c);

d; //?

答案是~~~~

True

因為他們都是 string~
看一下上面提的,只有"空字串"才是 falsy 的,
他們裡面都有 string 的值,就算他們看起來很 falsy xD。

以上~ 是今天的內容
接著是今天的重頭戲(?


心情轉折分享

最近在調整作息和研究這系列的時間,
因為覺得文章感覺都變得有點倉促(像在趕稿xDD)
非常違背我的初衷。

誰說鐵人賽一定要拼死拼活!?

圖片來源截圖自"反正我很閒"的影片0:54處->連結

目前我的想法是每天固定專屬於研究這系列的時間,
這樣不僅學習效果好,更不會因為沒有達到自己內心預期的進度造成心情鬱卒。
更重要的是,我希望身心靈都能建築在快樂學習上。
絕對不是因為我自己希望能每天打球運動,
俗話說的好,

打球打得好,程式沒煩惱,打球打不好,什麼都不好。

來分享一下我最近規律的作息和快樂學習時間表

時間 任務
7:30 ~ 8:00 起床賴床刷牙洗臉
8:00 ~ 9:15 黃金讀書寫文章時間
9:15 ~ 9:45 上班途中買早餐等等
9:45 ~ 19:00 上班 (中午休息時間可能會看大概 30 分鐘的書)
19:00 ~ 20:00 吃飯飯、耍廢、聽音樂
20:00 ~ 22:00 黃金讀書寫文章時間
22:00 ~ 24:00 打球揮灑油脂
00:00 ~ 7:30 洗澡睡覺~

(時間正負最多半小時)

希望你跟我都可以一起成為時間管理大師!
一起放鬆學習沒煩惱~

這樣的每天持續學習應該沒有違背鐵人的精神吧!?
素不素!?
素嘛!(自己說)

明天繼續分享關於強制轉型

感謝你的收看。
我們明天見~


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-22 08:26:00
【這些年我似是非懂的 Javascript】Day 7 - Natives 原生功能 https://ithelp.ithome.com.tw/articles/10241029?sc=rss.iron https://ithelp.ithome.com.tw/articles/10241029?sc=rss.iron

常用的原生功能我相信各位讀者多少都看過
以下列舉幾個常見常用的...]]>

常用的原生功能我相信各位讀者多少都看過
以下列舉幾個常見常用的。

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function(
  • RegExp()
  • Date()
  • Error()
  • Symbol() // ES6 新增的~

與你想像中的建構器不太一樣

我們時常在其他語言中會看到類似這樣的作法

const str = new String("Robin is handsome")

console.log(str.toString()); // "Robin is handsome"

看起來也非常合理,但是實際上在JS中可能有一些行為會與你想像的不同。

const str = new String("test yoyo")
typeof str // ?

各位猜看看這的型態是什麼?
答案是...

String

...嗎? 看起來非常合理!

但是實際上卻是...

Object

主要原因是他建立了一個字串包裹器的物件而不是單純建立 "test yoyo" 這個字串的基值。

Internal [[Class]]

你知道嗎...
typeof 是 "object" 的那些值內部額外會有一個特性,叫做 [[Class]]
我知道你內心想的是什麼!
但是不是你想的那種 Class
你把它想成是一種內部的分類,
這個特性很特別他無法被直接使用,
但是你可以使用 Object.prototype.toString(..) 方法來呼叫他。

Object.prototype.toString.call([6,6,6]); // "[object Array]"

Object.prototype.toString.call(/regex-literal/i); // "[object RegExp]"

undefinednull 也是

Object.prototype.toString.call(undefined); // "[object Undefined]"

Object.prototype.toString.call(null); // "[object Null]"

stringnumberboolean 並不是這樣,他們會由封裝用的包裹器涉入。
接著我們就來講封裝用的包裹器

封裝用的包裹器

還記得之前說的各種型態的小精靈嗎?
如果基型值沒有特性和方法想要存取類似 .length 或是 .toString() 需要包裹基型值的物件包裹器,那我該怎麼做?
答案是...
什麼都不用做!
JS 會自動封裝基本型別的值,
該注意的是你不應該藉由直接使用物件形式試著預先最佳化

簡單來說就是你什麼都不該做,因為這樣做只會讓你的程式碼執行得更慢。
舉例來說,
不要做 new String("foo") 或是 new Number(18) 之類的動作,
直接優先使用 "foo" 或是 18 字面形式的基型值就可以了。

如果我就是要做怎麼辦?
也不是不行但是可能會遇到一些雷,
例如以下範例

const a = new Boolean(false);

if(!fasle) console.log("TEST); // 不會執行。

... 為什麼?
因為你建立的是一個"物件包裹器",物件本身是 object,他又是 truthy,所以產生的行為就會跟你原本建立的 false,產生完全不一樣的結果。

解封裝

封印解除!

如果有一個物件包裹器想要拿裡面的值該怎麼做?
可以直接使用 valueOf() 這個方法。

const a = new String("test");
const b = new String(123);
const c = new String(true);

a.valueOf(); // "test"
b.valueOf(); // 123
c.valueOf(); // true

解封裝也有隱含發生的可能,例如以下範例

const a = new String("test");
const b = a + "test" // 直接拿取 a 解封後的基型值

typeof a // "object"
typeof b // "string"

作為建構器的 Natives

objectarrayfunction正規表達式 這些值比較常看到被使用的方式就是直接用字面的方式如下範例

const a = [];
const b = {};
const c = function(){..};
const d = /^a.b*c/g;

接下來要分享如果使用建構器的方式可能會遇到的問題。

Array

Array 建構器前面不用使用 new ,因為有加和沒加的結果是相同的。

const a = new Array(1,2,3);
const b = new Array(1,2,3);
const c = [1,2,3]

console.log(a); // [1,2,3]
console.log(b); // [1,2,3]
console.log(c); // [1,2,3]

當只有一個 number 參數帶入 Array 的建構器時,他不會把它當作陣列的內容,他會把他當一個長度來"預先設定陣列大小"。
可以觀察一下底下範例的差異。

const a = new Array(5);
const b = [ undefined, undefined, undefined, undefined, undefined];
const c = [];
c.length = 5;

console.log(a); // [ <5 empty items> ]
console.log(b); // [ undefined, undefined, undefined, undefined, undefined ]
console.log(c); // [ <5 empty items> ]

但是還記得我昨天在 這篇所論述的嗎?

JS 沒有預設大小這種事存在

也就等於是你有可能會插了空插槽,有可能會造成你創造出稀疏陣列,就是很莫名其妙的資料結構 xD

上面的範例是在 Chrome 所產生的,
然而在 Firefox 所顯示的卻不太一樣。

const a = new Array(5);

console.log(a); // [ <5 empty slots> ]

書上寫道原本更慘xD
原本在 FireFox所顯示的會長這樣

const a = new Array(5);

console.log(a); // [ , , , , , ]

在最後放一個額外的 , 是因為在 ES5 中的串列的尾隨逗號是被允許的所以最後一個會被忽略。
雖然從上面的 [ , , , , , ] 改為 [ <5 empty slots> ] 是一個改進沒錯...

But 事情永遠都不是那麼如人所願...
剛剛看 ab 的長的雖然不一樣但是顯示的行為相同,
但是他某些情況行為相同,但有時候又不同。

const a = new Array(5);
const b = [ undefined, undefined, undefined, undefined, undefined];

a.join('-');
b.join('-');

a.map(function(v,i){return i} ); // [ <5 empty items> ]
b.map(function(v,i){return i} ); // [ 0, 1, 2, 3, 4 ]

a.map 實際會失敗的原因簡單來說是那些插槽其實不是真的存在,就是他真的是"空插槽",不是真的插入 undefined

如果真的想要建置帶有實質的 undefined 值的陣列除了像我剛剛上面手動打之外你還可以使用 apply

const a = Array.apply(null, {length:5});

Anyway...
不要這樣做xDDD

Object 、 Function 、 RegExp

這三種也盡量不要使用建構器,請也直接使用字面的形式。

我記得如果你在你的專案有使用 ESLint 他也會跟你說母湯安餒。

正規表示法直接選用的理由除了語法簡單外,還有因為效能問題,JS 引擎會在程式碼執行前預先編譯和快取。

但是如果說正規表達式有動態的需求就可以譬如以下範例

const name = 'robin';

const namePattern = new RegExp("\\b(?:" + name + ")+\\b", "ig");

const matches = text.match(namePattern);

Date 和 Error

Date

這兩個的原生建構器比較常見,因為他們都沒有字面形式可以用 QQ
要建立日期物件值時可以用new Date(),如果不使用new 拿到的值則會是字串形式的日期~

Error

Error 這個建構器不管有沒有 new 行為都相同,通常會藉由 throw 運算子來使用這樣的錯誤物件如範例

function foo(){
    if(!x){
        throw new Error("x wasn't provided")
    }
}

Symbol

他跟 DateError 一樣沒有字面的形式,然後你不能使用 new ,因為他會直接噴錯xD

原生的原型 Native Prototypes

原生的建構器都自己的 .prototype 物件,以 String 來說

String#indexOf(..)
String#chartAt(..)
String#substr(..)
String#toUpperCase(..)
String#trim(..)

如果是一般使用直接使用的話會藉由原型委派的機制讓他可以使用這些方法。


以上是我今天分享的內容
感覺論述的越來越抽象xDD
但是某些讀著讀著就是各種
哦哦哦哦哦哦!!!

感謝你的收看
我們明天見


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-21 01:32:56
【這些年我似是非懂的 Javascript】Day 6 - 值 https://ithelp.ithome.com.tw/articles/10240473?sc=rss.iron https://ithelp.ithome.com.tw/articles/10240473?sc=rss.iron

嗨各位好,我是 Robin,
今天來分享 JS 中幾個內建值型...]]>

嗨各位好,我是 Robin,
今天來分享 JS 中幾個內建值型別,
希望我們能夠一起完全的理解且正確地善用他們~

陣列

可以儲存任何型別的容器

JS 的陣列特別的是不像其他強制施加型別的語言,他是可以儲存任何型別的容器,不管是 stringobjectnumber或著是另外一個 array

const a = ['1',{test:1},1,[0,1,2,3]];

a.length; // 4
a[0]; // '1'
a[1]; // {test:1}
a[2]; // 1
a[3]; // [0,1,2,3]

不用預設大小

在 JS 中你不必預先設定好 array 的大小,直接宣告就可以用了。

const a = [];

a.length; // 0
a[0] = 0;
a[1] = 1;
a[2] = 2;
a[3] = 3;

a.length; //4
console.log(a); // [0,1,2,3]

盡量別建立出有稀疏的 Array

const a = [];
a[0] = 1;
a[2] = 2;
a[4] = 4;

console.log(a[1]); // undefined
a.length; 5

這是可以 work 的,但是這樣可能會造成維護的人困擾,因為空著的是 undefined ,可是這跟直接使用 a[1] = undefined 沒有區別 ,嚴重可能會令你的同伴找 bug 找到瘋掉。

Key 值的強制轉型

在我們使用陣列時如果 key 是一個 string 並且能夠被強制轉型成十進位的 number 那 JS 就會強制將它變成 number 當成索引。

const a = [];

a['8'] = 8

a.length; // 9
a[8]; // 8

陣列其實可以額外增加特性

因為他是根本還是 object 所以其實你還是可以透過 stringkey 值來新增特性,但他並不會計算在 array 的長度裡。
然後...拜託先不要 xDD,如果你想要新增這樣的資料請使用 object ,把陣列留給使用索引的孩子。

const a = [1,2,3,4];

a['robin'] = '666';

a.length; // 4
a.robin; // '666'

類陣列 (Array-like)

什麼是類陣列?
就是長得跟陣列很像,但是他可能是串列(List)之類的,例如你在 DOM 操作之後回傳的其實是串列。
但是畢竟他不是真的 Array,所以你需要轉乘真正的 Array才能夠使用 indexOfconcatforEach 等的操作。
可以藉由使用 slice 或是 Array.from 來轉成真的 Array

字串

對於字串很多看法是由字元組成的 Array, 但是實質上他是,因為 JS 的 String 是不可變的,但是 Array是可變的

var a = 'foo';
var b = ['f','o','o'];

a[1]='g';
b[1]='g';

console.log(a); // 'foo'
console.log(b); // [['f','g','o']

在 JS 中我們可以直接更改陣列的內容,但是字串無法。
如果你很常需要將字串當陣列,那你應該實際直接將他們儲存為 array而不是字串,當你需要用到 string 表示時再加上 join('')就好了。

數字

JS 只有數值型別,並沒有整數的型別,所以 111111.0 對 JS 來說都是個整數。

十進位值前面的部分0他是選擇性的

const a = 0.42;
const b = .42;

a === b // true

後面的小數也是

const a = 42.0;
const b = 42;
const c = 42.0000000;

a === b // true
b === c // true

但以上都盡量不要使用,如果你不想要人要別人看你的 Code 會頭痛的話xD

奇妙的 "."

. 這個運算子是一個有效的數值字元,他會先被解讀成 number 字面的一部分,而不是解讀成特性存取器。
來看一下以下的範例

69.toFixed(2)

猜猜結果是什麼~

69.00

錯!

你會獲得一個 SyntaxError

但是當你使用以下的方式就可以正常 Work

69..toFixed(2)
69 .toFixed(2)
(69).toFixed(2)

Why?
因為如果 . 這個特性運算子不存在就無法取用 .toFixed

但還是不要使用有爭議的方式啦,畢竟程式碼是要靠人維護的 xD

指數形式的 number

主要是用來表示比較大的數字

const a = 1e4 // 10000 代表 1*10^4
const b = 1e8 // 100000000 代表 1*10^8

可用於其他基數的 number

例如 二進制、八進制、十六進制,這些格式都可以使用,但是注意盡量使用小寫的前綴,例如 0x0o 以及 0b,來避免 0O這類型造成的混淆。

超級蛋疼的小的十進位值


為什麼蛋疼...?
來!

0.1 + 0.2 //?

請各位評評理正常小學算法,大家都知道是 0.3 對吧!?
實際上...

對!沒錯你沒看錯。

對於使用 IEEE 754 的所有語言都是。
很多人提出過替代的方法,
但是總是沒有被選用,可能不是像我們用說的用想的那麼容易,
如果可以那早就已經被修復了,甚至其他語言也會使用,
但是事實就是沒有 QQ

結論

由此可知,我們不能相信 Number 的值是精確的,所以我們都不要使用它。

...

當然不可能啊 xDDD
我們可以放心的使用 JS 來處理整數,但是不要大於數百萬或數兆的數字。
另外如果如果真的有需要使用 JS 來比較兩種值的大小,
最廣為接受的是約整誤差,也就是容許很小很小的誤差值作為容許值。

ES6 中已定義好這個常數 Number.EPSILON 其值為 2^-52

使用方式如下

function closeEqual(n1, n2) {
  return Math.abs(n1 - n2) < Number.EPSILON;
}

const a = 0.1 + 0.2;
const b = 0.3;

closeEqual(a, b); // true
closeEqual(0.0000001, 0.0000002); // false

能夠被表示的最大浮點數值是 1.798e+308,並已經被定義好為 Number.MAX_VALUE
極小值則是 5e-324,非常趨近於零而且不是負的。

安全的整數範圍

那知道了蛋疼的小數之後,整數的安全範圍在 ES6 中已經定義了
最大值定義為了 Number.MAX_SAFE_INTEGER9007199254740991
最小值 Number.MIN_SAFE_INTEGER-9007199254740991

測試整數

測試一個值是否為整數

Number.isInteger(69); // true
Number.isInteger(69.0000); // true
Number.isInteger(69.0003); // false

測試一個值是否為安全整數

Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // true
Number.isSafeInteger(Number.MIN_SAFE_INTEGER); // true
Number.isSafeInteger(Math.pow(2,53)); // false

特殊值

非值的值

undefined 的型別中,只會有一個值就是 undefined
null 的型別中,只會有一個值就是 null
(聽起來有點繞口)
undefinednull 中細微的差異在於說

  • null 是一個空值或是曾經有一個值但是現在沒有了
  • undefined 是缺少值或還沒有值

Undefined

其實在非 strict 模式中你可以將 undefined 指定一個值給 undefined 這個全域變數。
聽起來就是一個非常母湯的做法,千萬不要這樣做 xD

特殊數字

非數字的數字

就是指 NaN 啦,之前提過 NaN 就是指 Not a number
什麼時候會出現?
比方說 const a = 1 / 'foo'; // NaN
還記得上次型態 typeof NaN // 'number' 嗎?
這一直以來都還免強說得過去,但是...

NaN === NaN // false 
NaN !== NaN // true

他不等於自己...
那我該如何測試他是不是 NaN?
可以使用 isNaN(..)這個內建的函式工具,這樣就解決了。

const a = 1 / 'foo';
windows.isNaN(a) // true

...

才怪他還有另外一個 Bug
就是...

windows.isNaN('foo') // true

而在 ES6 終於提供了一個替代的工具就是 Number.isNaN(..),能讓你安全無疑慮的使用 xD

Number.isNaN('foo') // false

感謝天感謝地。

無限


對!就是你小學所學的那種無限

const a = 1/0 // Infinity
const b = -1/0 // -Infinity

主要可以拿到無限這個值有兩種方法,一種就是上面看到的除以 0
另一種則是溢位( overflow )

在 ES6 中定義了正無限為 Number.POSITIVE_INFINITY,負無限為Number.NEGATIVE_INFINITY

無限除以無限會是什麼?
可能是 1 或是 無限,聽起來合理。
但是在 JS 他會回你 NaN

正的有限 number 除以 無限 會是 0
負的有限 number 除以 無限 則會是 -0

負零是什麼鬼!? 馬上來說

JS 提供了兩種零,一種是正零另一種則是負零,
除了直接指定 -0 之外,你也可以透過以下的方式產生

const a = 0/-5 // -0
const a = 0*-5 // -0

但是如果你將他 strinify 他卻會回傳給你 "0",如果反向從 string轉回來就正常。
比較運算也是會一個宇智波騙你

const a = 0/-5 // -0
const b = 0 //0

a == b; // true
-0 == 0; // true

a === b; // true
-0 === 0; //true

-0 < 0;  // false
a < b;  // false

那如果想分辨 0-0 該怎麼做?
可以使用以下的方式先判斷是否為0,再除以 n 比對是否是-Infinity

function isNegZero(n){
    n = Number(n)
    return (n === 0) && (1/n === -Infinity) 
}

isNegZero(-0); // true
isNegZero(0 / -5 ); // true
isNegZero(0); // false

但這些我個人覺得都不算重點,重點是為何需要?
書中寫道是因為除了學術上的需求外,特定的應用會需要,比方說動畫美影格的移動速率,就會以 number 的正負號來表示移動方向,如果沒有了正負號就好像失去了方向 xD
大概懂他的感覺,但是我沒有實際遇過所以感觸不深。

特殊相等性

在上面我們提到了 NaN 不等於自己, 0-0 又相等,那我們有沒有一個工具可以測試兩個值是否絕對相等?
有的!
ES6 中出現了一個工具叫做 Object.is(..),並且不會發生剛剛上面所說的那些特殊的情況 XD

const a = 1 / 'foo';
const b = -2 * 0;

Object.is(a, NaN); // true
Object.is(b, 0); // true
Object.is(b, 0); // false 

由此可知就算是使用 === ,也無法避免掉剛剛所說的那些特殊值,如果要比較特殊的情況使用 Object.is(..) 可能相對會來的安全些。

值和參考

JS 的基本型別中永遠都是藉由值的拷貝來指定或是傳遞,
看不懂沒關係我們看看範例

const a = 1;
const b = a; 

b++;
console.log(a)  // 1
console.log(b)  // 2

主要是因為前面所說的 因為 1 是一個純量的基型值,a 拿到了最初的一份拷貝,而 b 拿到的是該值的另一份拷貝,所以b的改變不會造成a的值產生變動。

複合值的 object 永遠都是會在指定或傳遞時建立參考的一份拷貝。
來也來看看範例

const a = [0,1,2,3];
const b = a;

b.push(4)
console.log(a)  // [0,1,2,3,4]
console.log(b)  // [0,1,2,3,4]

這邊的 ab 都是對同一個共有值 [0,1,2,3] 的個別參考,
不是 a 或是 b 擁有該值,
都不是!
而是他們一起去參考 [0,1,2,3] ,所以會造成單邊修改其實就是改動該參考值所以才會兩邊同時改變。

這邊提供一下我自己在網路上找到的文章。

JavaScript 的「傳值」與「傳址」

我個人覺得滿不錯的推薦一下~


以上是今天的內容
有點多,這篇我花比前幾篇還多的時間去研究和思索,
感覺力量提升(?
如果內容有錯或是觀念有錯麻煩再跟我說,有可能是我理解錯誤
歡迎討論
感謝你

我們明天見


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-20 10:26:14
【這些年我似是非懂的 Javascript】Day 5 - 飽受爭議的型別 https://ithelp.ithome.com.tw/articles/10239775?sc=rss.iron https://ithelp.ithome.com.tw/articles/10239775?sc=rss.iron

圖片來源

嗨各位你們好,我是 Robin~

之前在 【這些年我不懂的 Javascript】Day 3 - 你一定可以入的了門 #上篇
這篇有提到 JS 沒有支援有型別的變數,
但是有滿多強型別語言的擁護者會說:
JS 不該宣稱擁有型別系統 。
說實話包括我以前也會說 JS 沒有型別系統,
但我覺得每個語言都有它獨有的特色和精神,用別的語言爭論這件事好像沒什麼意思。

如何正確的將值轉換成不同的型別,對於每個型別有他的固定有的行為並且正確地理解它,我相信被最受抨擊的"強制轉型",對你來說可能就不是那麼的可怕。
俗話說的好

知己知彼百戰百勝

(今天不會講"強制轉型",因為我有偷看後面有專門的章節在講xD)

開始我們今天的學習~

內建型別

JS 定義了以下七種內建型別

  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol (ES6 新增的)

除了 null 之外其他型別使用 typeof 都有對應型別的值。
如下範例

typeof null // 'object'
typeof undefined // undefined
typeof true // 'boolean'
typeof 66 // 'number'
typeof "77" // 'string'
typeof {name: "robin"} // 'object'
typeof Symbol() // 'symbol'

每次我看到 null 內心就會出現當兵班長常說的...

又是你! 就你最特別! 標新立異!

就如同我之前所說的,
他是一個萬年臭蟲,
不要奢望他去修他,我們懷念他。

那我該如何透過其他型別來測試他是 null 呢?

const a = null;
(!a && a === "object"); // true

null是唯一一個是基型值卻是 falsy 的 ,然後 typeof 又會回傳 object

還記得前幾篇還有提到 函式
"函式實際上就是物件" 那...
你知道函式原來也可以使用 length 特性嗎? (我也是現在才知道)
但是他不一樣的是他會回傳的是"參數的個數",如下範例

const test = function(arg1, arg2, arg3){
    return
}

test.length // 3

未定義 ( undefined ) vs 未宣告 ( undeclared )

簡單來說,以我簡單不專業的以字面上分析...

  • 未定義 就是宣告了但是沒有給他值。
  • 未宣告 就是沒宣告根本沒這東西。

還是看不懂? 來看看範例

let a;
console.log(a); // undefined
console.log(b); // Uncaught ReferenceError: b is not defined

這感覺是很明顯的差異...
但是使用 typeof 時給我們的答覆卻是不如我們所料...

let a;
typeof a; // undefined
typeof b; // undefined

WTF...

書中寫道,這可能是 typeof 的一種特殊的安全防護機制,而這個安全防護機制是一項實用的特色,假如說你有一個全域變數叫做 DEBUG,假如你想要檢查這個 DEBUG 但不想要被 ReferenceError 拋出例外,那你就可以使用 typeof 的安全防護機制來檢查如以下範例。

// 出大事,拋出例外
if (DEBUG){
    // do something
}

// 使用 typeof 這個安全機制來檢查
if (typeof DEBUG !== "undefined"){
    // do something
}

如果不使用 typeof 這個安全防護就不能知道有沒有被宣告了嗎 Orz?
不!你還可以利用 "所有的全域變數都是全域物件" 這個特性,什麼意思?
還記得你宣告一個物件時當你使用一個沒有的特性他會回你 undefined 而不會被拋出例外,大概就是這個概念。
那在瀏覽器中我們就可以藉由 windows 這個全域物件來檢查,如下範例。

if (windows.DEBUG){
    // do something
}

if (!windows.DEBUG){
    // do something
}

而我個人滿常見還有另一種做法,叫做"依存性注入"這個設計模式,如以下範例。

const test = function(someFunc){
    const balabala = someFunc || function(){
        // do something
    }
    const coolfunc = balabala();
    // ...
}

但是在 ES6 的時代已經可以使用預設參數來取代。

有沒有一種一直在改朝換代的感覺xDD
突然覺得現在學 JS 好幸福,已經可以使用很便捷的方式撰寫。


感覺在寫前面的文章已經把自己對於該名詞都講得差不多了,以至於部分文章內容我不想在重複寫一遍,感覺自己讀起來有點不順(單純指排版),真希望可以一條龍的一次講 (但是我相信他這樣排一定有他的道理XD)。

目前讀下來覺得這本書的筆者都以深入淺出的方式解釋~讚讚!

明天來談談 "" 似乎有很多可以學習呢!

明天見


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-19 09:54:23 【這些年我似是非懂的 Javascript】Day 4 - 你一定可以入的了門 #下篇 https://ithelp.ithome.com.tw/articles/10238993?sc=rss.iron https://ithelp.ithome.com.tw/articles/10238993?sc=rss.iron

圖片來源

嗨各位今天來接續昨天那篇
【這些年我不懂的 Javascript】Day 3 - 你一定可以入的了門 #上篇

今日學習清單

  • 變數
  • 函式的範疇
  • 拉升 (Hosting)
  • 巢狀範疇
  • 條件式
    • If .. else .. if
    • Switch case
    • 三元運算子
  • 嚴格模式 ( Strict Mode )
  • 作為值的函式
  • 馬上被調用的函式 (IIFE)
  • Closure (閉包)
  • 模組
  • this 識別字
  • 原型
  • 舊功能與新特色
    • Polyfilling
    • Transpiling

Note:
以上大多數是屬於大概帶過,如果有涉略到後續幾本書的範圍,通常會講得很粗略,請見諒。


變數

在 JS 中變數 (包括函式名稱) 必須要是有效的識別字
基本上就是以 a-zA-Z$ 或是 _ 開頭,之後都可以包含以上這些加上 0-9,有些特定的字不能當作變數,就稱為保留字

函式的範疇

在函式內宣告任一變數或是函式,都只能在該函式使用。
有點像不可侵犯的領域(X

範例如下

function initMyName(){
    var name = 'Robin';
    function initMyage(){
        var age = '18';
    }
}

console.log(name) // ReferenceError
console.log(age) // ReferenceError
console.log(initMyage) // ReferenceError

拉升 (Hoisting)

當你使用 var 宣告變數時,他就會把你宣告的這個動作拉到該範疇的最頂端。
不懂? 來看看範例

console.log(test)

答案是~
// ReferenceError: test is not defined

一切都合理!
那再來!

console.log(test)
var test;

相信聰明的你一定知道...
答案還是
// ReferenceError: test is not defined

如果你心中想說 我就知道,那恭喜你答錯了。

答案是undefined

簡單來說他實際上變的會有點像這樣

var test;
console.log(test)
test;

這邊只是大概提一下,書中說之後會再說xD (撇清責任)
不過我還是忍不住找了一下相關的文章,
有興趣可以看一下這篇,我覺得寫得非常詳細。
我知道你懂 hoisting,可是你了解到多深?
推爆!

巢狀範疇

當你宣告ㄧ個變數,那個範疇內的所有位置都可以用,就是較底層和較內層的部份都可以取得到外層宣告的變數,但是外層沒辦法存取內層所宣告的變數。
範例如下

function foo(){
    var a = 2;
    function boo(){
        var b = 3;
        console.log(a) //2
    }
    console.log(b) // ReferenceError
}

條件式

除了前面提過的 if,你應該還要知道在 JS 中其他的條件式。

If .. else .. if

const age = 18;

if (age < 18){
    console.log('幼齒')
}
else if(age >18){
    console.log('成年囉')
}
else {
    console.log('剛好滿18')
}

雖然這樣看起來可以 work 但是顯得有點麻煩,因為你還是一直對 age 做判斷,有沒有更棒的判斷方式呢?

答案是有的!就是 switch case

Switch case

來改一下上面的範例

const age = 18;
switch(age) {
    case <18:
        console.log('幼齒');
        break;
    case >18:
        console.log('成年囉');
        break;
    default:
        console.log('剛好滿18');
}

基本上這樣看起來就是會比較能懂原來你這包全部都是對 age 做條件式的判斷。

那如果說是判斷沒那麼多東西的,寫 if else 和 switch 好像又有點尷尬怎麼辦,有沒有看起來可讀性更高而且更方便的呢?
有的!就是三元運算子

三元運算子

來直接看一下範例

const robin = 'boy';
(robin === 'boy')? console.log('男生啦'): console.log('女生啦');

基本上用法就是

(條件式) ? <如果是的話要幹嘛> : <不是的話要幹嘛>

是不是覺得~
原來那麼簡單呀!

嚴格模式 ( Strict Mode )

在 ES5 時引進了 "嚴格模式",簡單來說就是...

他會嚴厲的指責你幹嘛這樣幹嘛那樣的。
會叫你依照比較安全的且適當的,而且通常能夠使你的程式碼被引擎最佳化,所以聽到這是不是該把所有程式都加入 strict mode 了呢!?
心動不如馬上行動!

strict mode 是選擇性的你可以使他整個檔案都是 strict mode 也可以使單一 function 變成 strict 模式,這取決於你放哪個地方,如以下範例。

整個 file 使用 strict mode

'use strict';
// 整個 file 都是 strict mode

function testA(){
    // 這邊有使用 strict mode
    
    function testB(){
        // 這邊有使用 strict mode
    }
}
// 這邊有使用 strict mode

部分 function 使用 strict mode


function testA(){
    'use strict';
    // 這邊有使用 strict mode
    
    function testB(){
        // 這邊有使用 strict mode
    }
}
// 這邊沒有使用 strict mode

作為值的函式

就是把 function 丟進去給一個變數當值的意思,
直接看一下範例就知道我在說啥了~

var boo = function foo(){
    console.log("我愛一條柴")
}

boo() //我愛一條柴

馬上被調用的函式 (IIFE)

就是馬上被執行的函式,一般我們寫 function 都不會馬上被執行,一定是要被調用他才會執行,那如何做到呢?請看以下範例。

(function foo(){
    console.log('我直接被執行啦~')
})(); // 我直接被執行啦~

我個人滿常要寫快速的測試時會用到。

Closure (閉包)

這是大部分學 JS 最不能被理解的一塊,這邊之後會在範疇那本書會更深一點的講解,這邊簡單用以下範例介紹~

function makeAdder(x) {
  function add(y) {
    return x + y
  }
  return add
}
const addOne = makeAdder(1);
console.log(addOne(123)) // 124

看懂了嗎?
基本上就是在函式執行完畢(在上面的範例是指 makeAdder)之後,讓你可以"保留"該儲存的變數在該函式的範疇的一種方式。
以上面範例來說我就是將 1 這個數先暫存起來並且暫放在 addOne 這個變數,然後在執行他時帶了 123 這個值進去讓他加總得出 124 這個答案。

模組

在剛剛提到的閉包中最常見的手法就是模組模式,他能夠讓你定義私有的變數和函式,讓他們隱藏起來並開放可以從外部取用的公開 API。
請看以下範例

function User(){
    let name, pwd;
    
    function doRegist(userName, userPwd) {
        userName = name;
        userPwd = pwd;
        // do something 
    }
    
    const publicAPI = {
        regist: doRegist;
    }
    return publicAPI;
}

const robin = User();
robin.regist('robin', 'qq12345678qq')

關於這塊與閉包之後的文章還會提到(因為書上是這樣說的xDD)
這邊還是以簡單帶過讓大家知道可以這樣用。

this 識別字

this 指向誰,這個是連我到目前有時候都會有點搞不清楚的東西,
但是我有看到此系列的某本書名直接標題就是關於 this 想必也是需要花一點時間研讀,這邊簡單帶過。(因為我也不是很了解 Orz)
大部分時候的 this 是取決於"被誰呼叫",

function foo() {
  console.log(this.bar);
}

var bar = 'global';

var obj1 = {
  bar: 'obj1',
  foo: foo,
};

var obj2 = {
  bar: 'obj2',
};

foo(); // 'global'
obj1.foo(); // 'obj1'
foo.call(obj2); // 'obj2'
new foo(); // undefined

注意前面的範例中的最後四行,

  1. 不是 strict mode 時 foo() 會將 this 設定成全域物件,反之他則會變成 undefined,所以當你在 strict 模式中你會得到一個錯誤~
    但是在這裡你會拿到 global 這個值~
    因為前面說了在這邊他會把 this 變成全域物件。
  2. obj1.foo() 這邊他把 this 設為了 obj1 物件。
  3. foo.call(obj2) 把 this 設為了 obj2 物件。
  4. new foo() 把 this 產生一個全新的空物件。

如果你還是有點不懂...
沒關係持續關注訂閱加分享開啟小鈴鐺,
我之後在讀到那本書的時候會尋覓更好的譬喻 Orz

原型

這個是我目前也不太了解的一塊,大概知道他是什麼但是又不是那麼了解,但是又算滿常見的,這部分也是在 this 那本書會有詳細的說明,這邊也是簡單帶一下,基本上這個東西就是如果該物件的特性(你也可以想成功能啦),如果有缺少時的後備機制。
範例如下

const foo = {
    a: 88
}

var bar = Object.create(foo);

bar.b = 'Robin is handsome';

bar.b; // Robin is handsome
bar.a; // 88

bar.b 沒什麼好說的是我自己新增的, bar.a他就會去委派給 foo,也就是實際上 bar 不存在這個特性,但是如果有人用了他就去尋找看看 foo 有沒有這個東西,聽起來滿妙的。

舊功能與新特色

在 JS 版本的更新的情況下,部分舊版的瀏覽器並不支援新的功能,或是新的功能根本沒有在穩定版的瀏覽器實作。
這邊就來分享兩種可以將 JS 較新的功能帶到較舊的瀏覽器

Polyfilling

就是一個較新的功能定義但是卻產生具有相同的行為,例如 ES6 定義了一個功能為 Number..isNaN(..) 主要用來提供檢查是否為 NaN,棄用原本的 isNaN(..) ,這時我們就可以說我們可以輕易 polyfill 這個工具,而你不用管使用者使用的是不是支援 ES6 的瀏覽器如下範例改善 isNaN

if(!Number.isNaN){
    Number.isNaN = function isNaN(x){
        return x !==x;
    }
}

可是在實作時你可能會有疏失所以比較好的方式是使用一些可以幫你嚴格審核的函示庫書中提到 es5-shimes6-shim 可供使用。

Transpiling

那如果我沒辦法 polyfill 該語言的新語法怎麼辦,但是在舊版的瀏覽器中他又看不懂新語法導致會直接拋出錯誤。
此時就有一種工具是可以幫你把新語法轉換成等值的舊語法,
我只能說...
根本黑科技啊!!!

最常見的工具就是 BabelTraceur
至於我們為什麼要那麼麻煩使用新的語法編寫然後還要轉換到舊語法,有以下幾個重要的理由。

  1. 新語法中的設計是為了讓你的程式碼更好閱讀,科技是會進步的(X),不只是為了你個人維護,還為了你的團隊著想。
  2. 你如果為了舊的瀏覽器奮鬥,進行 transpile,並且提供新語法給最新的瀏覽器,你除了能獲得新語法的效能最佳化,也可以為瀏覽器的供應商有更多的程式碼可以幫他們測試,並讓他們可以實作以及最佳化。
  3. 早點使用可以讓 JS 委員會能夠提早發現問題,可能是新的語法有設計上的問題,就能避免像是之前提的 typeof null; // Object 一樣,可能無法修補一修補就會造成更大的漏洞。

ES6 提供了一個新的語法是預設參數值如下

function foo(name = 'robin'){
    console.log(name);
}

foo(); // robin
foo('ron'); // ron

那如果要自己 transpiling 該怎麼做?

function foo(){
    const name = arguments[0] !== void 0 ? arguments[0] : 'robin';
    console.log(name);
}

就是檢查 arguments[0] 是否為 undefined 如果是就給 robin 這個預設值。


今天文章到此入門也到此寫一寫越來越不像入門 xDD
不過沒關係,我自己學習起來看不懂的地方搞懂就是賺到(X

讀到這感覺目前都不是這本書的主軸,但是還是符合他說的
導讀,型別與文法,目前是導讀 xD
如果你覺得痛苦...
沒關係你還有我,因為我也...還好 (想不到吧)
感謝你讀到這。
明天見~


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-18 00:33:01 【這些年我似是非懂的 Javascript】Day 3 - 你一定可以入的了門 #上篇 https://ithelp.ithome.com.tw/articles/10238281?sc=rss.iron https://ithelp.ithome.com.tw/articles/10238281?sc=rss.iron

圖片來源

這篇要與您一同進入 JS 的入門世界,從基礎開始雖然乏味但是特別扎實,如果你已經有一定的基礎,那也很適合你檢視自己的觀念,如果有異直接發問與我討論,也可以幫助我釐清錯誤的觀念 xD

今日學習清單

  • 值與型別 ( Value and type )
  • 存在 JS 的萬年臭蟲
  • 極致隱藏版運算子 Void
  • 若有似無的 undefined
  • 物件 ( Object )
  • 陣列 ( Array )
  • 函式 ( Function )
  • 內建的方法
  • 比較值
    • 相等性
    • 不等性
  • Truthy & Falsy
  • 強制轉型 ( Coercion )

型別與值

Javascript 本身支援有型別的值,但是不支援有型別的變數
什麼意思?

白話版:

一個瓶子裡面可以裝東西,
你能定義瓶子裡面裝的東西是什麼型態,
比方說:
瓶子內裝著,而你可以定義裡面是這個型態
瓶子內裝著石頭,而你可以定義裡面是石頭這個型態
但是你不能定義這個瓶子是裝的或是裝石頭的。

請不要跟我說瓶子材質不一樣的問題xDD
運用你的想像力~ (・ω´・ )

程式版:

var a = 1;  // Number 
var b = "Hi"; //String
var c = [] ; // Object

當你使用 typeof 去檢視上面三個型態時,檢查的會是值的型態,而不是變數的型態。

存在 JS 的萬年臭蟲

typeof 1; // Number
typeof "Hi"; // String
typeof true; // Boolean
...

這些我們都知道,
再來!

typeof NaN; // Number

NaN => Not a Number
他主要是指無效的數字所以理解成 Number 也還說得過去...

但是...

typeof null; // ?  <- 猜猜我是誰

答案是...

Obejct

書中提到這是一個永遠不會修復的 Bug ,
原因是 Web 上有太多 Code 是依賴這個 Bug ,
如果修復的話會造成更多更多的 Bug, // 三壓
聽起來是一個塵封已久的霸王技術債。

極致隱藏版運算子 Void

我用了 JS 好歹也有個幾個月 (講得好像很久),
從來沒用過、沒聽過和沒看過 xDDD
(當然有可能是我太菜 (((゚Д゚;))))

他主要的功能只有一個就收到任何運算式或是值然後吐回 undefined
就是一個我給你錢你快做!

總而言之現在好像很少情境需要用到,
有興趣可以看這位大大的文章感覺講的滿細的。
推推 -> JS 冷知識: 你所不知道的 void

若有似無的 undefined

var a = undefined;

你把 a 設定成 undefined
但是其實跟沒做事一樣
就像是你用

var a;

達到的效果是一樣的
而要達到該變數為 undefined 有很多方法
包括剛剛上面講的 void

物件 ( Object )

他是一個複合值,什麼意思?
一般我們在 JavaScipt 說的基本上都是長這樣

var a ={
  b: "Hi~",
  c: 1232,
  d: true
};

他可以設定在這裡面的某個位置設定任何型別。
像是
a.b 就是 string
a.c 就是 Number
a.d 就是 Boolean

而你上面看到的 . 就叫做點記號法,而還有另一種存取方式叫做方括號記號法可以參考以下範例。

a[b]
a[c]
a[d]

而哪種比較好?
"通常"會直接選用點記號法比較好閱讀,原因很簡單!
因為比較短! xD
方括號記號法用在哪?
它存在就有他的意義!
假如你的物件特性名稱有特殊字元或是你存在一個變數就是使用方括號記號法的時機了。
例如

const name = 'Robin';
const myObject = {
    'Robin':{
        'age':18,
        'phone':'0912345678',
        'identity':'A123456788'
    },
    'Kevin':{
        'age':19,
        'phone':'0921345678',
        'identity':'A213456787'
    }
}

console.log(myObject[name])

output:

{
    'age':18,
    'phone':'0912345678',
    'identity':'A123456788'
}

那你可能會想說啊我就直接都用 點記號法,不行嗎?
答案是不行 xDD
來以上面的範例來說你直接使用點記號法~

console.log(myObject.name)

output:

undefined

原因是他會去找名為 namekey 值。

相信你已經蘆薈貫通了

陣列

看完一般的物件 (Object),陣列是什麼鬼!?
他還是一個物件 (Object) ,想不到吧!?
他是一個特化版的 object 型別,也可以說是 object 的子型別。
可以儲存任一型別的 object ( 跟剛剛講的物件一樣 ),
並且以數值化的索引位置來儲存。
直接來看範例

const arrayList = [
    'Robin',
    'Ron',
    'Kevin',
    'Robert',
    'York',
    'Haru'
];

arrayList[0]; // Robin
arrayList[1];// Ron
arrayList[2]; // Kevin
arrayList[3]; // Robert
arrayList[4]; // York
arrayList[5]; // Haru
arrayList.length; // 6

不懂?
來我們來看看以下這張圖

圖片來源

就是照順序放東西進去啦!
是不是很簡單~

函式

直接跟你說函式也是 object 的子型別,
但是不同的是當你使用 typeof 時他會回傳給你 function,這是他的一個主要的型別,並且可以擁有特性,就像是上面 object 物件一樣。 ( 這個我倒是看書才知道 xD )
來看範例

function foo(){
    return 18;
};

foo.bar = 'Robin';

typeof foo; // function
typeof foo(); // number
typeof foo.bar; // string

內建的方法

除了剛剛提到物件的那些特性,JS 還提供很多很猛很強大的功能,就是內建方法。
剛剛有看到陣列的範例中有 array.length; ,這就是內建的方法。
例如

const name = 'robin';
const pi = 3.141592653589;

name.toUpperCase(); // 'ROBIN'
pi.toFixed(2) // 3.14
...

那你會想說為什麼 toUpperCase 和那些東西我明明沒寫啊,
他怎麼知道?
簡單來說每個型別都有一個物件包裹器 (object wrapper),我覺得我會稱為他是型別小精靈,而這型別小精靈裡面富含很多內建的方法,如果你是他的型別他就會幫你做事,做他這個型別可以幫你做的事兒~

比較值

比較值有相等性不等性,最終都會回傳布林值的 true 或是 false

相等性

不相等 不等於 不等性,所以 !=!== 不在不等性裡頭。

在昨天那篇有提到 ===== 以及 !=!==,以往我自認為差別在於說一個會檢查值得型態一個不會,這個答案是錯誤的!
答案是一個允許強制轉型一個不允許強制轉型,也因為這樣 === 被稱為嚴格相等性,反之 == 則為寬鬆相等性。

那... 我該如何知道何時該使用 == 還是 === ?
簡單兩步驟讓你不再迷惘,
第一步你應該要知道 強制轉型 是如何 (how) 發生。
第二步你應該要知道 強制轉型 後值的變化會是如何,也就是 Truthy & Falsy
第三步你應該要遵守以下的四點準則

  • 當比較兩方有任何一方有可能會是 truefalse 時,你應該要使用 === 避免使用 ==
  • 當比較兩方有任何一方有可能是這些特定的值 ( 0, '', "", [] ),你應該要使用 === 避免使用 ==
  • 在其他所有的情況下,使用 == 都是安全的,甚至能夠提升可讀性並且簡化你的程式碼。
  • 如果你都不確定,就都使用 ===,但是以上三點都是希望你能夠更能理解你到底在寫什麼和思考可能會發生的情況。

而前兩步稍等會與您一同探討,了解並遵守以上四點準則和三步驟相信我這對你來說一點都不可怕。

不等性

< , <=, >, >= 這些都是,也可以叫做關係比較
那他跟相等性一樣嗎?
不,他沒有嚴格不等性,也就是他都允許強制轉型,

  • 字串也可以比較,就是依照字典順序(就是 ASCII的值比較大小)
'boo' > 'foo' //false
'boo' < 'foo' // true

Truthy & Falsy

什麼東西被強制轉換會變 false?

  • "" or '' (空字串)
  • 0, -0 ,NaN
  • undefined
  • false

什麼東西被強制轉換會變 true?
除了以上其他東西都會被轉為 ture

  • 非空的字串
  • 不等於零的有效數字
  • 陣列 (無論有沒有值在裡頭)
  • 物件 (無論有沒有值在裡頭)
  • 函式

強制轉型

剛剛和昨天都一而再再而三的講到強制轉型
而 JS 的強制轉型有兩種,一種是明確性的強制轉型,另一種則是隱含的,而令人害怕的和畏懼的是隱含性的,甚至有人說到 "強制轉型是邪惡的" ,因為當型別轉換在你不知情的情況下你會覺得出乎你的意料。

隱含性的強制轉型他到底是指什麼?
簡單來說在 JS 的世界中當你使用剛剛所講的 == 或是!=,就代表允許強制轉型,那可以藉由以下範例我們就可以知道他是怎麼一回事。

const a = 18;
const b = '18';

a == b // true 因為他把 b 的 '18' 轉成 Number
a === b // false

寫到這邊讓我想到之前看了工程師幹話的這篇文章,
工程師幹話 - 怎樣準備技術面試

文章末有提到

0 == null // false
0 > null // false

接著...

0 >= null

會壞掉 >///<

一開始,其實只是純粹看文章有趣,但實質上其實不懂這個梗,後來花了些時間尋求答案,得出以下原因。

  • 0 == null // null 在相等性的設計上不會嘗試轉型態
  • 0 > null // null 在這邊會嘗試轉型態會變成 0 接著就是 0>0 所以回傳 false。
  • 0 >= null // null 在這邊會嘗試轉型態會變成 0 接著就 0 >= null 所以回傳 true

不得不說 null 真特別呢... 你說是吧xD (有點歪樓)

那明確的強制轉型是什麼?
就是我的確有指定他要轉為某個型態,範例如下。

let age = '18';
age = Number(age);
console.log(age); // 18
console.log(typeof age); // Number

希望看完這裡你對於強制轉型不再害怕。


第二天心得

發現在讀的過程中有和以往的觀念有衝突時,找了滿多解答並釐清觀念,收穫滿多的,以前都是以先做了快速拿到成就感,但是往往會忘記回頭去檢視自己對於自己的程式碼熟悉程度,感覺這篇寫完內心更加堅定了 (才剛開始好像講得要結束了)

寫一寫發現文章有點太長了,決定拆篇寫。
怕讀完整篇你可能會睡著xD

感謝您的收看
我們明天繼續!


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-17 01:54:23 【這些年我似是非懂的 Javascript】Day 2 - 程式語言超入門 https://ithelp.ithome.com.tw/articles/10237450?sc=rss.iron https://ithelp.ithome.com.tw/articles/10237450?sc=rss.iron

圖片來源

今天來分享一下程式語言超入門,
基本上是以真的真的完全不知道程式是什麼為基礎的文章,
雖然說有一些看起來是專有名詞但是其實所講述的其實並不會很難理解。
如果你對程式很有興趣那不妨可以看看,
如果你有一定的程式基礎也許也可以應證一下你所想的對不對。
那我們開始囉~

Note: 以下會將 JavaScript 縮減寫成 JS ,
老司機們就不用多說明你懂的!新手也不要慌張,就只是縮寫而已。

程式碼

程式碼 aka Code
也就是我們常說的一個程式 ( program ) 或是原始碼 ( source code )。

電腦語言你就將他當作是一種語言,例如英文以特定的格式和規範這樣,電腦語言也同等道理。

敘句

簡單來說就是一句話的意思
以 JS 來說就長這樣

a = 1 + 2;

請把你以前數學的想法都拋之腦後,
你會想說數學等號左右相等所以 a = 3
但是這邊其實是指將右方的運算結果儲存至左方
所以是 1 + 2 算好等於 3 ,並儲存至 a 這個變數。

而你可能會發現

欸!最後有一個分號 ; 是什麼意思?

你就把它當作中文的句號 或是英文的句號 .
而程式主要在說的就是由多個敘句所組成的。
看到這邊其實聽起來不難吧!

運算式

剛剛知道了敘句之後這個應該就沒什麼問題了,
運算式就是由或是變數運算子(等等接著會說)所組成的就是運算式。
而剛剛看到的是也是。
那來看看下方的範例

a + 1;

以數學角度來看,看起來沒什麼問題啊!
但是對於程式來說其實沒什麼路用而且不常見 xDD
主要原因是他就算完然後沒有額外對任何變數作儲存或是處理。
對沒錯!
就是做爽的xD

運算子

我這邊稍為統整順便和說明一下 JS 常見的運算子

指定

= ,剛剛我在敘句所提到的應該還沒忘吧,右手邊的東西放到左手邊

數學

這聰明的你一定知道
+-*(乘)、/(除)。
這邊就不多作介紹,如果你真的不懂xD
可以不要害羞在底下留言跟我說
我保證不笑出聲

複合指定

不要怕!這就是將上面兩個提到的東西尬在一起而已。
範例:
a += 1
其實就只是 a = a+1
舉一反三!
所以 +=-=*=/=這些都是唷。

遞增和遞減

++--
就是... +1-1的意思。
a++ 就等同於 a = a+1
a-- 就等同於 a = a-1

存取物件的特性

.,把它想成中文,你可能會比較好理解。
中文: 一輛車"的"椅子
轉成
程式: car.chair
所以這邊程式就會抓到車子的椅子的特性
可能是紅色啊或是哪個廠牌的等等。
這個之後會講到物件(Object)
你會更能理解~

相等性

== 寬鬆相等、=== 嚴格相等、!= 寬鬆不相等、!== 嚴格不相等。
不要緊張這些明天我就會一一作說明,現在你只要把它當作數學的相等不相等
主要差別是在於說型態是否會強制轉型

比較

><>=<=
這些數學都看過了不用多說了吧。

邏輯

|| (or) 、 && (and)
之後也會對這邊特別說明條件式
這邊稍為做一點點的範例,
你可以先從以下看出一些端倪和差異~

1 || 0 // true
0 || 0 // false
1 && 1 // true
1 && 0 // false

值與型別

下篇會有詳細的介紹
但是這邊基本介紹一下
先以生活案例來說

今天昌彥買了一隻 3000 元的 手機

3000 -> 數字
手機 -> 物品

程式也有型別,在對值操作時或是要表達時會依據你的需求去選擇,
而先以上述範例在程式中

3000 -> Number
手機 -> String

註解

在日常生活中我相信買一些 3C 產品時,都會附贈一些說明書,
那通常呢...大部分人都會直接忽略,當有問題時,
就會翻開說明書看一下說明,找出問題在哪和使用說明,
而註解也差不多是這種淺移默化的存在
並且是會直接被忽略不會被執行,
也就是在裡面寫任何邏輯或是敘句也都會被忽略。

JS 中有兩種註解可以使用

  1. 單行註解
    顧名思義就是只註解一行
  • 用法:
    前面加上 //
  • 範例:
    c = a + 1; // c 是 a 加一
  1. 多行註解
    假使說你有很東西要註解或是要跨很多行,
    那多行註解就可以讓你不用每行都 //
  • 用法
    開頭 /* 中間包內容,結尾 */
  • 範例
    /* 我怎麼都不會被執行
       因為我是註解啊!
       你想知道答案嗎?
       去找吧!
       我把所有答案都放在那裡 */
    c = a + 1;
    

Note:

大學時教授總是說:
要寫註解啊!不然誰看得懂你在寫什麼?
但實際上不是每行程式都需要註解,
程式需要的是一個文件,
而在 JS 中有一個東西叫 JSDoc
他長的就是註解,但是他其實是一個以文件形式存在的註解。

區塊

aka Block,還記得那年夏天你在高中的課堂上的計概課嗎?
老師溫柔的握著你的手教你把 Block 畫出來... (夢到的)

{
...
}

如上述所見,JS 的區塊是以大括號把東西包起來~
而單一單純的大括號包起來在日常生活中幾乎是看不到的,
因為他通常會附加在一些控制敘句上( if、while、for ...等等)

條件式

日常生活中我們常常說,"如果"怎麼樣我們就怎麼樣,
這個如果後面就會加上一些條件,
譬如說
如果我超過18歲,我就已經老了。
如果我完成鐵人賽,我就買一台 Macbook Pro 犒賞自己。
...
之類的

那我們程式也有這種機制是可以判斷一些條件,
再決定該做什麼
不囉唆直接來範例!

var age = 23;
if( age > 18 ){
    console.log("唉...")
    console.log("老了")
}
console.log(`我已經${age}歲了`)

output:

唉...
老了
我已經23歲了

這邊要表述的是當條件達成時(年齡大於18歲)就會執行該 Block 內的敘句。

迴圈

日常生活中是不是很常看到新聞有奧客在跟店員跳針,
無限循環某件事或某句話。
而我們程式也可以!(驕傲個屁 xD)
改寫上面條件式的一個地方我們就可以無限的看到一直哀怨xDD

while

var age = 23;
while( age > 18 ){
    console.log("唉...")
    console.log("老了")
}

output:

唉...
老了
唉...
老了
唉...
老了
唉...
老了
...

是不是很簡單?
而迴圈不只有 while 還有 do whilefor

do while

var age = 23;
do {
    console.log("唉...")
    console.log("老了")
} while( age > 18 );

output:

唉...
老了
唉...
老了
唉...
老了
唉...
老了
...

Note:
這邊結果看起來相同,但是實質上如果判斷式不等於的話其實 do while 至少會做一次。

For

For 就會比剛剛的兩個 while 看起來還要複雜一些,
但是你不用擔心,他其實很實用且比想像中簡單。
他有三個參數

  1. 初始化子句
  2. 條件測試子句
  3. 更新子句

直接看範例

for (let age = 17 ; age < 28 ; age++){
    console.log("還年輕")
}
console.log("老了")

let age = 17
初始化子句,就是一開始初始值設定多少,我這邊就是設定17歲。
age < 28
條件測試子句,什麼條件下執行,我設定 28歲以內(不含)。

age++
更新子句,執行完一輪時最後要做的動作,這邊將年齡 +1。

output:

還年輕
還年輕
還年輕
...
老了 // 此時 age 變數已經 28 歲了

Break

如果想要中途停止迴圈的執行,那 Break 你就會用到,
以 while 迴圈為範例

var age = 18;
while( age < 28 ){
    console.log("唉...")
    console.log("老了")
    if( age === 20) break;
}

那他就會在 age20 時跳出迴圈。

小總結

整理一下

  • while (常用)
    先判斷條件再執行
  • for (常用)
    先判斷條件再執行
  • do while
    先執行在判斷條件

函式

你有想過當你寫一個功能時,會需要一直重複使用它,該如何是好!?
這時候函式就是你最佳良伴!
為了不讓你寫程式像是手動一樣一直重複寫,你就需要把功能變成一個函式,讓他可以不斷的被重複利用~
這邊做一個簡單的小範例

funtion repeatMyName(name,times){
    for(let n =1 ; n < times ; n++){
        console.log(name);
    }
}

repeatMyName("Robin",10);

output:

Robin
Robin
Robin 
... //重複總共十次

這邊做的就是我寫一個函式名為 repeatMyName,需要的參數是 nametimes,而做的事情就是重複印出指定次數的名字

而呼叫參數的方式就是剛剛定義的 repeatMyName後面加上()
接著裡面帶參數(argument)。

跑吧程式!

所謂跑程式就是執行的意思啦,

剛剛提到的一個程式是由多個敘句所組成的,
而你可以把它想成中文撰寫一篇文章所想闡述的事情,
而程式就是告訴電腦該怎麼做。

而我們寫的這些程式,其實電腦都...
看不懂xDDD

所以我們需要一些特殊的工具來翻譯成電腦看得懂的語言,
這邊就是我們常聽到的直譯器或是編譯器啦。
簡單來說...
直譯器就是翻一行做一行
編譯器就是我全部翻譯完了在做
這邊有一個馬哥的文章很香有興趣可以了解一下
資訊科普: 直譯器與編譯器的差別在哪裡?

說了這麼多是不是很想知道怎到底怎麼跑呀?
正在用電腦看文章的你
Windows 請按 F12
Mac 請按 command + option + i
Ubuntu 請按 ctrl +shift + i

如果以上都不行請參照以下

打開之後第一次接觸的你一定是一臉矇逼,
沒關係來!
我們直接切入主題讓你知道程式跑起來的感覺。

接著你就看到底下有一塊可以讓你輸入的地方。
我這邊就輸入我的名字 robin 放進名為name的變數 。

var name = 'robin'

然後再輸入一次剛剛所取名的變數 name

就 show 出我的名字啦~

此篇到此分享完畢!


第一天心得

翻開書的第一章節想說太簡單了吧,
寫著文章和想辦法敘述的讓真的初心者能夠懂真的是...
哈哈哈哈
哈哈哈
哈哈

嗚嗚嗚
好累QQ

還不按讚分享加訂閱!(跑錯棚)

今天到此結束
感恩你讀到這還不關掉頁面看我在講幹話
明天見~


參考來源:

你所不知道的 JS|導讀,型別與文法 (You Don't Know JS: Up & Going)

]]> Robin 2020-09-16 00:12:20 【這些年我似是非懂的 Javascript】Day 1 - 各就各位…預跑! https://ithelp.ithome.com.tw/articles/10236878?sc=rss.iron https://ithelp.ithome.com.tw/articles/10236878?sc=rss.iron

截圖自此

截圖自此影片

前言

嗨各位~ 我是 Robin
還不知道我是誰的可以看我上個系列的文章(還沒完結)
這篇有我的自我介紹~
如果懶得看我這邊也簡單稍微介紹一下~
筆者目前現職為軟體自動化測試工程師,還是小小菜鳥,正在努力學習 Javascript 。
以前都看著論壇上的大神在參加鐵人賽,這次也想努力一次!

為何參加了六角學院辦的鼠年全馬比賽又參加鐵人競賽?

簡單來說就是因為... 有參加過鐵人競賽看起來比較猛 xDD
(是不是聽起來很幼稚)

俗話說的好~
良善的壓力會迫使人進步~

標題的"這些年",其實我也才碰大約一年左右 xDDD
所以讀者不用預期我的文章很艱深,因為...
我很菜 Orz


文章主軸和規劃

今天是我的第一次鐵人賽的第一天,
講一下我這 30 天會如何規劃和文章的主軸,
這 30 天主要還是會以筆記的方式撰寫,
如果有餘力會盡量以之前以往的文章風格帶入更多比喻和幹話詮釋。

而這次的主題是~~~~
這四本很有名的書 -> YDKJS (You Don't Know JS)

真多 xD 其實已經買了大半年了還沒開始看
慚愧啊~ 慚愧~

而我就盡力讀,不求讀完只求盡力和對 JS 的進步,
剩餘沒讀完的部分還是會以文章的方式撰寫分享~

如果有我理解錯誤的地方,歡迎指正和包容。

文章內容都是以學術用途~ 部分內容為我消化過後所產生的,如果對此系列書內容有興趣,可以直接去書局購買唷~ 推推。

希望能順利完賽! ( ゚∀゚)o彡゚
如果你也在鐵人賽的這條船上,一起加油吧!

目錄

【這些年我似是非懂的 Javascript】Day1 - 各就各位…預跑!
【這些年我似是非懂的 Javascript】Day 2 - 程式語言超入門
【這些年我似是非懂的 Javascript】Day 3 - 你一定可以入的了門 #上篇
【這些年我似是非懂的 Javascript】Day 4 - 你一定可以入的了門 #下篇
【這些年我似是非懂的 Javascript】Day 5 - 飽受爭議的型別
【這些年我似是非懂的 Javascript】Day 6 - 值
【這些年我似是非懂的 Javascript】Day 7 - Natives 原生功能
【這些年我似是非懂的 Javascript】Day 8 - 魔幻邪惡的強制轉型 #第一章 # 心情轉折
【這些年我似是非懂的 Javascript】Day 9 - 魔幻邪惡的強制轉型 #第二章 #明確的強制轉型
【這些年我似是非懂的 Javascript】Day 10 - 魔幻邪惡的強制轉型 #第三章 #隱含的強制轉型
【這些年我似是非懂的 Javascript】Day 11 - 魔幻邪惡的強制轉型 #最終章 #隱含的強制轉型
【這些年我似是非懂的 Javascript】Day 12 - 文法 # 趴萬
【這些年我似是非懂的 Javascript】Day 13 - 文法 # 趴兔
【這些年我似是非懂的 Javascript】Day 14 - 範疇
【這些年我似是非懂的 Javascript】Day 15 - 語彙範疇
【這些年我似是非懂的 Javascript】Day 16 - 函式的範疇
【這些年我似是非懂的 Javascript】Day 17 - 區塊的範疇
【這些年我似是非懂的 Javascript】Day 18 - 拉升(Hoisting)
【這些年我似是非懂的 Javascript】Day 19 - 閉包 (Closure) # Part 1
【這些年我似是非懂的 Javascript】Day 20 - 閉包 (Closure) # Part 2
【這些年我似是非懂的 Javascript】Day 21 - 是這個不是那個的 this # Part 1.
【這些年我似是非懂的 Javascript】Day 22 - 是這個不是那個的 this # Part 2.
【這些年我似是非懂的 Javascript】Day 23 - 是這個不是那個的 this # Part 3.
【這些年我似是非懂的 Javascript】Day 24 - 是這個不是那個的 this # Part 4.
【這些年我似是非懂的 Javascript】Day 25 - 物件 # Part 1.
【這些年我似是非懂的 Javascript】Day 26 - 物件 # Part 2.
【這些年我似是非懂的 Javascript】Day 27 - 物件 # Part 3 # 特性描述器
【這些年我似是非懂的 Javascript】Day 28 - 物件 # Part 4 # 特性描述器 Combo
【這些年我似是非懂的 Javascript】Day 29 - 物件 # Part 5 # 特性存取的秘密
【這些年我似是非懂的 Javascript】Day 30 - 完賽心得

]]> Robin 2020-09-15 00:35:02

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

ASP.NET 伺服器控制項開發 :: 2008 iT 邦幫忙鐵人賽 https://ithelp.ithome.com.tw/users/20007956/ironman zh-TW Mon, 06 Jun 2022 20:19:06 +0800 [ASP.NET 控制項實作 Day29] 解決 DropDownList 成員 Value 值相同產生的問題(續) https://ithelp.ithome.com.tw/articles/10013458?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013458?sc=rss.iron 接續上一文
接下來還要覆寫 LoadPostData 方法,取得 __EVENTARGUMENT 這個 HiddenField 的值,並判斷與原 SelectedIndex 屬性值是...]]>
接續上一文
接下來還要覆寫 LoadPostData 方法,取得 __EVENTARGUMENT 這個 HiddenField 的值,並判斷與原 SelectedIndex 屬性值是否不同,不同的話傳回 True,使其產生 SelectedIndexChanged 事件。

        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

四、測試程式
在 TBDropDownList 的 SelectedIndexChanged 事件撰寫如下測試程式碼。

    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

執行程式,在 TBDropDownList 選取 "王五" 這個選項時,會正常顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。

接下選取 Value 值相同的 "陳六" 這個選項,也會正常引發 SelectedIndexChanged ,並顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/30/5830.aspx

]]>
jeff377 2008-10-30 21:23:12
[ASP.NET 控制項實作 Day29] 解決 DropDownList 成員 Value 值相同產生的問題 https://ithelp.ithome.com.tw/articles/10013457?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013457?sc=rss.iron DropDownList 控制頁的成員清單中,若有 ListItem 的 Value 值是相同的情形時,會造成 DropDownList 無法取得正確的 SelectedIndex 屬性值、且無...]]> DropDownList 控制頁的成員清單中,若有 ListItem 的 Value 值是相同的情形時,會造成 DropDownList 無法取得正確的 SelectedIndex 屬性值、且無法正確引發 SelectedIndexChanged 事件的問題;今天剛好在網路上看到有人在詢問此問題,所以本文將說明這個問題的源由,並修改 DropDownList 控制項來解決這個問題。
程式碼下載:ASP.NET Server Control - Day29.rar

一、DropDownList 的成員 Value 值相同產生的問題
我們先寫個測試程式來描述問題,在頁面上放置一個 DropDownList 控制項,設定 AutoPostBack=True,並加入四個 ListItem,其中 "王五" 及 "陳六" 二個 ListItem 的 Value 值相同。

    <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>

在 DropDownList 的 SelectedIndexChanged 事件,輸出 DropDownList 的 SelectedIndex 及 SelectedValue 屬性值。

    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

執行程式,在 DropDownList 選取 "李四" 這個選項時,會正常顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。

接下來選取 "陳六" 這個選項時,竟然發生奇怪的現象,DorpDownList 竟然顯示相同 Value 值的 "王五" 這個成員的 SelectedIndex 及 SelectedValue 屬性值。

二、問題發生的原因
我們先看一下 DropDownList 輸出到用戶端的 HTML 原始碼。

<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>

DropDownList 是呼叫 __doPostBack 函式,只傳入 eventTarget參數 (對應到 __EVENTTARGET 這個 HiddenField) 為 DropDownList 的 ClientID;當 PostBack 回伺服端時,在 DropDownList 的 LoadPostData 方法中,會取得用戶端選取的 SelectedValue 值,並去尋找對應的成員的 SelectedIndex 值。可是問題來了,因為 "王五" 與 "陳六" 的 Value 是相同的值,當在尋找符合 Value 值的成員時,前面的選項 "王五" 會先符合條件而傳回該 Index 值,所以先造成取得錯誤的 SelectedIndex 。

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

三、修改 DropDownList 控制項來解決問題
要解決這個問題最好的方式就是直接修改 DropDownList 控制項,自行處理前端呼叫 __doPostBack 的動作,將用戶端 DropDownList 選擇 SelectedIndex 一併傳回伺服端。所以我們繼承 DropDownList 命名為 TBDropDownList,覆寫 AddAttributesToRender 來自行輸出 PostBack 的用戶端指令碼,我們會用一個變數記錄 AutoPostBack 屬性,並強制將 AutoPostBack 屬性值設為 False,這是為了不要 MyBase 產生 PostBack 的指令碼;然後再自行輸出 AutoPostBack 用戶端指令碼,其中 __doPostBack 的 eventArgument 參數 (對應到 __EVENTARGUMENT 這個 HiddenField) 傳入 this.selectedIndex。

        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

在頁面上放置一個 TBDropDownList 控制項,設定與上述案例相同的成員清單。

        <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>

執行程式查看 TBDropDownList 控制項的 HTML 原始碼,呼叫 __doPostBack 函式的參數已經被修改,eventArgument 參數會傳入該控制項的 selectedIndex。

<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>

[超過字數限制,下一篇接續本文]

]]>
jeff377 2008-10-30 21:15:23
[ASP.NET 控制項實作 Day28] 圖形驗證碼控制項(續) https://ithelp.ithome.com.tw/articles/10013365?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013365?sc=rss.iron 接續一上文
二、實作圖形驗證碼控制項
雖然我們可以使用 Image 控制項來呈現 ValidateCode.aspx 頁面產生的驗證碼圖...]]>
接續一上文
二、實作圖形驗證碼控制項
雖然我們可以使用 Image 控制項來呈現 ValidateCode.aspx 頁面產生的驗證碼圖形,可是這樣只處理一半的動作,因為沒有處理「使用者輸入的驗證碼」是否與「圖形驗證碼」相符,所以我們將實作一個圖形驗證碼控制項,來處理掉所有相關動作。
即然上面的示範使用 Image 控制項來呈現驗證碼,所以圖形驗證碼控制項就繼承 Image 命名為 TBValidateCode。

    < _
    Description("圖形驗證碼控制項"), _
    ToolboxData("<{0}:TBValidateCode runat=server></{0}:TBValidateCode>") _
    > _
    Public Class TBValidateCode
        Inherits System.Web.UI.WebControls.Image
    
    End

新增 ValidateCodeUrl 屬性,設定圖形驗證碼產生頁面的網址。

        ''' <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

覆寫 Render 方法,若未設定 ValidateCodeUrl 屬性,則預設為 ~/Page/ValidateCode.aspx 這個頁面。另外我們在圖形的 ondbclick 加上一段用戶端指令碼,其作用是讓用戶可以滑鼠二下來重新產生一個驗證碼圖形。

        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

另外新增一個 ValidateCode 方法,用來檢查輸入驗證碼是否正確。還記得我們在產生驗證碼圖形時,同時把該驗證碼的值寫入 Session("_ValidateCode") 中吧,所以這個方法只是把用戶輸入的值與 Seesion 中的值做比對。

        ''' <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

三、測試程式
在頁面放置一個 TBValidateCode 控制項,另外加一個文字框及按鈕,供使用者輸入驗證碼後按下「確定」鈕後到伺服端做輸入值比對的動作。

        <bee:TBValidateCode ID="TBValidateCode1" runat="server" />
        <bee:TBTextBox ID="txtCode" runat="server"></bee:TBTextBox>
        <bee:TBButton ID="TBButton1" runat="server" Text="確定" />

在「確定」鈕的 Click 事件中,我們使用 TBValidateCode 控制項的 ValidateCode 方法判斷驗證碼輸入的正確性。

    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

執行程式,頁面就會隨機產生一個驗證碼圖形。

輸入正確的值按「確定」鈕,就會顯示「驗證碼輸入正確」的訊息。因為我們在同一頁面測試的關係,你會發現 PostBack 後驗證碼圖形又會重新產生,一般正常的做法是驗證正確後就導向另一個頁面。

當我們輸入錯誤的值,就會顯示「驗證碼輸入錯誤!」的訊息。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/29/5818.aspx

]]>
jeff377 2008-10-29 20:34:22
[ASP.NET 控制項實作 Day28] 圖形驗證碼控制項 https://ithelp.ithome.com.tw/articles/10013361?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013361?sc=rss.iron 在網頁上常把圖形驗證碼應用在登入或貼文的頁面中,因為圖形驗證碼具有機器不易識別的特性,可以防止機器人程式惡意的存取網頁。在本文中將實作一個圖形驗證碼的伺服器控制項,透過簡單的屬性設定就可以輕易地...]]> 在網頁上常把圖形驗證碼應用在登入或貼文的頁面中,因為圖形驗證碼具有機器不易識別的特性,可以防止機器人程式惡意的存取網頁。在本文中將實作一個圖形驗證碼的伺服器控制項,透過簡單的屬性設定就可以輕易地在網頁上套用圖形驗證碼。
程式碼下載:ASP.NET Server Control - Day28.rar

一、產生圖形驗證碼
我們先準備一個產生圖形驗證碼的頁面 (ValidateCode.aspx),這個頁面主要是繪製驗證碼圖形,並將其寫入記憶體資料流,最後使用 Response.BinaryWrite 將圖形輸出傳遞到用戶端。當我們輸出此驗證碼圖形的同時,會使用 Session("_ValidateCode") 來記錄驗證碼的值,以便後續與使用者輸入驗證碼做比對之用。

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

我們將此頁面置於 ~/Page/ValidateCode.aspx,當要使用此頁面的圖形驗證碼,只需要在使用 Image 控制項,設定 ImageUrl 為此頁面即可。

<asp:Image ID="imgValidateCode" runat="server" ImageUrl="~/Page/ValidateCode.aspx" />

[超過字數限制,下一篇接續本文]

]]>
jeff377 2008-10-29 20:31:45
[ASP.NET 控制項實作 Day27] 控制項依 FormView CurrentMode 自行設定狀態(續2) https://ithelp.ithome.com.tw/articles/10013241?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013241?sc=rss.iron 接續上一文
接下來設定做為新增、編輯使用的 TBFormView 控制項,我們只使用 EditItemTemplate 來同時處理新增、刪除,所以 EditItemTemplate ...]]>
接續上一文
接下來設定做為新增、編輯使用的 TBFormView 控制項,我們只使用 EditItemTemplate 來同時處理新增、刪除,所以 EditItemTemplate 需要同時具有「新增」、「更新」、「取消」三個按鈕。其中 ProductID 為主索引欄位,所以我們使用 TBTextBox 來繫結 ProductID 欄位,設定 FormViewModeState.InsertMode="Enable" 使控制項在新增模式時為可編輯,設定 FormViewModeState.EditMode="Disable" 使控制項在修改模式是唯讀的。

        <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>

2. 測試新增模式
接下來執行程式,一開始為瀏覽模式,以 TBGridView 來呈現資料。

按下 Header 的「新增」鈕,就會隱藏 TBGridView,而切換到 TBFormView 的新增模式。其中繫結 ProductID 欄位的 TBTextBox 為可編輯模式,而下方的按鈕只會顯示「新增」及「取消」鈕。

在新增模式輸入完畢後,按下「新增」鈕,資料錄就會被寫入資料庫。

3. 測試修改模式
接下來測試修改模式,按下「編輯」鈕,就會隱藏 TBGridView,而切換到 TBFormView 的修改模式。其中繫結 ProductID 欄位的 TBTextBox 為唯讀模式,而下方的按鈕只會顯示「更新」及「取消」鈕。

在修改模式輸入完畢後,按下「更新」鈕,資料錄就會被寫入資料庫。

4. 頁面程式碼
示範了上述的操作後,接下來我們回頭看一下頁面的程式碼。你沒看錯,筆者也沒貼錯,真的是一行程式碼都沒有,因為所有相關動作都由控制項處理掉了。

Partial Class Day27
    Inherits System.Web.UI.Page

End Class

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx

]]>
jeff377 2008-10-28 13:57:23
[ASP.NET 控制項實作 Day27] 控制項依 FormView CurrentMode 自行設定狀態(續1) https://ithelp.ithome.com.tw/articles/10013239?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013239?sc=rss.iron 接續上一文
二、讓 TextBox 控制項可自行維護狀態
接下來擴展 TextBox 控制項,繼承 TextBox 命名為 TBText...]]>
接續上一文
二、讓 TextBox 控制項可自行維護狀態
接下來擴展 TextBox 控制項,繼承 TextBox 命名為 TBTextBox。新增 FormViewModeState 屬性 (TBFormViewModeState 型別),依 FormView Mode 來設定控制項狀。並覆寫 PreRender 方法,在此方法中呼叫 DoFormViewModeStatus 私有方法,依 FormView 的模式來處理控制項狀態。

    ''' <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

三、測試程式
1. 設定控制項相關屬性
我們使用 Northwnd 資料庫的 Products資料表為例,以 GridView+FormView 示範資料「新增/修改/刪除」的操作。在頁面拖曳 SqlDataSource 控制項後,在頁面上的使用 TBGridView 來顯示瀏覽資料。TBGridView 的 FormViewID 設為關連的 TBFormVIew 控制項;另外有使用到 TBCommandField,設定 ShowHeaderNewButton=True,讓命令列具有「新增」鈕。

        <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>

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx

]]>
jeff377 2008-10-28 13:53:32
[ASP.NET 控制項實作 Day27] 控制項依 FormView CurrentMode 自行設定狀態 https://ithelp.ithome.com.tw/articles/10013233?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013233?sc=rss.iron GridV...]]> GridView+FormView 示範資料 新增/修改/刪除(進階篇:伺服器控制項) 一文中,示範了擴展 GridView 及 FormView 控制項,讓 GridView 可以透過屬性與 FormView 做關連來處理資料的「新增/修改/刪除」的動作。因為在該案例中,只使用 FormView 的 EditTemplate 同時處理「新增」及「修改」的動作,所以還需要自行撰寫部分程式碼去判斷控制項在新增或修改的啟用狀態,例如編號欄位在新增時為啟用,修改時就不啟用。在該文最後也提及其實有辨法讓這個案例達到零程式碼的目標,那就是讓控制項 (如 TextBox) 自行判斷所在的 FormView 的 CurrentMode,自行決定本身是否要「啟用/不啟用」、「顯示/隱藏」等狀態。本文以 TextBox 為例,說明如何修改 TextBox 讓它可以達到上述的需求。
程式碼下載:ASP.NET Server Control - Day27.rar
Northwnd 資料庫下載:NORTHWND.rar

一、TBFormViewModeState 類別

我們先定義 EControlState (控制項狀態) 列舉,描述控制項在特定模式的狀態為何。

    ''' <summary>
    ''' 控制項狀態列舉。
    ''' </summary>
    Public Enum EControlState
        ''' <summary>
        ''' 不設定。
        ''' </summary>
        NotSet = 0
        ''' <summary>
        ''' 啟用。
        ''' </summary>
        Enable = 1
        ''' <summary>
        ''' 不啟用。
        ''' </summary>
        Disable = 2
        ''' <summary>
        ''' 隱藏。
        ''' </summary>
        Hide = 3
    End Enum

再來定義 TBFormViewModeState 類別,用來設定控制項在各種 FormView 模式 (瀏覽、新增、修改) 中的控制項狀態。

''' <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

定義為 TBFormViewModeState 型別的屬性是屬於複雜屬性,要套用 TypeConverter(GetType(ExpandableObjectConverter)),讓該屬性可在屬性視窗 (PropertyGrid) 擴展以便設定屬性值,如下圖所示。

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/28/5806.aspx

]]> jeff377 2008-10-28 13:45:43
[ASP.NET 控制項實作 Day26] 讓你的 GridView 與眾不同 https://ithelp.ithome.com.tw/articles/10013209?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013209?sc=rss.iron 在網路上可以找到相當多擴展 GridView 控制項功能的文章,在筆者的部落格中也有多篇提及擴展 GridView、DataControlField、BoundFIeld 功能的相關文章,在本文...]]> 在網路上可以找到相當多擴展 GridView 控制項功能的文章,在筆者的部落格中也有多篇提及擴展 GridView、DataControlField、BoundFIeld 功能的相關文章,在本文將這些關於擴展 GridView 控制項功能及欄位類別的相關文章做一整理簡介,若需要擴展 GridView 相關功能時可以做為參考。
1. 擴展 GridView 控制項 - 無資料時顯示標題列
摘要:當 GridView 繫結的 DataSource 資料筆數為 0 時,會依 EmptyDataTemplate 及 EmptyDataText 的設定來顯示無資料的狀態。若我們希望 GridView 在無資料時,可以顯示欄位標題,有一種作法是在 EmptyDataTemplate 中手動在設定一個標題列,不過這種作法很麻煩。本文擴展 GridView 控制項,直接透過屬性設定就可以在無資料顯示欄位標題。

2. 擴展 GridView 控制項 - 支援 Excel 及 Word 匯出
摘要:GridView 匯出 Excel 及 Word 文件是蠻常使用的需求,此篇文章將擴展 GridView 控制項提供匯出 Excel 及 Word 文件的方法。一般在 GridView 匯出的常見下列問題也會在此一併被解決。

3. GridView+FormView 示範資料 新增/修改/刪除(進階篇:伺服器控制項)
摘要:擴展 GridView 及 FormView 控制項,在 GridView 控制項中新增 FormViewID 屬性,關連至指定的 FormView 控制項 ID,就可以讓 GridView 結合 FormView 來做資料異動的動作。

4. 擴展 CommandField 類別 - 刪除提示訊息
摘要:新增 DeleteConfirmMessage 屬性,設定刪除提示確認訊息。

5. 擴展 CommandField 類別 - 刪除提示訊息含欄位值
摘要:設定刪除提示確認訊息中可包含指定 DataField 欄位值,明確提示要刪除的資料列。

6. 讓 CheckBoxField 繫結非布林值(0 或 1)欄位
摘要:CheckBoxField 若繫結的欄位值為 0 或 1 時 (非布林值) 會發生錯誤,本文擴展 CheckBoxField 類別,讓 CheckBoxField 有辨法繫結 0 或 1 的欄位值。

7. 擴展 CheckBoxField 類別 - 支援非布林值的雙向繫結
摘要:CheckBoxField 繫結的欄位值並無法直接使用 CBool 轉型為布林值,例如 "T/F"、"是/否" 之類的資料,若希望使用 CheckBoxField 來顯示就比較麻煩,一般的作法都是轉為 TemplateField,自行撰寫資料繫結的函式,而且只能支援單向繫結。在本文直接改寫 CheckBoxField 類別,讓 CheckBoxField 可以直接雙向繫結 "T/F" 或 "是/否" 之類的資料。

8. 擴展 CommandField 類別 - Header 加入新增鈕
摘要:支援在 CommandField 的 Header 的部分加入「新增」鈕,執行新增鈕會引發 RowCommand 事件。

9. GridView 自動編號欄位 - TBSerialNumberField
摘要:繼承 DataControlField 來撰寫自動編號欄位,若 GridView 需要自動編號欄位時只需加入欄位即可。

10. 自訂 GridVie 欄位類別 - 實作 TBDropDownField 欄位類別
摘要:支援在 GridView 中顯示下拉清單的欄位類別。

11. 自訂 GridView 欄位 - 日期欄位
摘要:支援在 GridView 中顯示日期下拉選單編輯的欄位類別。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/27/5793.aspx

]]>
jeff377 2008-10-27 22:37:14
[ASP.NET 控制項實作 Day25] 自訂 GridView 欄位 - 日期欄位(續) https://ithelp.ithome.com.tw/articles/10013091?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013091?sc=rss.iron 接續上一文
四、覆寫 ExtractValuesFromCell 方法 - 擷取儲存格的欄位值
當用戶端使用 GridView 編輯後執...]]>
接續上一文
四、覆寫 ExtractValuesFromCell 方法 - 擷取儲存格的欄位值
當用戶端使用 GridView 編輯後執行更新動作時,會呼叫 ExtractValuesFromCell 方法,來取得儲存格的欄位值,以便寫入資料來源。所以我們要覆寫 ExtractValuesFromCell 方法,將 Cell 或 TDateEdit 控制項的值取出填入具 IOrderedDictionary 介面的物件。

        ''' <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

五、測試程式
我們使用 Northwnd 資料庫的 Employees 資料表為例,在 GridView 加入自訂的 TBDateField 欄位繫結 BirthDate 欄位,另外加入另一個 BoundField 的唯讀欄位,也同樣繫結 BirthDate 欄位來做比較。

            <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" />

執行程式,在編輯資料列時,TBDateField 就會以 TDateEdit 控制項來進行編輯。

使用 TDateEdit 編輯欄位值後,按「更新」鈕,資料就會被寫回資料庫。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/26/5777.aspx

]]>
jeff377 2008-10-26 17:04:50
[ASP.NET 控制項實作 Day25] 自訂 GridView 欄位 - 日期欄位 https://ithelp.ithome.com.tw/articles/10013083?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013083?sc=rss.iron 前二篇文章介紹了自訂 GridView 使用的下拉清單欄位 (TBDropDownField),對如何繼承 BoundField 類別下來改寫自訂欄位應該有進一步的了解。在 GridView 中...]]> 前二篇文章介紹了自訂 GridView 使用的下拉清單欄位 (TBDropDownField),對如何繼承 BoundField 類別下來改寫自訂欄位應該有進一步的了解。在 GridView 中輸入日期也常蠻常見的需求,在本文將再實作一個 GridView 使用的日期欄位,在欄位儲存格使用 TBDateEdit 控制項來編輯資料。
程式碼下載:ASP.NET Server Control - Day25.rar
Northwnd 資料庫下載:NORTHWND.rar

一、繼承 TBBaseBoundField 實作 TDateField
GridView 的日期欄位需要繫結資料,一般的作法是由 BoundField 繼承下來改寫;不過我們之前已經有繼承 BoundField 製作一個 TBBaseBoundField 的自訂欄位基底類別 (詳見「 [ASP.NET 控制項實作 Day23] 自訂 GridVie 欄位類別 - 實作 TBDropDownField 欄位類別」 一文),所以我們要實作的日期欄位直接繼承 TBBaseBoundField 命名為 TDateField,並覆寫 CreateField 方法,傳回 TDateField 物件。

    ''' <summary>
    ''' 日期欄位。
    ''' </summary>
    Public Class TBDateField
        Inherits TBBaseBoundField

        Protected Overrides Function CreateField() As DataControlField
            Return New TBDateField()
        End Function
    End Class

自訂欄位類別主要是要覆寫 InitializeDataCell 方法做資料儲存格初始化、覆寫 OnDataBindField 方法將欄位值繫結至 BoundField 物件、覆寫 ExtractValuesFromCell 方法來擷取儲存格的欄位值,下面我們將針對這幾個需要覆寫的方法做一說明。

二、覆寫 InitializeDataCell 方法 - 資料儲存格初始化
首先覆寫 InitializeDataCell 方法處理資料儲存格初始化,當唯讀狀態時使用 Cell 來呈現資料;若為編輯狀態時,則在 Cell 中加入 TBDateEdit 控制項,並將 TBDateField 的屬性設定給 TBDateEdit 控制項的相關屬性。然後將儲存格 (DataControlFieldCell) 或日期控制項 (TDateEdit) 的 DataBinding 事件導向 OnDataBindField 事件處理方法。

        ''' <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

TDateEdit 控制項為筆者自行撰寫的日期控制項,TDateEdit 控制項的相關細節可以參考筆者部落格下面幾篇文章有進一步說明。
日期控制項實作教學(1) - 結合 JavaScript
日期控制項實作教學(2) - PostBack 與 事件
TBDateEdit 日期控制項 - 1.0.0.0 版 (Open Source)

三、覆寫 OnDataBindField 方法 - 將欄位值繫結至 BoundField 物件
當 GridView 執行 DataBind 時,每個儲存格的 DataBinding 事件都會被導向 OnDataBindField 方法,此方法中我們會由資料來源取得指定欄位值,處理此欄位值的格式化時,將欄位值呈現在 Cell 或 TDateEdit 控制項上。

        ''' <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

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx

]]>
jeff377 2008-10-26 16:56:36
[ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結(續) https://ithelp.ithome.com.tw/articles/10013047?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013047?sc=rss.iron 接續上一文
三、由關連的資料來源擷取資料
再來就是重點就是要處理 PerformSelecrt 私有方法,來取得 Items 屬性的成員...]]>
接續上一文
三、由關連的資料來源擷取資料
再來就是重點就是要處理 PerformSelecrt 私有方法,來取得 Items 屬性的成員清單內容。PerformSelect 方法的作用是去尋找頁面上的具 IDataSource 介面的控制項,並執行此資料來源的 Select 方法,以取得資料來設定 Items 的清單內容。
step1. 尋找資料來源控制項
PerformSelect 方法中有使用 FindControlEx 方法,它是自訂援尋控制項的多載方法,是取代 FindControl 進階方法。程式碼中使用 FindControlEx 去是頁面中以遞迴方式尋找具有 IDataSource 介面的控制項,且 ID 屬性值為 TBDropDownList.ID 的屬性值。
step2. 執行資料來源控制項的 Select 方法
當找到資料來源控制項後 (如 SqlDataSource、ObjectDataSource ...等等),執行其 DataSourceView.Select 方法,此方法需入一個 DataSourceViewSelectCallback 函式當作參數,當資料來源控制項取得資料後回呼我們指定的 OnDataSourceViewSelectCallback 函式中做後序處理。
step3. 將取得的資料來設定生 Items 的清單內容
在 OnDataSourceViewSelectCallback 函式中接到回傳的具 IEnumerable 介面的資料,有可能是 DataView、DataTable ...等型別的資料。利用 DataBinder.GetPropertyValue 來取得 DataTextField 及 DataValueField 設定的欄位值,逐一建立 ListItem 項目,並加入 Items 集合屬性中。

        ''' <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

四、測試程式
使用上篇中同一個案例做測試,同樣以 Northwnd 資料庫的 Products 資料表為例。在 GridView 加入自訂的 TBDropDownField 欄位繫結 CategoryID 欄位,並設定 DataSourceID、DataTextField、DataValueField 屬性;另外加入另一個 BoundField 的唯讀欄位,也同樣繫結 CategoryID 欄位來做比較。

                <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" />

執行程式,在 GridView 瀏覽的模式時,TBDropDownField 的儲存格已經會呈現 Items 對應成員的顯示文字。

執行資料列編輯時,也可以正常顯示下拉清單的內容。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx

]]>
jeff377 2008-10-25 18:11:28
[ASP.NET 控制項實作 Day24] TBDropDownField 的 Items 屬性的資料繫結 https://ithelp.ithome.com.tw/articles/10013041?sc=rss.iron https://ithelp.ithome.com.tw/articles/10013041?sc=rss.iron 上篇中我們實作了 GridView 的 TBDropDownField 欄位類別,不過眼尖的讀者不知有沒有發覺我們並處理 Items 屬性取得成員清單的動作,而是直接設定儲存格內含的 TBDro...]]> 上篇中我們實作了 GridView 的 TBDropDownField 欄位類別,不過眼尖的讀者不知有沒有發覺我們並處理 Items 屬性取得成員清單的動作,而是直接設定儲存格內含的 TBDropDownList 控制項相關屬性 (DataSourceID、DataTextField、DataValueField 屬性) 後,就由 TDropDownList 控制項自行處理 Items 屬性的資料繫結。當 GridView 的資料列是編輯狀態時,下拉清單會顯示出 Items 的文字內容;可是瀏覽狀態的資料列,卻是顯示欄位原始值,無法呈現 Items 的文字內容。本文將說明如何自行處理 TBDropDownField 的 Items 屬性的資料繫結動作,並使唯讀狀態的資料列也可以呈現 Items 的文字內容。

程式碼下載:ASP.NET Server Control - Day24.rar
Northwnd 資料庫下載:NORTHWND.rar

一、Items 屬性的問題
我們重新看一次原本 TBDropDownField 類別在處理 Items 屬性的資料繫結取得清單內容的程式碼,在覆寫 InitializeDataCell 方法中,當儲存格為編輯模式時,會呈現 TBDropDownList 控制項並設定取得 Items 清單內容的相關屬性,讓 TBDropDownList 自行去處理它的 Items 屬性的清單內容。

'由資料來源控制項取得清單項目
oDropDownList.DataSourceID = Me.DataSourceID
oDropDownList.DataTextField = Me.DataTextField
oDropDownList.DataValueField = Me.DataValueField

不知你有沒有發覺,我們無論在 InitializeDataCell 及 OnDataBindField 方法中,都沒有針對 TBDropDownList 控制項做任何 DataBind 動作,那它是怎麼從 DataSourceID 關聯的資料來源擷取資料呢?因為 GridView 在執行 DataBind 時,就會要求所有的子控制項做 DataBind,所以我們只要設定好 BDropDownList 控制項相關屬性後,當 TBDropDownList 自動被要求資料繫結時就會取得 Items 的清單內容。
當然使用 TBDropDownList 控制項去處理 Items 的資料繫結動作最簡單,可是這樣唯讀的儲存格只能顯示原始欄位值,無法呈現 Items 中對應成員的文字;除非無論唯讀或編輯狀態,都要建立 TBDropDownList 控制項去取得 Items 清單內容,而唯讀欄位使用 TBDropDownList.Items 去找到對應成員的顯示文字,不過這樣的作法會怪怪的,而且沒有執行效能率。所以比較好的辨法,就是由 TBDropDownField 類別自行處理 Items 的資料繫結,同時提供給唯讀狀態的
DataControlFieldCell 及編輯狀態的 TBDropDownList 使用。

二、由 TBDropDownField 類別處理 Items 屬性的資料繫結
我們要自行處理 Items 屬性來取得成員清單,在 InitializeDataCell 方法中無須處理 Items 屬性,只需產生儲存格需要的子控制項,未來在執行子控制項的 DataBinding 時的 OnDataBindField 方法中再來處理 Items 屬性。

        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

在 OnDataBindField 方法中,我們加上一段處理 Items 屬性的程式碼如下,會利用 PerformSelecrt 私有方法,由關聯的資料來源 (即 DataSrouceID 指定的資料來源控制項) 擷取資料並產生 Items 的成員清單,在後面會詳細講解 PerformSelecrt 方法處理擷取資料的細節。因為 TBDropDownField 每個資料儲存格都會執行 OnDataBindField 方法,但 Items 取得成員清單的動作只需做一次即可,所以會以 FIsPerformSelect 區域變數來判斷是否已取得 Items 的成員清單,若已取過就不重新取得,這樣比較有執行效能。

            If Not Me.DesignMode Then
                If Not FIsPerformSelect Then
                    '從關聯的資料來源擷取資料
                    PerformSelect()
                    FIsPerformSelect = True
                End If
            End If

當取得儲存儲的對應的欄位值時,依此欄位值由 Items 集合去取得對應的 ListItem 成員,並以此 ListItem.Text 的文字內容來做顯示。

            '由 Items 去取得對應成員的顯示內容
            oListItem = Me.Items.FindByValue(CCStr(sText))
            If oListItem IsNot Nothing Then
                sText = oListItem.Text
            End If

若是由 TBDropDownList 所引發的 OnDataBindField 方法時,使用 SetItems 私有方法將 TBDropDownField.Items 屬性複製給 TBDropDownList.Item 屬性。

                ODropDownList = DirectCast(oControl, TBDropDownList)
                SetItems(ODropDownList)

SetItems 私有方法的程式碼如下。

        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

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/25/5772.aspx

]]>
jeff377 2008-10-25 18:09:12
[ASP.NET 控制項實作 Day23] 自訂GridVie欄位-實作TBDropDownField欄位(續3) https://ithelp.ithome.com.tw/articles/10012977?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012977?sc=rss.iron 接續上一文
四、測試程式
辛苦寫好 TBDropDownField 欄位類別時,接下來就是驗收成果的時候。我們以 Northwnd 資料...]]>
接續上一文
四、測試程式
辛苦寫好 TBDropDownField 欄位類別時,接下來就是驗收成果的時候。我們以 Northwnd 資料庫的 Products 資料表為例,將 TBDropDownList .DataField 設為 CategoryID 欄位來做測試。首先我們測試沒有 DataSoruceID 的情況,在 GridView 加入自訂的 TBDropDownField 欄位繫結 CategoryID 欄位,另外加入另一個 BoundField 的唯讀欄位,也同樣繫結 CategoryID 欄位來做比較。

                <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" />

執行程式,在 GridView 在唯讀模式,TBDropDownFIeld 可以正確的繫結 CategoryID 欄位值。

編輯某筆資料列進入編輯狀態,就會顯示 TBDropDownList 控制項,清單成員為我們在 Items 設定的內容。

使用 TBDropDownList 來做編輯欄位值,按下更新鈕,這時會執行 TBDropDownField.ExtractValuesFromCell 方法,取得儲存格中的值;最後由資料來源控制項將欄位值寫回資料庫。

接下來測試設定 TBDropDownField.DataSourceID 的情況,把 DataSourcID 指向含 Categories 資料表內容的 SqlDataSoruce 控制項。

                <bee:TBDropDownField  HeaderText="CategoryID"  
                    SortExpression="CategoryID" DataField="CategoryID" 
                    DataTextField="CategoryName" DataValueField="CategoryID" DataSourceID="SqlDataSource2">
                </bee:TBDropDownField>

執行程式查看結果,可以發現 TBDropDownList 控制項的清單內容也可以正常顯示 SqlDataSoruce 控制項取得資料。

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx

]]>
jeff377 2008-10-24 00:32:30
[ASP.NET 控制項實作 Day23] 自訂GridVie欄位-實作TBDropDownField欄位(續2) https://ithelp.ithome.com.tw/articles/10012973?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012973?sc=rss.iron 接續上一文
step4. 處理資料繫結
當 GridView 控制項在執行資料繫結時,儲存格的控制項就會引發 DataBinding 事...]]>
接續上一文
step4. 處理資料繫結
當 GridView 控制項在執行資料繫結時,儲存格的控制項就會引發 DataBinding 事件,而這些事件會被導向 OnDataBindField 方法來統一處理儲存格中控制項的繫結動作。

       ''' <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

step5. 取得儲存格中的值
另外我們還需要覆寫 ExtractValuesFromCell 方法,取得儲存格中的值。這個方法是當 GridView 的編輯資料要準備寫入資料庫時,會經由 ExtractValuesFromCell 方法此來取得每個儲存格的值,並將這些欄位值加入 Dictionary 參數中,這個準備寫入的欄位值集合,可以在 DataSource 控制項的寫入資料庫的相關方法中取得使用。

        ''' <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

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx

]]>
jeff377 2008-10-24 00:31:32
[ASP.NET 控制項實作 Day23] 自訂GridVie欄位-實作TBDropDownField欄位(續1) https://ithelp.ithome.com.tw/articles/10012971?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012971?sc=rss.iron 接續上一文
step2. 加入 TBBaseBoundField 的屬性
TBBaseBoundField 類別會內含 DropDown...]]>
接續上一文
step2. 加入 TBBaseBoundField 的屬性
TBBaseBoundField 類別會內含 DropDownList 控制項,所以加入設定 DropDownList 控制項的對應屬性;我們在 TBBaseBoundField 類別加入了 Items 、DataSourceID、DataTextField、DataValueField 屬性。其中 Items 屬性的型別與 DropDownList.Items 屬性相同,都是 ListItemCollection 集合類別,且 Items 屬性會儲存於 ViewState 中。

        ''' <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

step3.建立儲存格內含的控制項
GridView 是以儲存格 (DataControlFieldCell) 為單位,我們要覆寫 InitializeDataCell 方法來建立儲存格中的控制項;當儲存格為可編輯狀態時,就建立 DropDownList 控制項並加入儲存格中,在此使用上篇文章提及的 TBDropDownList 控制項來取代,以解決清單成員不存在造成錯誤的問題。若未設定 DataSourceID 屬性時,則由 Items 屬性取得自訂的清單項目;若有設定 DataSourceID 屬性,則由資料來源控制項 (如 SqlDataSource、ObjectDataSource 控制項) 來取得清單項目。
當建立儲存格中的控制項後,需要以 AddHeadler 的方法,將此控制項的 DataBinding 事件導向 OnDataBindField 這個事件處理方法,我們要在 OnDataBindField 處理資料繫結的動作。

        ''' <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

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx

]]>
jeff377 2008-10-24 00:25:02
[ASP.NET 控制項實作 Day23] 自訂GridVie欄位-實作TBDropDownField欄位 https://ithelp.ithome.com.tw/articles/10012965?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012965?sc=rss.iron GridView 是 ASP.NET 中一個相當常用的控制項,在 GridView 可加入 BoundField、CheckBoxField、CommandField、TemplateField...]]> GridView 是 ASP.NET 中一個相當常用的控制項,在 GridView 可加入 BoundField、CheckBoxField、CommandField、TemplateField ... 等不同型別的欄位,可是偏偏沒有提供在 GridView 中可呈現 DropDownList 的欄位型別;遇到這類需求時,一般的作法都是使用 TemplateField 來處理。雖然 TemplateField 具有相當好的設計彈性。可是在當 GridView 需要動態產生欄位的需求時,TemplateField 就相當麻煩,要寫一堆程式碼自行去處理資料繫結的動作。相互比較起來,BoundField、CheckBoxField ...等這類事先定義類型的欄位,在 GridView 要動態產生這些欄位就相當方便。如果我們可以把一些常用的 GridView 的欄位,都做成類似 BoundField 一樣,只要設定欄位的屬性就好,這樣使用上就會方便許多,所以在本文將以實作 DropDownList 欄位為例,讓大家了解如何去自訂 GridView 的欄位類別。
程式碼下載:ASP.NET Server Control - Day23.rar
Northwnd 資料庫下載:NORTHWND.rar

一、選擇合適的父類別
一般自訂 GridView 的欄位類別時,大都是由 DataControlField 或 BoundField 繼承下來改寫。若是欄位不需繫結資料(如 CommandFIeld),可以由 DataControlFIeld 繼承下來,若是欄位需要做資料繫結時(如 CheckBoxFIld,可以直接由 BoundField 繼承下來改寫比較方便。
DataControlField 類別是所有類型欄位的基底類別,BoundField 類別也是由 DataControlField 類別繼承下來擴展了資料繫結部分的功能,所以我們要實作含 DropDownList 的欄位,也是由 BoundField 繼承下來改寫。

二、自訂欄位基底類別
在此我們不直接繼承 BoundFIeld,而是先撰寫一個繼承 BoundField 命名為 TBBaseBoundField 的基底類別,此類別提供一些通用的屬性及方法,使我們更方便去撰寫自訂的欄位類別。

    ''' <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

三、實作 TBDropDownField 欄位類別
step1. 繼承 TBBaseBoundField 類別
首先新增一個類別,繼承 TBBaseBoundField 命名為 TBDropDownFIeld 類別,覆寫 CreateField 方法,傳回 TBDropDownFIeld 物件。

    Public Class TBDropDownField
        Inherits TBBaseBoundField

        Protected Overrides Function CreateField() As System.Web.UI.WebControls.DataControlField
            Return New TBDropDownField()
        End Function
    End Class

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/24/5762.aspx

]]>
jeff377 2008-10-24 00:19:12
[ASP.NET 控制項實作 Day22] 讓 DropDownList 不再因項目清單不存在而造成錯誤(續) https://ithelp.ithome.com.tw/articles/10012915?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012915?sc=rss.iron 接續上篇文章內容
三、解決 TBDropDownList 設定 DataSourceID 造成資料無法繫結的問題
要解決上述 TBDro...]]>
接續上篇文章內容
三、解決 TBDropDownList 設定 DataSourceID 造成資料無法繫結的問題
要解決上述 TBDropDownList 設定 DataSourceID 問題,需在設定 SelectedValue 屬性時,若 Items.Count=0 先用一個 FCachedSelectedValue 變數將正確的值先暫存下來,然後覆寫 PerformDataBinding 方法,當 DorpDownList 取得 DataSoruceID 所對應的項目清單內容後,因為這時 Items 的內容才會完整取回,再去設定一次 SelectedValue 屬性就可以正確的繫結資料。

    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

重新執行程式,切換到編輯模式時,TBDropDownList 就可以正確的繫結欄位值了。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx

]]>
jeff377 2008-10-23 07:03:54
[ASP.NET 控制項實作 Day22] 讓 DropDownList 不再因項目清單不存在而造成錯誤 https://ithelp.ithome.com.tw/articles/10012909?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012909?sc=rss.iron DropDownList 控制項常常會因為項目清單中不存在繫結的欄位,而發生以下的錯誤訊息。因為繫結資料的不完整或異常就會造成這樣的異常錯誤,在設計上實在是相當困擾,而且最麻煩的是這個錯誤在頁面...]]> DropDownList 控制項常常會因為項目清單中不存在繫結的欄位,而發生以下的錯誤訊息。因為繫結資料的不完整或異常就會造成這樣的異常錯誤,在設計上實在是相當困擾,而且最麻煩的是這個錯誤在頁面的程式碼也無法使用 Try ... Catch 方式來略過錯誤。其實最簡單的方式就去直接去修改 DropDownList 控制項,讓 DropDownList 控制項繫結資料時,就算欄位值不存在清單項目中也不要釋出錯誤,本文就要說明如何繼承 DorpDownList 下來修改,來有效解決這個問題。

程式碼下載:ASP.NET Server Control - Day22.rar
Northwnd 資料庫下載:NORTHWND.rar

一、覆寫 SelectedValue 屬性解決資料繫結的問題
DropDownList 控制項繫結錯誤的原因,可以由上圖的錯誤訊息可以大概得知是寫入 SelectedValue 屬性時發生的錯誤;所以我們繼承 DorpDownList 下來命名為 TBDropDownList,並覆寫 SelectedValue 屬性來解決這個問題。解決方式是在寫入 SelectedValue 屬性時,先判斷準備寫入的值是否存在項目清單中,存在的話才寫入 SelectedValue 屬性,若不存在則直接設定 SelectedIndex 屬性為 -1。

    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

我們以 Northwnd 資料庫的 Products 資料表做為測試資料,事先定義 DropDownList 的 Items 內容,其中第一個加入 "未對應" 的項目,將 SelectedValue 屬性繫結至 CategoryID 欄位。

                <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>

當資料的 CategoryID 欄位值不存在於 DropDownList 的 Items 集合屬性中時,就會顯示第一個 "未對應" 的項目。

二、TBDropDownList 設定 DataSoruceID 產生的問題
上述的解決方法在筆者的「讓 DropDownList DataBind 不再發生錯誤」一文中已經有提及,不過有讀者發現另一個問題,就是當 DropDownList 設定 DataSourceID 時卻會發生資料無法正常繫結,以下就來解決這個問題。
我們設定 TBDropDownList 的 DataSoruceID 來取得項目清單的內容,將 DataSourceID 設定為另一個取得 Categories 資料表內容的 SqlDataSource 控制項。

                <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>

當執行程式時,FormView 原本在瀏覽模式時的 CategoryID 欄位值為 7 (CategoryName 應為 Product)。

當按下「編輯」時切換到 EditItemTemplate 時,改用 TBDropDownList 繫結 CategoryID 欄位值,可以這時欲無法繫結正確的值。

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx

]]>
jeff377 2008-10-23 06:59:56
[ASP.NET 控制項實作 Day21] 實作控制項智慧標籤(續) https://ithelp.ithome.com.tw/articles/10012897?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012897?sc=rss.iron 接續 [ASP.NET 控制項實作 Day21] 實作控制項智慧標籤 一文
step2. 在智慧標籤面板加入屬性項目
DesignerA...]]>
接續 [ASP.NET 控制項實作 Day21] 實作控制項智慧標籤 一文
step2. 在智慧標籤面板加入屬性項目
DesignerActionPropertyItem 類別是設定智慧標籤面上的屬性項目,DesignerActionPropertyItem 建構函式的第一個參數(memberName) 為屬性名稱,這個屬性指的是 TBDateEditActionList 類別中的屬性,所以要在 TBDateEditActionList 新增一個對應的屬性。
例如在智慧標籤中加入 AutoPostBack 屬性項目,則在 TBDateEditActionList 類別需有一個對應 AutoPostBack 屬性。

            oItems.Add(New DesignerActionPropertyItem("AutoPostBack", _
                "AutoPostBack", "Behavior", "是否引發 PostBack 動作。"))

TBDateEditActionList.AutoPostBack 屬性如下,其中 Me.Component 指的是目前的 TDateEdit 控制項,透過 GetPropertyValue 及 SetPropertyValue 方法來存取控制項的指定屬性。

        ''' <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

step3. 在智慧標籤面板加入方法項目
DesignerActionMethodItem 類別是設定智慧標籤面上的方法項目,DesignerActionPropertyItem 建構函式的第二個參數(memberName) 為方法名稱,這個方法指的是 TBDateEditActionList 類別中的方法,所以要在 TBDateEditActionList 新增一個對應的方法。
例如在智慧標籤中加入 About 方法項目,則在 TBDateEditActionList 類別需有一個對應 About 方法。

            oItems.Add(New DesignerActionMethodItem(Me, "About", _
                "關於 TDateEdit 控制項", "About", _
                "關於 TDateEdit 控制項。", True))

TBDateEditActionList 的 About 方法只是單純顯示一個訊息視窗,一般你可以在這方法加入任何想在設計階段處理的動作。例如自動產生 GridView 的欄位、在 FormView 加入控制項並自動排版,這些都可以在此實現的。

        Public Sub About()
            MsgBox("TDateEdit 是結合 The Coolest DHTML Calendar 日期選擇器實作的控制項")
        End Sub

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx

]]>
jeff377 2008-10-22 18:02:28
[ASP.NET 控制項實作 Day21] 實作控制項智慧標籤 https://ithelp.ithome.com.tw/articles/10012896?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012896?sc=rss.iron 控制項通常會把常用屬性或功能顯示在智慧標籤中,提供使用者更簡便的快速設定,例如下圖為 GridView 的智慧。若要製作控制項的智慧標籤,需實作控制項的 ActionList 加入智慧標籤中要顯...]]> 控制項通常會把常用屬性或功能顯示在智慧標籤中,提供使用者更簡便的快速設定,例如下圖為 GridView 的智慧。若要製作控制項的智慧標籤,需實作控制項的 ActionList 加入智慧標籤中要顯示的項目,在本文將以 TDateEdit 控制項為例,進一步說明控制項的智慧標籤的實作方式。

程式碼下載:ASP.NET Server Control - Day21.rar

一、TDateEdit 控制項介紹
TDateEdit 控制項是筆者之前在部落格中實作的一個日期控制項,如下圖所示。它是結合 JavaScript 的 The Coolest DHTML Calendar 日期選擇器實作的控制項,我已將 TDateEdit 控制項的相關程式碼含入 Bee.Web.dll 組件中。TDateEdit 控制項的相關細節可以參考筆者部落格下面幾篇文章有進一步說明,本文將以 TDateEdit 控制項為例,只針對實作智慧標籤的部分做進一步說明。
日期控制項實作教學(1) - 結合 JavaScript
日期控制項實作教學(2) - PostBack 與 事件
TBDateEdit 日期控制項 - 1.0.0.0 版 (Open Source)

二、控制項加入智慧標籤
控制項要加入智慧標籤要實作控制項的 Designer,我們繼承 ControlDesigner 命名為 TBDateEditDesigner,然後覆寫 ActionLists 屬性,此屬性即是傳回智慧標籤中所包含的項目清單集合。在 ActionLists 屬性中一般會先加入父類別的 ActionLists 屬性,再加入自訂的 ActionList 類別,這樣才可以保留原父類別中智慧標籤的項目清單。

    ''' <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

我們自訂的 ActionList 為 TBDateEditActionList 類別,它在智慧標籤呈現的項目清單如下圖所示,接下去我們會說明 TBDateEditActionList 類別的內容。

三、自訂智慧標籤面板的項目清單集合
DesignerActionList 類別定義用於建立智慧標籤面板的項目清單的基底類別,所以我們首先繼承 DesignerActionList 命名為 TBDateEditActionList。

    ''' <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

接下來要覆寫 GetSortedActionItems 方法,它會回傳 DesignerActionItemCollection 集合型別,此集合中會傳回要顯示在智慧標籤面板的項目清單集合,所以我們要在 DesignerActionItemCollection 集合中加入我們要呈現的項目清單內容。

        ''' <summary>
        ''' 傳回要顯示在智慧標籤面板的項目清單集合。
        ''' </summary>
        Public Overrides Function GetSortedActionItems() As System.ComponentModel.Design.DesignerActionItemCollection
            Dim oItems As New DesignerActionItemCollection()

            '在此加入智慧標籤面板的項目清單	           

            Return oItems
        End Function

step1. 在智慧標籤面板加入靜態標題項目
首先介紹 DesignerActionHeaderItem 類別,它是設定靜態標題項目,例如我們在 TDateEdit 的智慧標籤中加入「行為」、「外觀」二個標題項目,其中 DesignerActionHeaderItem 建構函式的 category 參數是群組名稱,我們可以將相關的項目歸類到同一個群組。

Dim oItems As New DesignerActionItemCollection()

oItems.Add(New DesignerActionHeaderItem("行為", "Behavior"))
oItems.Add(New DesignerActionHeaderItem("外觀", "Appearance"))

[超過字數限制,下一篇接續本文]

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/22/5749.aspx

]]>
jeff377 2008-10-22 18:01:29
[ASP.NET 控制項實作 Day20] 偵錯設計階段的程式碼 https://ithelp.ithome.com.tw/articles/10012807?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012807?sc=rss.iron 上篇我們介紹了自訂 Designer 來輸出控制項設計階段的 HTML 碼,可是若你去對針 Designer 的程式碼下中斷點,你會發覺根本無法偵錯。因為程式在執行階段時期,根本不會執行 Des...]]> 上篇我們介紹了自訂 Designer 來輸出控制項設計階段的 HTML 碼,可是若你去對針 Designer 的程式碼下中斷點,你會發覺根本無法偵錯。因為程式在執行階段時期,根本不會執行 Designer 相關類別,所以你在 Designer 類別中下的中斷點完全無效;當然不可能這樣寫程式碼而用感覺去偵錯,本文將告訴你如何去偵錯設計階段的程式碼。
一、設計階段程式碼的錯誤
如果撰寫 Designer、Editor、ActionList 等設計階段的程式碼,當這些設計階段的程式碼發生錯誤,可能會發生設計頁面中控制項的錯誤情形,如下圖所示。因為控制項專案本身非啟動專案,在測試網站的設計頁面若控制項發生異常時會直接釋出錯誤,無法偵錯設計階段的程式碼;若真得要偵錯誤設計階段的問題,就要使用另一個 VS2008 來偵錯。

二、設定起始外部程式
要偵錯控制項設計階段的程式碼,要先將控制項專案(Bee.Web)設定為啟時專案。然後設定控制項專案的「屬性」,在「偵錯」頁籤中的起始動作選擇「起始外部程式」,選擇 VS2008 的執行檔位置,預設為 C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe。

三、開始偵錯設計階段程式碼
step1. 控制項專案開始偵錯
在設計階要偵錯的程式碼下中斷點,在控制項專案按下 F5 開始偵錯,這時會啟動另一個新的 VS2008 執行檔。

step2. 在新的 VS2008 的工具箱加入控制項
在新的 VS2008 中新增一個測試網站,在工具箱按右鍵執行「選擇項目」開啟「選擇工具箱項目」視窗,然後按「瀏覽」鈕按選擇控制項組件(Bee.Web.dll),將要偵錯的控制項加入工具箱中。


step3. 將控制項拖曳至頁面做設計動作
在新的 VS2008 中,將控制項拖曳至頁面,就會開始執行設計階段的程式碼,特定的設計動作就會執行到相對的設計階段程式碼,當執行到之前下的中斷點時就可以開始偵錯了。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/21/5741.aspx

]]>
jeff377 2008-10-21 00:28:45
[ASP.NET 控制項實作 Day19] 控制項設計階段的外觀 https://ithelp.ithome.com.tw/articles/10012682?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012682?sc=rss.iron 有一些控制項在執行階段是不會呈現,也就是說控制項本身在執行階段不會 Render 出 HTML 碼,例如 SqlDataSoruce、ScriptManager 這類控制項;那它們在設計階段的頁...]]> 有一些控制項在執行階段是不會呈現,也就是說控制項本身在執行階段不會 Render 出 HTML 碼,例如 SqlDataSoruce、ScriptManager 這類控制項;那它們在設計階段的頁面是如何呈現出來呢?本文將針對控制項設計階段的外觀做進一步的說明。
程式碼下載:ASP.NET Server Control - Day19.rar
一、控制項設計階段的 HTML 碼
Web 伺服器控制項的設計模式行為都是透過 ControlDesigner 來處理,連設計階段時控制項的外觀也是如此;控制項在設計階段與執行執行時呈現的外觀不一定相同,當然大部分會儘量一致,使其能所見即所得。
控制項在設計階段的 HTML 碼是透 ControlDesigner.GetDesignTimeHtml 方法來處理,在 ControlDesigner.GetDesignTimeHtml 預設會執行控制項的 RenderControl 方法,所以大部分的情況下設計階段與執行階段輸出的 HTML 碼會相同。當控制項的 Visible=False 時,執行階段是完全不會輸出 HTML 碼,可是在設計階段時會特別將控制項設定 Visible=True,使控制項能完整呈現。

ControlDesigner.GetDesignTimeHtml 方法

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

二、自訂控制項的 Designer
以 TBToolbar 為例,若我們在 RenderContents 方法未針對 Items.Count=0 做輸出 HTML 的處理,會發現未設定 Items 屬性時,在設計頁面上完全看不到 TBToolbar 控制項;像這種控制項設計階段的 HTML 碼,就可以自訂控制項的 Designer 來處理。

繼承 ControlDesigner 命名為 TBToolbarDesigner,這個類別是用來擴充 TBToolbar 控制項的設計模式行為。我們可以覆寫 GetDesignTimeHtml 方法,處理設計階段表示控制項的 HTML 標記,此方法回傳的 HTML 原始碼就是控制項呈現在設計頁面的外觀。所以我們可以在 TBToolbar.Items.Count=0 時,輸出一段提示的 HTML 碼,這樣當 TBToolbar 未設定 Items 屬性時一樣可以在設計頁面上呈現控制項。

    ''' <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

在 TBToolbar 控制項套用 DesignerAttribute 設定自訂的 TBToolbarDesigner 類別。

    <Designer(GetType(TBToolbarDesigner))> _
    Public Class TBToolbar
        Inherits WebControl

    End Class

重建控制項組件,切換到設計頁面上的看 TBToolbar 控制項未設定 Items 屬性時的外觀,就是我們在 TBToolbarDesigner.GetDesignTimeHtml 方法回傳的 HTML 碼。

如果你覺得上述設計階段的控制項有點太陽春,我們也可以輸出類似 SqlDataSource 控制項的外觀,將未設定 Items 屬性時輸出 HTML 改呼叫 CreatePlaceHolderDesignTimeHtml 方法。

            If oControl.Items.Count = 0 Then
                sHTML = MyBase.CreatePlaceHolderDesignTimeHtml("請設定 Items 屬性")
            Else
                sHTML = MyBase.GetDesignTimeHtml()
            End If

來看一下這樣修改後的結果,是不是比較專業一點了呢。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/20/5726.aspx

]]>
jeff377 2008-10-20 02:25:29
[ASP.NET 控制項實作 Day18] 修改集合屬性編輯器 https://ithelp.ithome.com.tw/articles/10012636?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012636?sc=rss.iron 上篇我們實作了「集合屬性包含不同型別的成員」,不過若有去使用屬性視窗編輯 TBToolbar 的 Items 屬性,你會發覺這個集合屬性編輯器無法加入我們定義不同型別的成員,只能加入最原始的集合...]]> 上篇我們實作了「集合屬性包含不同型別的成員」,不過若有去使用屬性視窗編輯 TBToolbar 的 Items 屬性,你會發覺這個集合屬性編輯器無法加入我們定義不同型別的成員,只能加入最原始的集合成員。是不是只能在 aspx 程式碼中手動去輸入呢?當然不需要這樣人工作業,只要改掉集合屬性編輯器就可以達到我們的需求,本文將介紹修改集合屬性編輯器的相關作法。
程式碼下載:ASP.NET Server Control - Day18.rar

一、自訂集合屬性編輯器
我們先看一下 TBToolbar.Items 屬性套用的 EditorAttribute,它是使用 CollectionEditor 類別來當作屬性編輯器,所以我們就是要繼承 CollectionEditor 類別下來修改成自訂的屬性編輯器。

< _
Editor(GetType(CollectionEditor), GetType(UITypeEditor)) _
> _
Public ReadOnly Property Items() As TBToolbarItemCollection

新增一個繼承 CollectionEditor 的 TBToolbarItemCollectionEditor 類別,並加入建構函式。此類別屬於 Bee.WebControls.Design 命名空間,通常我們會把設計階段使用的類別歸類到特別的命名空間便於管理及使用。

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

我們可以先修改 Items 屬性的 EditorAttribute,看看我們自訂的 TBToolbarItemCollectionEditor 是否能正常運作。不過這個屬性編輯器跟原本的沒什麼差異,因為我們只是單純繼承下來沒做任何異動,接下去我們就要開始來修改這個屬性編輯器。

< _
Editor(GetType(TBToolbarItemCollectionEditor), GetType(UITypeEditor)) _
> _
Public ReadOnly Property Items() As TBToolbarItemCollection

二、加入不同型別的集合成員
再來我們就要著手修改集合屬性編輯器,讓它可以加入不同型別的集合成員。覆寫 CollectionEditor 的 CanSelectMultipleInstances 方法傳回 True,這個方法是設定 CollectionEditor 是否允許加入多種不同型別的集合成員。

        Protected Overrides Function CanSelectMultipleInstances() As Boolean
            Return True
        End Function

再來覆寫 CreateNewItemTypes 方法,這個方法是取得這個集合編輯器可包含的資料型別,將集合可包含的資料型別以陣列傳回。

        ''' <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

重建控制項組件,使用 Items 的集合屬性編輯器,就可以發現「加入」鈕的下拉清單就會出現我們所定義的三種型別的集合成員,如此可以加入不同型別的成員了。

三、設定清單項目的顯示文字
在成員清單項目中預設會顯示成員含命名空間的型別,若我們要修改成比較有識別的顯示文字,例如 TBToolbarButton(Key=Add) 可以顯示「按鈕-Add」,這時可以覆寫 GetDisplayText 方法來設定清單項目的顯示文字。

        ''' <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

四、集合編輯器的屬性視窗的屬性描述
一般屬性視窗下面都會有屬性描述,可以集合屬性編輯器中的屬性視窗下面竟沒有屬性描述。若我們要讓它的屬性描述可以顯示,可以覆寫 CreateCollectionForm 方法,取得集合屬性編輯表單,再去設定表單上的 PropertyGrid.HelpVisible
= True 即可。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/19/5721.aspx

]]>
jeff377 2008-10-19 00:13:21
[ASP.NET 控制項實作 Day17] 集合屬性包含不同型別的成員 https://ithelp.ithome.com.tw/articles/10012600?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012600?sc=rss.iron 我們知道在 GridView 的 Columns 集合屬性中,可以包含不同型別的欄位,如 BoundFIeld、CheckBoxField、HyperLinkField ...等不同型別的欄位。...]]> 我們知道在 GridView 的 Columns 集合屬性中,可以包含不同型別的欄位,如 BoundFIeld、CheckBoxField、HyperLinkField ...等不同型別的欄位。如果我們希望工具列中不只包含按鈕,可以包含其他不同類型的子控制項,那該怎麼做呢?本文就以上篇中的 TBToolbar 控制項為案例,讓 Items 集合屬性可以加入 Button、TextBox、Label ...等不同的子控制項。
程式碼下載:ASP.NET Server Control - Day17.rar
一、不同型別的集合成員
我們的需求是讓工具列可以加入 Button、TextBox、Label 三種子控制項,所以繼承原來的 TBToolbarItem (只保留 Enabled 屬性),新增了 TBToolbarButton、TBToolbarTextbox、TBToolbarLabel 三個類別。

這些新增的成員類別都是繼承至 TBToolbarItem,所以在 aspx 程式碼中,手動輸入 Items 的成員時,就會列出這幾種定義的成員型別。

二、建立不同型別集合成員的子控制項
因為 Items 屬性的成員具不同型別,所以我們要改寫 RenderContents 方法,判斷成員型別來建立對應類型的子控制項。若為 TBToolbarButton 型別建立 Button 控制項、若為 TBToolbarTextbox 型別則建立 TextBox 控制項、若為 TBToolbarLabel 型別則建立 Label 控制項。其中 TBToolbarButton 建立的控制項為 TBButton,這個控制項是我們在「 [ASP.NET 控制項實作 Day3] 擴展現有伺服器控制項功能」一文中實作的具詢問訊息的按鈕控制項。

        ''' <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

我們手動在 aspx 程式碼中輸入不同型別的成員,TBToolbar 控制項就會呈現對應的子控制項。

三、執行程式
執行程式,就可以在瀏覽器看到呈現的工具列,當按下「刪除」時也會出現我們定義的詢問訊息。

輸出的 HTML 碼如下

<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>

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/18/5718.aspx

]]>
jeff377 2008-10-18 00:05:57
[ASP.NET 控制項實作 Day16] 繼承 WebControl 實作 Toolbar 控制項 https://ithelp.ithome.com.tw/articles/10012507?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012507?sc=rss.iron 前面我們討論過「繼承 CompositeControl 實作 Toolbar 控制項」,本文將繼承 WebControl 來實作同樣功能的 Toolbar 控制項,用不同的方式來實作同一個控制項...]]> 前面我們討論過「繼承 CompositeControl 實作 Toolbar 控制項」,本文將繼承 WebControl 來實作同樣功能的 Toolbar 控制項,用不同的方式來實作同一個控制項,進而比較二者之間的差異。
程式碼下載:ASP.NET Server Control - Day16.rar

一、繼承 WebControl 實作 TBToolbar 控制項
step1. 新增繼承 WebControl 的 TBToolbar 控制項
新增繼承 WebControl 的 TBToolbar 控制項,你也可以直接原修改原 TBToolbar 控制項,繼承對象由 CompositeControl 更改為 WebControl即可。跟之前一樣在 TBToolbar 控制項加入 Items 屬性及 Click 事件。
另外 TBToolbar 控制項需實作 INamingContainer 界面,此界面很特殊沒有任何屬性或方法,INamingContainer 界面的作用是子控制項的 ClientID 會在前面加上父控制項的 ClickID,使每個子控制項有唯一的 ClientID。

step2. 建立工具列按鈕集合
覆寫 RenderContents 方法,將原本 TBToolbar (複合控制項) 的 CreateChildControls 方法中建立工具列按鈕程式碼,搬移至 RenderContents 方法即可。

        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

上述的直接搬移過來的程式碼還有個問題,就是原來的使用 AddHandler 來處理按鈕事件的方式變成沒有作用了?因為現在不是複合式控制項,當前端的按鈕 PostBack 傳回伺服端時,TBToolbar 不會事先建立子控制槓,所以機制會找不到原來產生的按鈕,也就無法使用 AddHandler 來處理事件了。

AddHandler oButton.Click, AddressOf ButtonClickEventHandler

step3. 處理 Click 事件
因為不能使用 AddHandler 來處理按鈕事件,所以我們就自行使用 Page.ClientScript.GetPostBackEventReference 方法來產生 PostBack 動作的用戶端指令碼,按鈕的 OnClientClick 去執行 PostBack 的動作。

            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

TBToolar 控制項輸出的 HTML 碼如下

<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>

要自行處理 PostBack 的事件,需實作 IPostBackEventHandler 介面,在 RaisePostBackEvent 方法來引發 TBToolbar 的 Click 事件。

    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

二、測試程式
在測試頁面上放置 TBToolbar 控制項,在 Click 事件撰寫測試程式碼。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/17/5706.aspx

]]>
jeff377 2008-10-17 00:05:40
[ASP.NET 控制項實作 Day15] 複合控制項隱藏的問題 https://ithelp.ithome.com.tw/articles/10012425?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012425?sc=rss.iron 上一篇我們使用複合控制項(繼承 CompositeControl)的方式來實作 TBToolbar 控制項,本文將針對複合控制項做一些測試,說明在使用複合控制項要注意的一些問題。
程...]]>
上一篇我們使用複合控制項(繼承 CompositeControl)的方式來實作 TBToolbar 控制項,本文將針對複合控制項做一些測試,說明在使用複合控制項要注意的一些問題。
程式碼下載:ASP.NET Server Control - Day15.rar
一、複合控制項建立子控制項的時機
還記得我們之前介紹複合控制項時有談到 CompositeControl 類別會確保我們存取子控制項時,它的子控制項一定會事先建立;也就是當我們使用 Controls 屬性去存取子控制項時,一定會執行 CreateChildControls 方法,以確保子控制項事先被建立。我們看一下 CompositeControl 類別的 Controls 屬性的寫法就可以了解其中的原由,在存取 CompositeControl.Controls 屬性時,它會先執行 Control.EnsureChildControls 方法;而 EnsureChildControls 方法會去判斷子控制項是否已建立,若未建立會去執行 CreateChildControls 方法,這也就是為什麼 CompositeControl 有辨法確保子控制項事先被建立的原因。

CompositeControl.Controls 屬性如下

Public Overrides ReadOnly Property Controls As ControlCollection
    Get
        Me.EnsureChildControls
        Return MyBase.Controls
    End Get
End Property

Control.EnsureChildControls 方法如下

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

二、複合控制項隱藏的問題
我們以上篇的 TBToolbar 控制項為例,撰寫一些測試案例來說明複合控制項的問題。在撰寫測試案例之前,我們先修改一下 TBToolbar 控制項,覆寫 LoadViewState 及 SaveViewState 方法,將 Items 屬性儲存於 ViewState 中以維持狀態。

在測試頁面上放置「測試一」、「測試二」、「PostBack」三個按鈕,這三個按鈕的動作如下。
「測試一」按鈕:在工具列直接新增一個按鈕。
「測試二」按鈕:先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。
「PostBack」按鈕:單純執行 PostBack,不撰寫程式碼。

三個按鈕的程式碼如下所示。

    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

案例一:執行「測試一」按鈕,在工具列直接新增一個按鈕。
當按下「測試一」按鈕時,工具列可以正常加入我們新增的按鈕。

案例二:執行「測試二」按鈕,先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。
重新執行程式,當按下「測試二」按鈕時,你會發現奇怪的現象,工具列竟然沒有加入我們新增的按鈕?

此時再按下「PostBack」按鈕,工具列才會出現我們剛剛加入的按鈕。

為什麼會發生這種怪現象呢?其實原因很簡單,因為 FindControl 時會去存取 Controls 屬性,而這時子控制項已經被建立了;而之前再用 Items 屬性加入新按鈕,它已經不會在重建子控制項,導致第一時間沒有加入新按鈕。不過 Items 屬性會被存在 ViewState 中,所以當執行「PostBack」按鈕時,就會出現我們剛剛新增的按鈕。

三、解決方式
要解決上述「測試二」的問題,只要覆寫 TBToolbar 控制項的 Render 方法,在 Render 前執行 RecreateChildControls 方法,強制重建子控制項。

        ''' <summary>
        ''' 覆寫 Render 方法。
        ''' </summary>
        Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
            Me.RecreateChildControls()
            MyBase.Render(writer)
        End Sub

再一次執行「測試二」的動作,就會發現執行結果就會正常了。

四、結語
在複合控制項的 Render 前執行 RecreateChildControls 方法可以強制重建子控制項,可是這樣又會引發另一個問題,那就是當直接存取子控制項去修改子控制項的屬性後,一旦在 Render 又重建子控制項,那之前設定子控制項狀態又被全部重建了,所以需特別注意有這樣的情形。另外複合控制項有可能重覆執行建立子控制的動作,在執行效能上也比較不佳。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/16/5695.aspx

]]>
jeff377 2008-10-16 00:14:12
[ASP.NET 控制項實作 Day14] 繼承 CompositeControl 實作 Toolbar 控制項 https://ithelp.ithome.com.tw/articles/10012339?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012339?sc=rss.iron 之前我們簡單介紹過繼承 CompositeControl 來實作複合控制項,在本文我們將以 Toolbar 控制項為例,以複合控制項的作法(繼承 CompositeControl )來實作 To...]]> 之前我們簡單介紹過繼承 CompositeControl 來實作複合控制項,在本文我們將以 Toolbar 控制項為例,以複合控制項的作法(繼承 CompositeControl )來實作 Toolbar 控制項,此工具列控制項包含 Items 屬性來描述工具列項目集合,依 Items 屬性的設定來建立工具列按鈕,另外包含 Click 事件可以得知使用按了那個按鈕。
程式碼下載:ASP.NET Server Control - Day14.rar
一、工具列項目集合類別
工具列包含多個按鈕,新增 TBToolbarItem 類別來描述工具列項目,TBToolbarItem 類別包含 Key、Text、Enabled 三個屬性;而 TBToolbarItemCollection 為 TBToolbarItem 的集合類別來描述工具列按鈕集合。

二、實作 TBToolbar 控制項
step1. 新增繼承 CompositeControl 的 TBToolbar 控制項

    < _
    Description("工具列控制項。"), _
    ParseChildren(True, "Items"), _
    ToolboxData("<{0}:TBToolbar runat=server ></{0}:TBToolbar>") _
    > _
    Public Class TBToolbar
        Inherits CompositeControl
    End Class 

step2. 新增 Items 屬性,描述工具列項目集合

        ''' <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

step3. 新增 Click 事件
TBToolbar 類別新增 Click 事件,當按下按鈕時會引發 Click 事件,由 Click 的事件引數 e.Key 可以得知使用者按了那個按鈕。

        ''' <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

step4. 建立工具列按鈕集合
覆寫 CreateChildControls 方法,依 Items 屬性的設定,來建立工具列中的按鈕集合。每個按鈕的 Click 事件都導向 ButtonClickEventHandler 方法,來處理所有按鈕的 Click 動作,並引發 TBToolbar 的 Click 事件。

        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

三、測試程式
在頁面拖曳 TBToolbar 控制項,並設定 Items 屬性,如入新增、修改、刪除三個按鈕。

在 TBToolbar 控制項的 Click 事件加入測試程式碼,輸出引發 Click 事件的 e.Key。

    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

執行程式,當按了工具列上的按鈕時,就會引發 Click 事件,並輸出該按鈕對應的 Key。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/15/5687.aspx

]]>
jeff377 2008-10-15 00:13:50
[ASP.NET 控制項實作 Day13] Flash 控制項 https://ithelp.ithome.com.tw/articles/10012267?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012267?sc=rss.iron Flash 也是網頁常用的 ActiveX 插件,在本文中將繼承 TBActiveX 下來撰寫 TBFlash 控制項,用來輸出網頁套用 Flash 的相關 HTML 碼。
程式碼下...]]>
Flash 也是網頁常用的 ActiveX 插件,在本文中將繼承 TBActiveX 下來撰寫 TBFlash 控制項,用來輸出網頁套用 Flash 的相關 HTML 碼。
程式碼下載:ASP.NET Server Control - Day13.rar

一、網頁 Flash 的原始 HTML 碼
我們先觀查在網頁中套用 Flash 插件的原始 HTML 碼,以點部落首頁抬頭的 Flash 原始碼為例如下,其中 <object> tag 的 codebase attribute 是指 Flash 插件的下載位置及版本。

<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>

在 <object> tag 中必要的 attribute 為 classid、codebase、movie、width、height,而 <embed> tag 的必要 attribute 為 src、pluginspage、width、height,其他選擇性的 attribute 可參閱以下網頁。

Flash OBJECT and EMBED tag attributes
http://kb.adobe.com/selfservice/viewContent.do?externalId=tn\_12701

二、實作 TFlash 控制項
了解 Flash 的原始 HTML 碼後,我們就可以開始著手撰寫 TBFlash 控制項,想辨法來輸出所需要的 HTML 碼。

step1. 新增 TBFlash 控制項繼承至 TBActiveX
我們先在 TBActiveX 控制項新增一個 CodeBase 屬性,用來設定 ActiveX 插入的下載位置及版本,然後新增 TBFlash 控制項繼承至 TBActiveX,並在建構函式中設定 MyBase.ClassId 及 MyBase.CodeBase 屬性。

    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 

step2. 加入相關屬性
在 TBFlash 加入 MovieUrl 及 Quality 屬性,MovieUrl 為 Flash 檔案來源,Quality 為影音品質。

step3. 輸出 Flash 相關參數
覆寫 CreateChildControls 方法,輸出 MovieUrl 及 Quality 屬性對應的參數,以及在 Params 集合屬性設定的參數。

        ''' <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

三、測試程式
在頁面拖曳 TBFlash 控制項,設定 MovieUrl 及 Quality 屬性,若有需要加入其他參數,可自行設定 Params 集合屬性。執行程式就可以在頁面上看到呈現出來的 Flash。

        <bee:TBFlash ID="TBFlash1" runat="server" Height="90px" 
            MovieUrl="http://files.dotblogs.com.tw/dotjum/ad/debug.swf" Quality="High" 
            Width="728px">
        </bee:TBFlash>

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/14/5674.aspx

]]>
jeff377 2008-10-14 00:16:30
[ASP.NET 控制項實作 Day12] 繼承 TBActiveX 重新改寫 TBMediaPlayer 控制項 https://ithelp.ithome.com.tw/articles/10012196?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012196?sc=rss.iron 上篇介紹的 TBActiveX 控制項,它可以支援網頁 Media Player 的設定,這跟前面提及的 TBMediaPlayer 功能相同。TBActiveX 具有網頁設定 ActiveX ...]]> 上篇介紹的 TBActiveX 控制項,它可以支援網頁 Media Player 的設定,這跟前面提及的 TBMediaPlayer 功能相同。TBActiveX 具有網頁設定 ActiveX 通用屬性,所以 TBMediaPlayer 基本上是可以由 TBActiveX 繼承下來,再加入 Media Player 特有的屬性即可。本文將原來的 TBMediaPlayer 控制項,繼承的父類別由 WebControl 改為 TBActiveX 類別,重新改寫 TBMediaPlayer 控制項。
程式碼下載:ASP.NET Server Control - Day12.rar

一、改寫 TBMediaPlayer 控制項
TBMediaPlayer 控制項原本是繼承 WebControl,現改繼承對象為 TBActiveX,來重新改寫 TBMediaPlayer 控制項。

step1. TBMediaPlayer 繼承至 TBActiveX
新增 TBMediaPlayer 控制項,繼承至 TBActiveX,並在建構函式設定 Media Player ActiveX 的 ClassId。

    Public Class TBMediaPlayer
        Inherits TBActiveX

        ''' <summary>
        ''' 建構函式。
        ''' </summary>
        Sub New()
            MyBase.ClassId = "6BF52A52-394A-11D3-B153-00C04F79FAA6"
        End Sub
    End Class

step2. 加入相關屬性
跟原來的 TBMediaPlayer 控制項一樣,加入 Url、AutoStart、UIMode 三個屬性,可視情形加入需要設定的屬性。

step3. 加入 Media Player 參數
覆寫 CreateChildControls 方法,動態依屬性設定在 Params 集合屬性加入參數。雖然 TBMediaPlayer 控制項目前只有 Url、AutoStart、UIMode 三個屬性,但是父類別 TBActiveX 具有 Params 集合屬性,所以開發人員可以視需求加入其他未定義的參數。

        ''' <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

二、執行程式
在頁面拖曳 TBMediaPlayer 控制項,設定 Url、AutoStart、UIMode 屬性,若有需要加入其他參數,可自行設定 Params 集合屬性。執行程式就可以在頁面上看到呈現出來的 Media Player。

        <bee:TBMediaPlayer ID="TBMediaPlayer1" runat="server" AutoStart="True" 
            Height="249px" Url="D:\Movie_01.wmv" Width="250px">
        </bee:TBMediaPlayer>

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/13/5663.aspx

]]>
jeff377 2008-10-13 00:13:29
[ASP.NET 控制項實作 Day11] ActiveX 伺服器控制項 https://ithelp.ithome.com.tw/articles/10012159?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012159?sc=rss.iron Media Player 與 Flash 之類在網頁上執行的外掛控制項,都是屬於 ActiveX 控制項,它們套用在 HTML 碼中的方式差不多,除了要指定 ClassID 以外,ActiveX...]]> Media Player 與 Flash 之類在網頁上執行的外掛控制項,都是屬於 ActiveX 控制項,它們套用在 HTML 碼中的方式差不多,除了要指定 ClassID 以外,ActiveX 使用的參數(相當於 ActiveX 控制項的屬性)以 Param Tag 來表示。本文標題命名為「ActiveX 伺服器控制項」就是避免誤解為 ActiveX 控制項,而是在 ASP.NET 中輸出 ActiveX 相關 HTML 碼的伺服器控制項;我們可透過 ActiveX 伺服器控制項可以用來輸出網頁上引用 ActiveX 的通用 HTML 碼,另外 ActiveX 的參數會以集合屬性來呈現,所以也會一併學習到集合屬性的撰寫方式。
程式碼下載:ASP.NET Server Control - Day11.rar

一、集合屬性
ActiveX 的 Param 參數是集合屬性,所以我們定義了 TBActiveParam 類別描述 ActiveX 參數,包含 Name 及 Value 屬性;而 TBActiveXParamCollection 為 TBActiveParam 的集合類別,用來描述 ActiveX 參數集合。TBActiveXParamCollection 繼承 CollectionBase,加入操作集合的 Add、Insert、Remove、IndexOf、Contains 等方法,關於集合屬性的用法可以參閱筆者在部落格的「撰寫伺服器控制項的集合屬性 (CollectionBase)」一文中有詳細說明。

二、實作 ActiveX 伺服器控制項
step1. 新增繼承 WebControl 的 TBActiveX

step2. 覆寫 TagKey 屬性,傳回 object 的 Tag

        Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag
            Get
                Return HtmlTextWriterTag.Object
            End Get
        End Property

step3. 新增 ClassId 屬性,描述 ActiveX 的 ClassId
定義 ClassId 屬性,並覆寫 AddAttributesToRender 來輸出此屬性。

        ''' <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

step4. 新增 Params 屬性,描述 ActiveX 的參數集合
定義 Params 屬性,型別為 TBActiveXParamCollection 類別,套用 EditorAttribute 設定 CollectionEditor 為集合編輯器。

        ''' <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

當編輯 Params 時,會使用的 CollectionEditor 集合編輯器。

step5. 輸出 ActiveX 參數
覆寫 CreateChildControls 方法,在此方法依 Params 集合屬性定義依序來輸出 ActiveX 的參數集合。

        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

三、執行程式
上一篇我們使用 TBMediaPlayer 控制項來設定 Media Player,在此我們改用 TBActiveX 控制項來設定 Media Player,一樣可以呈現相同的結果。

        <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>

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/12/5659.aspx

]]>
jeff377 2008-10-12 04:20:27
[ASP.NET 控制項實作 Day10] Media Player 控制項 https://ithelp.ithome.com.tw/articles/10012142?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012142?sc=rss.iron 我們在前面幾篇文章中,已經簡要的對伺服器控制項做了基本介紹,接下來的幾篇文章中我們要開始實作伺服器控制項。在網頁上常使用 Media Player 來撥放影片,在 ASP.NET 中沒有現成的控...]]> 我們在前面幾篇文章中,已經簡要的對伺服器控制項做了基本介紹,接下來的幾篇文章中我們要開始實作伺服器控制項。在網頁上常使用 Media Player 來撥放影片,在 ASP.NET 中沒有現成的控制項來處理 Media Player,只能在 aspx 中加入 Media Player 相關的程式碼;本文將示範如何製作一個 Media Player 控制項,讓我們在 ASP.NET 中更方便的使用 Media Player。
程式碼下載:ASP.NET Server Control - Day10.rar

一、Media Player 原始 HTML 碼
在製作 Media Player 控制項之前,你需要先了解 Media Player 原本的 HTML 碼,控制項的作用就是想辨法把這些寫在 aspx 中的 HTML 碼交由控制項來輸出而已,以下為網頁中加入 Media Player 的 HTML 碼範例。

<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>

在下面的網頁有 Media Player 相關參數說明。
http://www.mioplanet.com/rsc/embed\_mediaplayer.htm

二、實作 Media Player 控制項
step1.首先新增由 WebControl 繼承下來的 TBMediaPlayer 類別。

    Public Class TBMediaPlayer
        Inherits WebControl

    End Class

step2.覆寫 TagKey 屬性,傳回 object 的 Tag。

        Protected Overrides ReadOnly Property TagKey() As System.Web.UI.HtmlTextWriterTag
            Get
                Return HtmlTextWriterTag.Object
            End Get
        End Property

step3.輸出 HTML Tag 的 Attribute
在 object Tag 中包含 style、classid、type 二個 Attribute,WebControl 本身會處理 style,所以我們只需覆寫 AddAttributesToRender 方法,處理 classid 及 type 二個 Attribute,記得覆寫 AddAttributesToRender 方法時最後要加入 MyBase.AddAttributesToRender(writer),才會執行父類別的 AddAttributesToRender 方法。

        ''' <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

step4.加入 Url 屬性
加入指定播放檔案來源的 Url 屬性,其中套用 EditorAttribute 設定 UrlEditor,使用 Url 專用的編輯器來設定屬性。

        ''' <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

step5.輸出 Url 參數
接下來覆寫 CreateChildControls 方法,輸出 Url 參數。

        ''' <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

step6.輸出 Media Player 其他參數
你可以將 Media Player 的參數設定皆使用相對應的屬性來設定,然後使用 step5 的方式來輸出所有設定的參數值。

三、Media Player 控制項程式碼
Media Player 控制項的完整程式碼如下,此控制項只加入 URL、AutoStart、UIMode 三個參數,你可以視需求情形將使用到的參數定義為屬性來做設定即可。
因為此處有字元數限制,完整的程式碼請參閱筆者部落格相同文章
http://www.dotblogs.com.tw/jeff377/archive/2008/10/11/5655.aspx

四、執行程式
把 TBMediaPlayer 控制項拖曳至頁面,設定好屬性後,執行程式就可以在頁面上看到呈現出來的 Media Player。

        <bee:TBMediaPlayer ID="TBMediaPlayer1" runat="server" Height="250px" 
            Width="250px" Url="~/test.wmv" />

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/11/5655.aspx

]]>
jeff377 2008-10-11 19:08:27
[ASP.NET 控制項實作 Day9] 控制項常用 Attribute 介紹(2) https://ithelp.ithome.com.tw/articles/10012060?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012060?sc=rss.iron 接續上篇 Attribute 的介紹,本文將再介紹一些伺服器控制項常用的 Attribute。
六、ToolboxDataAttribute 類別<...]]>
接續上篇 Attribute 的介紹,本文將再介紹一些伺服器控制項常用的 Attribute。
六、ToolboxDataAttribute 類別
作用:指定當自訂控制項從工具箱拖曳到頁面時,為此自訂控制項產生的預設標記。
當我們新增一個伺服器控制項,它就會預設在控制項類別套用 ToolboxDataAttribute,定義在控制項在 aspx 程式碼中的標記。

<ToolboxData("<{0}:TBButton runat=server ></{0}:TBButton>")> _
Public Class TBButton
    Inherits System.Web.UI.WebControls.Button
End Class

七、DefaultPropertyAttribute 類別
作用:指定類別的預設屬性。
下面的範例中,MyTextbox 類別套用 DefaultPropertyAttribute,設定 Text 屬性為預設屬性。

<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

八、DefaultEventAttribute 類別
作用:指定控制項的預設事件。
下面的範例中,MyTextbox 類別套用 DefaultEventAttribute,設定 TextChanged 為預設屬性。

<DefaultEvent("TextChanged")> _
Public Class MyTextbox
    Inherits WebControl

    ''' <summary>
    ''' TextChanged 事件。
    ''' </summary>
    Public Event TextChanged As EventHandler
End Class

當設計階段在頁面上的 MyTextbox 控制項點二下時,就會產生預設事件的處理函式。

    Protected Sub MyTextbox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyTextbox3.TextChanged

    End Sub

九、LocalizableAttribute 類別
作用:指定屬性是否應該當地語系化。
當屬性套用設為為 true 的 LocalizableAttribute 時,其屬性值會儲存在資源檔中,未來不需修改程式碼就可以將這些資源檔當地語系化。

        <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

十、DesignerAttribute 類別
作用:設定控制項在設計階段服務的類別。
指定一個設計階段的服務類別,來管理控制項在設計階段的行為,例如控制項的設計階段外觀、智慧標籤內容。例如下面範例的 TBGridView 控制項就定義了 TBGridViewDesigner 來實作設計階段的行為,未來的章節中也會介紹如何實作控制項的 Designer。

    < Designer(GetType(TBGridViewDesigner)) > _
    Public Class TBGridView
        Inherits GridView
    End Class

十一、EditorAttribute 類別
作用:指定在屬性視窗中編輯屬性值的編輯器。
例如 ListBox 控制項的 Items 屬性,在屬性視窗編輯 Items 屬性時,會彈出 Items 集合屬性的編輯器。以下範例就是定義 Items 屬性的編輯器類別為 TBListItemsCollectionEditor,未來的章節中也會介紹如何實作屬性的 Editor。

        <Editor(GetType(TBListItemsCollectionEditor), GetType(System.Drawing.Design.UITypeEditor))> _
        Public Overrides ReadOnly Property Items() As ListItemCollection

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/10/5653.aspx

]]>
jeff377 2008-10-10 11:27:02
[ASP.NET 控制項實作 Day8] 控制項常用 Attribute 介紹(1) https://ithelp.ithome.com.tw/articles/10012016?sc=rss.iron https://ithelp.ithome.com.tw/articles/10012016?sc=rss.iron Property 與 Attribute 二個術語一般都是翻譯成「屬性」,例如類別的屬性,是使用英文的 Property,而 HTML/XML 的元素屬性,使用的英文則是 Attribute。在...]]> Property 與 Attribute 二個術語一般都是翻譯成「屬性」,例如類別的屬性,是使用英文的 Property,而 HTML/XML 的元素屬性,使用的英文則是 Attribute。在 .NET 中 Property 與 Attribute 的意義及用法不同,不過微軟線上文件也將它翻譯為「屬性」,這可能讓人發生困擾及誤解;筆者比較喜歡的方式就是 Property 是屬性,Attribute 就維持原文。在 .NET 中類別或屬性上可以套用上不同的 Attribute,使類別或屬性具有不同的特性,本文將介紹一些在伺服器控制項常使用到的 Attribute。
一、DescriptionAttribute 類別
作用:指定控制項或屬性的描述。
當 DescriptionAttribute 套用至控制項的類別時,設定的描述內容就會出現在工具箱中控制項的提示。

<Description("按鈕控制項")> _
Public Class TBButton
    Inherits System.Web.UI.WebControls.Button
End Class


當 DescriptionAttribute 套用至控制項的屬性時,在屬性視窗下面就會出現設定的屬性描述內容。

<Description("詢問訊息")> _
Public Property ConfirmMessage() As String

二、DefaultValueAttribute 類別
作用:指定屬性的預設值。
使用 DefaultValueAttribute 設定屬性的預設值,若設定的屬性值與預設值相同時,此屬性值就不會出現在 aspx 程式碼中;筆者強烈建議屬性一定套用 DefaultValueAttribute,一來在 aspx 中的程式碼會比較少,另外一個重點是若因為某些因素需要修改屬性的預設值時,所有已開發頁面的控制項屬性值會一併變更;因為當初屬性值是預設值,沒有被寫入 aspx 程式碼中,所以一但控制項的屬性預設值變更,頁面已佈屬的控制項的屬性值就會全面適用。

        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

三、CategoryAttribute 類別
作用:指定屬性或事件的分類名稱,當屬性視窗設定為 [分類] 模式時,以群組方式來顯示屬性或事件。
例如設定 ConfirmMessage 屬性在 "Behavior" 分類,則 ConfirmMessage 屬性會被歸類到「行為」分類。

        <Category("Behavior")> _
        Public Property ConfirmMessage() As String

四、BindableAttribute 類別
作用:指定成員是否通常使用於繫結。
在資料繫結設定視窗中中,指定屬性是否預設會出現在屬性清單中。

<Bindable(True)> _
Public Property ConfirmMessage() As String

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/09/5647.aspx

]]>
jeff377 2008-10-09 21:11:43
[ASP.NET 控制項實作 Day7] 設定工具箱的控制項圖示 https://ithelp.ithome.com.tw/articles/10011933?sc=rss.iron https://ithelp.ithome.com.tw/articles/10011933?sc=rss.iron 當我們把自訂控制項加入到工具箱中時,你會發現所有的控制項預設都是同樣的圖示,雖然控制項的圖示不變更不會有什麼影響,不過我們還是希望為自訂控制項加上合適的外衣,本文將介紹如何設定工具箱控制項圖示。...]]> 當我們把自訂控制項加入到工具箱中時,你會發現所有的控制項預設都是同樣的圖示,雖然控制項的圖示不變更不會有什麼影響,不過我們還是希望為自訂控制項加上合適的外衣,本文將介紹如何設定工具箱控制項圖示。
一、加入控制項圖示檔
首先要準備一個 16 x 16 的點陣圖(bmp),如下所示。

將此圖檔加入至「伺服器控制項專案」中,可以如下圖所示,用一個特定的資料夾來儲存所有工具箱的圖示。

然後在圖檔的屬性視窗中,設定建置動作為「內嵌資源」。

二、設定控制項的圖示
首先定義一個 TBResource 類別,此為一個空的類別,其命名空間需與根命名空間相同,做為引用資源檔時使用。並加上控制項圖示的 WebResource 定義,因為根命名空間是 Bee.Web,而圖檔名稱為 TBButton.bmp,所以定義如下所示。

假設我們要設定 TBButton 的工具箱圖示,則在 TBButton 類別套用 ToolboxBitmapAttribute 如下,其中第一個參數為 GetType(TBResource),第二個參數為圖檔檔名。

重新編輯伺服器控制項專案,再將 Bee.Web.dll 組件的控制項加入工具箱中,你就可以發現 TBButton 的圖示已經變成設定的圖示了。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/08/5624.aspx

]]>
jeff377 2008-10-08 22:26:13
[ASP.NET 控制項實作 Day6] 事件與 PostBack https://ithelp.ithome.com.tw/articles/10011861?sc=rss.iron https://ithelp.ithome.com.tw/articles/10011861?sc=rss.iron 一般類別的事件撰寫很單純,不過在 ASP.NET 中與前端使用者互動產生的事件就不是那麼簡單了;在以往的 ASP 年代是沒有事件這回事的,而在 ASP.NET 把網頁程式撰寫真正的物件導向化,用...]]> 一般類別的事件撰寫很單純,不過在 ASP.NET 中與前端使用者互動產生的事件就不是那麼簡單了;在以往的 ASP 年代是沒有事件這回事的,而在 ASP.NET 把網頁程式撰寫真正的物件導向化,用戶端使用者的操作透過 PostBack 來產生相對應的事件。例如前端使用者按鈕後會引發伺服端 Button 的 Click 事件,當前端使用者輸入文字框完畢後離開後會引發伺服端 TextBox 的 TextChanged 事件,在本文中就是要說明如何透過 PostBack 來產生與使用者互動的事件。
一、IPostBackEventHandler 與 IPostBackDataHandler 介面
控制項要處理 PostBack 產生的事件,必須實作 IPostBackEventHandler 或 IPostBackDataHandler 介面,這二個介面有什麼差別呢?例如 Button 是實作IPostBackEventHandler 介面,由控制項產生的 PostBack 直接引發事件,如 Button 的 Click 事件。例如 TextBox 是實作 IPostBackDataHandler 介面,當頁面產生 PostBack 時,會傳用戶端輸入的新值給控制項,由控制項本身去決定是否引發該事件;以 TextBox 舉例來說,它會判斷新值與舊值不同時才會引發 TextChanged 事件。

二、IPostBackEventHandler 介面實作
首先介紹 IPostBackEventHandler 介面,它包含 RaisePostBackEvent 方法,控制項在此方法中需處理要引發那些事件。我們繼承 WebControl 撰寫 MyButton 類別來說明 IPostBackEventHandler 介面,我們簡化控制項程式碼直接在 Render 方法輸入按鈕的 HTML 原始碼,並定義一個 Click 事件。實作 IPostBackEventHandler 介面的 RaisePostBackEvent 方法,在此方法中直接引發 Click 事件。

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

在頁面上拖曳 MyButton 控制項,在屬性視窗找到 Click 事件,點二下產生 Click 事件處理函式,並撰寫測試程式碼如下。

    Protected Sub MyButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyButton1.Click
        Me.Page.Response.Write("MyButton Click 事件")
    End Sub

執行程式,當按下 MyButton 按鈕時,就會執行它的 RaisePostBackEvent 方法,進而引發 Click 事件,也就會執行 Click 事件處理函式的程式碼。

三、IPostBackDataHandler 介面實作
IPostBackDataHandler 介面包含 LoadPostData 及 RaisePostDataChangedEvent 方法,當頁面 PostBack 時,會尋找具 IPostBackDataHandler 介面的控制項,先執行LoadPostData 方法,控制項在 LoadPostData 方法中會判斷用戶端傳入值決定是否引發事件,若 LoadPostData 傳回 True 表示要引發事件,此時會執行RaisePostDataChangedEvent 方法去決定要引發那些事件,反之傳回 False 表示不引發事件。

我們繼承 WebControl 撰寫 MyText 類別來說明 IPostBackDataHandler 介面,我們簡化控制項程式碼直接在 Render 方法輸入文字框的 HTML 原始碼,並定義一個 TextChanged 事件。在 LoadPostData 方法中我們會判斷用戶端傳入值與目前的值是否不相同,不相同時才會傳回 True,此時才會執行 RaisePostDataChangedEvent 方法,進而引發 TextChanged 事件。

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

在頁面上拖曳 MyTextbox 及 MyButton 控制項,在 MyButton 的 Click 及 MyTextbox 的 TextChanged 事件撰寫如下測試程式碼。

    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

執行程式,在 MyTextbox 輸入 "A",再按下 MyButton,因為 MyTextbox 的值「空字串->"A"」,所以會引發 MyTextbox 的 TextChanged 事件及 MyButton 的 Click 事件。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格

]]>
jeff377 2008-10-07 23:30:19
[ASP.NET 控制項實作 Day5] 屬性與 ViewState https://ithelp.ithome.com.tw/articles/10011745?sc=rss.iron https://ithelp.ithome.com.tw/articles/10011745?sc=rss.iron 在 ASP.NET 中,控制項的屬性與 ViewState 有著密不可分的關係,透過 ViewState 才有辨法維護控制項的屬性值。在本文中將介紹屬性與 ViewState 的關係,並說明屬性...]]> 在 ASP.NET 中,控制項的屬性與 ViewState 有著密不可分的關係,透過 ViewState 才有辨法維護控制項的屬性值。在本文中將介紹屬性與 ViewState 的關係,並說明屬性如何存取 ViewState 是比較有效率的方式。
當你加入一個「ASP.NET 伺服器控制項」時,類別中預設會有一個 Text 屬性寫法的範例如下所示,屬性的讀寫都是直接存取 ViewState,這是一般常見的控制項屬性寫法。可是這種屬性的寫法是沒有效率的,因為 ViewState 的成員是 Object 型別,每次讀取屬性時都是由 ViewState 取出指定鍵值的成員再轉型為屬性的型別,寫入屬性的動作也是直接寫入 ViewState 中。

    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

比較好的方式應該是讀取 ViewState 成員只做一次型別轉換的動作,而寫入 ViewState 的動作可以在 Render 前做批次寫入的動作即可。為了達到這樣的需求,我們可以覆寫 LoadViewState 及 SaveViewState 方法來處理屬性與 ViewState 的存取動作;當控制項初始化後會執行 LoadViewState 方法,來載入 ViewState 還原的控制項狀態,當控制項 Render 之前,會執行 SaveViewState 方法,將控制項的最終狀態存入 ViewState 中,也就是在此方法之後對控制項所做的任何變更都將會被忽略。

我們改寫屬性的寫法,不直接用 ViewState 來存取屬性,而是改用「屬性區域變數」來存取屬性,在 LoadViewState 時載入 ViewState 到屬性區域變數,而 SaveViewState 時再將屬性區域變數寫入 ViewState 中。我們依此方式將 Text 屬性改寫如下。

    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

利用上述的方式,我們可以在 LoadViewState 批次載入所有屬性值,而在 SaveViewState 批次寫入屬性值,如此在讀取屬性就不用一直做型別轉換的動作以改善效率。

結語
雖然屬性一般都是儲存於 ViewState 中,不過若是一些無關緊要的屬性或是完全不會執行階段就變更的屬性,可以考慮不需要將這些屬性儲存於 ViewState 中;因為 ViewState 是個兩面刃,ViewState 可以很輕易幫我們維護屬性值,不過相對的也增加了面頁的傳輸量,所以可以視實際情形來決定屬性是否要儲存於 ViewState 中。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/07/5601.aspx

]]>
jeff377 2008-10-06 21:17:20
[ASP.NET 控制項實作 Day4] 複合控制項 https://ithelp.ithome.com.tw/articles/10011633?sc=rss.iron https://ithelp.ithome.com.tw/articles/10011633?sc=rss.iron 複合控制項就是控制項可包含其他子控制項,複合控制項繼承至 System.Web.UI.WebControls.CompositeControl,例如 Login 及 Wizard 等控制項就是屬...]]> 複合控制項就是控制項可包含其他子控制項,複合控制項繼承至 System.Web.UI.WebControls.CompositeControl,例如 Login 及 Wizard 等控制項就是屬於複合控制項。我們常在網頁上常看到一種輸入日期的方式是年月日三個下拉清單,本文將利用複合控制項來實作這個年月日下拉清單控制項,示範如何實作複合控制項。
一、CompositeControl 類別的特性
CompositeControl 類別是抽象類別,它會實作 INamingContaner 介面,INamingContaner 介面會在子控制項的 ClinetID 加上父控制項的 ID,以確保頁面上控制項的 ClientID 是唯一的。繼承 CompositeControl 類別一般都是覆寫 CreateChildControls 方法,在此方法中將建立子控制項並加入 Controls 集合屬性中;當存取其子控制項時,若子控制項未建立,會執行 CreateChildControls 方法,以會確保所有子控制項皆已在存取 ControlCollection 之前建立。
二、日期下拉清單輸入器
我們繼承 CompositeControl 類別,命名為 TBDropDownDate。這個控制項會包含年月日三個下拉清單(DropDownList),所以我們只要在 CreateChildControls 方法中依序建立年月日的 DropDownList 子控制項,並加入 Controls 集合屬性中即可。

''' <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

在設定階段拖曳 TBDropDownDate 到頁面上,就可以看到我們在 CreateChildControls 方法中所加入的子控制項。

執行程式,檢視它的 HTML 原始碼,會發現年月日的子控制項的 ClientID 都會在原 ID 前加上父控制項的 ID,這樣命名規則可以確保所有的控制項的 ClinetID 都是唯一值。

<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>

三、結語
我們已經看過三類伺服器控制項的簡單案例,不過這三個案例都只是簡單說明控制項 UI 的部分,一個完整的控制項需具備屬性、方法、事件、設計階段支援...等,在後面的文章中,我們將陸續針對這些部分做詳細的介紹。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/05/5583.aspx

]]>
jeff377 2008-10-05 22:22:25
[ASP.NET 控制項實作 Day3] 擴展現有伺服器控制項功能 https://ithelp.ithome.com.tw/articles/10011562?sc=rss.iron https://ithelp.ithome.com.tw/articles/10011562?sc=rss.iron 相對於由無到有開發控制項,繼承現有現伺服器控制項是比較簡單且實用的方式;若希望在現有的控制項增加某些屬性或功能,直接繼承該控制項下來擴展功能是最快的方式,例如「按下 Button 會彈出詢問訊息...]]> 相對於由無到有開發控制項,繼承現有現伺服器控制項是比較簡單且實用的方式;若希望在現有的控制項增加某些屬性或功能,直接繼承該控制項下來擴展功能是最快的方式,例如「按下 Button 會彈出詢問訊息」、「TextBox 設為 ReadOnly 時,可以取得前端傳回的 Text 屬性」這類需求,都可以直接繼承原控制項下來,加上我們需要的功能即可。以下我們就以一個簡單的案例來說明如何繼承現有伺服器下來擴展功能。
一、擴展 Button 控制項:按鈕加上詢問訊息
按下按鈕執行某些動作前,有時會詢問使用者是否執行該動作;例如按下刪除鈕,會詢問使用者是否確定要執行刪除的動作。當然這只需要簡單的 JavaScript 就可以完成,不過相對於 .NET 的程式語言,JavaScript 是非常不易維護的用戶端指令碼,如果能讓開發人員完全用不到 JavaScript,那何樂不為呢? 那就由 Button 控制項本身提供加上詢問訊息的功能就可以,相關的 JavaScript 由控制項去處理。
一般要在 Button 加上詢問訊息,只要在 OnClientClick 屬性設定如下的 JavaScript 即可。我們的目的只是讓開發人員連設定 OnClientClick 屬性的 JavaScript 都省略,直接設定要詢問的訊息即可,接下來我們就要開始實作這個控制項。

<asp:Button ID="Button1" runat="server" Text="Button"  OnClientClick="if (confirm('確定執行嗎?')==false) {return false;}" />   

在 Bee.Web 專案中,加入「ASP.NET 伺服器控制項」,此控制項繼承 Button 下來命名為 TBButton (命名空間為 Bee.Web.WebControls)。在 TBButton 類別中加入 ConfirmMessage 屬性,用來設定詢問訊息的內容。然後在 Render 方法將詢問詢息的 JavaScript 設定到 OnClientClick 屬性即可。

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

將 TBButton 拖曳到測試頁面,設定 ConfirmMessage 屬性。

<bee:TBButton ID="TBButton1" runat="server" ConfirmMessage="確定刪除此筆資料嗎?" Text="刪除" />

執行結果如下。

二、結語
筆者在開發 ASP.NET 的應用程式過程中,通常會習慣把所有現有控制項繼承下來,無論目前需不需要擴展控制項功能。這種方式對於開發大型系統是相當有幫助的,因為無法預期在系統開發的過程中會不會因為某些狀況,而臨時需要擴展控制項的功能,所以就先全部繼承下來以備不時之需,也為未來保留修改的彈性。

三、相關連結
擴展 CommandField 類別 - 刪除提示訊息
按鈕加上詢問訊息

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/04/5578.aspx

]]>
jeff377 2008-10-04 21:24:49
[ASP.NET 控制項實作 Day2] 建立第一個伺服器控制項 https://ithelp.ithome.com.tw/articles/10011523?sc=rss.iron https://ithelp.ithome.com.tw/articles/10011523?sc=rss.iron 上一篇中已經建立「ASP.NET 伺服器控制項」專案,接下來我們將學習來撰寫第一個伺服器控制項。
撰寫伺服器控制項大致分為下列三種方式
1.由無到有建立全新的控制項,一般...]]>
上一篇中已經建立「ASP.NET 伺服器控制項」專案,接下來我們將學習來撰寫第一個伺服器控制項。
撰寫伺服器控制項大致分為下列三種方式
1.由無到有建立全新的控制項,一般會繼承至 System.Web.UI.Control 或 System.Web.UI.WebControls.WebControl 類別。
2.繼承現有控制項,擴展原有控制項的功能,如繼承原有 TextBox 來擴展功能。
3.複合式控制項,將多個現有的控制項組合成為一個新的控制項,例如 TextBox 右邊加個 Button 整合成一個控制項,一般會繼承至 System.Web.UI.WebControls.CompositeControl 類別。

本文將先介紹第1種方式,由無到有來建立控制項,後面的文章中會陸續介紹第2、3種方式的控制項。要建立全新的控制項會繼承至 Control 或 WebControl,沒有 UI 的控制項可由 Control 繼承下來 (如 SqlDataSource),具 UI 的控制項會由 WebControl 繼承下來。接下來的範例中,我們將繼承 WebControl 來建立第一個 MyTextBox 控制項。

一、新增 MyTextBox 控制項
在 Bee.Web 專案按右鍵選單,執行「加入\新增項目」,選擇「ASP.NET 伺服器控制項」,在名稱文字框中輸入 MyTextbox,按下「確定」鈕,就會在專案中加入 MyTextbox 控制項類別。

新加入的控制項預設有一個 Text 屬性,以及覆寫 Render 方法。Render 方法是「將控制項呈現在指定的 HTML 寫入器中」,簡單的說就是在 Render 方法會將控制項對應的 HTML 碼輸出,用來呈現在用戶端的瀏覽器上。假設我們要撰寫一個網頁上的文字框,那就先去看一下文字框在網頁中對應的 HTML 碼,然後在 Render 方法中想辨法輸出這些 HTML 碼即可。

二、輸出控制項的 HTML 碼
你可以使用 FrontPage 之類的 HTML 編輯器,先編輯出控制項的呈現方式,進而去觀查它的 HTML 碼,再回頭去思考如何去撰寫這個伺服器控制項。假設 MyTextbox 控制項包含一個文字框及一個按鈕,那最終輸出的 HTML 碼應該如下。

<input id="Text1" type="text" />
<input id="Button1" type="button" value="button" />

我們在 MyTextbox 的 RenderContents 方法中輸出上述的 HTML 碼。

    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

建置控制項專案,然後拖曳 MyTextbox 在測試頁面上,設計階段就會呈現出我們期望的結果。

執行程式,在瀏覽器看一下 MyTextbox 控制項輸出的結果,是不是跟我們預期的一樣呢。

三、屬性套用到控制項 HTML 碼
控制項不可能單純這樣輸出 HTML 碼而已,控制項的相關屬性設定,一般都影響到輸出的 HTML 碼。假設 MyTextbox 有 Text 及 ButtonText 二個屬性,分別對應到 文字框的內容及按鈕的文字,MyTextbox 本來就有 Text 屬性,依像畫蘆葫新增 ButtonText 屬性。

    < _
    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

RenderContents 方法改寫如下。

    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

重新建置控制項專案,在頁面上測試 MyTextbox 的 Text 及 ButtonText 屬性。

四、使 ClientID (HTML 原始碼控制項的 ID) 是唯一值
在頁面上放置二個 MyTextbox 控制項,執行程式,在瀏覽器中檢查 MyTextbox 的 HTML 原始碼。你會發現 MyTextbox 會以一個 span 包住控制項的內容,而每個控制項的輸出的 ClientID 是唯一的。不過 MyTextbox 內含的文字框及按鈕卻會重覆,所以一般子控制項的 ClientID 會在前面包含父控制項的 ID。

<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>

所以我們再次修改 RenderContents 方法的程式碼

    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

執行程式,再次檢視 HTML 原始碼,所有的 ClinetID 都會是唯一的。

<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>

五、控制項前置詞
自訂控制項的預設前置詞是 cc1,不過這是可以修改的,在專案中的 AssemblyInfo.vb 檔案中,加入如下定義即可。詳細的作法請參考筆者部落格中的「自訂伺服器控制項前置詞」一本有詳細介紹,在此不再累述。

'設定控制項的標記前置詞
<Assembly: TagPrefix("Bee.Web.WebControls", "bee")>

六、結語
本文中是用土法鍊鋼的方法在撰寫伺服器控制項,一般在實作控制項時會有更好的方式、更易維護的寫法,後續的文章中會陸續介紹相關作法。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/03/5573.aspx

]]>
jeff377 2008-10-03 23:51:35
[ASP.NET 控制項實作 Day1] 建立 ASP.NET 伺服器控制項專案 https://ithelp.ithome.com.tw/articles/10011408?sc=rss.iron https://ithelp.ithome.com.tw/articles/10011408?sc=rss.iron 在 ASP.NET 開發環境中,我們常使用現成的控制項直接拖曳至頁面中使用,有沒有想過我們也可以開發自用的控制項呢?本文將本文以 VS2008 為開發工具,VB.NET 為開發程式語言,來說明如...]]> 在 ASP.NET 開發環境中,我們常使用現成的控制項直接拖曳至頁面中使用,有沒有想過我們也可以開發自用的控制項呢?本文將本文以 VS2008 為開發工具,VB.NET 為開發程式語言,來說明如何建立「伺服器控制項」專案,以及如何測試開發階段的的伺服器控制項。
一、建立「ASP.NET 伺服器控制項」專案
首先執行功能表「檔案\新增專案」,在專案類型中選擇 Visual Basic -> Web,選取「ASP.NET 伺服器控制項」範本,在名稱文字框中輸入專案名稱,也就是組件的檔案名稱,我們輸入 Bee.Web 為專案名稱,組件檔案為 Bee.Web.dll,按下「確定」鈕即會建立新的「ASP.NET 伺服器控制項」專案。

在新建立「ASP.NET 伺服器控制項」專案中,會預設加入一個伺服器控制項類別(ServerControl1.vb),這個伺服器控制項已經事件幫我們加入一些控制項的程式碼。目前暫不做任何修改,直接使用此控制項來做測試說明。

接下來執行功能表「專案\Bee.Web 屬性」,設定此組件的根命名空間,一般慣用的根命名空間都會與組件名稱相同,以方便加入參考時可以快速找到相關組件。

我們先儲存這個「ASP.NET 伺服器控制項」專案,指定儲存位置,按下「儲存」鈕。整個專案相關檔案,會儲存在以專案名稱的資料夾中。

二、加入測試網站
不要關閉目前「ASP.NET 伺服器控制項」專案,執行功能表「檔案\加入\新網站」,選擇「ASP.NET 網站」,會在方案中加入一個網站,來測試開發階段的伺服器控制項使用。

在測試網站加入參考,選擇「專案」頁籤,此頁籤中會列出該方案中其他可加入參考的專案,選取 Bee.Web 專案,按下「確定」鈕。

先在 Bee.Web 專案中執行「建置」動作,然後切換到測試網站的頁面設計,工具箱中就會出現 ServerControl1 伺服器控制項。這個控制項就可以直接拖曳至頁面中使用,這個控制項只是單純 Render 出 Text 屬性值,你可以在控制項屬性視窗中,更改 Text 屬性值為 "測試文字",就會看到這個控制項顯示 "測試文字"。將測試網站設為啟動專案,按下「F5」執行程式,就會看到該控制在執行階段的結果。

備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/02/5562.aspx

]]>
jeff377 2008-10-02 23:16:29


https://matters.news/@twcctz500/undefined-健康是最好的禮物蛋黃油https-www-facebook-com-eggsoil-bafyreifu3vznb6tdpc5axcoyjkxkyoqxzodprbhhg67b4pofzvsilba5be

<?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(&quot;__EVENTARGUMENT&quot;))                If (Me.SelectedIndex &lt;&gt; 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(&quot;TBDropDownList: Index={0} Value={1}&quot;, DropDownList2.SelectedIndex, DropDownList2.SelectedValue)        Me.Response.Write(sText)    End Sub </code></pre> <p>執行程式,在 TBDropDownList 選取 &quot;王五&quot; 這個選項時,會正常顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay29DropDownListValue_112DF/image_thumb_10.png" alt="" /></p> <p>接下選取 Value 值相同的 &quot;陳六&quot; 這個選項,也會正常引發 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,其中 &quot;王五&quot; 及 &quot;陳六&quot; 二個 ListItem 的 Value 值相同。</p> <pre><code>    &lt;asp:DropDownList ID=&quot;DropDownList1&quot; runat=&quot;server&quot; AutoPostBack=&quot;True&quot;&gt;            &lt;asp:ListItem Value=&quot;0&quot;&gt;張三&lt;/asp:ListItem&gt;            &lt;asp:ListItem Value=&quot;1&quot;&gt;李四&lt;/asp:ListItem&gt;            &lt;asp:ListItem Value=&quot;2&quot;&gt;王五&lt;/asp:ListItem&gt;            &lt;asp:ListItem Value=&quot;2&quot;&gt;陳六&lt;/asp:ListItem&gt;    &lt;/asp:DropDownList&gt; </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(&quot;DropDownList: Index={0} Value={1}&quot;, DropDownList1.SelectedIndex, DropDownList1.SelectedValue)        Me.Response.Write(sText)    End Sub </code></pre> <p>執行程式,在 DropDownList 選取 &quot;李四&quot; 這個選項時,會正常顯示該成員的 SelectedIndex 及 SelectedValue 屬性值。<br /> <img src="http://files.dotblogs.com.tw/jeff377/0810/ASP.NETDay29DropDownListValue_112DF/image_thumb.png" alt="" /></p> <p>接下來選取 &quot;陳六&quot; 這個選項時,竟然發生奇怪的現象,DorpDownList 竟然顯示相同 Value 值的 &quot;王五&quot; 這個成員的 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>&lt;select name=&quot;DropDownList1&quot; onchange=&quot;javascript:setTimeout('__doPostBack(\'DropDownList1\',\'\')', 0)&quot; id=&quot;DropDownList1&quot;&gt; &lt;option selected=&quot;selected&quot; value=&quot;0&quot;&gt;張三&lt;/option&gt; &lt;option value=&quot;1&quot;&gt;李四&lt;/option&gt; &lt;option value=&quot;2&quot;&gt;王五&lt;/option&gt; &lt;option value=&quot;2&quot;&gt;陳六&lt;/option&gt; &lt;/select&gt; </code></pre> <p>DropDownList 是呼叫 __doPostBack 函式,只傳入 eventTarget參數 (對應到 __EVENTTARGET 這個 HiddenField) 為 DropDownList 的 ClientID;當 PostBack 回伺服端時,在 DropDownList 的 LoadPostData 方法中,會取得用戶端選取的 SelectedValue 值,並去尋找對應的成員的 SelectedIndex 值。可是問題來了,因為 &quot;王五&quot; 與 &quot;陳六&quot; 的 Value 是相同的值,當在尋找符合 Value 值的成員時,前面的選項 &quot;王五&quot; 會先符合條件而傳回該 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 &lt;&gt; 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(&quot;onchange&quot;)                sScript = String.Format(&quot;__doPostBack('{0}',{1})&quot;, Me.ClientID, &quot;this.selectedIndex&quot;)                writer.AddAttribute(HtmlTextWriterAttribute.Onchange, sScript)                Me.AutoPostBack = True            End If        End Sub </code></pre> <p>在頁面上放置一個 TBDropDownList 控制項,設定與上述案例相同的成員清單。</p> <pre><code>        &lt;bee:TBDropDownList ID=&quot;DropDownList2&quot; runat=&quot;server&quot; AutoPostBack=&quot;True&quot;&gt;            &lt;asp:ListItem Value=&quot;0&quot;&gt;張三&lt;/asp:ListItem&gt;            &lt;asp:ListItem Value=&quot;1&quot;&gt;李四&lt;/asp:ListItem&gt;            &lt;asp:ListItem Value=&quot;2&quot;&gt;王五&lt;/asp:ListItem&gt;            &lt;asp:ListItem Value=&quot;2&quot;&gt;陳六&lt;/asp:ListItem&gt;        &lt;/bee:TBDropDownList&gt; </code></pre> <p>執行程式查看 TBDropDownList 控制項的 HTML 原始碼,呼叫 __doPostBack 函式的參數已經被修改,eventArgument 參數會傳入該控制項的 selectedIndex。</p> <pre><code>&lt;select name=&quot;DropDownList2&quot; id=&quot;DropDownList2&quot; onchange=&quot;__doPostBack('DropDownList2',this.selectedIndex)&quot;&gt; &lt;option selected=&quot;selected&quot; value=&quot;0&quot;&gt;張三&lt;/option&gt; &lt;option value=&quot;1&quot;&gt;李四&lt;/option&gt; &lt;option value=&quot;2&quot;&gt;王五&lt;/option&gt; &lt;option value=&quot;2&quot;&gt;陳六&lt;/option&gt; &lt;/select&gt; </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>    &lt; _    Description(&quot;圖形驗證碼控制項&quot;), _    ToolboxData(&quot;&lt;{0}:TBValidateCode runat=server&gt;&lt;/{0}:TBValidateCode&gt;&quot;) _    &gt; _    Public Class TBValidateCode        Inherits System.Web.UI.WebControls.Image        End </code></pre> <p>新增 ValidateCodeUrl 屬性,設定圖形驗證碼產生頁面的網址。</p> <pre><code>        ''' &lt;summary&gt;        ''' 圖形驗證碼產生頁面網址。        ''' &lt;/summary&gt;        &lt; _        Description(&quot;圖形驗證碼產生頁面網址&quot;), _        DefaultValue(&quot;&quot;) _        &gt; _        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 = &quot;~/Page/ValidateCode.aspx&quot;            End If            If Me.BorderWidth = Unit.Empty Then                Me.BorderWidth = Unit.Pixel(1)            End If            If Me.AlternateText = String.Empty Then                Me.AlternateText = &quot;圖形驗證碼&quot;            End If            Me.ToolTip = &quot;滑鼠點二下可重新產生驗證碼&quot;            Me.ImageUrl = sUrl            If Not Me.DesignMode Then                sScript = String.Format(&quot;this.src='{0}?flag='+Math.random();&quot;, Me.Page.ResolveClientUrl(sUrl))                Me.Attributes(&quot;ondblclick&quot;) = sScript            End If            Me.Style(HtmlTextWriterStyle.Cursor) = &quot;pointer&quot;            MyBase.Render(writer)        End Sub </code></pre> <p>另外新增一個 ValidateCode 方法,用來檢查輸入驗證碼是否正確。還記得我們在產生驗證碼圖形時,同時把該驗證碼的值寫入 Session(&quot;_ValidateCode&quot;) 中吧,所以這個方法只是把用戶輸入的值與 Seesion 中的值做比對。</p> <pre><code>        ''' &lt;summary&gt;        ''' 檢查輸入驗證碼是否正確。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Code&quot;&gt;輸入驗證碼。&lt;/param&gt;        ''' &lt;returns&gt;驗證成功傳回 True,反之傳回 False。&lt;/returns&gt;        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>        &lt;bee:TBValidateCode ID=&quot;TBValidateCode1&quot; runat=&quot;server&quot; /&gt;        &lt;bee:TBTextBox ID=&quot;txtCode&quot; runat=&quot;server&quot;&gt;&lt;/bee:TBTextBox&gt;        &lt;bee:TBButton ID=&quot;TBButton1&quot; runat=&quot;server&quot; Text=&quot;確定&quot; /&gt; </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(&quot;驗證碼輸入正確&quot;)        Else            Me.Response.Write(&quot;驗證碼輸入錯誤!&quot;)        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(&quot;_ValidateCode&quot;) 來記錄驗證碼的值,以便後續與使用者輸入驗證碼做比對之用。</p> <pre><code>Partial Class ValidateCode    Inherits System.Web.UI.Page    ''' &lt;summary&gt;    ''' 產生圖形驗證碼。    ''' &lt;/summary&gt;    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() = {&quot;Times New Roman&quot;, &quot;MS Mincho&quot;, &quot;Book Antiqua&quot;, _                                      &quot;Gungsuh&quot;, &quot;PMingLiU&quot;, &quot;Impact&quot;}        '驗證碼的字元集,去掉了一些容易混淆的字元        Dim oCharacter As Char() = {&quot;2&quot;c, &quot;3&quot;c, &quot;4&quot;c, &quot;5&quot;c, &quot;6&quot;c, &quot;8&quot;c, _                                    &quot;9&quot;c, &quot;A&quot;c, &quot;B&quot;c, &quot;C&quot;c, &quot;D&quot;c, &quot;E&quot;c, _                                    &quot;F&quot;c, &quot;G&quot;c, &quot;H&quot;c, &quot;J&quot;c, &quot;K&quot;c, &quot;L&quot;c, _                                    &quot;M&quot;c, &quot;N&quot;c, &quot;P&quot;c, &quot;R&quot;c, &quot;S&quot;c, &quot;T&quot;c, _                                    &quot;W&quot;c, &quot;X&quot;c, &quot;Y&quot;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    ''' &lt;summary&gt;    ''' 產生圖形驗證碼。    ''' &lt;/summary&gt;    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 = &quot;no-cache&quot;        Response.AppendHeader(&quot;Pragma&quot;, &quot;No-Cache&quot;)        '將驗證碼圖片寫入記憶體流,並將其以 &quot;image/Png&quot; 格式輸出        Dim oStream As New MemoryStream()        Try            CreateValidateCodeImage(oStream, sCode, 4, 100, 40, 18)            Me.Session(&quot;_ValidateCode&quot;) = sCode            Response.ClearContent()            Response.ContentType = &quot;image/Png&quot;            Response.BinaryWrite(oStream.ToArray())        Finally            '釋放資源            oStream.Dispose()        End Try    End Sub End Class </code></pre> <p>我們將此頁面置於 ~/Page/ValidateCode.aspx,當要使用此頁面的圖形驗證碼,只需要在使用 Image 控制項,設定 ImageUrl 為此頁面即可。</p> <pre><code>&lt;asp:Image ID=&quot;imgValidateCode&quot; runat=&quot;server&quot; ImageUrl=&quot;~/Page/ValidateCode.aspx&quot; /&gt; </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=&quot;Enable&quot; 使控制項在新增模式時為可編輯,設定 FormViewModeState.EditMode=&quot;Disable&quot; 使控制項在修改模式是唯讀的。</p> <pre><code>        &lt;bee:TBFormView ID=&quot;TBFormView1&quot; runat=&quot;server&quot; DataKeyNames=&quot;ProductID&quot; DataSourceID=&quot;SqlDataSource1&quot;            DefaultMode=&quot;Edit&quot; SingleTemplate=&quot;EditItemTemplate&quot; BackColor=&quot;White&quot; BorderColor=&quot;#CCCCCC&quot;            BorderStyle=&quot;None&quot; BorderWidth=&quot;1px&quot; CellPadding=&quot;3&quot; GridLines=&quot;Both&quot; Visible=&quot;False&quot;&gt;            &lt;FooterStyle BackColor=&quot;White&quot; ForeColor=&quot;#000066&quot; /&gt;            &lt;RowStyle ForeColor=&quot;#000066&quot; /&gt;            &lt;EditItemTemplate&gt;                ProductID:                &lt;bee:TBTextBox ID=&quot;TextBox1&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductID&quot;) %&gt;'&gt;                  &lt;FormViewModeState EditMode=&quot;Disable&quot; InsertMode=&quot;Enable&quot;&gt;                  &lt;/FormViewModeState&gt;                &lt;/bee:TBTextBox&gt;                '省略                &lt;asp:LinkButton ID=&quot;LinkButton1&quot; runat=&quot;server&quot; CausesValidation=&quot;True&quot; CommandName=&quot;Insert&quot;                    Text=&quot;新增&quot; /&gt;                 &lt;asp:LinkButton ID=&quot;UpdateButton&quot; runat=&quot;server&quot; CausesValidation=&quot;True&quot; CommandName=&quot;Update&quot;                    Text=&quot;更新&quot; /&gt;                 &lt;asp:LinkButton ID=&quot;UpdateCancelButton&quot; runat=&quot;server&quot; CausesValidation=&quot;False&quot;                    CommandName=&quot;Cancel&quot; Text=&quot;取消&quot; /&gt;            &lt;/EditItemTemplate&gt;        &lt;/bee:TBFormView&gt; </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>    ''' &lt;summary&gt;    ''' 文字框控制項。    ''' &lt;/summary&gt;    &lt; _    Description(&quot;文字框控制項。&quot;), _    ToolboxData(&quot;&lt;{0}:TBTextBox runat=server&gt;&lt;/{0}:TBTextBox&gt;&quot;) _    &gt; _    Public Class TBTextBox        Inherits TextBox        Private FFormViewModeState As TBFormViewModeState        ''' &lt;summary&gt;        ''' 依 FormViewMode 來設定控制項狀態。        ''' &lt;/summary&gt;        &lt; _        Category(WebCommon.Category.Behavior), _        NotifyParentProperty(True), _        DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _        PersistenceMode(PersistenceMode.InnerProperty), _        DefaultValue(&quot;&quot;) _        &gt; _        Public ReadOnly Property FormViewModeState() As TBFormViewModeState            Get                If FFormViewModeState Is Nothing Then                    FFormViewModeState = New TBFormViewModeState                End If                Return FFormViewModeState            End Get        End Property        ''' &lt;summary&gt;        ''' 處理控制項狀態。        ''' &lt;/summary&gt;        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        ''' &lt;summary&gt;        ''' 依 FormView 的模式來處理控制項狀態。        ''' &lt;/summary&gt;        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        ''' &lt;summary&gt;        ''' 覆寫。引發 PreRender 事件。        ''' &lt;/summary&gt;        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>        &lt;bee:TBGridView ID=&quot;TBGridView1&quot; runat=&quot;server&quot; AutoGenerateColumns=&quot;False&quot; DataKeyNames=&quot;ProductID&quot;            DataSourceID=&quot;SqlDataSource1&quot; FormViewID=&quot;TBFormView1&quot;&gt;            &lt;Columns&gt;                &lt;bee:TBCommandField ShowDeleteButton=&quot;True&quot; ShowEditButton=&quot;True&quot;                    ShowHeaderNewButton=&quot;True&quot; &gt;                &lt;/bee:TBCommandField&gt;                                '省略                            &lt;/Columns&gt;        &lt;/bee:TBGridView&gt; </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>    ''' &lt;summary&gt;    ''' 控制項狀態列舉。    ''' &lt;/summary&gt;    Public Enum EControlState        ''' &lt;summary&gt;        ''' 不設定。        ''' &lt;/summary&gt;        NotSet = 0        ''' &lt;summary&gt;        ''' 啟用。        ''' &lt;/summary&gt;        Enable = 1        ''' &lt;summary&gt;        ''' 不啟用。        ''' &lt;/summary&gt;        Disable = 2        ''' &lt;summary&gt;        ''' 隱藏。        ''' &lt;/summary&gt;        Hide = 3    End Enum </code></pre> <p>再來定義 TBFormViewModeState 類別,用來設定控制項在各種 FormView 模式 (瀏覽、新增、修改) 中的控制項狀態。</p> <pre><code>''' &lt;summary&gt; ''' 依 FormViewMode 來設定控制項狀態。 ''' &lt;/summary&gt; &lt; _ Serializable(), _ TypeConverter(GetType(ExpandableObjectConverter)) _ &gt; _ Public Class TBFormViewModeState    Private FInsertMode As EControlState = EControlState.NotSet    Private FEditMode As EControlState = EControlState.NotSet    Private FBrowseMode As EControlState = EControlState.NotSet    ''' &lt;summary&gt;    ''' 在新增模式(FormViewMode=Insert)的控制項狀態。    ''' &lt;/summary&gt;    &lt; _    NotifyParentProperty(True), _    DefaultValue(GetType(EControlState), &quot;NotSet&quot;) _    &gt; _    Public Property InsertMode() As EControlState        Get            Return FInsertMode        End Get        Set(ByVal value As EControlState)            FInsertMode = value        End Set    End Property    ''' &lt;summary&gt;    ''' 在編輯模式(FormViewMode=Edit)的控制項狀態。    ''' &lt;/summary&gt;    &lt; _    NotifyParentProperty(True), _    DefaultValue(GetType(EControlState), &quot;NotSet&quot;) _    &gt; _    Public Property EditMode() As EControlState        Get            Return FEditMode        End Get        Set(ByVal value As EControlState)            FEditMode = value        End Set    End Property    ''' &lt;summary&gt;    ''' 在瀏覽模式(FormViewMode=ReadOnly)的控制項狀態。    ''' &lt;/summary&gt;    &lt; _    NotifyParentProperty(True), _    DefaultValue(GetType(EControlState), &quot;NotSet&quot;) _    &gt; _    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 轉型為布林值,例如 &quot;T/F&quot;、&quot;是/否&quot; 之類的資料,若希望使用 CheckBoxField 來顯示就比較麻煩,一般的作法都是轉為 TemplateField,自行撰寫資料繫結的函式,而且只能支援單向繫結。在本文直接改寫 CheckBoxField 類別,讓 CheckBoxField 可以直接雙向繫結 &quot;T/F&quot; 或 &quot;是/否&quot; 之類的資料。<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>        ''' &lt;summary&gt;        ''' 使用指定 DataControlFieldCell 的值填入指定的 IDictionary 物件。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Dictionary&quot;&gt;用於儲存指定儲存格的值。&lt;/param&gt;        ''' &lt;param name=&quot;Cell&quot;&gt;包含要擷取值的儲存格。&lt;/param&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列的狀態。&lt;/param&gt;        ''' &lt;param name=&quot;IncludeReadOnly&quot;&gt;true 表示包含唯讀欄位的值,否則為 false。&lt;/param&gt;        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 &gt; 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 = &quot; &quot;) 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 &gt; 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>            &lt;bee:TBDateField DataField=&quot;BirthDate&quot; HeaderText=&quot;BirthDate&quot;                SortExpression=&quot;BirthDate&quot; DataFormatString=&quot;{0:d}&quot;                ApplyFormatInEditMode=&quot;True&quot; CalendarStyle=&quot;Winter&quot; /&gt;                        &lt;asp:BoundField DataField=&quot;BirthDate&quot; HeaderText=&quot;BirthDate&quot;                SortExpression=&quot;BirthDate&quot; DataFormatString=&quot;{0:d}&quot;                ApplyFormatInEditMode=&quot;True&quot; ReadOnly=&quot;true&quot; /&gt; </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>    ''' &lt;summary&gt;    ''' 日期欄位。    ''' &lt;/summary&gt;    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>        ''' &lt;summary&gt;        ''' 資料儲存格初始化。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Cell&quot;&gt;要初始化的儲存格。&lt;/param&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列狀態。&lt;/param&gt;        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>        ''' &lt;summary&gt;        ''' 將欄位值繫結至 BoundField 物件。        ''' &lt;/summary&gt;        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 = &quot; &quot;                End If                DirectCast(oControl, TableCell).Text = sText            Else                If Not TypeOf oControl Is TBDateEdit Then                    Throw New HttpException(String.Format(&quot;{0}: Wrong Control Type&quot;, 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>        ''' &lt;summary&gt;        ''' 從關聯的資料來源擷取資料。        ''' &lt;/summary&gt;        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), &quot;ID&quot;, 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        ''' &lt;summary&gt;        ''' 擷取資料的回呼函式。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;data&quot;&gt;取得的資料。&lt;/param&gt;        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>                &lt;bee:TBDropDownField  HeaderText=&quot;CategoryID&quot;                      SortExpression=&quot;CategoryID&quot; DataField=&quot;CategoryID&quot;                    DataTextField=&quot;CategoryName&quot; DataValueField=&quot;CategoryID&quot; DataSourceID=&quot;SqlDataSource2&quot;&gt;                &lt;/bee:TBDropDownField&gt;                &lt;asp:BoundField DataField=&quot;CategoryID&quot; HeaderText=&quot;CategoryID&quot;                    SortExpression=&quot;CategoryID&quot;  ReadOnly=&quot;true&quot; /&gt; </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>                &lt;bee:TBDropDownField  HeaderText=&quot;CategoryID&quot;                      SortExpression=&quot;CategoryID&quot; DataField=&quot;CategoryID&quot; &gt;                    &lt;Items&gt;                    &lt;asp:ListItem Value=&quot;&quot;&gt;未對應&lt;/asp:ListItem&gt;                    &lt;asp:ListItem Value=&quot;2&quot;&gt;Condiments&lt;/asp:ListItem&gt;                    &lt;asp:ListItem Value=&quot;3&quot;&gt;Confections&lt;/asp:ListItem&gt;                    &lt;/Items&gt;                &lt;/bee:TBDropDownField&gt;                &lt;asp:BoundField DataField=&quot;CategoryID&quot; HeaderText=&quot;CategoryID&quot;                    SortExpression=&quot;CategoryID&quot;  ReadOnly=&quot;true&quot; /&gt; </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>                &lt;bee:TBDropDownField  HeaderText=&quot;CategoryID&quot;                      SortExpression=&quot;CategoryID&quot; DataField=&quot;CategoryID&quot;                    DataTextField=&quot;CategoryName&quot; DataValueField=&quot;CategoryID&quot; DataSourceID=&quot;SqlDataSource2&quot;&gt;                &lt;/bee:TBDropDownField&gt; </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>       ''' &lt;summary&gt;        ''' 將欄位值繫結至 BoundField 物件。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;sender&quot;&gt;控制項。&lt;/param&gt;        ''' &lt;param name=&quot;e&quot;&gt;事件引數。&lt;/param&gt;        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 = &quot; &quot;                End If                DirectCast(oControl, TableCell).Text = sText            Else                If Not TypeOf oControl Is TBDropDownList Then                    Throw New HttpException(String.Format(&quot;{0}: Wrong Control Type&quot;, 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>        ''' &lt;summary&gt;        ''' 使用指定 DataControlFieldCell 物件的值填入指定的 System.Collections.IDictionary 物件。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Dictionary&quot;&gt;用於儲存指定儲存格的值。&lt;/param&gt;        ''' &lt;param name=&quot;Cell&quot;&gt;包含要擷取值的儲存格。&lt;/param&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列的狀態。&lt;/param&gt;        ''' &lt;param name=&quot;IncludeReadOnly&quot;&gt;true 表示包含唯讀欄位的值,否則為 false。&lt;/param&gt;        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 &gt; 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 = &quot; &quot;) 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 &gt; 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>        ''' &lt;summary&gt;        ''' 清單項目集合。        ''' &lt;/summary&gt;        &lt; _        Description(&quot;清單項目集合。&quot;), _        DefaultValue(CStr(Nothing)), _        PersistenceMode(PersistenceMode.InnerProperty), _        DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _        Editor(GetType(ListItemsCollectionEditor), GetType(UITypeEditor)), _        MergableProperty(False), _        Category(&quot;Default&quot;)&gt; _        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        ''' &lt;summary&gt;        ''' 資料來源控制項的 ID 屬性。        ''' &lt;/summary&gt;        Public Property DataSourceID() As String            Get                Return FDataSourceID            End Get            Set(ByVal value As String)                FDataSourceID = value            End Set        End Property        ''' &lt;summary&gt;        ''' 提供清單項目文字內容的資料來源的欄位。        ''' &lt;/summary&gt;        &lt; _        Description(&quot;提供清單項目文字內容的資料來源的欄位。&quot;), _        DefaultValue(&quot;&quot;) _        &gt; _        Public Property DataTextField() As String            Get                Return FDataTextField            End Get            Set(ByVal value As String)                FDataTextField = value            End Set        End Property        ''' &lt;summary&gt;        ''' 提供清單項目值的資料來源的欄位。        ''' &lt;/summary&gt;        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>        ''' &lt;summary&gt;        ''' 資料儲存格初始化。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Cell&quot;&gt;要初始化的儲存格。&lt;/param&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列狀態。&lt;/param&gt;        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>    ''' &lt;summary&gt;    ''' 資料欄位基礎類別。    ''' &lt;/summary&gt;    Public MustInherit Class TBBaseBoundField        Inherits BoundField        Private FRowIndex As Integer = 0        ''' &lt;summary&gt;        ''' 資料列是否為編輯模式。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列狀態。&lt;/param&gt;        Public Function RowStateIsEdit(ByVal RowState As DataControlRowState) As Boolean            Return (RowState And DataControlRowState.Edit) &lt;&gt; DataControlRowState.Normal        End Function        ''' &lt;summary&gt;        ''' 資料列是否為新增模式。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列狀態。&lt;/param&gt;        Public Function RowStateIsInsert(ByVal RowState As DataControlRowState) As Boolean            Return (RowState And DataControlRowState.Insert) &lt;&gt; DataControlRowState.Normal        End Function        ''' &lt;summary&gt;        ''' 資料列是否為編輯或新增模式。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列狀態。&lt;/param&gt;        Public Function RowStateIsEditOrInsert(ByVal RowState As DataControlRowState) As Boolean            Return RowStateIsEdit(RowState) OrElse RowStateIsInsert(RowState)        End Function        ''' &lt;summary&gt;        ''' 判斷儲存格是否可編輯(新增/修改)。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列狀態。&lt;/param&gt;        Friend Function CellIsEdit(ByVal RowState As DataControlRowState) As Boolean            Return (Not Me.ReadOnly) AndAlso RowStateIsEditOrInsert(RowState)        End Function        ''' &lt;summary&gt;        ''' 資料列索引。        ''' &lt;/summary&gt;        Friend ReadOnly Property RowIndex() As Integer            Get                Return FRowIndex            End Get        End Property        ''' &lt;summary&gt;        ''' 儲存格初始化。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Cell&quot;&gt;要初始化的儲存格。&lt;/param&gt;        ''' &lt;param name=&quot;CellType&quot;&gt;儲存格類型。&lt;/param&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列狀態。&lt;/param&gt;        ''' &lt;param name=&quot;RowIndex&quot;&gt;資料列之以零起始的索引。&lt;/param&gt;        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        ''' &lt;summary&gt;        ''' 是否需要執行資料繫結。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;RowState&quot;&gt;資料列狀態。&lt;/param&gt;        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        ''' &lt;summary&gt;        ''' 覆寫 SelectedValue 屬性。        ''' &lt;/summary&gt;        Public Overrides Property SelectedValue() As String            Get                Return MyBase.SelectedValue            End Get            Set(ByVal value As String)                If Me.Items.Count &lt;&gt; 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        ''' &lt;summary&gt;        ''' 覆寫 SelectedValue 屬性。        ''' &lt;/summary&gt;        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 內容,其中第一個加入 &quot;未對應&quot; 的項目,將 SelectedValue 屬性繫結至 CategoryID 欄位。</p> <pre><code>                &lt;bee:TBDropDownList ID=&quot;DropDownList1&quot; runat=&quot;server&quot;                    SelectedValue='&lt;%# Bind(&quot;CategoryID&quot;) %&gt;'&gt;                    &lt;asp:ListItem Value=&quot;&quot;&gt;未對應&lt;/asp:ListItem&gt;                    &lt;asp:ListItem Value=&quot;2&quot;&gt;Condiments&lt;/asp:ListItem&gt;                    &lt;asp:ListItem Value=&quot;3&quot;&gt;Confections&lt;/asp:ListItem&gt;                &lt;/bee:TBDropDownList&gt; </code></pre> <p>當資料的 CategoryID 欄位值不存在於 DropDownList 的 Items 集合屬性中時,就會顯示第一個 &quot;未對應&quot; 的項目。<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>                &lt;bee:TBDropDownList ID=&quot;DropDownList1&quot; runat=&quot;server&quot;                    SelectedValue='&lt;%# Bind(&quot;CategoryID&quot;) %&gt;' DataSourceID=&quot;SqlDataSource2&quot;                    DataTextField=&quot;CategoryName&quot; DataValueField=&quot;CategoryID&quot;&gt;                &lt;/bee:TBDropDownList&gt;                &lt;asp:SqlDataSource ID=&quot;SqlDataSource2&quot; runat=&quot;server&quot;                    ConnectionString=&quot;&lt;%$ ConnectionStrings:Northwnd %&gt;&quot;                    SelectCommand=&quot;SELECT CategoryID, CategoryName, Description, Picture FROM Categories&quot;                    ProviderName=&quot;&lt;%$ ConnectionStrings:Northwnd.ProviderName %&gt;&quot; &gt;                &lt;/asp:SqlDataSource&gt; </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(&quot;AutoPostBack&quot;, _                &quot;AutoPostBack&quot;, &quot;Behavior&quot;, &quot;是否引發 PostBack 動作。&quot;)) </code></pre> <p>TBDateEditActionList.AutoPostBack 屬性如下,其中 Me.Component 指的是目前的 TDateEdit 控制項,透過 GetPropertyValue 及 SetPropertyValue 方法來存取控制項的指定屬性。</p> <pre><code>        ''' &lt;summary&gt;        ''' 是否引發 PostBack 動作。        ''' &lt;/summary&gt;        Public Property AutoPostBack() As Boolean            Get                Return CType(GetPropertyValue(Me.Component, &quot;AutoPostBack&quot;), Boolean)            End Get            Set(ByVal value As Boolean)                SetPropertyValue(Me.Component, &quot;AutoPostBack&quot;, value)            End Set        End Property    ''' &lt;summary&gt;    ''' 設定物件的屬性值。    ''' &lt;/summary&gt;    ''' &lt;param name=&quot;Component&quot;&gt;屬性值將要設定的物件。&lt;/param&gt;    ''' &lt;param name=&quot;PropertyName&quot;&gt;屬性名稱。&lt;/param&gt;    ''' &lt;param name=&quot;Value&quot;&gt;新值。&lt;/param&gt;    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    ''' &lt;summary&gt;    ''' 取得物件的屬性值。    ''' &lt;/summary&gt;    ''' &lt;param name=&quot;Component&quot;&gt;具有要擷取屬性的物件。&lt;/param&gt;    ''' &lt;param name=&quot;PropertyName&quot;&gt;屬性名稱。&lt;/param&gt;    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, &quot;About&quot;, _                &quot;關於 TDateEdit 控制項&quot;, &quot;About&quot;, _                &quot;關於 TDateEdit 控制項。&quot;, True)) </code></pre> <p>TBDateEditActionList 的 About 方法只是單純顯示一個訊息視窗,一般你可以在這方法加入任何想在設計階段處理的動作。例如自動產生 GridView 的欄位、在 FormView 加入控制項並自動排版,這些都可以在此實現的。</p> <pre><code>        Public Sub About()            MsgBox(&quot;TDateEdit 是結合 The Coolest DHTML Calendar 日期選擇器實作的控制項&quot;)        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>    ''' &lt;summary&gt;    ''' TBDateEdit 控制項的設計模式行為。    ''' &lt;/summary&gt;    Public Class TBDateEditDesigner        Inherits System.Web.UI.Design.ControlDesigner        ''' &lt;summary&gt;        ''' 取得控制項設計工具的動作清單集合。        ''' &lt;/summary&gt;        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>    ''' &lt;summary&gt;    ''' 定義 TBDateEdit 控制項智慧標籤面板的項目清單集合。    ''' &lt;/summary&gt;    Public Class TBDateEditActionList        Inherits DesignerActionList        ''' &lt;summary&gt;        ''' 建構函式。        ''' &lt;/summary&gt;        Public Sub New(ByVal owner As ControlDesigner)            MyBase.New(owner.Component)        End Sub    End Class </code></pre> <p>接下來要覆寫 GetSortedActionItems 方法,它會回傳 DesignerActionItemCollection 集合型別,此集合中會傳回要顯示在智慧標籤面板的項目清單集合,所以我們要在 DesignerActionItemCollection 集合中加入我們要呈現的項目清單內容。</p> <pre><code>        ''' &lt;summary&gt;        ''' 傳回要顯示在智慧標籤面板的項目清單集合。        ''' &lt;/summary&gt;        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(&quot;行為&quot;, &quot;Behavior&quot;)) oItems.Add(New DesignerActionHeaderItem(&quot;外觀&quot;, &quot;Appearance&quot;)) </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 &lt;&gt; 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>    ''' &lt;summary&gt;    ''' 擴充 TBToolbar 控制項的設計模式行為。    ''' &lt;/summary&gt;    Public Class TBToolbarDesigner        Inherits System.Web.UI.Design.ControlDesigner        ''' &lt;summary&gt;        ''' 用來在設計階段表示控制項的 HTML 標記。        ''' &lt;/summary&gt;        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 = &quot;&lt;div style=&quot;&quot;background-color: #C0C0C0; border:solid 1px; width:200px&quot;&quot;&gt;請設定 Items 屬性&lt;/div&gt;&quot;            Else                sHTML = MyBase.GetDesignTimeHtml()            End If            Return sHTML        End Function    End Class </code></pre> <p>在 TBToolbar 控制項套用 DesignerAttribute 設定自訂的 TBToolbarDesigner 類別。</p> <pre><code>    &lt;Designer(GetType(TBToolbarDesigner))&gt; _    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(&quot;請設定 Items 屬性&quot;)            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>&lt; _ Editor(GetType(CollectionEditor), GetType(UITypeEditor)) _ &gt; _ 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        ''' &lt;summary&gt;        ''' 建構函式。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Type&quot;&gt;型別。&lt;/param&gt;        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>&lt; _ Editor(GetType(TBToolbarItemCollectionEditor), GetType(UITypeEditor)) _ &gt; _ 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>        ''' &lt;summary&gt;        ''' 取得這個集合編輯器可包含的資料型別。        ''' &lt;/summary&gt;        ''' &lt;returns&gt;這個集合可包含的資料型別陣列。&lt;/returns&gt;        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>        ''' &lt;summary&gt;        ''' 取出指定清單項目的顯示文字。        ''' &lt;/summary&gt;        Protected Overrides Function GetDisplayText(ByVal value As Object) As String            If TypeOf value Is TBToolbarButton Then                Return String.Format(&quot;按鈕 - {0}&quot;, CType(value, TBToolbarButton).Key)            ElseIf TypeOf value Is TBToolbarTextbox Then                Return &quot;文字框&quot;            ElseIf TypeOf value Is TBToolbarLabel Then                Return String.Format(&quot;標籤 - {0}&quot;, 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>        ''' &lt;summary&gt;        ''' 覆寫 RenderContents 方法。        ''' &lt;/summary&gt;        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        ''' &lt;summary&gt;        ''' 建立工具列按鈕。        ''' &lt;/summary&gt;        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        ''' &lt;summary&gt;        ''' 建立工具列文字框。        ''' &lt;/summary&gt;        Private Function CreateToolbarTextbox(ByVal Item As TBToolbarTextbox) As Control            Dim oTextBox As TextBox            oTextBox = New TextBox            Return oTextBox        End Function        ''' &lt;summary&gt;        ''' 建立工具列標籤。        ''' &lt;/summary&gt;        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>&lt;span id=&quot;TBToolbar1&quot;&gt; &lt;input type=&quot;submit&quot; name=&quot;TBToolbar1$Add&quot; value=&quot;新增&quot; onclick=&quot;__doPostBack('TBToolbar1','Add');&quot; id=&quot;TBToolbar1_Add&quot; /&gt; &lt;input type=&quot;submit&quot; name=&quot;TBToolbar1$Edit&quot; value=&quot;修改&quot; onclick=&quot;__doPostBack('TBToolbar1','Edit');&quot; id=&quot;TBToolbar1_Edit&quot; /&gt; &lt;input type=&quot;submit&quot; name=&quot;TBToolbar1$Delete&quot; value=&quot;刪除&quot; onclick=&quot;if (confirm('確定刪除嗎?')==false) {return false;}__doPostBack('TBToolbar1','Delete');&quot; id=&quot;TBToolbar1_Delete&quot; /&gt; &lt;span&gt;關鍵字&lt;/span&gt; &lt;input name=&quot;TBToolbar1$ctl01&quot; type=&quot;text&quot; /&gt; &lt;input type=&quot;submit&quot; name=&quot;TBToolbar1$Search&quot; value=&quot;搜尋&quot; onclick=&quot;__doPostBack('TBToolbar1','Search');&quot; id=&quot;TBToolbar1_Search&quot; /&gt; &lt;/span&gt; </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        ''' &lt;summary&gt;        ''' 覆寫 RenderContents 方法。        ''' &lt;/summary&gt;        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 = &quot;請設定 Items 屬性。&quot;                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>&lt;span id=&quot;TBToolbar1&quot;&gt; &lt;input type=&quot;submit&quot; name=&quot;TBToolbar1$Add&quot; value=&quot;新增&quot; onclick=&quot;__doPostBack('TBToolbar1','Add');&quot; id=&quot;TBToolbar1_Add&quot; /&gt; &lt;input type=&quot;submit&quot; name=&quot;TBToolbar1$Edit&quot; value=&quot;修改&quot; onclick=&quot;__doPostBack('TBToolbar1','Edit');&quot; id=&quot;TBToolbar1_Edit&quot; /&gt; &lt;input type=&quot;submit&quot; name=&quot;TBToolbar1$Delete&quot; value=&quot;刪除&quot; onclick=&quot;__doPostBack('TBToolbar1','Delete');&quot; id=&quot;TBToolbar1_Delete&quot; /&gt; &lt;/span&gt; </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(&amp;H100)) Then        Me.flags.Set(&amp;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(&amp;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 = &quot;新按鈕&quot;        oItem.Key = &quot;NewButton&quot;        TBToolbar1.Items.Add(oItem)        Me.Response.Write(&quot;「測試一」按鈕&quot;)    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=&quot;Add&quot; 的按鈕        oButton = TBToolbar1.FindControl(&quot;Add&quot;)        '再加入新按鈕        oItem = New TBToolbarItem()        oItem.Text = &quot;新按鈕&quot;        oItem.Key = &quot;NewButton&quot;        TBToolbar1.Items.Add(oItem)        Me.Response.Write(&quot;「測試二」按鈕&quot;)    End Sub    Protected Sub Button3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button3.Click        '單純 PostBack,無程式碼        Me.Response.Write(&quot;「PostBack」按鈕&quot;)    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>        ''' &lt;summary&gt;        ''' 覆寫 Render 方法。        ''' &lt;/summary&gt;        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>    &lt; _    Description(&quot;工具列控制項。&quot;), _    ParseChildren(True, &quot;Items&quot;), _    ToolboxData(&quot;&lt;{0}:TBToolbar runat=server &gt;&lt;/{0}:TBToolbar&gt;&quot;) _    &gt; _    Public Class TBToolbar        Inherits CompositeControl    End Class </code></pre> <p><strong>step2. 新增 Items 屬性,描述工具列項目集合</strong></p> <pre><code>        ''' &lt;summary&gt;        ''' 工具列項目集合。        ''' &lt;/summary&gt;        &lt; _        Description(&quot;工具列項目集合。&quot;), _        PersistenceMode(PersistenceMode.InnerProperty), _        DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _        Editor(GetType(CollectionEditor), GetType(UITypeEditor)) _        &gt; _        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>        ''' &lt;summary&gt;        ''' Click 事件引數。        ''' &lt;/summary&gt;        Public Class ClickEventArgs            Inherits System.EventArgs            Private FKey As String = String.Empty            ''' &lt;summary&gt;            ''' 項目鍵值。            ''' &lt;/summary&gt;            Public Property Key() As String                Get                    Return FKey                End Get                Set(ByVal value As String)                    FKey = value                End Set            End Property        End Class        ''' &lt;summary&gt;        ''' 按下工具列按鈕所引發的事件。        ''' &lt;/summary&gt;        &lt; _        Description(&quot;按下工具列按鈕所引發的事件。&quot;) _        &gt; _        Public Event Click(ByVal sender As Object, ByVal e As ClickEventArgs)        ''' &lt;summary&gt;        ''' 引發 Click 事件。        ''' &lt;/summary&gt;        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        ''' &lt;summary&gt;        ''' 建立子控制項。        ''' &lt;/summary&gt;        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(&quot;您按了 {0}&quot;, 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 原始碼為例如下,其中 &lt;object&gt; tag 的 codebase attribute 是指 Flash 插件的下載位置及版本。</p> <pre><code>&lt;object id=&quot;ShockwaveFlash2&quot; height=&quot;90&quot; width=&quot;728&quot;  codebase=&quot;http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0&quot;  classid=&quot;clsid:D27CDB6E-AE6D-11cf-96B8-444553540000&quot;&gt; &lt;param value=&quot;http://files.dotblogs.com.tw/dotjum/ad/debug.swf&quot; name=&quot;movie&quot;/&gt; &lt;param value=&quot;high&quot; name=&quot;quality&quot;/&gt; &lt;param value=&quot;#000000&quot; name=&quot;bgcolor&quot;/&gt; &lt;embed height=&quot;90&quot; width=&quot;728&quot; type=&quot;application/x-shockwave-flash&quot;  pluginspage=&quot;http://www.macromedia.com/go/getflashplayer&quot; quality=&quot;high&quot;  src=&quot;http://files.dotblogs.com.tw/dotjum/ad/debug.swf&quot;/&gt; &lt;/object&gt; </code></pre> <p>在 &lt;object&gt; tag 中必要的 attribute 為 classid、codebase、movie、width、height,而 &lt;embed&gt; 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        ''' &lt;summary&gt;        ''' 建構函式。        ''' &lt;/summary&gt;        Sub New()            MyBase.ClassId = &quot;D27CDB6E-AE6D-11CF-96B8-444553540000&quot;            MyBase.CodeBase = &quot;http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0&quot;        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>        ''' &lt;summary&gt;        ''' 加入 MediaPlayer 參數。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Name&quot;&gt;參數名稱。&lt;/param&gt;        ''' &lt;param name=&quot;Value&quot;&gt;參數值。&lt;/param&gt;        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        ''' &lt;summary&gt;        ''' 建立 Embed 標記。        ''' &lt;/summary&gt;        Private Function CreateEmbed() As HtmlControls.HtmlGenericControl            Dim oEmbed As HtmlControls.HtmlGenericControl            Dim oParam As TBActiveXParam            oEmbed = New HtmlControls.HtmlGenericControl()            oEmbed.TagName = &quot;embed&quot;            oEmbed.Attributes(&quot;src&quot;) = Me.ResolveClientUrl(Me.MovieUrl)            oEmbed.Attributes(&quot;pluginspage&quot;) = &quot;http://www.macromedia.com/go/getflashplayer&quot;            oEmbed.Attributes(&quot;height&quot;) = Me.Height.ToString            oEmbed.Attributes(&quot;width&quot;) = Me.Width.ToString            'Embed 的 Attributes 加入 Params 集合屬性的設定            For Each oParam In Me.Params                If oParam.Name &lt;&gt; &quot;movie&quot; Then                    oEmbed.Attributes(oParam.Name) = oParam.Value                End If            Next            Return oEmbed        End Function        ''' &lt;summary&gt;        ''' 建立子控制項。        ''' &lt;/summary&gt;        Protected Overrides Sub CreateChildControls()            Dim oEmbed As HtmlControls.HtmlGenericControl            '加入 movie 參數            AddParam(&quot;movie&quot;, Me.ResolveClientUrl(Me.MovieUrl))            '加入 quality 參數            If Me.Quality &lt;&gt; EQuality.NotSet Then                AddParam(&quot;quality&quot;, 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>        &lt;bee:TBFlash ID=&quot;TBFlash1&quot; runat=&quot;server&quot; Height=&quot;90px&quot;            MovieUrl=&quot;http://files.dotblogs.com.tw/dotjum/ad/debug.swf&quot; Quality=&quot;High&quot;            Width=&quot;728px&quot;&gt;        &lt;/bee:TBFlash&gt; </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        ''' &lt;summary&gt;        ''' 建構函式。        ''' &lt;/summary&gt;        Sub New()            MyBase.ClassId = &quot;6BF52A52-394A-11D3-B153-00C04F79FAA6&quot;        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>        ''' &lt;summary&gt;        ''' 加入 MediaPlayer 參數。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Name&quot;&gt;參數名稱。&lt;/param&gt;        ''' &lt;param name=&quot;Value&quot;&gt;參數值。&lt;/param&gt;        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        ''' &lt;summary&gt;        ''' 覆寫 CreateChildControls 方法。        ''' &lt;/summary&gt;        Protected Overrides Sub CreateChildControls()            '加入 Url 參數            If Me.Url &lt;&gt; String.Empty Then                AddParam(&quot;URL&quot;, Me.ResolveClientUrl(Me.Url))            End If            '加入 autoStart 參數            If Me.AutoStart Then                AddParam(&quot;autoStart&quot;, &quot;true&quot;)            End If            '加入 uiMode 參數            If Me.UIMode &lt;&gt; EUIMode.NotSet Then                AddParam(&quot;uiMode&quot;, 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>        &lt;bee:TBMediaPlayer ID=&quot;TBMediaPlayer1&quot; runat=&quot;server&quot; AutoStart=&quot;True&quot;            Height=&quot;249px&quot; Url=&quot;D:\Movie_01.wmv&quot; Width=&quot;250px&quot;&gt;        &lt;/bee:TBMediaPlayer&gt; </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>        ''' &lt;summary&gt;        ''' 覆寫 AddAttributesToRender 方法。        ''' &lt;/summary&gt;        Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)            '加入 MediaPlayer ActiveX 元件的 classid            writer.AddAttribute(&quot;classid&quot;, String.Format(&quot;clsid:{0}&quot;, Me.ClassId))            MyBase.AddAttributesToRender(writer)        End Sub </code></pre> <p><strong>step4. 新增 Params 屬性,描述 ActiveX 的參數集合</strong><br /> 定義 Params 屬性,型別為 TBActiveXParamCollection 類別,套用 EditorAttribute 設定 CollectionEditor 為集合編輯器。</p> <pre><code>        ''' &lt;summary&gt;        ''' ActiveX 控制項參數集合。        ''' &lt;/summary&gt;        &lt; _        Description(&quot;控制項參數集合。&quot;), _        PersistenceMode(PersistenceMode.InnerProperty), _        DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _        Editor(GetType(CollectionEditor), GetType(UITypeEditor)) _        &gt; _        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(&quot;param&quot;)            oParam.Attributes.Add(&quot;name&quot;, Name)            oParam.Attributes.Add(&quot;value&quot;, Value)            Me.Controls.Add(oParam)        End Sub        ''' &lt;summary&gt;        ''' 建立子控制項。        ''' &lt;/summary&gt;        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>        &lt;bee:TBActiveX ID=&quot;TBActiveX1&quot; runat=&quot;server&quot;            ClassId=&quot;6BF52A52-394A-11D3-B153-00C04F79FAA6&quot; Height=&quot;250px&quot; Width=&quot;250px&quot;&gt;            &lt;Params&gt;                &lt;bee:TBActiveXParam Name=&quot;URL&quot; Value=&quot;d:/Movie_01.wmv&quot; /&gt;                &lt;bee:TBActiveXParam Name=&quot;autoStart&quot; Value=&quot;true&quot; /&gt;            &lt;/Params&gt;        &lt;/bee:TBActiveX&gt; </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>&lt;OBJECT id=&quot;VIDEO&quot; width=&quot;320&quot; height=&quot;240&quot; style=&quot;position:absolute; left:0;top:0;&quot; CLASSID=&quot;CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6&quot; type=&quot;application/x-oleobject&quot;&gt; &lt;PARAM NAME=&quot;URL&quot; VALUE=&quot;your file or url&quot;&gt; &lt;PARAM NAME=&quot;SendPlayStateChangeEvents&quot; VALUE=&quot;True&quot;&gt; &lt;PARAM NAME=&quot;AutoStart&quot; VALUE=&quot;True&quot;&gt; &lt;PARAM name=&quot;uiMode&quot; value=&quot;none&quot;&gt; &lt;PARAM name=&quot;PlayCount&quot; value=&quot;9999&quot;&gt; &lt;/OBJECT&gt; </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>        ''' &lt;summary&gt;        ''' 覆寫 AddAttributesToRender 方法。        ''' &lt;/summary&gt;        Protected Overrides Sub AddAttributesToRender(ByVal writer As System.Web.UI.HtmlTextWriter)            '加入 MediaPlayer ActiveX 元件的 classid            writer.AddAttribute(&quot;classid&quot;, &quot;clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6&quot;)            writer.AddAttribute(&quot;type&quot;, &quot;application/x-oleobject&quot;)            MyBase.AddAttributesToRender(writer)        End Sub </code></pre> <p><strong>step4.加入 Url 屬性</strong><br /> 加入指定播放檔案來源的 Url 屬性,其中套用 EditorAttribute 設定 UrlEditor,使用 Url 專用的編輯器來設定屬性。</p> <pre><code>        ''' &lt;summary&gt;        ''' 播放檔案來源。        ''' &lt;/summary&gt;        &lt; _        Description(&quot;播放檔案來源&quot;), _        Bindable(True), _        Category(&quot;Appearance&quot;), _        Editor(GetType(UrlEditor), GetType(UITypeEditor)), _        UrlProperty(), _        DefaultValue(&quot;&quot;) _        &gt; _        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>        ''' &lt;summary&gt;        ''' 加入參數。        ''' &lt;/summary&gt;        ''' &lt;param name=&quot;Name&quot;&gt;參數名稱。&lt;/param&gt;        ''' &lt;param name=&quot;Value&quot;&gt;參數值。&lt;/param&gt;        Private Sub AddParam(ByVal Name As String, ByVal Value As String)            Dim oParam As HtmlControls.HtmlGenericControl            oParam = New HtmlControls.HtmlGenericControl(&quot;param&quot;)            oParam.Attributes.Add(&quot;name&quot;, Name)            oParam.Attributes.Add(&quot;value&quot;, Value)            Me.Controls.Add(oParam)        End Sub        Protected Overrides Sub CreateChildControls()            '加入 Url 參數            AddParam(&quot;url&quot;, 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>        &lt;bee:TBMediaPlayer ID=&quot;TBMediaPlayer1&quot; runat=&quot;server&quot; Height=&quot;250px&quot;            Width=&quot;250px&quot; Url=&quot;~/test.wmv&quot; /&gt; </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>&lt;ToolboxData(&quot;&lt;{0}:TBButton runat=server &gt;&lt;/{0}:TBButton&gt;&quot;)&gt; _ 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>&lt;DefaultProperty(&quot;Text&quot;)&gt; _ Public Class MyTextbox    Inherits WebControl    Public Property Text() As String        Get            Return CType(Me.ViewState(&quot;Text&quot;), String)        End Get        Set(ByVal value As String)            Me.ViewState(&quot;Text&quot;) = value        End Set    End Property End Class </code></pre> <p><strong>八、DefaultEventAttribute 類別</strong><br /> 作用:指定控制項的預設事件。<br /> 下面的範例中,MyTextbox 類別套用 DefaultEventAttribute,設定 TextChanged 為預設屬性。</p> <pre><code>&lt;DefaultEvent(&quot;TextChanged&quot;)&gt; _ Public Class MyTextbox    Inherits WebControl    ''' &lt;summary&gt;    ''' TextChanged 事件。    ''' &lt;/summary&gt;    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>        &lt;Localizable(True)&gt; _        Public Property Text() As String            Get                Return CType(Me.ViewState(&quot;Text&quot;), String)            End Get            Set(ByVal value As String)                Me.ViewState(&quot;Text&quot;) = value            End Set        End Property </code></pre> <p><strong>十、DesignerAttribute 類別</strong><br /> 作用:設定控制項在設計階段服務的類別。<br /> 指定一個設計階段的服務類別,來管理控制項在設計階段的行為,例如控制項的設計階段外觀、智慧標籤內容。例如下面範例的 TBGridView 控制項就定義了 TBGridViewDesigner 來實作設計階段的行為,未來的章節中也會介紹如何實作控制項的 Designer。</p> <pre><code>    &lt; Designer(GetType(TBGridViewDesigner)) &gt; _    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>        &lt;Editor(GetType(TBListItemsCollectionEditor), GetType(System.Drawing.Design.UITypeEditor))&gt; _        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>&lt;Description(&quot;按鈕控制項&quot;)&gt; _ 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>&lt;Description(&quot;詢問訊息&quot;)&gt; _ 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        &lt;DefaultValue(&quot;&quot;)&gt; _        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 屬性在 &quot;Behavior&quot; 分類,則 ConfirmMessage 屬性會被歸類到「行為」分類。</p> <pre><code>        &lt;Category(&quot;Behavior&quot;)&gt; _        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>&lt;Bindable(True)&gt; _ 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    ''' &lt;summary&gt;    ''' Click 事件。    ''' &lt;/summary&gt;    Public Event Click As EventHandler    ''' &lt;summary&gt;    ''' 引發 Click 事件。    ''' &lt;/summary&gt;    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    ''' &lt;summary&gt;    ''' 覆寫 Render 方法。    ''' &lt;/summary&gt;    Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)        Dim sHTML As String        sHTML = String.Format(&quot;&lt;INPUT TYPE=Submit Name={0} Value = '按鈕'/&gt;&quot;, 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(&quot;MyButton Click 事件&quot;)    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(&quot;Text&quot;), String)        End Get        Set(ByVal value As String)            Me.ViewState(&quot;Text&quot;) = value        End Set    End Property    ''' &lt;summary&gt;    ''' TextChanged 事件。    ''' &lt;/summary&gt;    Public Event TextChanged As EventHandler    ''' &lt;summary&gt;    ''' 引發 TextChanged 事件。    ''' &lt;/summary&gt;    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 &lt;&gt; 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    ''' &lt;summary&gt;    ''' 覆寫 Render 方法。    ''' &lt;/summary&gt;    Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)        Dim sHTML As String        sHTML = String.Format(&quot;&lt;INPUT Type=text Name={0} Value={1} &gt;&quot;, 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(&quot;MyButton Click 事件&quot;)        Me.Page.Response.Write(&quot;&lt;br/&gt;&quot;)    End Sub    Protected Sub MyTextbox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyTextbox1.TextChanged        Me.Page.Response.Write(&quot;MyTextbox TextChanged 事件&quot;)        Me.Page.Response.Write(&quot;&lt;br/&gt;&quot;)    End Sub </code></pre> <p>執行程式,在 MyTextbox 輸入 &quot;A&quot;,再按下 MyButton,因為 MyTextbox 的值「空字串-&gt;&quot;A&quot;」,所以會引發 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(&quot;Text&quot;))            If s Is Nothing Then                Return String.Empty            Else                Return s            End If        End Get        Set(ByVal Value As String)            ViewState(&quot;Text&quot;) = 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    ''' &lt;summary&gt;    ''' 載入 ViewState 還原控制項狀態。    ''' &lt;/summary&gt;    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    ''' &lt;summary&gt;    ''' 將控制項狀態寫入 ViewState 中。    ''' &lt;/summary&gt;    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>''' &lt;summary&gt; ''' 日期下拉清單輸入器。 ''' &lt;/summary&gt; &lt; _ ToolboxData(&quot;&lt;{0}:TBDropDownDate runat=server&gt;&lt;/{0}:TBDropDownDate&gt;&quot;) _ &gt; _ 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 = &quot;Year&quot;        For N1 = 1950 To 2010            oYear.Items.Add(N1.ToString)        Next        Me.Controls.Add(oYear) '加入子控制項        Me.Controls.Add(New LiteralControl(&quot;年&quot;))        '月下拉清單區間為 1-12        oMonth = New DropDownList        oMonth.ID = &quot;Month&quot;        For N1 = 1 To 12            oMonth.Items.Add(N1.ToString)        Next        Me.Controls.Add(oMonth) '加入子控制項        Me.Controls.Add(New LiteralControl(&quot;月&quot;))        '日下拉清單區為為 1-31        oDay = New DropDownList        oDay.ID = &quot;Day&quot;        For N1 = 1 To 12            oDay.Items.Add(N1.ToString)        Next        Me.Controls.Add(oDay) '加入子控制項        Me.Controls.Add(New LiteralControl(&quot;日&quot;))    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>&lt;span id=&quot;TBDropDownDate1&quot;&gt; &lt;select name=&quot;TBDropDownDate1$Year&quot; id=&quot;TBDropDownDate1_Year&quot;&gt; ....省略 &lt;select name=&quot;TBDropDownDate1$Month&quot; id=&quot;TBDropDownDate1_Month&quot;&gt; ....省略 &lt;select name=&quot;TBDropDownDate1$Day&quot; id=&quot;TBDropDownDate1_Day&quot;&gt; &lt;/span&gt; </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>&lt;asp:Button ID=&quot;Button1&quot; runat=&quot;server&quot; Text=&quot;Button&quot;  OnClientClick=&quot;if (confirm('確定執行嗎?')==false) {return false;}&quot; /&gt;   </code></pre> <p>在 Bee.Web 專案中,加入「ASP.NET 伺服器控制項」,此控制項繼承 Button 下來命名為 TBButton (命名空間為 Bee.Web.WebControls)。在 TBButton 類別中加入 ConfirmMessage 屬性,用來設定詢問訊息的內容。然後在 Render 方法將詢問詢息的 JavaScript 設定到 OnClientClick 屬性即可。</p> <pre><code>Namespace WebControls    &lt; _    Description(&quot;按鈕控制項&quot;), _    ToolboxData(&quot;&lt;{0}:TBButton runat=server&gt;&lt;/{0}:TBButton&gt;&quot;) _    &gt; _    Public Class TBButton        Inherits System.Web.UI.WebControls.Button        &lt;Description(&quot;詢問訊息&quot;)&gt; _        Public Property ConfirmMessage() As String            Get                Dim sConfirmMessage As String                sConfirmMessage = CStr(ViewState(&quot;ConfirmMessage&quot;))                If sConfirmMessage Is Nothing Then                    Return String.Empty                Else                    Return sConfirmMessage                End If            End Get            Set(ByVal value As String)                ViewState(&quot;ConfirmMessage&quot;) = value            End Set        End Property        ''' &lt;summary&gt;        ''' 覆寫 Render 方法。        ''' &lt;/summary&gt;        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 &lt;&gt; String.Empty Then                sScript = Me.OnClientClick                '詢問訊息的 JavaScript                sConfirm = String.Format(&quot;if (confirm('{0}')==false) {{return false;}}&quot;, Me.ConfirmMessage)                If sScript = String.Empty Then                    Me.OnClientClick = sConfirm                Else                    Me.OnClientClick = sConfirm &amp; sScript                End If            End If            MyBase.Render(writer)        End Sub    End Class End Namespace </code></pre> <p>將 TBButton 拖曳到測試頁面,設定 ConfirmMessage 屬性。</p> <pre><code>&lt;bee:TBButton ID=&quot;TBButton1&quot; runat=&quot;server&quot; ConfirmMessage=&quot;確定刪除此筆資料嗎?&quot; Text=&quot;刪除&quot; /&gt; </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>&lt;input id=&quot;Text1&quot; type=&quot;text&quot; /&gt; &lt;input id=&quot;Button1&quot; type=&quot;button&quot; value=&quot;button&quot; /&gt; </code></pre> <p>我們在 MyTextbox 的 RenderContents 方法中輸出上述的 HTML 碼。</p> <pre><code>    Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)        Dim sHTML As String        sHTML = &quot;&lt;input id=&quot;&quot;Text1&quot;&quot; type=&quot;&quot;text&quot;&quot; /&gt;&quot; &amp; _                &quot;&lt;input id=&quot;&quot;Button1&quot;&quot; type=&quot;&quot;button&quot;&quot; value=&quot;&quot;button&quot;&quot; /&gt;&quot;        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>    &lt; _    Bindable(True), _    Category(&quot;Appearance&quot;), _    DefaultValue(&quot;&quot;), _    Localizable(True)&gt; _    Property ButtonText() As String        Get            Dim s As String = CStr(ViewState(&quot;ButtonText&quot;))            If s Is Nothing Then                Return String.Empty            Else                Return s            End If        End Get        Set(ByVal Value As String)            ViewState(&quot;ButtonText&quot;) = 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 = &quot;&lt;input id=&quot;&quot;Text1&quot;&quot; type=&quot;&quot;text&quot;&quot; value=&quot;&quot;{0}&quot;&quot;/&gt;&quot; &amp; _                &quot;&lt;input id=&quot;&quot;Button1&quot;&quot; type=&quot;&quot;button&quot;&quot; value=&quot;&quot;{1}&quot;&quot; /&gt;&quot;        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>&lt;span id=&quot;MyTextbox1&quot;&gt; &lt;input id=&quot;Text1&quot; type=&quot;text&quot; value=&quot;這是文字&quot;/&gt; &lt;input id=&quot;Button1&quot; type=&quot;button&quot; value=&quot;這是按鈕&quot; /&gt; &lt;/span&gt; &lt;br /&gt; &lt;span id=&quot;MyTextbox2&quot;&gt; &lt;input id=&quot;Text1&quot; type=&quot;text&quot; value=&quot;這是文字&quot;/&gt; &lt;input id=&quot;Button1&quot; type=&quot;button&quot; value=&quot;這是按鈕&quot; /&gt; &lt;/span&gt; </code></pre> <p>所以我們再次修改 RenderContents 方法的程式碼</p> <pre><code>    Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)        Dim sHTML As String        sHTML = &quot;&lt;input id=&quot;&quot;{0}_Text&quot;&quot; type=&quot;&quot;text&quot;&quot; value=&quot;&quot;{1}&quot;&quot;/&gt;&quot; &amp; _                &quot;&lt;input id=&quot;&quot;{0}_Button&quot;&quot; type=&quot;&quot;button&quot;&quot; value=&quot;&quot;{2}&quot;&quot; /&gt;&quot;        sHTML = String.Format(sHTML, Me.ID, Me.Text, Me.ButtonText)        writer.Write(sHTML)    End Sub </code></pre> <p>執行程式,再次檢視 HTML 原始碼,所有的 ClinetID 都會是唯一的。</p> <pre><code>&lt;span id=&quot;MyTextbox1&quot;&gt; &lt;input id=&quot;MyTextbox1_Text&quot; type=&quot;text&quot; value=&quot;這是文字&quot;/&gt; &lt;input id=&quot;MyTextbox1_Button&quot; type=&quot;button&quot; value=&quot;這是按鈕&quot; /&gt; &lt;/span&gt; &lt;br /&gt; &lt;span id=&quot;MyTextbox2&quot;&gt; &lt;input id=&quot;MyTextbox2_Text&quot; type=&quot;text&quot; value=&quot;這是文字&quot;/&gt; &lt;input id=&quot;MyTextbox2_Button&quot; type=&quot;button&quot; value=&quot;這是按鈕&quot; /&gt; &lt;/span&gt; </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>'設定控制項的標記前置詞 &lt;Assembly: TagPrefix(&quot;Bee.Web.WebControls&quot;, &quot;bee&quot;)&gt; </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 -&gt; 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 屬性值為 &quot;測試文字&quot;,就會看到這個控制項顯示 &quot;測試文字&quot;。將測試網站設為啟動專案,按下「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>