Django它是免費開放原始碼

  • 451
  • 0
  • 2022-07-04

Django它是免費開放原始碼

From Django 1.11 to Django 2.1 :: 2019 iT 邦幫忙鐵人賽 https://ithelp.ithome.com.tw/users/20111829/ironman https://ithelp.ithome.com.tw/images/ironman/10th/fb.jpg From Django 1.11 to Django 2.1 :: 2019 iT 邦幫忙鐵人賽 https://ithelp.ithome.com.tw/users/20111829/ironman zh-TW Tue, 07 Jun 2022 22:16:23 +0800 Day30 : Django 第三方登入 - 以Github為例 https://ithelp.ithome.com.tw/articles/10206389?sc=rss.iron https://ithelp.ithome.com.tw/articles/10206389?sc=rss.iron 這篇標題應該寫成

無痛連續寫兩篇文章

開個玩笑 ^^",好巧不巧約明後天面試,等同於是完全沒時間寫文章,就直接來怒刷一波 Day30 ...]]> 這篇標題應該寫成

無痛連續寫兩篇文章

開個玩笑 ^^",好巧不巧約明後天面試,等同於是完全沒時間寫文章,就直接來怒刷一波 Day30 吧!

轉眼間就過了一個月(茶

原本想要講其他的東西,不過覺得 第三方登入 的功能比較有人看?

是 比較幫助的到大家 ^^"

所以就直接從零開始無痛學第三方登入教學了(喂

讓我們進入正題!


正文開始

Django 沒有提供內建的第三方登入...所以 下台一鞠躬 (全劇終

.
.
.
.

喂!! 才不是,這也太隨便了吧,可能是心情異常興奮導致的 (?

好拉,不開玩笑了! 但是 Django 真的沒有提供內建的第三方登入

那要怎麼辦呢? 不用擔心! 因為早就有人為了這種狀況沒提供了應對措施,也就是說!

要使用第三方登入就必須額外下載套件囉!

更詳細的教學,大家也可以參考這裡

至於有提供那些第三方登入,這裡列一些常見的網站給大家,更多詳細內容可以洽[這裡]
Supported Providers

  • Facebook (both OAuth2 and JS SDK)
  • Github (OAuth2)
  • Google (OAuth2)
  • Twitter (OAuth)

假設你都是跟著我陽春的腳步走過來的話,那你可以直接在這個虛擬環境下 pip install django-allauth,就可以看到安裝完成的訊息囉!

Successfully installed certifi-2018.10.15 chardet-3.0.4 defusedxml-0.5.0 django-allauth-0.38.0 idna-2.7 oauthlib-2.1.0 python3-openid-3.1.0 requests-2.20.0 requests-oauthlib-1.0.0 urllib3-1.24

安裝完畢以後,要到 top-level 的 settings.py 進行設定,有許多部份都要調整,所以我這裡就分開敘述囉!

首先是 INSTALLED_APPS

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
   # 這六個都是要加進來的唷
    'django.contrib.auth',
    'django.contrib.messages',
    'django.contrib.sites',

    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    # 把需要的第三方登入都放這裡
    'allauth.socialaccount.providers.github', 
]

再來,額外新增AUTHENTICATION_BACKENDS,找個空白處新增即可,習慣加在 settings 最後方!

AUTHENTICATION_BACKENDS = (
    # Needed to login by username in Django admin, regardless of `allauth`
    'django.contrib.auth.backends.ModelBackend',
    # `allauth` specific authentication methods, such as login by e-mail
    'allauth.account.auth_backends.AuthenticationBackend',
)
)

最後,不要忘記再新增這一行

SITE_ID = 1

最後,避免與前面的內容發生衝突, url.py 修改成這樣

# 只有兩行
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('allauth.urls')),
]

最後,還記得嗎? INSTALLED_APPS 有更改,就要記得下python manage.py migrate

Operations to perform:
  Apply all migrations: account, admin, auth, contenttypes, sessions, sites, socialaccount
Running migrations:
  Applying account.0001_initial... OK
  Applying account.0002_email_max_length... OK
  Applying sites.0001_initial... OK
  Applying sites.0002_alter_domain_unique... OK
  Applying socialaccount.0001_initial... OK
  Applying socialaccount.0002_token_max_lengths... OK
  Applying socialaccount.0003_extra_data_default_dict... OK

接著,我們就到 Github 來創建OAuth吧!
因為我原本就有新增一個了,所以將新增內容附在下方,大家對照欄位填入就OK囉!
https://ithelp.ithome.com.tw/upload/images/20181029/20111829KVuRrxBNuf.jpg

創建完成之後就可以看到專屬你的 ID 及 Secret
https://ithelp.ithome.com.tw/upload/images/20181029/20111829PmJgpwOPPM.jpg

接著執行 python manage.py runserver,到 admin 你便可以看到許多生面孔,一開始先修改網域位址成 127.0.0.1
https://ithelp.ithome.com.tw/upload/images/20181029/20111829o7EZEsSEIz.jpg

https://ithelp.ithome.com.tw/upload/images/20181029/20111829i80uxyIbVa.jpg

https://ithelp.ithome.com.tw/upload/images/20181029/201118292tYOFRskjJ.jpg

接著我們要將剛剛的 ID 及 Secret 新增到 紅色框框的那個欄位
https://ithelp.ithome.com.tw/upload/images/20181029/20111829CQojNbFdvl.jpg

之後點選右上角的 新增,便能夠成功進到新增欄位的畫面拉!
https://ithelp.ithome.com.tw/upload/images/20181029/20111829njSh0QjRXU.jpg

這裡目前只有Github可以選擇,因為我們在 INSTALLED_APPS 新增的第三方只有Github
而 ID 和 Secret 則是填入剛剛從 Github 創建 OAuth 得到的兩組 序號
https://ithelp.ithome.com.tw/upload/images/20181029/20111829q2fXZzJCiQ.jpg

完成上述步驟之後,我們趕緊來測試功能吧!

在 top-level 的 views.py 新增 HomePage,透過內建的Template來新增我們的網頁

class HomePage(TemplateView):
	template_name = 'home.html'

接著在 templates 底下 修改 home.html

{% extends "base.html" %}
{% load socialaccount %}
{% block title %}
Allauth Test
{% endblock %}
{% block content %}
<h2>Allauth Test</h2>
{% if user.is_authenticated %}
  <p>Hello, {{ user.username }}!</p>
  <a href="{% url 'account_logout' %}">Logout</a><br>
{% else %}
  <p>You are not logged in</p>
  <ul>
    <li><a href="{% url 'account_login' %}">Login Page</a></li>
    <li><a href="{% provider_login_url 'github' %}">Github登入</a></li>
  </ul>
{% endif %}
{% endblock %}

最後再將 urls.py 修改.....就!!!

from django.contrib import admin
from django.urls import path, include
from .views import  HomePage

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('allauth.urls')),
    path('', HomePage.as_view(), name='home'),
    ]

大功告成! 我們來看成品吧!

https://ithelp.ithome.com.tw/upload/images/20181029/20111829VEYkeJgZQL.jpg

Wheeeeee~~ 熟悉的畫面
https://ithelp.ithome.com.tw/upload/images/20181029/20111829AKouRQzJLj.jpg

https://ithelp.ithome.com.tw/upload/images/20181029/20111829UIxD01wgdc.jpg
如此一來, Django 第三方登入就大功告成囉!


本日小節

今天(最後一天?)我們學會如何製作 Django 第三方登入的功能,其實官方的資料都寫得很清楚,而接下來就是要根據你所要的第三方登入網站,再去客製化你的網站囉!

30天心得

難以想像能夠完成這 30天的心得分享,就如我第一天說的,我只不過是早大家學 Django 兩個禮拜而已,但是藉由這一次的機會,真的是讓我有很大的成長,原本不熟悉的變熟悉,熟悉的變得更熟悉,而在找資料的同時,更是激發自己的好奇心去學去看了更多的資料跟教學,真的相當感動!

Django 對我來說,這並不是結束,而是另一個旅程的開始,謝謝鐵人舉辦這樣的活動更激勵我學習的動力!

最後,謝謝大家的欣賞,若是有任何問題,歡迎大家提問~

]]> Percy 2018-10-30 00:00:59
Day29 : Django 註冊 https://ithelp.ithome.com.tw/articles/10206355?sc=rss.iron https://ithelp.ithome.com.tw/articles/10206355?sc=rss.iron 前言

Day28,我...]]> 前言

Day28,我們了解如何使用 Django 來作登入、登出

那今天我們就來講 申請帳戶,讓這個功能更完整吧!


正文開始

目前我們已經知道,只要引用 path('accounts/', include('django.contrib.auth.urls')),就等於是有以下的路徑可以使用!

accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']

但是這裡並沒有註冊的功能,因此我們必須自己來刻一個

聽起來好像很難,但你不用擔心 ^^"

Django 只是放在不同的地方而已 UserCreationForm

因為它是一個 ModelForm,所以這類別已經跟內見資料庫綁定了!

所以我們只要撰寫邏輯就可以了,很簡單吧!

首先,我們先在 top-level 的 views.py 撰寫邏輯吧!

from django.contrib.auth.forms import UserCreationForm # 新增
def register(request):
	if request.method == 'POST':
		form = UserCreationForm(request.POST)
		print("Errors", form.errors)
		if form.is_valid():
			form.save()
			return redirect('')
		else:
			return render(request, 'registration/register.html', {'form':form})
	else:
		form = UserCreationForm()
		context = {'form': form}
		return render(request, 'registration/register.html', context)

接著在 top-level 的 urls.py 修改

from .views import test, register # 新增 register
from django.contrib import admin
from django.urls import path, include
from .views import test, register
from django.views.generic.base import TemplateView
urlpatterns = [
	...略
    path('register/', register, name='register'),
]

最後在 templates 底下的 registration 新增 register.html

{% extends 'base.html' %}
{% block title %}
Register Page
{% endblock title %}
{% block content %}
  <form method="POST">
      {% csrf_token %}
      {{ form.as_p }}
      {% for field in form %}
        {{ field.errors }}
      {% endfor %}
  <button type="submit">送出</button>
{% endblock content %}

就可以有這樣子的畫面囉!
https://ithelp.ithome.com.tw/upload/images/20181029/201118296hsKQ0vVjQ.jpg

接著試著輸入資料來測試你可以發現,檢查欄位的工作 Django 都幫你做好了,是不是很方便呢 ^^

https://ithelp.ithome.com.tw/upload/images/20181029/201118299RvmQnRpd1.jpg

密碼設定得不夠好也是
https://ithelp.ithome.com.tw/upload/images/20181029/20111829ivNaaBZczr.jpg

只要註冊成功,就會將註冊帳密儲存至資料庫,並把畫面轉換至根目錄 redirect('/')

https://ithelp.ithome.com.tw/upload/images/20181029/20111829QfQpxaXedl.jpg

接著,我們透過 管理員 來看是否成功建立這個使用者!
https://ithelp.ithome.com.tw/upload/images/20181029/20111829lUEtI5vjSo.jpg

你就可以發現我們註冊成功囉! (歡呼
因此我們也能夠在登入介面完成登入的動作囉!
https://ithelp.ithome.com.tw/upload/images/20181029/20111829JXMWmM8w8S.jpg


今日小節

今天透過昨天的登入/登出介面,再進階跟大家說明註冊的功能,如此一來,我們的登入介面的功能又更齊全一點囉!

今天就先講到這,我們明天見 ^^!

]]> Percy 2018-10-29 21:08:27
Day28 : Django auth - 登入/登出 https://ithelp.ithome.com.tw/articles/10206063?sc=rss.iron https://ithelp.ithome.com.tw/articles/10206063?sc=rss.iron 前言

今天來說些實際會應用的功能吧!

Django 提供了相當方便的 auth 介面,可以讓我們更快速的完成 login/logout 的功能

廢話...]]> 前言

今天來說些實際會應用的功能吧!

Django 提供了相當方便的 auth 介面,可以讓我們更快速的完成 login/logout 的功能

廢話不多說,我們直接進入正題吧!


正文開始

如果你還有印象的話,在一開始我們介紹 settings 的時候,就有看到許多預設的設定

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'vendor.apps.VendorConfig', # extra
    'welcome.apps.WelcomeConfig', # extra
]

而最下面那兩個 apps 是在前幾天介紹其他 Django 的功能時所建立的,所以實際上在我們創建好一個專案時,Django在這個INSTALLED_APPS就已經有六個內建的設定,那我們今天要說的是 django.contrib.auth

要使用 Django 內建的 auth 相當的簡單,只要在 urls.py

# 新增 urls.py 再進行修改
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
	... 略
    path('accounts/', include('django.contrib.auth.urls')), # 新增
]

對於 include 而言, django.contrib.auth.urls 就另一個檔案,以下是它的原始碼

from django.contrib.auth import views
from django.urls import path

urlpatterns = [
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.LogoutView.as_view(), name='logout'),

    path('password_change/', views.PasswordChangeView.as_view(), name='password_change'),
    path('password_change/done/', views.PasswordChangeDoneView.as_view(), name='password_change_done'),

    path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
    path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]

從上面的原始碼我們可以知道,再我們定義好了 path('logout/', views.LogoutView.as_view(), name='logout'),等同於是額外新增了以下這些路徑可以使用

accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']

這些官網也都有提到,但貼在這讓大家充分了解

當然你也可以自行定義這個網只要導向哪裡,這部分就讓大家自行去了解了 (偏題了

接著只要新增我們的 template 就可以了 (((蛤,你在跟我開玩笑?? 這麼快?

沒錯,就是這麼快 ^^"

但是新增這一個 template的位置及名稱是有限制的,要在 top-level 的 template 底下新增 registration 然後新增 login.html

# template/registration/login.html
{% extends "base.html" %}
{% block title %}
Login
{% endblock title %}
{% block content %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
  <tr>
    <td>{{ form.username.label_tag }}</td>
    <td>{{ form.username }}</td>
  </tr>
  <tr>
    <td>{{ form.password.label_tag }}</td>
    <td>{{ form.password }}</td>
  </tr>
</table>
<input type="submit" value="login">
</form>
{% endblock content %}

然而,當使用者成功登入以後,我們要幫他轉網址,這時候只要到 settings 新增

LOGIN_REDIRECT_URL = '/'

即可!

說實在話,到目前為止我都還沒有在初始化面建立網頁,我們就來建一下吧!

from django.contrib import admin
from django.urls import path, include
from .views import test
from django.views.generic.base import TemplateView # 新增
urlpatterns = [
	...略
    path('accounts/', include('django.contrib.auth.urls')),
    path('', TemplateView.as_view(template_name='home.html')) # 新增
]

透過 TemplateView 這個 CBV,我們能夠直接把 template的屬性加在後方直接使用!

{% extends "base.html" %}
{% block title %}
HomePage
{% endblock %}
{% block content %}
{% if user.is_authenticated %}
  Hello, {{ user.username }}!
{% else %}
  <p>You are not logged in</p>
  <a href="{% url 'login' %}">Login Page</a>
{% endif %}
{% endblock %}

在完成登入之後,就可以看到這個畫面
https://ithelp.ithome.com.tw/upload/images/20181028/20111829McdhOr696x.jpg

接著就只剩下登出的動作囉,我們要做的事情跟登入,它需要轉網址的設定,最簡單的方式就是在 settings 新增

LOGOUT_REDIRECT_URL = '/'

並將 home.html 修改成這樣

{% extends "base.html" %}
{% block title %}
HomePage
{% endblock %}
{% block content %}
{% if user.is_authenticated %}
  Hello, {{ user.username }}!</br>
  <a href="{% url 'logout' %}">Logout</a><br>
{% else %}
  <p>You are not logged in</p>
  <a href="{% url 'login' %}">Login Page</a>
{% endif %}
{% endblock %}

這是登出後的畫面
https://ithelp.ithome.com.tw/upload/images/20181028/20111829DtiK7XB8Rq.jpg

這樣就完成了一個陽春的登入及登出


今日小節

今天帶大家了解 Django 內建的 auth 所提供的強大能力,大家只要再費點功夫,把畫面改成自己喜歡的樣子,而我只是帶大家了解如何操作,所以就沒有在 html、css 下太多工夫囉 ^^"

今天介紹到這邊,我們明天見~

]]> Percy 2018-10-28 22:29:15
Day27 : CBV vs. FBV https://ithelp.ithome.com.tw/articles/10205779?sc=rss.iron https://ithelp.ithome.com.tw/articles/10205779?sc=rss.iron 邁入Day27!

今天不說更深入的內容,來講點古吧!

在這段時間,我們從最一開始的FBV - Function-Based View,講到了...]]> 邁入Day27!

今天不說更深入的內容,來講點古吧!

在這段時間,我們從最一開始的FBV - Function-Based View,講到了更方便開發的 CBV - Class-Based View

小弟我一開始邁入 CBV 的當下,除了看教學直接使用,見識它的強大之外,更是激起了我對 CBV 的求知慾

而當下我就在想,感覺就是會有 CBV 及 FBV 兩派的支持者,於是我就去 google 了,果然不出我所料

讓我們看看 Vitor Fretas 怎麼說

對於小弟我而言...,第一次接觸的就是 Django 2.1,雖然這段時間在找資料的時候也有發現 Django 2 & 1 之間的差異性,但如果要討論到 CBV...

就要追朔到 Django 1.3! 因為 Django CBV 是在 1.3版之後才推出的,那麼也就是說老一輩的前輩們都是運用著 FBV在操作邏輯

在我第一次接觸 FBV,讓我覺得 : WOW,這寫法怎麼這麼簡單,就可以達到我的目的

  • 簡單
  • 容易理解
  • 直觀

這三點就夠足以凸顯 FBV 的特色了,但相對的,寫了這麼多的 FBV,慢慢你會發覺都是在做這些事情

  1. 根據 request 決定邏輯
  2. 做處理 (get or create ...)
  3. 回傳 context

程式碼演示 :

def vendor_create_view(request):
    form = RawVendorForm(request.POST or None)
    if form.is_valid(): # 決定邏輯
        Vendor.objects.create(form.cleaned_data) # 作處理
        form = RawVendorForm()
        # return redirect('/vendor/')
    context = {
        'form' : form
    }
    return render(request, "vendors/vendor_create.html", context) # 回傳空的表單

也因為重複性的作這件事情,所以才衍生出了 CBV

CBV 之所以會被大家稱讚,最主要的原因不外乎是因為 類別的延展性,這樣的特性也遵循著 DRY 的性質

雖然 CBV 提供了我們相當足夠的模板來完成事情,但這對於剛接觸 CBV 的 新手而言,真的是一個!
如果只是想作出非常簡易功能的網站,或許還能夠應付得來,但是當要開始客製化自己的功能時,勢必要去做覆寫的動作,然而往往悲劇就是從這一刻開始產生的

為了解決這個問題問題,除了上網找資料以外,也無可避免地要去看 Source Code 到底在搞什麼把戲,於是乎你會發現,這棵~~繼承樹好大顆~~

不過這些 one-time pain,只要結束之後,對於 master CBV 的路已經不遠了,只是這段痛持續的時間不會太短就是了^_^"

最後,究竟是 CBV 比較好還是 FBV比較好,這並沒有一個標準答案,要看需求來作決定

那這邊引用 Vitor Fretas提供的適合用在 CBV 及 FBV 的案例

  • CBV : 你想要的功能在某一個CBV都提供了(e.g. ListView),而你純粹覆寫一些屬性,CBV比較適合
  • FBV : 同時處理多個表單,FBV比較適合!

最後是 Django Documentation 提到的一些建議

If you find you’re struggling to implement your view as a subclass of a generic view, then you may find it more effective to write just the code you need, using your own class-based or functional views.


今日小節

今天主要帶大家了解CBVFBV各自的特色及使用時機,雖然沒有身處過純 FBV 的版本,但是它依舊在撰寫邏輯上能帶給我們相當大的幫助 ^^

今天就這樣,明天見~

]]> Percy 2018-10-27 23:52:54
Day26 : Django - CBV (續) https://ithelp.ithome.com.tw/articles/10205365?sc=rss.iron https://ithelp.ithome.com.tw/articles/10205365?sc=rss.iron Day25 所提到的 Class-B...]]> Day25 所提到的 Class-Based Views (CBV),我們知道如何透過 Django 提供的模板來更快速地完成我們想要的網頁,今天我們就再繼續來聊聊 CBV 其他的模板吧!

今天要介紹的有

  • CreateView
  • UpdateView

廢話不多說,就讓我們直接進入主題吧 ^^


CreateView

首先,我們先在 views.py 建立一個 VendorCreateView:

from django.views.generic import CreateView
class VendorCreateView(CreateView):
    # form_class = VendorModelForm
    model = Vendor
    fields='__all__'
    template_name = 'vendors/vendor_create.html'

而我們建立的這一個 View 的方法有種 :

  • model + fields : 透過 我們所想要使用的 資料資料欄位來建立表格,透過我上面的範例,可以建立如下圖的view
    https://ithelp.ithome.com.tw/upload/images/20181026/20111829mi59RoDhGY.jpg
    如果把 fields 改成
fields= ['vendor_name', 'store_name']

https://ithelp.ithome.com.tw/upload/images/20181026/20111829PAFcQCMkxh.jpg
相信大家就一目瞭然了吧 ^^
下會附上 model 及 template 程式碼 供大家對照上面的範例

# model - Vendor
class Vendor(models.Model):
	vendor_name = models.CharField(max_length = 20) # 攤販的名稱
	store_name = models.CharField(max_length = 10) # 攤販店家的名稱
	phone_number = models.CharField(max_length = 20) # 攤販的電話號碼
	address = models.CharField(max_length = 100) # 攤販的地址
    ...略

# template - vendor_create.html
{% extends "base.html" %}
{% block title %} My store {% endblock %}
{% block content%}
<h2> Vendor Create</h2>
<form method='POST'>
  {% csrf_token %}
  {{ form.as_p }}
<input type="submit" value="save" />
</form>
{% endblock %}

還有不要忘記了 urls.py 的設定!

from .views import (
    ...略
    VendorCreateView,
)
app_name = 'vendors'
urlpatterns = [
    path('', VendorListView.as_view(), name='index'),
    path('<int:pk>/', VendorDetailView.as_view(), name='vendor_id'),
    path('create/', VendorCreateView.as_view(), name='create'), # 新增
    ...略
]
  • form_class : 我們也可以透過綁定 ModelForm 的方式來更簡化撰寫的過程

更明確的說法是,其實 model 及 fields 這兩者都已經在 ModelForm 就已經撰寫完畢了,等於是你可以直接套用 ModelForm 的格式,這樣子的好處是更方便我們維護

此時我們的程式碼可以修改成這樣,VendorModelForm於下方供大家參考

# CreateView
class VendorCreateView(CreateView):
    form_class = VendorModelForm
    # model = Vendor
    # fields= ['vendor_name', 'store_name']
    template_name = 'vendors/vendor_create.html'
    
# modelForm 
class VendorModelForm(forms.ModelForm):
    class Meta:
        model = Vendor
        fields = '__all__'
            # fields = (
            #         'vendor_name',
            #         'store_name',
            #         'phone_number',
            #         'address',
            # )
        labels = {
            'vendor_name': _('攤販名稱'),
            'store_name' : _('店名'),
            'phone_number' : _('電話'),
            'address' : _('地址'),
        }

如此一來,短短的幾行程式碼,就能夠完成我們大部分的事情
https://ithelp.ithome.com.tw/upload/images/20181026/20111829oHtBXNNW1f.jpg

然後我們馬上就來新增資料吧! 然後這裡示範一個錯誤給大家了解
https://ithelp.ithome.com.tw/upload/images/20181026/201118293ZsOVLCgRU.jpg

在我們資料進行驗證,成功並儲存了之後,你必須要告訴它要轉到哪一個網址,所以在 model 必須定義 get_absolute_url(self)

# model - vendor
def get_absolute_url(self):
		return reverse("vendors:vendor_id", kwargs={"id": self.id})

我們再次試著儲存資料
https://ithelp.ithome.com.tw/upload/images/20181026/201118291gVDsxqqOk.jpg

然後就會成功轉到`127.0.0.1/int:pk/去囉 ^^
https://ithelp.ithome.com.tw/upload/images/20181026/20111829cEG7m61eXK.jpg

UpdateView

有了CreateView,要完成一個簡易的 UpdateView 會相當的簡單,我們一樣在 views.py 新增 VendorUpdateView

# from django.views.generic import UpdateView # 新增
class VendorUpdateView(UpdateView):
    form_class = VendorModelForm
    template_name = 'vendors/vendor_create.html'
    queryset = Vendor.objects.all() # 這很重要

之所以要加上 queryset 的原因是因為

UpdateView 會呼叫 get_object()

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(BaseUpdateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(BaseUpdateView, self).post(request, *args, **kwargs)

但是 CreateView 並不會呼叫 get_object()

    def get(self, request, *args, **kwargs):
        self.object = None
        return super(BaseCreateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = None
        return super(BaseCreateView, self).post(request, *args, **kwargs)

要更了解 Django 內建的類別,就要開始看 Source code,這些都是 one-time pain,有機會就把類別拿起來看吧 ^^

接著再將 urls.py 加入此行即可

from .views import (
    ...略
    VendorUpdateView,
)
app_name = 'vendors'
urlpatterns = [
    ...略
    path('<int:pk>/update/', VendorUpdateView.as_view(), name='update'), # 新增
    ...略
]

如此一來就能夠修改view囉!
https://ithelp.ithome.com.tw/upload/images/20181026/20111829Op64rw0782.jpg


本日小節

今天延後昨天的議題 CBV,CBV固然方便,但是因為繼承了很多類別,所以想要更深入的了解如何操作,就勢必要去看 Source code囉!

希望今天的內容對你有幫助 ^^ 我們明天見

哪怕路再長,哪怕受再多的傷,哪怕前方有阻擋,也要照亮 From 南西肯恩

]]>
Percy 2018-10-26 17:56:29
Day25 : Django Class-Based Views (CBV) https://ithelp.ithome.com.tw/articles/10205130?sc=rss.iron https://ithelp.ithome.com.tw/articles/10205130?sc=rss.iron 經過了這麼長的一段時間,相信你對 Django 基礎的運作模式一定相當熟悉了,大致上就這四步

  1. Model
  2. View
  3. Template...]]> 經過了這麼長的一段時間,相信你對 Django 基礎的運作模式一定相當熟悉了,大致上就這四步

    1. Model
    2. View
    3. Template
    4. Url

    有了 Model,等於是擁有了資料庫雛形,接著在 View 處理 商業邏輯,取得我們想要顯示的資料,然後在 Template決定我們資料要怎麼擺飾! 最後再將 網址view 作對應

    使用 Django 來建一個網頁是相當輕鬆的一件事情 ^^

    而當你這麼想的時候,Django就跳出來了並說了一句
    https://ithelp.ithome.com.tw/upload/images/20181025/20111829bGM21glPNu.jpg

    因為在這之前我們在 views 所撰寫的商業邏輯都單純只是一個function,雖然使用它就可以幫我們處理相當多的事情,但是他並不具備著 類別繼承 的特性

    貼心的 Django 為了方便我們開發,都幫我們鋪好路了,提供了許多內建的 Class-Based Views,而我們只要去繼承它們,便能幫助我們完成相當多的事情,廢話不多說,直接帶大家來看範例吧!


    我們來創建兩個 Class-Based Views,分別是

    1. ListView
    2. DetailView
    # views.py
    from django.views.generic import ListView, DetailView # 新增
    
    # Create your views here.
    # 繼承 ListView
    class VendorListView(ListView):
        model = Vendor
        template_name = 'vendor/vendor_list.html'
    
    # 繼承 DetailView
    class VendorDetail(DetailView):
        model = Vendor
        # queryset = Vendor.objects.all()
        template_name = 'vendor/vendor_detail.html'
    
    • model : 我們要使用哪一個 Model,更白話就是我們要使用哪一個資料庫,它與 queryset = Vendor.objects.all()是同義的,而queryset的好處就是我們能夠拿我們想要的資料( 還記得嗎? 透過 filter 我們可以只取得部分資料),而前者 model = Vendor 是一個 簡易 (Shorthand) 的寫法
    • template_name : 我們要使用哪一個 template,命名之後才能覆寫唷!

    寫好了 template的位址,接下來要在指定的目錄寫 vendor_lst.html 及 vendor_detail.html

    # vendor_lst.html 
    {% extends 'base.html' %}
    {% block content %}
    {% for vendor in object_list %}
     <div class="">
       <h2><a href="{% url 'vendors:vendor_id' vendor.id %}">{{ vendor.store_name }}</a></h2>
       <p>{{ vendor.vendor_name }}</p>
     </div>
    {% endfor %}
    {% endblock content %}
    
    
    # vendor_detail.html
    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    <h1> 店家 : {{ vendor.vendor_name}} </h1>
    <ul>
      <li>店名 : {{ vendor.store_name}}</li>
      <li>電話 : {{ vendor.phone_number}}</li>
      <li>地址 : {{ vendor.address}}</li>
    </ul>
    {% endblock %}
    

    這裡你要注意的地方有 :

    • object_list: 當我們使用 Listview,它會回傳 object_list,所有的 Vendor 資料都會存在裡面,又因為它是 queryset 的型態,所以我們必須使用 for-loop 來存取它
    • vendor_detail.html : 存取DetailView的方式比較特別,你可以使用 object or 小寫的 Model name - vendor

    接著修改一下 urls.py

    urlpatterns = [
        path('', VendorListView.as_view(), name='index'),
        path('<int:id>/', VendorDetailView.as_view(), name='vendor_id'),
    
    • as_view() : 當你使用了內建的類別,就是要透過as_view()的方式,讓它變得跟原本呼叫函式一樣,也就是 views.<somefunctions> 之類的

    如此一來你便能透過 ListView 及 DetailView 更簡易的來完成之前我們所做的事情囉 ^^"
    https://ithelp.ithome.com.tw/upload/images/20181025/20111829VQSSKsnuuf.jpg
    https://ithelp.ithome.com.tw/upload/images/20181025/20111829LlygZnlPzw.jpg


    今日小節

    今天主要跟大家介紹更多 Django 幫我們造好的輪子,雖然說它真的很方便,不過相對的,因為它是由一大堆的類別繼承下來的一個模板,所以實際上你要記得名稱就比較多,好用的方法,但勢必有它的規則要遵循 ^^"

    ]]>
    Percy 2018-10-25 23:30:12 Day24 : 深入探討 命名空間 https://ithelp.ithome.com.tw/articles/10204796?sc=rss.iron https://ithelp.ithome.com.tw/articles/10204796?sc=rss.iron Day23 的一些命名空間的介紹,才...]]> Day23 的一些命名空間的介紹,才讓我有機會進入命名空間這一個坑,官網上所介紹的命名空間其實要來的複雜許多

    原本想要在 Day23 再額外介紹 更深入的命名空間,但是我發現內容多到可以直接上一篇新的xD

    Day23 介紹的只是一個最直觀且易懂的方法

    今天的主題原本不在計畫中,我原本不打算用這一招的

    https://ithelp.ithome.com.tw/upload/images/20181024/20111829xTDRKcGc0a.jpg

    但是頭都栽下去了,在找資料的過程中發現這一個議題,並沒有看到中文網站在介紹這個 (或許是我關鍵字下的太差...

    還是有拉,但是我還是不懂,感覺像是在看照文本翻譯的教學

    所以今天的主題著重在 深入探討 命名空間 !

    再次強調,官網講得很清楚,不過範例給得太少且用字晦澀,如果你是第一看的人,很難能夠領悟


    轉換 命名空間
    這邊我會附上官網的教學,你可以做對照,相信你能更容易領悟!

    在開始之前,先帶大家了解命名空間 其實分成兩種,我們可以分別把它們想成 本名綽號

    1. 應用命名空間 (Application Namespace) : 本名
    2. 實例命名空間 (Instance Namespace) : 綽號

    這裡要先強調一個觀念,實例命名空間必須唯一,想想看,一個人會有很多個綽號,但是你不可能幫別人取兩次相同的綽號,對吧 ^^

    接著我會透過 Day23的範例替大家說明 命名空間搜尋的規則

    # top-level urls.py
    from django.contrib import admin
    from django.urls import path, include
    from .views import test
    urlpatterns = [
        path('admin/', admin.site.urls),
        # vendors app 有兩個綽號 f-vendors 及 s-vendors
        path('welcome/', include('vendor.urls', namespace='f-vendors')),
        path('vendor/', include('vendor.urls', namespace='s-vendors')),
        path('test/', test, name='index'),
    ]
    
    # vendor/urls.py
    from django.contrib import admin
    from django.urls import path
    
    from . import views
    app_name = 'vendors'
    urlpatterns = [
        path('', views.showtemplate, name='index'),
        path('<int:id>/', views.singleVendor, name='vendor_id'),
        path('create', views.vendor_create_view, name='create'),
        path('fcreate', views.food_create_view, ),
    ]
    

    因為版面關係,這裡用 App Namespcae 代表 Application Namespace

    假如我們要搜尋的是 vendors:index,這它的搜尋規則是

    1. 先找 應用命名空間(App Namespace) - 也就是我們的 app_name,如果存在,則接下來找底下的特徵(就是path() 每一個定義的 name),而此時 app_name的特徵有 index、vendor_id、create
    2. 如果 應用命名空間底下的特徵有符合我們要搜尋的 index,它所 反轉 (reverse) 的網址便是對應到前方的'',因此網址便是 vendor/
    3. 如果找不到符合的命名空間,則接下來會試著去找 預設應用命名空間(Default App Namespace),它的概念便是去找與自己名稱相符的綽號,也就是說 實例命名空間 可以和 應用命名空間 取相同的名稱,這種情況下,這個實例命名空間就是應用命名空間的 預設應用命名空間
    4. 但如果剛好我們的綽號都沒有相符,那便是代表我們沒有預設應用命名空間,下一個步驟便是去找,所有綽號中最後一個取的綽號,也就是 s-vendors
    5. 最後一步,就是如果在第一步已經沒有找到相符應用命名空間,則會直接去找綽號 - 實例命名空間

    接下來直接帶大家看範例!

    因為第1步及第2步已經帶大家了解過了,所以我們直接說明 3~5

    我創建一個 test.html 來帶大家了解,可以搭配著 urls.py 來看

    # top-level urls.py
    from django.contrib import admin
    from django.urls import path, include
    from .views import test
    urlpatterns = [
        ...略
        # vendors app 有兩個綽號 f-vendors 及 s-vendors
        path('welcome/', include('vendor.urls', namespace='f-vendors')),
        path('vendor/', include('vendor.urls', namespace='s-vendors')),
        path('test/', test, name='index'),
    ]
    
    <p> vendors:index :</p> {% url 'vendors:index' %}<br/>
    

    因為此時我的 {% url 'vendors:index' %} 不是建立在 app_name - vendors底下,所以找不到符合的應用命名空間(3),所以會用最後一個被綁定的實例空間 - s-vendor

    此時我們的網頁上就會秀出 /vendor/
    https://ithelp.ithome.com.tw/upload/images/20181024/20111829yCNrG67c0j.jpg

    而當你調皮搗蛋 交換了綽號的位置

    urlpatterns = [
        ...略
        # 互換位置
        path('vendor/', include('vendor.urls', namespace='s-vendors')),
        path('welcome/', include('vendor.urls', namespace='f-vendors')),
        path('test/', test, name='index'),
    ]
    

    此時我們的網頁上就會秀出 /welcome/
    https://ithelp.ithome.com.tw/upload/images/20181024/201118293Z9dO3fliv.jpg

    也就是說! 因為找不到 預設的應用命名空間,所以它就用最後一個被綁定的!
    但此時如果你又將 test.html 改成這樣

    urlpatterns = [
        ...略
        # 出現了 預設應用命名空間
        path('vendor/', include('vendor.urls', namespace='vendors')),
        path('welcome/', include('vendor.urls', namespace='f-vendors')),
        path('test/', test, name='index'),
    ]
    

    我們網頁就會重新秀出 /vendor/,因為 預設應用命名空間 出現了,所以它就先採納 預設應用命名空間,而不是 最後一個被綁定的!
    https://ithelp.ithome.com.tw/upload/images/20181024/20111829QdFf2mqJcT.jpg

    最後,我們將 test.html 修改成這樣

    <p>s-vendors:index :</p> {% url 's-vendors:index' %}<br/>
    <p>f-vendors:index :</p> {% url 'f-vendors:index' %}<br/>
    

    此時我們的網頁上就會秀出
    https://ithelp.ithome.com.tw/upload/images/20181024/201118291JzP8Vo5TI.jpg

    因為 應用命名空間 找不到,所以就改用 實例命名空間 去找囉!

    那又如果說... 你不替 實例命名空間 取綽號

    urlpatterns = [
        ...略
        #不定義 綽號 - 實例命名空間,則主動被授予 應用命名空間
        path('vendor/', include('vendor.urls')),
        path('welcome/', include('vendor.urls')),
        path('test/', test, name='index'),
    ]
    

    那麼 Django 會自動將自己的 應用命名空間 給它們當 實例命名空間,但是 Django 會警告你不要這麼做! 因為此時這兩個傢伙 使用相同的綽號 - vendors

    WARNINGS:
    ?: (urls.W005) URL namespace 'vendors' isn't unique. You may not be able to reverse all >URLs in this namespace

    所以此時大家都擁有 預設應用命名空間,而我們的規則是,誰有 預設命名空間,誰就會被使用

    <p> vendors:index :</p> {% url 'vendors:index' %}<br/>
    

    https://ithelp.ithome.com.tw/upload/images/20181024/20111829ST6sRBMf3y.jpg

    呼,此時的你問了 : 這樣的命名有什麼幫助? 就拿官網的投票網站來做說明吧!
    它可以應用在區別 使用者 及 管理者,也就是說 投票系統都做在一起,但是透過不同的身分(也就是不同的權限!)去瀏覽相同的網頁,而不用說要為不同身分去作兩個投票系統 ^^"


    今日小節

    今天我們深入探討了命名空間,主要覺得這議題也蠻重要的,雖然遠遠不及單純的使用 app_name:xxxxx 來的直觀,不過多學點總不是壞事 ^^,希望這對你有幫助!

    如果這篇 命名空間 的介紹有誤的,歡迎各方好手指點,感恩~

    From BY

    ]]>
    Percy 2018-10-24 22:30:22
    Day23 : Django 命名空間 https://ithelp.ithome.com.tw/articles/10204346?sc=rss.iron https://ithelp.ithome.com.tw/articles/10204346?sc=rss.iron 今天要延續 Day22 所提到的內文,將...]]> 今天要延續 Day22 所提到的內文,將 url 冠上它各自的綽號 name

    urlpatterns = [
        path('', views.showtemplate, name='index'),
        path('<int:id>/', views.singleVendor, name='vendor_id'),
        path('create', views.vendor_create_view, name = 'create'),
        ...略
    ]
    

    從上面的內容我們可以將 name 用熟悉的綽號代替

    • '' : 小美
    • '<int:id>' : 阿明
    • 'create' : 小鐵

    但是此時你的你應該查覺到的!!

    在我人生中,相同綽號的不只一個人阿 !! 沒有錯,就算是 小美、阿明或是小鐵 待在 河馬班,但是不代表說長頸鹿班就不會有小美、阿明或是小鐵阿!

    https://ithelp.ithome.com.tw/upload/images/20181023/20111829PjOz5SVQmF.jpg
    (小美 : 關我屁事

    或許,我們能將能將綽號取的更有獨特性一點 惣流·明日香·蘭格雷

    https://ithelp.ithome.com.tw/upload/images/20181023/20111829UsXmneDnY6.jpg

    挖哩咧,要記住這名字,跟要我背出每間學校的IP位址一樣困難

    因此,為了避免 name 重複性的問題,我們勢必要在該 urls.py 加上 班級名稱,而在 Django 裡面,我們稱作它為 app_name

    也就是說,我們的 urls.py 要修改成這樣

    from django.contrib import admin
    from django.urls import path
    
    from . import views
    app_name = 'vendors' # 新增
    urlpatterns = [
        path('', views.showtemplate, name='index'),
        path('<int:id>/', views.singleVendor, name='vendor_id'),
        path('create', views.vendor_create_view, name='create'),
        ...略
    ]
    

    透過這個方法我們就能避免重複的名字發生,並且可以知道,這是屬於河馬班的阿明 還是 長頸鹿班的小美 ^^

    等一下....! 修改還沒完成

    如果你還記得,這個 url name 最初就是要給 reverse 函式作轉換用的,所以原先的 Model的 reverse也要修改,不然 Django 就會跟你抱怨你只告訴他對象,而沒有告訴她班級唷!

    class Vendor(models.Model):
    	vendor_name = models.CharField(max_length = 20) # 攤販的名稱
    	store_name = models.CharField(max_length = 10) # 攤販店家的名稱
    	phone_number = models.CharField(max_length = 20) # 攤販的電話號碼
    	address = models.CharField(max_length = 100) # 攤販的地址
    
    	def get_absolute_url(self):
    		return reverse("vendors:vendor_id", kwargs={"id": self.id}) # 修改
    

    如此一來,網頁就能正常顯示囉!
    https://ithelp.ithome.com.tw/upload/images/20181023/20111829J7ER7DLX2K.jpg

    今日小節

    今天內容相對簡單,大家應該都能秒懂,不過後續還有一些命名空間的心得分享,只好等我一更囉 ^^"

    1024 一更, 更新內容太多,直接新增一篇在 Day 24 囉!

    ]]>
    Percy 2018-10-23 23:36:34
    Day22 : Django urls name https://ithelp.ithome.com.tw/articles/10204069?sc=rss.iron https://ithelp.ithome.com.tw/articles/10204069?sc=rss.iron Day 20 ,我們學會了如何透過 ...]]> Day 20 ,我們學會了如何透過 path() 來動態的產生更多的網址

    但在實作上,我們知道網路位址為只在哪,但是使用者並不曉得!

    因此我們可以在前端網頁加上超連結,讓使用這可以透過連結到其他頁面

    這裡先示範 hard code 的寫法

    # vendor template 底下的 detail.html
    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    {% for vlist in vendor_list %}
      <h1> 店家 : {{vlist.vendor_name}} </h1>
      <p> <a href="/vendor/{{vlist.id}}"> More detail...</a> </p>
    {% endfor %}
    {% endblock %}
    
    # 對應 views 的 showtemplate
    def showtemplate(request):
        vendor_list = Vendor.objects.all()
        context = {'vendor_list': vendor_list}
        # print(vendor_list)
        return render(request, 'vendors/detail.html', context)
    

    另一種方法則是,我們都知道 vlist 參數是一個物件,也就是透過 Model - Vendor 所創建出來的,因此我們能在該 Model 下創建一個函式,也就是說

    class Vendor(models.Model):
    	vendor_name = models.CharField(max_length = 20) # 攤販的名稱
    	store_name = models.CharField(max_length = 10) # 攤販店家的名稱
    	phone_number = models.CharField(max_length = 20) # 攤販的電話號碼
    	address = models.CharField(max_length = 100) # 攤販的地址
        
        # 新增
    	def get_absolute_url(self):
    		return f"/vendor/{self.id}/"
            
    # 並將 detail.html 修改如下
    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    {% for vlist in vendor_list %}
      <h1> 店家 : {{vlist.vendor_name}} </h1>
      <p> <a href= {{ vlist.get_absolute_url }}> More detail...</a> </p>
    {% endfor %}
    {% endblock %}
    

    之後下 python manage.py runserver,上面這兩種方法,你都能夠看到這樣的頁面
    https://ithelp.ithome.com.tw/upload/images/20181022/20111829fwfLMS1vCV.jpg

    雖然這樣能夠成功完成我們要的功能,不過千萬不要這麼做!! 這會讓你之後維護到 咪咪帽帽

    因此,今天要告訴大家的方法是,大家如果還有印象,我們撰寫 urlspatterns 時,會有一個 name 的欄位

    # 回顧 urls.py
    urlpatterns = [
        path('', views.showtemplate),
        path('<int:id>/', views.singleVendor, name='vendor_id'), # 這一行
        path('create', views.vendor_create_view),
        path('fcreate', views.food_create_view),
    ]
    

    這一個 name 的概念就是,為這一個 path 命名,也就是說以後在別的地方使用這一個名稱 vendor_id,再將其反轉換 reverse(),便能夠方便的讓我們命名以及維護一個網址

    那我們要如何操作呢?

    讓我們延用剛剛定義的 get_absolute_url 吧!

    from django.urls import reverse # 新增
    
    # 將 get_absolute_url 修改如下
    	def get_absolute_url(self):
    		return reverse("vendor_id", kwargs={"id": self.id})
    

    第一個參數要擺的便是我們定義的 urls name - vendor_id,且因為在 urlspattern 裡面有規定要吃一個 整數 id,因此後方則是要將額外的參數 (kwargs) 一起帶進這一個 reverse() 函式

    urlpatterns = [
        path('<int:id>/', views.singleVendor, name='vendor_id'),
        ...略
    ]
    

    你說這樣子的寫的好處在哪,好處就是 今天需要修改網址位址,只要修改 urlspattern 即可,也就是說

    # 我在 網址前方多加了 haha
    urlpatterns = [
        path('haha<int:id>/', views.singleVendor, name='vendor_id'),
        ...略
    ]
    

    超連結的後方也會自動加上 haha!
    https://ithelp.ithome.com.tw/upload/images/20181022/20111829h7bS7G9xTd.jpg

    因為此時此刻,我們得到網址的方法是跟著 path name 綁定,所以 只要 不動到 <int:id> , Django 會動態的幫你處理超連結綁定的工作唷! 就再也不會因為 hard code 而苦惱拉 ^^"


    今天小節

    我們今天談到了 path 裡面的 name 如何結合 reverse 來達到 動態網址的效果,不再因為 hard-coded 的方式而讓你苦惱,reverse 可以傳入的參數不只是 kwargs, args 也可以,這部分就留給大家去研究囉!

    今天講到這,我們明天見 ^^

    import this ... the zen of Python!

    ]]>
    Percy 2018-10-22 15:37:27
    Day 21 : 網頁例外 https://ithelp.ithome.com.tw/articles/10203801?sc=rss.iron https://ithelp.ithome.com.tw/articles/10203801?sc=rss.iron 繼 Day20 ,透過path的設定,我們現在有了更多的網頁

    但是在實作上,可能會因為找不到該網站而發生錯誤,因此我們在撰寫程式的同時,也要考量到發生例外的狀況,因此 Day20 ...]]> 繼 Day20 ,透過path的設定,我們現在有了更多的網頁

    但是在實作上,可能會因為找不到該網站而發生錯誤,因此我們在撰寫程式的同時,也要考量到發生例外的狀況,因此 Day20 的程式碼還需要改良

    而當我們在瀏覽網頁的同時,若是該網頁不存在,就會發生 HTTP 404

    對於這方面的處理,Django提供了一個方便的方法,能夠讓我們處理發生 HTTP 404 的問題

    因此 Day20 的 singleVendor 函式 要修改成如下

    # 額外 import Http404
    from django.http import Http404
    
    def singleVendor(request, id):
        try:
            vendor_list = Vendor.objects.get(id=id)
        except Vendor.DoesNotExist:
            raise Http404
        context = {
            'vendor_list': vendor_list
        }
        return render(request, 'vendors/vendor_detail.html', context)
    

    如此一來就能夠正常的顯示HTTP 404
    https://ithelp.ithome.com.tw/upload/images/20181021/20111829dwcbc013cj.jpg
    不過這跟實際上看到的 404 還是不一樣,因為 Django 目前還處在 Debug 的模式,也就是說你能夠在 settings.py 看到這個設定

    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = True
    

    因此我們要去修改 Debug 模式

    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = False
    

    不過同時,你也要將允許的網域加在 settings.py 的 ALLOWED_HOSTS,這裡因為只要測試功能,所以我們可以改成這樣,所以實際上的設定值就要取決於你的網站而做設定囉!

    ALLOWED_HOSTS = ['*']
    

    接著再來看網頁的話,你就可以看到正常的 HTTP 404 頁面
    https://ithelp.ithome.com.tw/upload/images/20181021/20111829u7WThioLGn.jpg

    因此當網頁要上線的時候,務必要將 Debug模式 關掉呀!!
    ALLOWED_HOSTS = ['*']是個壞榜樣 好孩子不要亂學

    但是我們也一直強調了,Django 的中心思想就是 DRY (Don't Repeat Yourself),因此 Django 另外還提供了一個更方便的方法 get_object_or_404

    從字面上,我們就可以知道它的意思就是

    • 取得一個物件 否則
    • 觸發 HTTP 404

    也就是說,你能夠將程式碼修改成這樣

    from django.shortcuts import get_object_or_404 # 新增
    
    def singleVendor(request, id):
        vendor_list = get_object_or_404(Vendor, id=id)
        # try:
        #     vendor_list = Vendor.objects.get(id=id)
        # except Vendor.DoesNotExist:
        #     raise Http404
        context = {
            'vendor_list': vendor_list
        }
        return render(request, 'vendors/vendor_detail.html', context)
    

    如此一來 Django 就幫你完成了例外處理的工作,勢必幫你在開發上省掉很多這種例外的時間呢! ^^

    當然你有時間,也別忘了去看看 get_object_or_404 的原始碼 ( 增廣見聞

    def get_object_or_404(klass, *args, **kwargs):
        """
        Use get() to return an object, or raise a Http404 exception if the object
        does not exist.
    
        klass may be a Model, Manager, or QuerySet object. All other passed
        arguments and keyword arguments are used in the get() query.
    
        Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
        one object is found.
        """
        queryset = _get_queryset(klass)
        if not hasattr(queryset, 'get'):
            klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
            raise ValueError(
                "First argument to get_object_or_404() must be a Model, Manager, "
                "or QuerySet, not '%s'." % klass__name
            )
        try:
            return queryset.get(*args, **kwargs)
        except queryset.model.DoesNotExist:
            raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
    

    若是你今天要處理的並不只是一個物件, Django 也有幫你準備 get_list_or_404

    而你可以很容易就理解兩者之間的差異

    • get_object_or_404 : 透過 get 取得一個物件
    • get_list_or_404 : 透過 filter 取得一些物件

    今日小節

    今天帶大家了解,Django 如何在撰寫網頁時,更方便地處理這一些例外,而在使用這些好用的函式時,有空去追個 code也是一件相當有趣的事情^^

    今天介紹到這邊,我們明天見

    祈福 台鐵乘客 及 救難國軍 平安

    ]]> Percy 2018-10-21 23:35:57
    Day20 : 動態網址 - path 應用 https://ithelp.ithome.com.tw/articles/10203370?sc=rss.iron https://ithelp.ithome.com.tw/articles/10203370?sc=rss.iron Day13 我們已經見識到的 Dja...]]> Day13 我們已經見識到的 Django 2 中 path 的強大,它可以讓我們不須透過regex的方式,便能幫助我們處理掉許多網址對應的問題

    我們今天就來看看要如何實際操作這個傢伙吧 ^^


    [正文開始]

    這是我們原先 urls.pyviews.py 的模樣

    # urls.py
    from django.contrib import admin
    from django.urls import path
    
    from . import views
    
    urlpatterns = [
        path('', views.showtemplate),
        ...略
    ]
    
    # views.py
    def showtemplate(request):
        vendor_list = Vendor.objects.all()
        context = {'vendor_list': vendor_list}
        # print(vendor_list)
        return render(request, 'vendors/vendor_detail.html', context)
    

    上面所做的事情,是將所有 Vendor 的資料顯示到 vendor_detail.html 裡面

    那今天要在單獨的介面顯示一筆一筆的資料,那要怎麼完成呢?

    一開始我們要將 urls.py 加上這一段,我們要將每一個攤販的 id 傳至 views 的 singleVendor

    urlpatterns = [
        path('<int:id>/', views.singleVendor, name='vendor'),
        ...略
    ]
    

    接著到 views 新增 singleVendor 的 函式

    def singleVendor(request, id):
        vendor_list = Vendor.objects.get(id=id)
        context = {
            'vendor_list': vendor_list
        }
        return render(request, 'vendors/vendor_detail.html', context)
    

    在這裡要注意到的是,在函式除了 request,還多加了一個 id,而這個參數是對應到 urls.py 裡面 <int:id>

    也就是說!! urls 裡面放置的參數,必須要與我們函式傳入的參數相同!!

    https://ithelp.ithome.com.tw/upload/images/20181020/201118293UYBRSeVZF.png

    如果你使用了不一樣的參數名稱,也就是說把 urls.py 的 id 改成 my_id

    path('<int:my_id>/', views.singleVendor, name='vendor')
    

    那麼最後就只會被狠狠報錯一頓,因為在singleVendor函式所帶的參數不是 my_id 而是 id
    https://ithelp.ithome.com.tw/upload/images/20181020/201118294H7gOu9MXz.jpg


    接著就能來看我們的成果了

    執行 python manage.py runserver 前往 http://127.0.0.1:8000/vendor/1/

    就可以看到 ...錯誤! 什麼!?

    https://ithelp.ithome.com.tw/upload/images/20181020/20111829G0WKpN2S7A.jpg

    理由很簡單 ^^"

    我原本在 vendor_detail.html 撰寫的內容如下

    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    {% for vlist in vendor_list %}
      <h1> 店家 : {{vlist.vendor_name}} </h1>
      <ul>
        <li>店名 : {{ vlist.store_name}}</li>
        <li>電話 : {{ vlist.phone_number}}</li>
        <li>地址 : {{ vlist.address}}</li>
      </ul>
    {% endfor %}
    {% endblock %}
    

    因為今天當我並不是想要取多筆,而只想要取得一筆的時候,它只是一個 物件 而已,因此不具備可 迭代(iterable) 的特性

    因此我們的 vendor_detail.html 要修改成這樣, 也就是把 for loop 拿掉,並修改相對應的變數

    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    <h1> 店家 : {{vendor_list.vendor_name}} </h1>
    <ul>
      <li>店名 : {{ vendor_list.store_name}}</li>
      <li>電話 : {{ vendor_list.phone_number}}</li>
      <li>地址 : {{ vendor_list.address}}</li>
    </ul>
    {% endblock %}
    

    如此一來,我們便能在該頁面http://127.0.0.1:8000/vendor/1/成功取得一筆資料囉!

    https://ithelp.ithome.com.tw/upload/images/20181020/20111829gtlyaqwPA2.jpg


    今天帶大家看一個簡易的 path 使用方法,透過相對簡易的方式,便能達到想要的效果 ^^

    今天先講到這,我們明天見!!

    Simple is better than Complex From Vitor Freitas

    ]]>
    Percy 2018-10-20 14:23:45
    Day19 : Django Form https://ithelp.ithome.com.tw/articles/10203227?sc=rss.iron https://ithelp.ithome.com.tw/articles/10203227?sc=rss.iron Day18,我們講完了 Django...]]> Day18,我們講完了 Django 的 ModelForm,而其方法便是直接透過 Model 來創建 Form,讓我們能夠更方便地去處理資料庫。

    然而,有時候我們不需要直接將使用者所輸入的內容存進資料庫,而是針對使用者填入的內容做處理及判斷,最後再做出相對應的處理,e.g. 這時候我們便能夠使用 Django 所提供的 Form

    也因為我們並不是透過 Model 來創建這一個 Form 的緣故,我們 創建Form 就像 創建Model的資料庫型態 一樣

    聽起來很抽象,那直接帶大家看例子!

    我們先再 forms.py 創建一個 RawVendorForm

    # 創建一個 Raw Form
    class RawVendorForm(forms.Form):
        vendor_name = forms.CharField()
        store_name = forms.CharField()
        phone_number = forms.CharField()
        
    # Model - Vendor 
    class Vendor(models.Model):
    	vendor_name = models.CharField(max_length = 20) # 攤販的名稱
    	store_name = models.CharField(max_length = 10) # 攤販店家的名稱
    	phone_number = models.CharField(max_length = 20) # 攤販的電話號碼
    	address = models.CharField(max_length = 100) # 攤販的地址
    

    從上面的 Raw Form 與 Model 做比較,是不是覺得兩個非常相似?

    而這就跟我上面所說的是同一件事情, 創建Form 就跟 創建Model 很像

    接下來! 就跟 ModelForm 一樣,將 Form 引用到 views.py

    from .forms import RawVendorForm # 新增 RawVendorForm
    
    # 新增
    def vendor_create_view(request):
        form = RawVendorForm(request.POST or None)
        if form.is_valid():
            print(form.cleaned_data)
            form = VendorForm()
        context = {
            'form' : form
        }
        return render(request, "vendors/vendor_create.html", context)
    

    而這裡做的事情,是可以完全仿照出 ModelForm 來完成的

    完成 創建Form 以及 修改view 這兩件事情以後,我們試著填入我們的資料
    https://ithelp.ithome.com.tw/upload/images/20181019/20111829iZSlD010XQ.jpg

    只要我們所填入的資料,驗證成功之後,就會隨之產生出cleaned_data,而 form.cleaned_data的型態便是一個Dict

    {'vendor_name': 'TC', 'store_name': '130', 'phone_number': '13579'}
    

    而我們要將這個資料新增到 Model - Vendor 裡面,就要將 view 修改如下

    def vendor_create_view(request):
        form = RawVendorForm(request.POST or None)
        if form.is_valid():
            Vendor.objects.create(**form.cleaned_data) # 新增
            form = RawVendorForm()
        context = {
            'form' : form
        }
        return render(request, "vendors/vendor_create.html", contex
    

    **form.cleaned_data : 因為 create 裡面所帶的參數為 **kwargs,故傳入 Dict 就必須加上 ** 來 unpack

    form 不像是 modelform 一樣具備 save() 可以直接儲存資料的功能,所以只能透過ORM的方式來存取

    https://ithelp.ithome.com.tw/upload/images/20181019/20111829qIAAm8vsyG.jpg

    你問我怎麼沒有地址,因為我的form沒有建立地址阿xD


    關於 form 及 modelform 的使用時機,在網路上並找不到一個很明確的說法,因為大多數的事情,雙方都能夠辦的到,而目前為止我找到最有幫助的訊息就是

    All form classes are created as subclasses of either django.forms.Form or django.forms.ModelForm. You can think of ModelForm as a subclass of Form. Form and ModelForm actually inherit common functionality from a (private) BaseForm class, but this implementation detail is rarely important.

    In fact if your form is going to be used to directly add or edit a Django model, a ModelForm can save you a great deal of time, effort, and code, because it will build a form, along with the appropriate fields and their attributes, from a Model class.

    從官網上找到的資訊,可以得知 ModelForm 是來自 Form 的產物,而會有這樣的設計,最大的目的還是為了,幫你省下去做重複事情大量的時間,透過 ModelForm 你可以更方便的去操作資料庫

    今天大致上講到這裡

    今天的時間真的是擠出來的 From BY
    10/20 - 小更

    ]]>
    Percy 2018-10-19 23:54:14
    Day18 : Django ModelForm https://ithelp.ithome.com.tw/articles/10202804?sc=rss.iron https://ithelp.ithome.com.tw/articles/10202804?sc=rss.iron 到目前為止,我們已經了解 Django MVT 的 架構 以及 特色

    接下來,我們便能夠延伸這些架構,來讓我們完...]]> 到目前為止,我們已經了解 Django MVT 的 架構 以及 特色

    接下來,我們便能夠延伸這些架構,來讓我們完成更多的事情

    回想一下,現今網路這麼蓬勃發展,走到哪裡都可以看到各式各樣的文章及留言,但有時候,一篇文章底下的留言肯定是比文章內容還要精采的,有時候除看留言,你為了要帶動風向,自己也會留下足跡

    Anyway ^^"

    今天要帶大家來了解如何透過 Django 製作一個 Model Form


    [正文開始]

    我們先在 vendor/ 建立 forms

    from django import forms
    
    from .models import Vendor, Food
    
    class VendorForm(forms.ModelForm):
        class Meta:
            model = Vendor
            fields = '__all__'
            
    

    model : 我要使用哪一個 Model
    fileds : 使用 Model 的哪些欄位

    建立好之後,再到 views 對這一個 Model Form 做處理

    from .forms import VendorForm # 要記得 import 相對應的 Model Form 唷!
    
    # 針對 vendor_create.html
    def vendor_create_view(request):
        form = VendorForm(request.POST or None)
        if form.is_valid():
            form.save()
    
        context = {
            'form' : form
        }
        return render(request, "vendors/vendor_create.html", context)
    

    is_vaild : 建立在 form 底下的方法,可以用來驗證資料是否正確

    並到 templates 新增 vendor_create.html

    # 為了展示給大家看,這裡的code並不完整
    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    <h2> Vendor Create</h2>
    <form method='POST'>
      {{ form.as_p }}
    <input type="submit" value="save" />
    </form>
    {% endblock %}
    

    這裡的 form 相信大家不陌生了,它就是透過 views 回傳的 Dict,但是後方的 as_p 是新的夥伴,而它就是 Django 提供給 forms 顯示的一種方式,目前總共有 3 種方式,分別是

    • as_table
    • as_p
    • as_ul

    而它所代表的意思便是,我們的 form 要透過 HTML 的 <tr> or <p> or <li> 來裝飾

    值得注意的一點是,這個 form 只是方便大家辨識所用,但實際上它並沒有包覆著 標籤,所以這一個部分要自己完成唷!

    最後!! 還有一件重要的事情要做,要記得到 urls.py 設定 Route

    urlpatterns = [
        path('', views.showtemplate),
        path('create', views.vendor_create_view), # 新增
    ]
    

    上述的設定都完成之後,到http://127.0.0.1:8000/vendor/create便能看到這樣的畫面
    https://ithelp.ithome.com.tw/upload/images/20181018/20111829bvv77FXfmu.jpg

    蛤? 不想看到英文,那你可以這麼做 ^^"
    修改 forms

    from django.utils.translation import gettext_lazy as _ # 新增
    
    class VendorForm(forms.ModelForm):
        class Meta:
            model = Vendor
            fields = '__all__'
            # 新增 labels 對應
            labels = {
                'vendor_name': _('攤販名稱'),
                'store_name' : _('店名'),
                'phone_number' : _('電話'),
                'address' : _('地址'),
            }
    

    如此一來便能夠克服預設是顯示 變數名稱 的問題了!
    https://ithelp.ithome.com.tw/upload/images/20181018/20111829woOeOVDJBQ.jpg

    接著,你試著 新增 Vendor
    https://ithelp.ithome.com.tw/upload/images/20181018/20111829vuDJbI65my.jpg

    然後馬上就被 Django 佛逼鄧(forbidden) 了
    https://ithelp.ithome.com.tw/upload/images/20181018/20111829FvaN5o3RH6.jpg

    原因就是,為了避免發生 CSRF 的攻擊,Django提供了一個簡單的方法可以幫你做驗證

    # 新增 {% csrf_token %} 
    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    <h2> Vendor Create</h2>
    <form method='POST'>
      {% csrf_token %} 
      {{ form.as_p }}
    <input type="submit" value="save" />
    </form>
    {% endblock %}
    

    接著新增 Vendor 就不會有問題了
    https://ithelp.ithome.com.tw/upload/images/20181018/20111829R0e6JJve6n.jpg

    接著去 http://127.0.0.1:8000/vendor/ 可以看到我們的攤販就成功新增一筆了!
    https://ithelp.ithome.com.tw/upload/images/20181018/201118299ZWEY15klN.jpg

    但是你會發現,在我們新增 攤販 之後,畫面沒有作任何更動,會讓你開始懷疑.... 我剛剛有新增成功嗎?!

    其實是有成功的 ^^ (畢竟我們在 顯示介面 有看到新增一筆資料)

    但是後續的動作並沒有處理好,因此,我們應該將成功新增的資料作 清空 的動作

    修改 views

    def vendor_create_view(request):
        form = VendorForm(request.POST or None)
        if form.is_valid():
            form.save()
            form = VerdorForm() # 清空 form
    
        context = {
            'form' : form
        }
        return render(request, "vendors/vendor_create.html", context)
    

    除此一來,我們便能夠在新增完資料之後,又能將畫面清空囉 ^^


    今天主要透過我們建立的 Model 的模板 來新增一個 Model Form,而它的好處便是,我們能直接對這一個資料庫做處理,而今天我們便完成了 CREATE 的事情!

    你說,Django 相當的好用又強大,對吧!

    今天我們就講到這裡,明天見~

    每天問著自己 : 你想要什麼? 然後一步步前進,逐漸強大 From BY

    ]]> Percy 2018-10-18 22:01:38
    Day17 : Django 插拔性 https://ithelp.ithome.com.tw/articles/10202425?sc=rss.iron https://ithelp.ithome.com.tw/articles/10202425?sc=rss.iron Day14,我們提到了有關 Djan...]]> Day14,我們提到了有關 Django Template 的一些運作過程,因此我們知道 Django 會透過什麼樣的順序去找 Template 的路徑

    那我們今天要來談的是,這一個 插拔性 究竟是什麼,今天不會講到技術層面的議題,而是去探討 Django 這樣的一個特色,形成了一個什麼樣的規範。


    [正文開始]

    前兩天我們都在探討 Django Template 運作的模式,如果你弄懂了 MVT ,那麼未來當有人問起你 Django 的 MVT 是什麼? 相信現在這對你已經不是太大的問題!

    我們知道,Django 的 app 是透過功能來作區別,但當我們架設一個網站,內容及架構一定是有相對大的規模的,不同功能的 app 還是可能會有相同名稱的 template,因此我們要區別這些相同名稱,但是用在不同 app 的 template,最直觀的做法就是新增一個專屬於該 app 的資料夾

    因此,我們新增一個資料夾 vendor,將隸屬於 app - vendor的 template - detail.html,放進該資料夾裡面

    C:.
    │ ...略
    ├───templates
    │   │   base.html
    │   │
    │   └───vendor
    │           detail.html
    

    為了讓名稱更有鑑別性,我把 template - test.html 改名為 detail.html

    因為路徑有更動了,所以 views.py 也要修改

    from django.shortcuts import render
    from .models import Vendor
    # Create your views here.
    def showtemplate(request):
        vendor_list = Vendor.objects.all()
        context = {'vendor_list': vendor_list}
        # print(vendor_list)
        return render(request, 'vendor\detail.html', context)
    

    相信這路徑的更動對大家來說不會有理解上的困難,但接下來要來強調 插拔性 的重要

    插拔性的概念就是,相同的 app 在不同 project,並透過一些額外設定,依然能夠正常的運作

    但是此時, project 所建立的 template 並不會跟著 app 移動,除非它原本就在 app 裡面!

    沒錯,這便是 Django 的一個貼心設計,但是貼心設計之下,意味著你要照著它的規則走

    也就是說,在 app 底下的 template,它必須額外建立該 app 名稱的資料夾

    來看範例吧!
    在 vendor 底下 建立 templates\vendors

    ├───vendor
    │   │  ...略
    │   │
    │   ├───templates
    │   │   └───vendors
    │   │           vendor_detail.html
    

    vendor_detail.html

    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    {% for vlist in vendor_list %}
      <h1> 店家 : {{vlist.vendor_name}} </h1>
      <ul>
        <li>店名 : {{ vlist.store_name}}</li>
        <li>電話 : {{ vlist.phone_number}}</li>
        <li>地址 : {{ vlist.address}}</li>
      </ul>
    {% endfor %}
    {% endblock %}
    

    並將 views.py 修改成這樣

    from django.shortcuts import render
    from .models import Vendor
    # Create your views here.
    def showtemplate(request):
        vendor_list = Vendor.objects.all()
        context = {'vendor_list': vendor_list}
        # print(vendor_list)
        return render(request, 'vendors/vendor_detail.html', context)
    

    我這裡故意將 vendors/vendor_detail.html 打成 vendors/vendor_detail2.html 讓大家看一下錯誤訊息

    https://ithelp.ithome.com.tw/upload/images/20181017/20111829YRrMym4pRT.jpg

    在 Django 中, Template 搜尋的次序這個議題我們已經有討論過了,此時你會發現,此時多了一個順位2

    也就是說,當你在 app 新增 Templates 資料夾,Django會一併搜尋這個資料夾,這麼做的好處就是遵循 Django - DRY (Don't Repeat Yourself) 的精神,不管之後這個 app 要被重新用到哪個專案,相對應的templates也會跟著 app走

    此時,跟 外層 templates 中 vendor 資料夾,也就不需要拉 ^^

    記得名字要取 template"s",曾經的我就少一個s,就不小心入了這個坑,就對應上面我所說的 : 一個貼心設計之下,意味著你要照著它的規則走


    今天內容差不多介紹到這裡,講的是 Django 設計上的精神,希望大家都能理解 ^_^

    我們明天見

    ]]>
    Percy 2018-10-17 21:16:05
    Day16 : Template 範本 https://ithelp.ithome.com.tw/articles/10202006?sc=rss.iron https://ithelp.ithome.com.tw/articles/10202006?sc=rss.iron Day15,我們已經知道怎麼從資料庫...]]> Day15,我們已經知道怎麼從資料庫去撈資料,並將它顯示到網頁上

    到這裡,我相信大家對於 Django MVT 的架構已經不陌生了 ^_^

    那我們接著就來做更多的範例吧!

    但是在這之前,先來回想一下,你是否曾經瀏覽某一個網站時,會看到該網站的介面其實很大同小異,用IT邦幫忙的網站來做舉例,你會發現不管到哪一個網站,你都能夠看到上方那一排連結

    https://ithelp.ithome.com.tw/upload/images/20181016/20111829niKJKHKFjb.jpg

    換句話說,在該個網站,每一個界面都有這一段程式碼,但實際上在開發的時候,一直操作重複的程式碼並不是我們樂見的!

    因此我們可以透過 Django 撰寫範本,接著再讓其他 template 去繼承它 ,講這麼多,還是沒有範例來的清楚,請看範例~


    我們就先來撰寫一個簡易的範本吧!

    在 templates\ 底下建立一個範本 base.html

    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title>{% block title %} {% endblock %}</title>
      </head>
      <body>
      	{% block content %}
        {% endblock %}
      </body>
    </html>
    

    此時,你發現HTML裡面多了一個陌生人 block,其實這個block的概念就是 :
    {% block <name> %} : 定義一個區間,讓其他繼承者在去做更動

    如此一來,我們很容易地就完成了一個範本

    接著,我們要讓其它的 template去繼承它, 讓 test.html 繼承 base.thml,因此 test.html 便修改成這樣

    {% extends "base.html" %}
    {% block title %} My store {% endblock %}
    {% block content%}
    {% for vlist in vendor_list %}
      <h1> 店家 : {{vlist.vendor_name}} </h1>
      <ul>
        <li>店名 : {{ vlist.store_name}}</li>
        <li>電話 : {{ vlist.phone_number}}</li>
        <li>地址 : {{ vlist.address}}</li>
      </ul>
    {% endfor %}
    {% endblock %}
    

    {% extends %} : 就是告訴 Django 告訴 HTML,我有靠山 ,是 我繼承了別人(別忘了要把範本用兩個雙引號括號起來
    {% block <name> %} : 這個block就是去對照 base.html 的格式,將修改的內容填到block裡面唷!

    如此一來,我們再到 127.0.0.1:8000/vendor/ ,便能看到跟原本的 test.html 一模一樣的畫面囉!
    https://ithelp.ithome.com.tw/upload/images/20181016/20111829xjqx3uuzJW.jpg


    小節 :
    今天的內容相對得很好懂,不過還是用一張圖片來總結今天所說的

    https://ithelp.ithome.com.tw/upload/images/20181016/20111829R2WhIkI8tU.jpg

    透過 範本 的方式,可以幫助我們更方便的操作網頁中重複的內容

    小時候總認為時間過很慢,長大總認為時間不夠用 From BY

    ]]>
    Percy 2018-10-16 20:12:42
    Day15 : Template 的演練 https://ithelp.ithome.com.tw/articles/10201594?sc=rss.iron https://ithelp.ithome.com.tw/articles/10201594?sc=rss.iron Day14, 我們介紹了一些 Dja...]]> Day14, 我們介紹了一些 Django - Template 的運作原理,目的是為了讓大家瞭解說,Template在背後是依照什麼順序去搜尋路徑的

    而今天,我會帶大家實際看一些例子,讓大家了解 Template 與 View 之間是怎麼運作的

    就讓我們一起來挖掘 Template 的秘密吧!


    我們曾經提到, Models - 就是定義我們資料存放的型態,而且我們可以透過ORM的方式去取得,這個細節我們在 Day6 - 資料庫的創建Day9 - 使用Shell創建資料 都有介紹過

    然而我們之所以要存放資料,一部分當然是要拿來做顯示用,而這一些邏輯處理就是要寫在 View 中,也就是說,View會透過ORM的方式,去做存取資料的動作,接著再透過DICT MAPPING,去對應到我們 Template 定義的資料欄位,如此一來我們便能成功將資料顯示

    那寫得這麼臭這麼長,還是帶大家看範例,大家會更清楚

    昨天我們在 vendor/urls.py 設定好了這一個路徑,並將它指到 views.showtemplate,不過這名稱不太好,而且內容其實不太完整,我們先來修改一下內容

    # 修改前
    urlpatterns = [
        path('', views.showtemplate),
    ]
    
    # 修改後
    urlpatterns = [
        # 後方的 name 可以先忽略,目前不會用到
        path('', views.vendor_index, name="vendor_index"),
    ]
    

    然後是今天的主軸之一 views, 我們要將 vendor/views 修改成如下

    from django.shortcuts import render
    from .models import Vendor
    # Create your views here.
    def showtemplate(request):
        vendor_list = Vendor.objects.all() # 把所有 Vendor 的資料取出來
        context = {'vendor_list': vendor_list} # 建立 Dict對應到Vendor的資料,
        return render(request, 'test.html', context)
    

    在這裡要先解釋什麼是 render,簡單來說... 就是Django提供你很多方便你操作的函式,而rendor可以將我們要傳達的資料一併打包,再透過 HttpResponse 回傳到瀏覽器

    下面提供 rendor 的 source code

    def render(request, template_name, context=None, content_type=None, status=None, using=None):
        """
        Return a HttpResponse whose content is filled with the result of calling
        django.template.loader.render_to_string() with the passed arguments.
        """
        content = loader.render_to_string(template_name, context, request, using=using)
        return HttpResponse(content, content_type, status)
    

    from django.shortcuts import render,好shortcuts 不用嗎?
    這麼做最主要的原因就是,能讓開發者省掉那些重覆的動作,所以Django就把它都寫在Shortcuts了
    還有像是redirectget_object_or_404 ...等等,很好用的函式 ^^

    若你想要先了也怎麼操作,最單純的使用方法就是 render(request,, context)

    而 context 就是我們從資料庫撈回來的資料,並使用 key 去指向這些資料

    接著,輪到另一位主角 template,還記得我們創立了一個 test.html,那我們今天要將裡面的程式碼修改成這樣

    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title></title>
      </head>
      <body>
        {% for vlist in vendor_list %}
        <h1> 店家 : {{vlist.vendor_name }} </h1>
        <ul>
          <li>店名 : {{ vlist.store_name }}</li>
          <li>電話 : {{ vlist.phone_number }}</li>
          <li>地址 : {{ vlist.addres }}</li>
        </ul>
        {% endfor %}
      </body>
    </html>
    

    相信聰明如你,這段程式碼你一定能看得懂,只是細節不是很理解,這邊請一一聽我訴說

    • {% %} - opeoblock : 簡單來說,你可以填入 template tags ,蛤?! 你問我有哪些可以用? 去找吧! 我把 template tag全都放在哪裡! 大秘寶
      在這裡,你可以先記住有 for loopif 就算足夠了 ^^"
    • {{ }} - openvariable : 可以填入變數名稱

    先有這樣的概念,今天的template根本就難不倒你囉!

    白話文 : 對於 vendor_list 裡面的每一個物件,去對應到物件的

    1. vendor_name
    2. store_name
    3. phone_number
    4. address

    所以我們便能在 http://127.0.0.1:8000/vendor/ 看到資料,是不是就跟我們建立的資料相同呢!
    https://ithelp.ithome.com.tw/upload/images/20181015/201118290LA0x0CrLb.jpg


    小節 : 用一張圖來說明今天做的事情,並搭配今天說的這段話去看,相信大家都會懂 ^^
    https://ithelp.ithome.com.tw/upload/images/20181015/20111829NolsLrYCXl.jpg

    View會透過ORM的方式,去做存取資料的動作,接著再透過DICT MAPPING,去對應到我們 Template 定義的資料欄位,如此一來我們便能成功將資料顯示

    今天把最基礎的傳達資料講完囉 ^^

    寫文章配煙花 是人生一大享受 From BY

    ]]>
    Percy 2018-10-15 20:45:23
    Day14 : Template 的運作 https://ithelp.ithome.com.tw/articles/10201397?sc=rss.iron https://ithelp.ithome.com.tw/articles/10201397?sc=rss.iron 總算來到了介紹Template的部分,好讓 Django 的MVT概念得以成型!

    在這之前我們已經介紹了 M - Model...]]> 總算來到了介紹Template的部分,好讓 Django 的MVT概念得以成型!

    在這之前我們已經介紹了 M - ModelV - View

    而 Template 就是 Django 的 T,今天我會把主軸放在Template的運作上

    最一開始先帶大家回憶一下我們目前的專案的結構

    礙於版面的關係,我刪除了部分不會影響今天講解的資料
    C:.
    │   db.sqlite3
    │   manage.py
    ├───ithome
    │   │   settings.py
    │   │   urls.py
    │   │   wsgi.py
    │   │   __init__.py
    │   └───__pycache__
    ├───vendor
    │   │   admin.py
    │   │   apps.py
    │   │   models.py
    │   │   tests.py
    │   │   urls.py
    │   │   views.py
    │   │   __init__.py
    │   ├───migrations
    │   └───__pycache__
    └───welcome
        │   admin.py
        │   apps.py
        │   models.py
        │   tests.py
        │   urls.py
        │   views.py
        │   __init__.py
        ├───migrations
    

    Template 是什麼? 它其實就跟 MVC 架構的 V - view是一樣的! 就是要給使用者看到的畫面

    那我今天建立了 template,總該讓 Django 知道我的template放在哪! 不然 Django 怎麼會知道你放在哪呢?
    就算你不告訴 Django, 它還是有預設會去找的地方,這就是 Django的一個大特色 (先賣個關子 ^^

    那我們要怎麼讓 Django知道放在哪呢? 答案其實都在 settings 裡面!

    打開 資料夾 ithome\settings.py,搜尋 TEMPLATES,你便能夠看到以下設定

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    

    這密密麻麻誰搞得懂它在幹嘛...

    沒有錯,但目前你只要知道兩件事情就好

    1. 上面是 Django 預設去找 TEMPLATES 的設定
    2. DIRS : 這是請Django額外搜尋TEMPLATES的目錄

    而這個額外的TEMPLATES我們會放習慣放在與 apps - vendor 同一個階層,也就是說

    C:.
    │   db.sqlite3
    │   manage.py
    │
    ├───ithome
    │   │   settings.py
    │   │   urls.py
    │   │   wsgi.py
    │   │   __init__.py
    │   └───__pycache__
    ├───templates   ←←←←←←
    ├───vendor
    ...以下省略
    

    那我們就要將這個templates的目錄填入 DIRS裡面,也就是說你可以寫這樣
    'DIRS': ["[yourpath]/templates",

    將目錄寫死,是其中一種方法,但那肯定不是個好方法,因為今天你的程式換個位置之後它就動不了了

    那我們當然要靈活一點,那就是取得目前這個project的路徑,但是這部份我們可以利用 Django 提供的 BASE_DIR
    在 settings.py 搜尋 BASE_DIR 就可以找到它
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

    透過 BASE_DIR,我們就擁有了預設的專案的路徑,所以 DIRS 我們可以寫成這樣
    'DIRS': [BASE_DIR+"/templates"] or os.path.join(BASE_DIR, 'templates')

    在這裡不得不說 Django 真的很貼心,它把 project 目錄已經幫妳寫好在 BASE_DIR 了

    上面兩種方法結果是一樣的,目的就是為更靈活的設定路徑,設定完成之後,Django 就會知道以後要找 templates,就要額外搜尋這一個路徑


    那在這裡先做一個錯誤範例讓大家了解 Templates 搜尋的過程

    urls.py在 settings.py 的設定如下,讓大家做對照

    urlpatterns = [
    	path('welcome/', include('welcome.urls')),
        path('vendor/', include('vendor.urls')),
        path('admin/', admin.site.urls),
    ]
    

    先在 vendor/urls.py 設定路徑

    urlpatterns = [
        path('', views.showtemplate),
    ]
    

    然後在 vendor/views.py 建立相對應的 view

    from django.shortcuts import render
    
    # Create your views here.
    def showtemplate(request):
        # 今天先不探討什麼是 render,先記得它會去撈 test.html
        return render(request, 'test.html')
    

    接著執行 python manage.py runserver,搜尋127.0.0.1:8000/vendor/,你會看到這些錯誤訊息
    https://ithelp.ithome.com.tw/upload/images/20181014/20111829j5aL7VlcaT.jpg

    紅色框框就是我們設定的 TEMPLATES 路徑,因為我們還沒有在此路徑建立test.html,所以他在搜尋完我們額外建立的路徑之後,便會搜尋其他預設的路徑,如果都找不到test.html,就是出現上面大家看到的畫面

    此時,只要在 TEMPLATES 建立 test.html 便能解決這一個問題

    # 建立 test.html,顯示 Hey there, u can see me!
    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title></title>
      </head>
      <body>
        <h1> Hey there, u can see me! </h1>
      </body>
    </html>
    

    接著,重新整理一下畫面,你便能夠看到template回傳的畫面拉!
    https://ithelp.ithome.com.tw/upload/images/20181014/20111829yPGKCl7t4m.jpg


    小節 :
    今天主要介紹 Templates 的功能,以及帶大家了解如何設定,還有了解大致上了解它的運作原理

    但是template最有特色的部分還沒有講到,這個就留到後面再跟大家介紹囉! ^ ^

    重覆學習的路上,還會處處發現許多自己不知道或是不熟悉的部分,可以說是一大收穫 From BY

    ]]> Percy 2018-10-14 22:34:15
    Day13 : path & re_path vs. url https://ithelp.ithome.com.tw/articles/10201230?sc=rss.iron https://ithelp.ithome.com.tw/articles/10201230?sc=rss.iron 今天我們來談談在 Django2.X 及 Django1.11 中 一個較大的轉變,那就是網址配對的寫法,白話來講,就是在 urls.py 中,今天當使用者進入不同的網站時,它是怎麼去做查詢(l...]]> 今天我們來談談在 Django2.X 及 Django1.11 中 一個較大的轉變,那就是網址配對的寫法,白話來講,就是在 urls.py 中,今天當使用者進入不同的網站時,它是怎麼去做查詢(lookup)的,今天的主題共分為這三類

    1. path
    2. re_path
    3. url

    廢話不多說,就讓我們開始吧!

    我們在 Day4,就有介紹 urls.py 的用途,就是擔任橋梁的腳色

    用下面這兩張圖來做說明
    ithome/urls.py ↓
    https://ithelp.ithome.com.tw/upload/images/20181013/201118294F8eL8LUKf.jpg

    welcome/urls.py ↓
    https://ithelp.ithome.com.tw/upload/images/20181013/20111829ACptcc7hp9.jpg

    當我們今天欲查詢 /welcome/,那麼它會去撈 views.index 的資料,並將內容顯示在使用者的螢幕上

    而在 Django 2 以前, 網址的寫法都是用 url() 搭配 正規表示法(regular expression),範例如下

    # 今天的介紹我都會使用官網的範例來做說明
    from django.conf.urls import url
    
    from . import views
    
    urlpatterns = [
        url(r'^articles/2003/$', views.special_case_2003),
        url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
        url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
        url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
    ]
    

    想法相當直觀,今天欲查詢的頁面,會一個一個做配對,直到找到與自己配對的網址,在根據後方的views來做相對的回應

    當然也會有找不到頁面的狀況,相信大家都很熟悉,若是今天欲搜尋的網址找不到,那就是 404 not found,網站會貼心提醒你此網頁不存在

    像是Chrome預設的介面
    https://ithelp.ithome.com.tw/upload/images/20181013/20111829EzYeE0zCBY.jpg

    而在 Django 2,url已經正式被打入冷宮了(幫QQ),而是由另外兩個人來代替,分別是

    • path
    • re_path

    而url在未來不久,就要被驅逐出境了

    This function is an alias to django.urls.re_path(). It’s likely to be deprecated in a future release.

    從上述那句話可以得知, url 的精神還在,只是被小三 re_path取代了,而你出自於好奇心去追source code

    from django.urls import include, re_path
    from django.views import defaults
    
    __all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'url']
    
    ...略
    
    def url(regex, view, kwargs=None, name=None):
        return re_path(regex, view, kwargs, name)
    
    

    如果你學到的方法是使用url,在Django 2中你當然可以繼續使用,但既然它也是呼叫re_path來幫它完成事情,那也就沒有繼續使用url的必要拉 ^^"

    接著,來到了今天的重頭戲 path,它是在Django 2 一個全新的函式,有了它,在寫url時可以省下你不少的功夫,舉例來說

    #原本的寫法
    url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    
    #修改後的寫法
    path('articles/<int:year>/', views.year_archiv),
    path('articles/<int:year>/<int:month>', views.montu_archiv)
    

    <int:year><int:month> 前面所代表的就是資料型態,後方代表的是資料變數,而這些變數就是要傳給views做處理的!

    也就是說,網址是
    articles/XXXX 及 articles/XXXX/XX (XX裡填的 都要是 整數)

    分別就會對應到

    year_archive 及 month_archive

    也就是說,我們在相同目錄下的views裡所定義的函式,會多帶這些參數

    # 示意圖
    # 這裡多傳入 year
    def year_archive(request, year):
        ...
    
    # 這裡多傳入 year 及 month
    def month_archive(request, year, month):
        ...
        
    

    如此一來,我們便能透過更簡潔的方式寫 url ^^

    想當然爾,能夠傳入的參數型態當然不只有<int>囉! 還有這一些型態可以使用

    • str : 怎麼能缺少字串呢! 你說對吧! 而且當你沒有設定型態的話,它預設就是用 str 唷!
    • slug : 類似於 str,但不一樣的地方在於,slug還多包含了ASCII符號,例 : coding-is-so-fun
    • uuid : 用來辨識用的資訊,為了就是避免發生碰撞囉 ^^

    uuid的原文上也有提到 :
    the annual risk of someone being hit by a meteorite is estimated to be one chance in 17 billion

    發生機率是170億的機會,所以不用太擔心拉!

    今天主要帶大家了解 Django 2 在 url 上的轉變,若你剛開始學 Django 2,從今天開始使用path及re_path吧!

    唯有不斷的主動學習,才知道自己哪裡不足 From BY

    ]]>
    Percy 2018-10-13 22:54:19
    Day12 : Models & Admin (3) https://ithelp.ithome.com.tw/articles/10201074?sc=rss.iron https://ithelp.ithome.com.tw/articles/10201074?sc=rss.iron 默默地 Models & Admin 就邁入第三篇了,因為Django提供了許多方法來修改Admin的介面,實在是無法一個一個拿出來討論

    我就拿幾個出來跟大家分享,分別是<...]]> 默默地 Models & Admin 就邁入第三篇了,因為Django提供了許多方法來修改Admin的介面,實在是無法一個一個拿出來討論

    我就拿幾個出來跟大家分享,分別是

    1. fields
    2. search_fields
    3. ordering

    那~ 讓我們開始吧!


    為了能夠讓上面的方法有更顯著的效果,我在 Food 的資料庫新增了兩筆資料,資料庫如下

    之前透過圖片擷取,會顯得資料太模糊,因此這裡我會透過表格方式呈現

    ID FOOD NAME PRICE NAME FOOD VENDOR
    1 馬芬 45 Alex
    2 烤布蕾 35 Alex
    3 羅宋 65 Alex
    4 甜甜圈 25 Alex
    5 菠蘿麵包 60 Alex

    Food model 提供大家做對照

    class Food(models.Model):
    	food_name = models.CharField(max_length = 30) # 食物名稱
    	price_name = models.DecimalField(max_digits = 3, decimal_places=0) # 食物價錢
    	food_vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE) # 代表這食物是由哪一個攤販所做的
    

    先來講 fields,它的功用是限制 Admin 可以修改的欄位,如此一來可以避免發生不必要的修改,若是食物名稱及店家再登記之後都不會再做修改了,只有價錢(物價持續上漲)會進行變動,那麼我們要加上
    fields = [filed_name]

    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
    	list_display = [field.name for field in Food._meta.fields]
    	list_filter = (morethanfourty,)
    	fields = ['price_name'] # 顯示欄位
        
    

    當我們點擊修改介面時,此時只剩下Price可以修改了
    https://ithelp.ithome.com.tw/upload/images/20181012/20111829WdPCc5Yu0E.jpg

    順道一提 將fields那欄改成 exclude=['food_name', 'food_vendor'],執行出來的結果會跟上面的一模一樣,字面上來看其實很好理解,fields代表的是包含,exclude則是除外


    接著是 search_field ,相信聰明如你也能猜到它是什麼意思,就是 搜尋 !!

    設定方法為search_fields = [filed_name],經由設定這一個欄位,我們可以更方便找到相關的資料

    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
    	list_display = [field.name for field in Food._meta.fields]
    	list_filter = (morethanfourty,)
    	search_fields = ('food_name','price_name') # 搜尋欄位
    

    在上述 Django 的搜尋中,如果你搜尋麵包,那就等同於於SQL的
    WHERE ( food_name ILIKE '麵包' OR price_name ILIKE '麵包')
    如此一來,你便能夠搜尋到與麵包相關的結果
    https://ithelp.ithome.com.tw/upload/images/20181012/201118290o8I2b0gc2.jpg

    在這裡因為搜尋的是中文,所以 ILIKE 跟 LIKE 沒什麼兩樣拉 哈哈 ^^"


    ordering 是今天的重頭戲,我就不賣關子了,它就是排序的概念,通常我們可能想找最大或最小的幾筆時,這個最好用了!!

    廢話不多說,我們直接對 price_name 下手,新增 ordering = [filed_name]

    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
    	list_display = [field.name for field in Food._meta.fields]
    	# list_display =['id', 'food_name']
    	list_filter = (morethanfourty,)
    	search_fields = ('food_name','price_name')
    	ordering = ('price_name',) # 價格 由小到大 排序
    

    如此一來便能看到價格排序好的模樣
    https://ithelp.ithome.com.tw/upload/images/20181012/20111829W4STBaba8Q.jpg

    那如果你想要 由大到小 排序,那該怎麼做呢? 相信這難不倒大家,就是在前方加一個符號 -
    也就是說

    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
    	list_display = [field.name for field in Food._meta.fields]
    	# list_display =['id', 'food_name']
    	list_filter = (morethanfourty,)
    	search_fields = ('food_name','price_name')
    	ordering = ('-price_name',) # 新增 -,資料改為 由大到小 排序
    

    輕輕鬆鬆就能夠看到相反的結果囉!
    https://ithelp.ithome.com.tw/upload/images/20181012/201118299sbm63Ertg.jpg


    以上,就是今天 models & admin 第三篇,大致上就介紹這些,其他還有相當多的功能可以使用,不過其餘的部分就交給大家自行去練功囉 Django Docs

    說不定等我辭窮的時候就會有個番外篇 xDD,很符合電影情節吧! 只要出個3集就一定要有一個番外篇

    Anyway,明天正式回歸到 viewsurls 的部分囉! 然後接 templates 的部分! 大家敬請期待!

    templates在我背後,它現在很火
    templates : 什麼時候才要輪到我!!

    ]]> Percy 2018-10-12 23:18:42
    Day11 : Models & Admin (2) https://ithelp.ithome.com.tw/articles/10200944?sc=rss.iron https://ithelp.ithome.com.tw/articles/10200944?sc=rss.iron 今天我們繼續來討論,究竟 Models 與 Admin之間還藏著什麼不可告人的秘密

    Day10所提到的,Django 在創建資料庫的時候,實際上背後偷偷地增加的 key = id 的欄位,為了顯示這個id,我們必須創造一個 models 類別的管理者,必須將管理者一併登入進 Admin.py 才行

    而你好不容易在 models.py 新增的 VendorAdmin,之後還要去 admin.py 登陸,你可能會覺得有點麻煩,有沒有更方便的做法? 答案是有的! 而且還能一併將原本登入進去的 Vendor 拿掉

    方法就是在 VendorAdmin 上方加上 decorator
    @admin.register(classname)

    @admin.register(Vendor)
    class VendorAdmin(admin.ModelAdmin):
        # 這裡我一併將 Vendor 類別 其它的欄位都加進來了
    	list_display = ['id', 'vendor_name', 'store_name', 'phone_number', 'address']
    

    不要忘記去 admin.py 將 Vendor 註解掉唷!

    from django.contrib import admin
    
    from .models import Vendor, Food, VendorAdmin
    
    # Register your models here.
    # admin.site.register(Vendor)   # < 註解
    admin.site.register(Food)
    

    完成上述兩事情,再下 python manage.py runserver,它能正常的運作,這方法是不是相對簡單很多 ^ ^

    https://ithelp.ithome.com.tw/upload/images/20181011/20111829UTsFjAmJIH.jpg

    Python 的 decorator是一個蠻tricky的方法,不過了解操作手法之後,你會發現它的強大! 這裡不說明它的功能,不過推薦一個猴子都看得懂的英文教學 Primer on Python Decorators

    同一個管理者可以登入多個類別,但是list_display的設定必須兩個類別都有相同的欄位,因此這裡我們不建議這麼做

    上面我們試著將每一個欄位都SHOW出來了,但此刻你不知道是不是也會想 : 如果我的欄位不只這幾個,那一個個填鴨上去,那真的很無聊! 但其實我們可以更聰明的讓Python幫我們填
    修改 list_display = field.name for field in Vendor._meta.fields

    @admin.register(Vendor)
    class VendorAdmin(admin.ModelAdmin):
    	list_display = [field.name for field in Vendor._meta.fields]
        # 上下兩者目的相同
        # list_display = ['id', 'vendor_name', 'store_name', 'phone_number', 'address']
    

    此時我們將 Food 也一併透過相同的方式登陸進來

    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
    	list_display = [field.name for field in Food._meta.fields]
    

    並使用Food的資料來做說明,我預先加入了另外兩筆資料進來
    https://ithelp.ithome.com.tw/upload/images/20181011/20111829ZEWbyc12Tq.jpg

    當資料庫一大的時候,很多資料常常不是我們想要看的,我們可能只想看某一類的資料,像是只想搜尋某種價位的食物,那麼你就需要用到 過濾 list_filter,在Food裡面新增 list_filter,並將要過濾的欄位填入list_filter = ('price_name',)

    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
    	list_display = [field.name for field in Food._meta.fields]
        # 過濾 price_name
    	list_filter = ('price_name',)
    

    我現在才意識我取了一個爛名字,到底什麼是price_name xD,應該是 food_price,anyway,大家了解就好

    新增完便能夠看到右方多了一個過濾的欄位,此時你便能透過 food_price price_name 進行過濾的動作
    https://ithelp.ithome.com.tw/upload/images/20181011/20111829UFGXl3N5qu.jpg

    這只是一個簡單的過濾,有時候你想做的不只是這樣子,因為10號剛領薪,你想要犒賞自己,只想要找 >50價位的麵包,但是資料庫顯示的資料可能有 60 65 70... 各種價位的麵包,這時候你便可以自行設定

    # 額外 import 這個套件
    from django.utils.translation import gettext_lazy as _
    
    # 自行宣告 類別
    class Morethanfifty(admin.SimpleListFilter):
    
    	title = _('price')
    	parameter_name = 'compareprice' # url最先要接的參數
    
    	def lookups(self, request, model_admin):
    		return (
    			('>50',_('>50')), # 前方對應下方'>50'(也就是url的request),第二個對應到admin顯示的文字
    			('<=50',_('<=50')),
    		)
        # 定義查詢時的過濾條件
    	def queryset(self, request, queryset):
    		if self.value() == '>50':
    			return queryset.filter(price_name__gt=50)
    		if self.value() == '<=50':
    			return queryset.filter(price_name__lte=50)
                
    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
    	list_display = [field.name for field in Food._meta.fields]
        # 將 Morethanfifty 填入
    	list_filter = (Morethanfifty,)
    

    我們選擇了 >50 的選項,此時只會顯示>50的食物,我們就能夠好好犒賞一下自己了 ^^
    https://ithelp.ithome.com.tw/upload/images/20181011/20111829HCS99RofN1.jpg

    選擇了 >50 會顯示的 url為 http://127.0.0.1:8000/admin/vendor/food/?compareprice=>50
    前方的 compareprice 就是對應到我上面所說的 parameter_name,而 >50 則是我們定義的其中一個選項

    是不是很簡單(X
    除了使用Django內建的 list_filter,你也可以自行設定區間範圍

    今天差不多就先講到這裡,自定義過濾的部分若是有不懂的地方也歡迎留言,不過我相信大家應該都沒麼問題

    model 跟 admin 實在有太多東西可以玩拉 (哈哈

    ]]> Percy 2018-10-11 23:43:08
    Day10 : Models & Admin (1) https://ithelp.ithome.com.tw/articles/10200799?sc=rss.iron https://ithelp.ithome.com.tw/articles/10200799?sc=rss.iron Day9 介紹了透過Shell來完成...]]> Day9 介紹了透過Shell來完成CRUD的功能,而這種將關聯式資料庫映射到物件導向(OO)

    這樣子的一個抽象化技術就叫做 ORM(Object-relational mapping),這樣子說你可能還不是很了解,那麼更直白的意思就是,我們不使用資料庫的語法,e.g. SQL,便能夠達成操作資料庫的事情

    至於ORM更詳細的資料,因為不在我們討論的範圍內,大家可以再去GOOGLE ^ ^

    眼尖的各位如果操作的時候有發現,任何我們從資料庫取回的資料,它的名稱都不具有可讀性

    >>> from vendor.models import Vendor
    >>> v1 = Vendor.objects.get(id=1)
    >>> v1
    <Vendor: Vendor object (1)>
    >>>
    

    上述範例,我們可以得知從 Vendor 資料庫撈 id = 1的資料,但是取回來的資料名稱是<Vendor: Vendor object (1)>,這對於我們在操作及管理上,實在不太方便

    若是要將回傳的資料變得具有可讀性,我們可以將它的值設定為我們所熟知或是方便辨識的內容

    要達到這個目的的方法就是去覆寫 __str__ , 它會去改變資料所要顯示的值

    from django.db import models
    
    class Vendor(models.Model):
    	vendor_name = models.CharField(max_length = 20) # 攤販的名稱
    	store_name = models.CharField(max_length = 10) # 攤販店家的名稱
    	phone_number = models.CharField(max_length = 20) # 攤販的電話號碼
    	address = models.CharField(max_length = 100) # 攤販的地址
    
        # 覆寫 __str__
    	def __str__(self):
    		return self.vendor_name
    
    class Food(models.Model):
    	food_name = models.CharField(max_length = 30) # 食物名稱
    	price_name = models.DecimalField(max_digits = 3, decimal_places=0) # 食物價錢
    	food_vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE) # 代表這食物是由哪一個攤販所做的
    	
    	def __str__(self):
            return self.food_name
    

    完成上述的事情,再重新載入類別 Vendor,你便可以看到它所回傳的值,已經變成Alex,也就是它的名稱(vendor_name)了!

    >>> from vendor.models import Vendor
    >>> v1 = Vendor.objects.get(id=1)
    >>> v1
    <Vendor: Alex>
    >>>
    

    因為我們只是在 Models 覆寫 __str__,而不是修改Models的資料型態,所以所們不需要進行 makemigrations

    再進一步討論這張圖片,眼尖的各位不曉得會不會產生一個疑問,它的主鍵 (PK) 跑去哪了?
    https://ithelp.ithome.com.tw/upload/images/20181010/201118292667uZ8qFY.jpg

    沒有錯,這一張圖片上並沒有顯示PK的欄位,因為實際上,在沒有指定PK的值之後,Django都會默默地幫你加上 PK,也就是默默加上這一段 id = models.AutoField(primary_key=True)

    若是你要手動指定,方法也很簡單,就是將欄位(Field)裡面填入primary_key=True,並且如果你有指定了這一段,Django就不會自動幫補上id = models.AutoField(primary_key=True)

    這時你不免想問 : 既然我的PK存在,那我的PK要怎麼顯示呢?

    方法就是額外新增一個管理該類別的類別

    from django.db import models
    # 新增
    from django.contrib import admin
    
    class Vendor(models.Model):
    	vendor_name = models.CharField(max_length = 20) # 攤販的名稱
    	store_name = models.CharField(max_length = 10) # 攤販店家的名稱
    	phone_number = models.CharField(max_length = 20) # 攤販的電話號碼
    	address = models.CharField(max_length = 100) # 攤販的地址
    
    
    
    	def __str__(self):
    		return self.vendor_name
    
    ...中間略
    
    # 新增
    class VendorAdmin(admin.ModelAdmin):
    	list_display = ('id', 'vendor_name') 
    

    上面主要進行了兩個步驟,分別是

    1. 新增 from django.contrib import admin
    2. 新增該類別的管理者 VendorAdmin
      取名為VendorAdmin是為了增加可讀性方便未來管理,list_display 是固定寫法(很重要!),管理類別Vendor要顯示哪一些內容,後面用tuple() or list[]都可以,只要將該欲顯示的欄位填入即可!

    千萬不要取list_display以外的名稱,因為這是Django制式寫法,而且 Django看不懂也不會跟你報錯,這對於未來debug而言是一個很大的坑

    再新增完上面的敘述之後,還要去 admin.py 進行設定

    from django.contrib import admin
    # 後方新增 你所建立的 "類別名稱",這裡我的名稱是VendorAdmin
    from .models import Vendor, Food, VendorAdmbin
    
    # Register your models here.
    # 將管理者類別 VendorAdmin 填至該類別後方
    admin.site.register(Vendor, VendorAdmbin)
    admin.site.register(Food)
    

    完成之後,便能夠看到欄位出現 id
    https://ithelp.ithome.com.tw/upload/images/20181010/20111829gjD9BZaWJu.jpg

    今天大致上講到這裡,其餘的明天繼續!

    祝大家雙十快樂拉 ^ ^

    ]]>
    Percy 2018-10-10 23:07:34
    Day9 : 使用 Shell 創建資料 https://ithelp.ithome.com.tw/articles/10200620?sc=rss.iron https://ithelp.ithome.com.tw/articles/10200620?sc=rss.iron Day8 的內容,我們可以透過 Day8 的內容,我們可以透過 admin 在後台方便地管理資料

    除此之外,我們也可以透過 Shell 來管理這一些資料,因為一旦我們創建好了這些資料模型(Models),Django便會提供我進行 Create、Retrieve、Update及Delete 的方式來操作資料庫,也就是所謂的CRUD

    正確來說是 Create、Read、Update及Delete,但是 Retrieve 跟 Read 想要表達的意思其實很相近,因此只要了解CRUD主要涵義就是 新增、讀取、更新、刪除 就好

    進入 Shell 操作 資料庫的方式也很簡單,鍵入 py manage.py shell,便能夠進入 Python Shell

    https://ithelp.ithome.com.tw/upload/images/20181009/201118298qPI32ZXZW.jpg

    接著我們把 Models.py 的類別 VendorFood 引用進來
    from vendor.models import Food, Vendor

    之後你就可以開始操作 vendor app 的資料庫囉!

    此時先下 Vendor.objects.all(),因為我們的資料庫目前是空的,你便能夠觀察它會回傳一個空的資料庫 <QuerySet []>

    >>> Vendor.objects.all()
    <QuerySet []>
    

    而新增一筆 Vendor 資料有兩種方法

    vendor = Vendor(vendor_name = "Alex", store_name="xelAshop", phone_number="09236", address='xs')
    vendor.save()
    
    or
    
    vendor = Vendor.objects.create(vendor_name = "Alex", store_name="xelAshop", phone_number="09236", address='xs')
    

    之後再輸入一次 Vendor.objects.all(),就可以看到多了一筆資料,而我們也可以透過鍵值(Key)
    來取得相對應的值(Value)

    >>> Vendor.objects.all()
    <QuerySet [<Vendor: Vendor object (1)>]>
    >>> vendor = Vendor.objects.get(id=1) # 取得第一筆資料
    >>> vendor.vendor_name # 相對應的鍵值
    'Alex'
    >>> vendor.store_name
    'xelAshop'
    >>> vendor.address
    'xs'
    >>> vendor.phone_number
    '09236'
    

    若是想要**修改(Update)**某一筆資料,也可以透過類似的方法修改

    # 將 vendor_name 是 Alex 的資料欄位中的 address 改成 Kao
    Vendor.objects.filter(vendor_name = "Alex").update(address = 'Kao')
    

    filter 顧名思義就是過濾的功能,根據後方我們所填入的 字典(Dict),來一一過濾,修改完畢再存取一次,你便能發現資料已經成功被修改了^^

    >>> vendor = Vendor.objects.get(id=1)
    >>> vendor.address
    'Kao'
    

    這裡我們新增一個食物(突然很想吃馬芬)至剛剛建立好的攤販vendor,便可以發現food成功關聯到vendor

    >>> food = Food.objects.create(food_name='馬芬', price_name='45', food_vendor = vendor)
    >>> food.food_vendor
    <Vendor: Vendor object (1)>
    >>> food.food_vendor.vendor_name
    'Alex'
    >>> food.food_vendor.store_name
    'xelAshlp'
    

    而 刪除(Delete) 相對簡單,只要將愈刪除的資料讀取進來,再後方加上**.delete()**即可

    # 這裡只是示意,並沒有進行刪除的動作
    Food.objects.get(id=1).delete()
    

    最後我們到admin查看一下剛建立好的資料,顯示出來的就跟我們上面所說的一樣 ^^
    https://ithelp.ithome.com.tw/upload/images/20181009/20111829yksbblBkQw.jpg

    https://ithelp.ithome.com.tw/upload/images/20181009/20111829a7Z3IqYgZO.jpg

    小結 : Django 提供一個強大的 CRUD,看似很複雜,但實際上相當容易! 只要稍微操作一下,相信大家很快就會熟練的

    ]]>
    Percy 2018-10-09 23:25:38
    Day8 : 創建管理員 - Admin https://ithelp.ithome.com.tw/articles/10200494?sc=rss.iron https://ithelp.ithome.com.tw/articles/10200494?sc=rss.iron Day6 的介紹,我們已經知道要怎麼...]]> Day6 的介紹,我們已經知道要怎麼去創建一個資料庫,並告訴 Django 去追蹤它的變化,並將相對應的變化記錄起來

    實際上,它已經存在於我們的資料庫了,只是你還沒有看到它,就算你看到了,它現在裡面也是空的 ^^"

    蛤... 真的這麼不相信我嗎?

    那只好先帶你去會會它,以免讓你認為我在欺騙你們^^"

    在進去之前,我們要先創建一個管理者(superuser),這樣我們才能夠進到該介面
    python manage.py createsuper

    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py createsuperuser
    使用者名稱 (leave blank to use 'bxchen'): ithome
    電子信箱: ithome@example.com
    Password:
    Password (again):
    這個密碼過短。請至少使用 8 個字元。
    Bypass password validation and create user anyway? [y/N]: y
    Superuser created successfully.
    

    只要照著它所要求的去填入即可,密碼有規定要超過8個字元,這樣比較安全! 我只是方便起見,好孩子不要亂學 ^^"

    建立完之後便能夠進到管理者的介面囉! 在這之前 不要忘了 python manage.py runserver

    在打開網頁後,別忘了要將網址後方加入 /admin ,之後你就會看見這個熟悉的介面
    https://ithelp.ithome.com.tw/upload/images/20181008/201118290OW96LXQXi.jpg

    輸入剛剛所設定好的帳號密碼後,就會看到下方的介面
    https://ithelp.ithome.com.tw/upload/images/20181008/20111829sITQ6wudP7.jpg

    這時候你就會想 : 明明就沒有資料庫阿,哪來的資料庫,你果然在騙人!

    慢著 ^^" 回來~ 我真的沒有騙你

    vendor 資料庫是真的存在的,但是如果我們沒有跟admin註冊說要顯示 vendor,所以沒有看到它是很正常的事情

    為了讓創建的models顯示在資料庫上,我們要去修改 admin.py 的內容
    打開 vendor app 底下的 admin.py,並將它修改成如下,要記得import Vendor 及 Food 唷!

    from django.contrib import admin
    
    from .models import Vendor, Food
    
    # Register your models here.
    admin.site.register(Vendor)
    admin.site.register(Food)
    

    修改完並儲存之後,再去看一次網站

    https://ithelp.ithome.com.tw/upload/images/20181008/20111829baksVyq8kY.jpg

    它們就活生生的蹦上來了 (恭喜老爺恭賀夫人

    其實你也可以透過這介面去新增資料,但是這個太容易了,我相信對大家來說這不是問題

    Vendors 在新增欄位的時候,要求輸入的形式都跟 Day6 我們所設計的一樣
    https://ithelp.ithome.com.tw/upload/images/20181008/20111829A55fQC8orW.jpg

    此外,比較特別的就是 Foods 的新增欄位中,有下拉式的選單,不過因為目前我們沒有任何攤販,所以食物當然也對應不到任何人囉
    https://ithelp.ithome.com.tw/upload/images/20181008/201118291GoyZukfmm.jpg

    今天差不多講到這,原本今天要講 Shell 的,但是講著講著就偏離掉了 哈哈
    無妨~ 至少你不會再去懷疑資料庫是不是真的存在了 ^^"

    哪裡有精神時光屋,不要忘記算我一份 From BY

    ]]>
    Percy 2018-10-08 22:40:39
    Day7 : 探討 INSTALLED_APPS https://ithelp.ithome.com.tw/articles/10200276?sc=rss.iron https://ithelp.ithome.com.tw/articles/10200276?sc=rss.iron 邁入Day7! 今天就來些輕鬆一點的內容吧

    今天我們來談談在 邁入Day7! 今天就來些輕鬆一點的內容吧

    今天我們來談談在 Day5 提到的 INSTALLED_APPS 內的寫法,其實也蠻tricky的這個議題

    如果你依稀記得 Day6 的 vendor,要 Django 追蹤的方法就是將 vendor.apps.VendorConfig,加入 INSTALLED_APP

    到這裡都沒有問題!

    但是小弟我剛開始在學的時候,參考的來源不只一個網站,然後發現大家寫法不同,但是卻都能夠執行!

    這是什麼意思呢? 也就是說 Day6 的 INSTALLED_APP,你可以這樣寫

    INSTALLED_APPS = [
        'vendor',
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    ]
    

    你可以使用更簡單的寫法 app name - 也就是將 vendor.apps.VendorConfig 替換成 vendor

    阿你就會開始疑惑說 : 這麼簡單的寫法為什麼不直接告訴我就好了!!

    沒有不告訴你啊,剛剛不是講了嗎 ^^"

    A list of strings designating all applications that are enabled in this Django installation. Each string should be a dotted Python path to:

    • an application configuration class (preferred)
    • a package containing an application

    所以說,其實這兩種寫法都可以,但是推薦你使用 vendor.apps.VendorConfig

    這也算是 版本演化 下的一個產物

    在 Django 1.9以前,下 python manage.py startapp [someapps]是不會自動產生 apps.py 在該app資料夾底下的,而你去打開 vendor 底下的 apps.py 便可以看到

    from django.apps import AppConfig
    
    class VendorConfig(AppConfig):
        name = 'vendor'
    

    而上述的敘述要搭配著下面這一段文字看

    When INSTALLED_APPS simply contains the dotted path to an application module, Django checks for a default_app_config variable in that module.
    If it’s defined, it’s the dotted path to the AppConfig subclass for that application.
    If there is no default_app_config, Django uses the base AppConfig class.

    也就是說,我們託付給 Django 管理 apps,它的運作模式就是去看該路徑是否有 default_app_config 這個變數,如果有這個變數,才會繼續去看後面所接的路徑,很抽象對吧,直接帶你看一個例子

    我們來到內部的 ithome\settings.py,來追 INSTALLED_APPS 的設定 django.contrib.admin

    該檔案在虛擬環境 Django 套件下
    cd .(你的目錄).\ithome\ithome_enve\Lib\site-packages\django\contrib\admin

    
    省略部分內容 ...
    def autodiscover():
        autodiscover_modules('admin', register_to=site)
        
    default_app_config = 'django.contrib.admin.apps.AdminConfig'
    

    在 __init__.py 你便可以看到這個設定,抓到 default_app_config,我們再繼續追 apps.AdminConfig

    from django.apps import AppConfig
    from django.contrib.admin.checks import check_admin_app, check_dependencies
    from django.core import checks
    from django.utils.translation import gettext_lazy as _
    
    class SimpleAdminConfig(AppConfig):
    ...省略
    
    class AdminConfig(SimpleAdminConfig):
        """The default AppConfig for admin which does autodiscovery."""
    
        def ready(self):
            super().ready()
            self.module.autodiscover()
    

    trace到這邊,相信大家就會很明瞭了 ^^

    大致上就是這樣,文章上面也強調,這是時代下演化出的產物,所以在 Django 1.7 以前這些內容都已經固定了,所以這些功能就只是取決於我們要不要使用,而不用去動它們

    但是現在呢

    New applications should avoid default_app_config. Instead they should require the dotted path to the appropriate AppConfig subclass to be configured explicitly in INSTALLED_APPS.

    我們應該避免使用單純 app 的名稱,或是在 __init__.py 建立default_app_config = vendor.apps.VendorConfig

    而是採用更明確的方式,在 settings.py 的 INSTALLED_APPS 加入 [someapp].apps.[SomeApps]Config

    當然, INSTALLED_APP 還有更多功能可以玩,不過今天主軸擺在 探討 INSTALLED_APPS

    希望這篇內容能夠釐清也在這探討INSTALLED_APPS寫法的人^^

    參考資源
    釐清polls.apps.PollsConfig 及 polls (1)
    釐清polls.apps.PollsConfig 及 polls (2)

    stackoverflow裡有無數的巨人,而自己也朝著成為巨人的路邁進 From BY

    ]]> Percy 2018-10-07 15:13:49
    Day6 : 資料庫的創建 - Models https://ithelp.ithome.com.tw/articles/10200181?sc=rss.iron https://ithelp.ithome.com.tw/articles/10200181?sc=rss.iron Day5 資料庫的概念相對的抽象,今天來就來講些具體的內容 - 資料庫的創建 ^^

    那我們要怎麼創建資料庫呢?

    A model is the...]]> Day5 資料庫的概念相對的抽象,今天來就來講些具體的內容 - 資料庫的創建 ^^

    那我們要怎麼創建資料庫呢?

    A model is the single, definitive source of truth about your data. It contains the essential fields and behaviors of the data you’re storing. Django follows the DRY Principle. The goal is to define your data model in one place and automatically derive things from it.
    This includes the migrations - unlike in Ruby On Rails, for example, migrations are entirely derived from your models file, and are essentially just a history that Django can roll through to update your database schema to match your current models.

    那就是去 Models.py 檔案撰寫,根據自己的需求(想要的資料型態)來決定資料庫的樣貌

    此時你就會想: 那我沒有經驗,我要從何撰寫啊?

    不用擔心! 這件事情在文件上面也說了

    Each model is a Python class that subclasses django.db.models.Model.

    因為在前面我們也有講過,大部分的事情Django已經幫你處理好了,我們要完成的事情就是撰寫這背後的邏輯,所以我們根據 Django 所提供的資料型態便能夠完成大多數創建資料庫的事情!

    今天我們就來創建一個簡易的餐廳app吧!

    但是餐廳有點長,改用攤販 vendor 好了,先來創建一個 vendor app!

    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py startapp vendor
    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>cd vendor
    

    創建完之後進入該資料夾,開始來撰寫 models.py !!

    from django.db import models
    
    # Create your models here.
    class Vendor(models.Model):
    	vendor_name = models.CharField(max_length = 20) # 攤販的名稱
    	store_name = models.CharField(max_length = 10) # 攤販店家的名稱
    	phone_number = models.CharField(max_length = 20) # 攤販的電話號碼
    	address = models.CharField(max_length = 100) # 攤販的地址
    
    class Food(models.Model):
    	food_name = models.CharField(max_length = 30) # 食物名稱
    	price_name = models.DecimalField(max_digits = 3, decimal_places=0) # 食物價錢
    	food_vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE) # 代表這食物是由哪一個攤販所做的
    

    每一個class都是繼承django.db.models.Model,還記得吧 ^^! 所以類別名稱後方的()就是塞 models.Model,相信這對你來說不是什麼大問題

    接著我們要來定義這個資料庫的型態,設計的架構在上方程式碼註解都說得相當清楚,而 CharField 就是其中一種,把它想成一段字串,你可能就很明瞭了^^,而它後方的 max_length 是必要的參數,記得一定要加上去,顧名思義,它就是限制你最多可以儲存的長度

    而在 Food 類別,不一樣的地方是 DecimalField,是儲存十進制的數字,後面參數分別代表 最大長度小數點後方的位數

    畢竟台灣目前最小單位就是元,沒有分跟角了,代 0 相信大家比較不陌生!
    (佛心店家,最貴1000元還有找

    最關鍵的地方在最後一個 ForeignKey,在 Django中是 多對一(many-to-one)的關聯,而前方的參數代表的意思就是對應到哪一個類別,這裡對應到的是 Vendor,而後方的 on_delete 代表的是當對應的類別被刪除之後,這些對應到別人的資料要怎麼被處理,而 CASCADE 就是一倂刪除

    所以
    food_vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE)
    一言以蔽之
    一個攤販會賣各式各樣的食物,當它今天收店了,你就也再也吃不到它賣的每一樣食物了
    就是這麼簡單 ^^

    我們寫好了簡易的攤販app的資料庫,那我們要怎麼傳達給 Django,讓他知道我們這裡有一個 model 寫好等你來追蹤哩?
    其實在 Day5 有先提到,要追蹤該app,就要將它寫入 installed_apps裡面

    因此,在settings.py裡面,我們要將內容加上 vendor.apps.VendorConfig

    INSTALLED_APPS = [
        'vendor.apps.VendorConfig',
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    ]
    

    這樣就能確保 Django 有開始在追蹤 vendor app 囉!
    因為我們在 models.py 做了更動,我們要告訴 Django 我們修改了一些內容,所以我們要輸入 :
    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py makemigrations vendor

    之後你便會看到,Django幫你搬家之後給你簡易版的回饋 ^^

    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py makemigrations vendor
    Migrations for 'vendor':
      vendor\migrations\0001_initial.py
        - Create model Food
        - Create model Vendor
        - Add field food_vendor to food
    

    更詳細的內容則是可以到 vendor app裡面的 migrations 查看,因為這是我們第一次做變動,所以名稱也取得很明瞭
    0001_initial.py

    # Generated by Django 2.1.2 on 2018-10-06 15:25
    
    from django.db import migrations, models
    import django.db.models.deletion
    
    
    class Migration(migrations.Migration):
    
        initial = True
    
        dependencies = [
        ]
    
        operations = [
            migrations.CreateModel(
                name='Food',
                fields=[
                    ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                    ('food_name', models.CharField(max_length=30)),
                    ('price_name', models.DecimalField(decimal_places=0, max_digits=3)),
                ],
            ),
            migrations.CreateModel(
                name='Vendor',
                fields=[
                    ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                    ('vendor_name', models.CharField(max_length=20)),
                    ('store_name', models.CharField(max_length=10)),
                    ('phone_number', models.CharField(max_length=20)),
                    ('address', models.CharField(max_length=100)),
                ],
            ),
            migrations.AddField(
                model_name='food',
                name='food_vendor',
                field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='vendor.Vendor'),
            ),
        ]
    

    但實際上,我們其實只是將變動的資訊儲存起來而已,你要實際上讓它更動,則要下
    python manage.py migrate vendor 0001

    0001 代表的是 遷移的代號(migration_name)

    Operations to perform:
      Apply all migrations: vendor
    Running migrations:
      Applying vendor.0001_initial... OK
    

    這樣實際上!! 我們就建好了 vendor 資料庫囉 ^^

    蛤? 你問我要去哪看,這又是另外一篇文章的事情了呢,哈哈

    今天就先講到這裡吧^^

    沒有什麼事情是絕對的,透過不同角度去看待一件事情,你會發現更多 From BY

    ]]> Percy 2018-10-06 23:47:48 Day5 : Django 資料庫的概念 https://ithelp.ithome.com.tw/articles/10200071?sc=rss.iron https://ithelp.ithome.com.tw/articles/10200071?sc=rss.iron 雖然說資料庫不是萬能的,但是沒有資料庫萬萬不能!!

    Day 4的範例是為了讓大家了解 Django架構的運作模式

    所以 Day4 的範例,並沒有去做資料庫的動作

    但是如果每次資料都是這樣,只能呈現一段文字,難免都會顯得枯燥乏味

    想像一下今天在玩寶可夢,打開遊戲你看到了一段文字
    歡迎進入寶可夢的世界
    哦!!! 還是 粗體

    然後裡面沒有捕捉、培養,更沒有跟朋友一起打塔的功能,然後你就這樣拿著這一段文字去跟另一位拿著這段文字的朋友互相討論寶可夢,會顯得相當無趣,而且還蠻搞笑的 (打著打著我自己也笑了...xD

    那為了要能夠做出更多功能,呈現出各式各樣的資料,我們要依靠的就是 資料庫(Database)了

    那 Django 要怎麼設定呢? Django它很貼心的,幫我們完成了很多事情
    在 內部的 ithome 資料夾底下,可以看到 settings.py 檔案,我們將它打開來,並且搜尋DATABASES,你就會看到下面這一段文字

    # Database
    # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        }
    }
    

    上圖是Django資料庫的設定
    沒錯! Django一開始就幫你預設使用 SQLite 了,等於是說,資料庫的設定你可以遵循著 Django 預設的模式走,不需要做更改

    但這並不意味著 Django不支援使用其他的資料庫,它還能使用的有

    • MySQL
    • PostgreSQL
    • Oracle
    • ...以下省略

    你可以根據自己的需求來決定要使用哪一種資料庫^^,而最大的差別,像是使用MySQL,就要新增 USERPASSWORD的設定,就不像 SQLite一樣,你可以直接拿來使用

    接著我們搜尋另一個關鍵字 INSTALLED_APPS

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    ]
    

    這傢伙的概念其實很好懂,也很 tricky ^^",翻開說明文件你會看到他的解釋為 :

    That holds the names of all Django applications that are activated in this Django instance. Apps can be used in multiple projects, and you can package and distribute them for use by others in their projects.

    前面那一段話就是在解釋這一個 INSTALLED_APPS 的作用,換句話說,只要將你創建的 apps 放進來這裡,配合 著 migrate,則它就會去監督該 apps 的 models,相信你還沒有忘記 models的作用,就是資料庫的型態^_^,那為什麼說它很tricky,這部分留到後半段再告訴大家

    而後面那一段話 Apps can be...,其實就是在強調 Django 的另一項特色 可插拔性,而這個特色也是遵循著 Django的一個中心理念DRY

    Don’t repeat yourself (DRY)
    Every distinct concept and/or piece of data should live in one, and only one, place. Redundancy is bad. Normalization is good.
    The framework, within reason, should deduce as much as possible from as little as possible.

    也就是說,很多的 apps 在我們打造完之後,可能會因為不同的需求而被挪到其他地方去使用,而要重新造一個相同的輪子

    講完了這些概念,相信你對 Django 資料庫的概念有了進一步的認識 ^^

    我們接著就來使用 migrate 這個功能吧!

    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>py manage.py migrate
    Operations to perform:
      Apply all migrations: admin, auth, contenttypes, sessions
    Running migrations:
      Applying contenttypes.0001_initial... OK
      Applying auth.0001_initial... OK
      Applying admin.0001_initial... OK
      Applying admin.0002_logentry_remove_auto_add... OK
      Applying admin.0003_logentry_add_action_flag_choices... OK
      Applying contenttypes.0002_remove_content_type_name... OK
      Applying auth.0002_alter_permission_name_max_length... OK
      Applying auth.0003_alter_user_email_max_length... OK
      Applying auth.0004_alter_user_username_opts... OK
      Applying auth.0005_alter_user_last_login_null... OK
      Applying auth.0006_require_contenttypes_0002... OK
      Applying auth.0007_alter_validators_add_error_messages... OK
      Applying auth.0008_alter_user_username_max_length... OK
      Applying auth.0009_alter_user_last_name_max_length... OK
      Applying sessions.0001_initial... OK
    

    migrate的用途這裡再強調一次,它會去看 installed_apps裡面的設定,並根據設定來找相對應的models,並根據我們所使用的資料庫來轉換成該資料庫的形式

    今天大致上講到這裡,內容比較抽象,明天再來些實際的操作帶大家了解 ^^

    不積跬步,無以致千里;不積小流,無以成江海,一步一腳印卡實在 From BY

    ]]> Percy 2018-10-05 23:50:31
    Day4 : 打造Apps跟Django say hello! https://ithelp.ithome.com.tw/articles/10199933?sc=rss.iron https://ithelp.ithome.com.tw/articles/10199933?sc=rss.iron 今天要來介紹 Django 的 app!!

    你可能會懷疑,為什麼是叫 app,畢竟看到 app 最直觀的想法是 應用程式,但是這麼想其實是有偏差的...]]> 今天要來介紹 Django 的 app!!

    你可能會懷疑,為什麼是叫 app,畢竟看到 app 最直觀的想法是 應用程式,但是這麼想其實是有偏差的,畢竟App的原意是一項應用,各式各樣的技術也會產出不同的應用,所以被稱作 應用程式,也不能說它錯拉 ^^"

    那這個 apps 跟Day3講到的 project有什麼關聯性?

    What’s the difference between a project and an app?
    An app is a Web application that does something – e.g., a Weblog system, a database of public records or a simple poll app. A project is a collection of configuration and apps for a particular website. A project can contain multiple apps. An app can be in multiple projects.

    根據上述所法,我們可以知道 app 在 Django 代表的是一個網站的應用,這樣講太中式翻譯了!
    其實一個 app 所代表的就是一項功能,根據功能的差異來分類app, e.g. 登入功能、點餐功能 ...等等
    而我們的專案,裡面就會包含各式各樣的功能,雖然 apps 只是我們專案中一部份的內容,但是在Django中,這些 apps的架構都劃分的相當清楚!

    Django comes with a utility that automatically generates the basic directory structure of an app, so you can focus on writing code rather than creating directories.

    從上面那一句話我們可以明白,Django 很貼心地幫我們完成了小細節的部分,我們只要專注在商業邏輯的撰寫即可^^

    那 Django 的 app 要怎麼建呢? 其實就跟 project 一樣簡單!
    python manage.py startapp [app_name]

    後方的 [app_name] 只是一個示意 ^^",不是要你們跟著我打[app_name]

    講到這裡,想要分享一個概念

    除非是官方正式的Docs,不然千萬不要別人說一句就照打一句,你要盡可能的去思考,這一行指令,背後的用意何在! 有時候官方寫的可能還會有錯(這發生機率很小,但就是有遇到過Q

    Anyway,題外話

    這裡我先建立一個**歡迎(welcome)**功能的 app

    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py startapp welcome
    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>tree /F ..
    
    ithome
    │   ├───ithome
    │   └───welcome
    │       │   admin.py
    │       │   apps.py
    │       │   models.py
    │       │   tests.py
    │       │   views.py
    │       │   __init__.py
    │       │
    │       └───migrations
    │               __init__.py
    │ (省略部分內容....)
    

    在創建好 app welcome,可以發現裡面有六個檔案及一個資料夾,分別是

    • admin.py : 設定資料庫呈現的模式,之後會跟models溝通
    • models.py : 建構你的資料庫型態
    • tests.py : 這是拿來檢查商業邏輯的地方,也就是用來測試你的邏輯是否有遺漏,這裡我們沒有要討論太多這方面的議題,但是你要記住,寫測試是一件相當重要的事情,千萬不能小看使用者的潛能
    • views.py : 相信你還沒有忘記,在 Day1我們這一位擔任控制者(controller)的身分,沒錯! 它就是寫商業邏輯的地方,它會跟urls.py做呼應,並將所需傳達給前端
    • urls.py : 它擔任著橋樑的角色,讓views.py與相對的網站做對應。蛤? 你說怎麼沒見到 urls.py,因為我們要自己建阿^^,在這裡建議可以把內部 ithome的urls.py複製貼過來就好,但是內容要修改!! 待會再來說明修改的部分
    • apps.py : 這裡你只要先了解,這是用來區別app的一個檔案即可
    • init.py : 相信大家都還沒有忘記^^,就是告訴Python這資料夾是個套件
    • migrations : 這資料夾裡面存放的內容,記錄著models裡面所創建的資料庫型態,這部分後面會詳談

    介紹完所有app裡面所包含的檔案,我覺得再過不久,你就能慢慢察覺到 Django MVT的精神所在 ^^

    我們先試著跟 Django 打聲招呼吧! 打開views.py (在這,我是使用 Sublime 當作編輯器,至於要使用什麼編輯器,純粹是依照各自的需求跟喜好,沒有絕對^^

    我們先一起來修改 welcome\views.py
    https://ithelp.ithome.com.tw/upload/images/20181004/201118298B6UCsJJ1g.jpg

    接著,我們將 ithome\urls.py複製到 welcome\ 資料夾下,並將裡面的內容修改如下
    https://ithelp.ithome.com.tw/upload/images/20181004/20111829cFRN9XQqOE.jpg

    因為上面的是針對welcome裡的url做對應(類似區域性概念),我們也要對廣域的ithome裡的urls.py做修改
    https://ithelp.ithome.com.tw/upload/images/20181004/20111829ft90tX6Gsq.jpg

    include 概念就是,它會將 weclome.urls 下的所有 url 前面都冠上 welcome/的意思,這樣子你才不會說在 welcome.urls 裡的每一個 url都要加上 welcome/,這樣多累阿 ^^"

    完整的式意圖如下 :
    https://ithelp.ithome.com.tw/upload/images/20181004/20111829UbDniEKO5n.jpg

    這樣整個架構都建好了,接下來我們來 run 看看結果吧! 應該還沒忘記指令吧 ^^
    python manage.py runserver

    預設是空畫面是正常的,因為預設畫面本來就還沒有設定囉!
    所以只要將網址後面加上 /welcome/ 即可!
    https://ithelp.ithome.com.tw/upload/images/20181004/201118291SgoTyBvnm.jpg

    大功告成!!

    呼,希望上面的式意圖能讓你大致上了解整個架構是怎麼兜起來的 ^^

    那今天就先醬拉,明天見~

    學海無涯,回頭是岸,不進則退,至少希望明天的自己都能比今天多了解一些知識 From BY

    ]]> Percy 2018-10-04 23:13:45
    Day3 : 建置Project https://ithelp.ithome.com.tw/articles/10199733?sc=rss.iron https://ithelp.ithome.com.tw/articles/10199733?sc=rss.iron :今天要來邁入建 Project 的部分囉,有沒有很興奮?!

    :沒有

    :...什麼?! 看來你對 Django 還是相當陌生,還無法對它產生熱忱!

    打從...]]> :今天要來邁入建 Project 的部分囉,有沒有很興奮?!

    :沒有

    :...什麼?! 看來你對 Django 還是相當陌生,還無法對它產生熱忱!

    打從娘胎到現在,你們彼此之間可能才見過幾次面,就像擦肩而過的陌生人一樣,等到下一次再相見之時,在你腦海裡才慢慢浮現出,這個人好眼熟,但是我就是想不起來

    這樣一直反覆下去會沒完沒了,也不是個辦法!!

    讓我帶你來更了解 Django,我相信你們會對它印象更加深刻^_^

    在現實生活中,我們偶爾會對周遭熟悉的人簡稱 ""

    :欸,你中午想吃什麼啊?

    :不知道,你提一個!

    :你想吃飯還是吃麵? 不然我們去吃丹丹好了!

    :不要那個好油,我正在減肥!

    :那不然騎去火車站吃?

    :不要,那個好遠!

    然後你馬上心理無限的,那不然你去吃X好了。

    那我們對於剛見面的人都會很不自主的稱呼對方全名

    如果今天跟 Django 同班,你想要找他搭話,如果講不出它的名字,或是講錯了,說真的都會蠻尷尬的 ^^"

    Django is pronounced JANG-oh. Rhymes with FANG-oh. The “D” is silent.

    我也曾經叫錯一段時間,導致它跟我生悶氣,時不時跟我報Error

    哈哈,題外話,那我們進入今天的主軸** Project **吧!


    [正文開始]

    Day2 我們提到了啟動虛擬的環境,再開始前我們還是再確認一下 Python是否支援 Django的版本吧!

    (ithome_enve) C:\Users\User\Desktop\ithome>python -m django --version
    2.1.2
    
    (ithome_enve) C:\Users\User\Desktop\ithome>python --version
    Python 3.6.6
    

    確認好 Day2 所說的版本支援關係,我們可以來進行下一個步驟了,建 project!

    建 project 的方式很直觀,指令如下
    django-admin startproject [projectname]

    (ithome_enve) C:\Users\User\Desktop\ithome>django-admin startproject ithome
    
    (ithome_enve) C:\Users\User\Desktop\ithome>cd ithome
    
    (ithome_enve) C:\Users\User\Desktop\ithome>dir ithome
    2018/10/03  下午 07:50    <DIR>          .
    2018/10/03  下午 07:50    <DIR>          ..
    2018/10/03  下午 07:50    <DIR>          ithome
    2018/10/03  下午 07:50               553 manage.py
    

    好了!
    建完專案,收工!

    哈哈,玩笑話^^

    建立完成之後,前往該目錄我們可以發現多了一個資料夾 ithomemanage.py
    下圖是整個新增完專案的架構

    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>tree /F ..
    C:\USERS\USER\DESKTOP\ITHOME
    ├───ithome
    │   │   manage.py
    │   │
    │   └───ithome
    │           settings.py
    │           urls.py
    │           wsgi.py
    │           __init__.py
    │
    └───ithome_enve
    ...以下省略
    
    • 外部的資料夾 ithome,因為只是一個容器,所以之後要改名成taiwan_no.1當然也可以
    • manage.py 是將來要操作 Django的一個元件,基於好奇心,你可以把它打開來看,你可以發現os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ithome.settings',這是要將內部的 ithome 資料夾的settings設定為環境變數的操作,觀賞就好,但是千萬不要動到裡面的內容!!
    • 內部的資料夾ithome,這名字就是對應到manage.pyithome.settings,也就是說...,內部的ithome千萬不要動到嘿^^

    如果你像我一樣打死不從,試著玩弄Django,更改manage.py及內部ithome的名稱,再去runserver,那麼 Django 馬上報error給你看,因為在 startproject [projectname]之後,Django就已經是 [projectname] 的形狀了。

    • ithome/init.py,看到標準的__init__.py出現,就是在告訴Python,我是一個套件^^,如果你對這個沒什麼概念,可以去看Docs
    • ithome/settings.py,重頭戲來了! settings掌管了整個專案的運作,這會牽涉到後面更多的內容,大家只要先知道,它很重要就好^^
    • ithome/url.py,這個url扮演著橋梁的角色,會根據你所設定的網址去對應到view,這裡聽起來還很抽象,不過大家先有這個觀念即可!
    • ithome/wsgi.py,WSGI則是扮演著web server與web application之間溝通的一個介面(關於這部分我也還在摸,希望能在後半段部分講給大家聽 ^^

    建立好專案之後,我們先試著將專案run起來吧!
    python manage.py runserver

    (ithome_enve) C:\Users\User\Desktop\ithome\ithome>python manage.py runserver
    Performing system checks...
    
    System check identified no issues (0 silenced).
    
    You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
    Run 'python manage.py migrate' to apply them.
    October 03, 2018 - 21:17:48
    Django version 2.1.2, using settings 'ithome.settings'
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CTRL-BREAK.
    [03/Oct/2018 21:19:24] "GET / HTTP/1.1" 200 16348
    

    看到這個畫面就代表執行成功嚕 ^^
    https://ithelp.ithome.com.tw/upload/images/20181003/201118295HDCun0HDm.jpg

    如果你想要看到中文介面,那麼可以到ithomo\settings.py進行修改,打開檔案搜尋LANGUAGE_CODE,就可以看到下面這一排設定

    LANGUAGE_CODE = 'en-us'
    
    TIME_ZONE = 'UTC'
    
    USE_I18N = True
    
    USE_L10N = True
    
    USE_TZ = True
    

    將 LANGUAGE_CODE = 'en-us' 修改成 'en-TW',然後你就會發現 Django 開始報 Error了,WHY?
    因為 Django 1.7 之後,預設的中文要使用 'zh-Hant'來表示,其中 t 代表繁體中文,順帶一提 'zh-Hans' 是簡體中文

    既然都來settings了,就一起把TIME_ZONE給設定好吧! 因為預設是UTC,要把它改成Asia/Taipei,這樣之後在寫邏輯處理的時候才能夠兜起來 ^^

    所以最後你的設定會變成

    LANGUAGE_CODE = 'zh-Hant'
    
    TIME_ZONE = 'Asia/Taipei'
    
    USE_I18N = True
    
    USE_L10N = True
    
    USE_TZ = True
    

    專案的部分大致上就這樣,希望你會漸漸對 Django 產生興趣 ^_^
    明天再來繼續講關於App的部分

    睡前把 BUG DE完,像是喝一杯熱牛奶一樣,有助於睡眠 From BY

    ]]> Percy 2018-10-03 21:47:12
    Day2 : 安裝 Django 2.1 https://ithelp.ithome.com.tw/articles/10199575?sc=rss.iron https://ithelp.ithome.com.tw/articles/10199575?sc=rss.iron 今天我們就來說一下Django安裝的流程吧!

    一位技術人員平時沒什麼時間去了解歷史,但是我認為趁這個時間來了解一下,也是相當有趣的一件事情!!

    相信你們都有聽過 Ja...]]> 今天我們就來說一下Django安裝的流程吧!

    一位技術人員平時沒什麼時間去了解歷史,但是我認為趁這個時間來了解一下,也是相當有趣的一件事情!!

    相信你們都有聽過 Java 的由來,沒有嗎....那請食用!
    Why did they decide to call it Java?

    Django也有它的由來,它來自一位知名的爵士吉他手Django Reinhardt,從官網可以得知,創始者是因為喜歡這一名吉他手才將它命名為Django(有些網站提到是因為創始者之一Adrian Holovaty喜歡這名吉他手
    Anyway,這部份帶大家稍微了解就好 ^_^

    在開始介紹安裝流程之前,讓我們先來看一下Django的發展!

    https://ithelp.ithome.com.tw/upload/images/20181002/20111829LFOmHmbrNw.jpg

    2017/4/4 推出了 Django1.11,同年的12/2 Django 2.0誕生了,然而就在今年前幾個月,也就是8/1,Django 2.1 問世了。
    就如同官網上面所提到的

    Feature releases (A.B, A.B+1, etc.) will happen roughly every eight months. These releases will contain new features, improvements to existing features, and such.

    發展速度之快,相對的相容性的問題也隨之產生,不同版本的 Django,能夠支援的Python也有差異,透過下面這一張圖,我們可以知道

    https://ithelp.ithome.com.tw/upload/images/20181002/20111829dRbYwFj2BE.jpg

    目前 Python 2.7 支援 Django 1.11,若是你想要開發 Django 2.0+ 的版本,就要使用 Python 3.4+

    The Django 1.11.x series is the last to support Python 2. The next major release, Django 2.0, will only support Python 3.4+.

    在這裡你可能會問我,那我要學Django 2.0+ 還是 Django 1.11? 我可以明確告訴你 Django 2.0+

    Django 1.11 is designated as a long-term support release. It will receive security updates for at least three years after its release. Support for the previous LTS, Django 1.8, will end in April 2018.

    就長遠的角度來看,學習 Django 2.0+ 是較好的選擇,但是有些公司通常都不會使用這麼新的技術!!!
    那麼你再根據公司要求向下學習 Django 1.11即可,畢竟許多的功能都是因為舊版本的不足或是不必要而加以優化及簡化,然而

    Django在走,進步要有 (單押*1)

    科技的創新,對於舊版本的框架相對也會產生資安的問題。


    [正文開始]

    實務上,因為相容性問題,開發跟維護使用虛擬環境,可以避免許多不預期的問題

    方便起見,在這裡我使用Windows的環境介紹

    在安裝 Django 之前,要先去 Python 官網下載 Python 3.6.6,下圖使用 Python 3.7 做說明,你應該看到的是 Python 3.6.6,要記得 ☑ Add Python 3.6 to PATH
    https://ithelp.ithome.com.tw/upload/images/20181002/20111829evoijThnRv.jpg

    安裝完成之後,打開你的CMD
    https://ithelp.ithome.com.tw/upload/images/20181002/20111829HTIJNGbowI.jpg

    如果是顯示 Python [版本],那就表示安裝完成!

    接著,要來透過 pip 來安裝 Django,在 Python 3.4版以後,在安裝 Python同時,pip預設會一起安裝
    pip 是 python 的套件管理系統,沒吃過Python,但是相信你也聽過Python套件的強大
    透過pip install [套件名稱],便能夠開始安裝該套件,等一下!!!

    我想你應該還沒有忘記上面我才剛提到,使用虛擬環境吧?

    沒錯! 在安裝 Django之前,我們可以透過 Python來建立虛擬環境
    https://ithelp.ithome.com.tw/upload/images/20181002/20111829QK9ys8UaLX.jpg

    蛤? 你問我 pyvenv去哪了?

    The pyvenv script has been deprecated as of Python 3.6 in favor of using python3 -m venv to help prevent any potential confusion as to which Python interpreter a virtual environment will be based on.

    這樣應該就很清楚了吧 ^_^

    我們創建好了之後,要記得啟用它,啟用完畢之後,你便可以看到前面出現虛擬環境的標記,這就代表成功啟用了
    https://ithelp.ithome.com.tw/upload/images/20181002/20111829WmPT6AZ3vY.jpg
    建立好虛擬環境,我們總算可以來建立 Django 了 ^^"

    這裡使用不加上任何版本的方式安裝 Django,我們在安裝完之後,可以下 django-admin --version來確認版本,看到版本號碼就表示安裝成功嚕!
    https://ithelp.ithome.com.tw/upload/images/20181002/20111829EzKH47dJuf.jpg

    這裡我們展示如果要安裝的 Django 1.11,則在後面加上該版本即可
    https://ithelp.ithome.com.tw/upload/images/20181002/20111829FRGxn0KCvL.jpg

    Wheeeeeeeeee~~
    到這裡就表示 Django 安裝完成了!
    明天我們再來開始實際Django吧^^

    打文章不僅是複習,還能從過程中挖掘新知 From BY

    ]]> Percy 2018-10-02 14:24:40
    Day1 : 認識 Django https://ithelp.ithome.com.tw/articles/10199515?sc=rss.iron https://ithelp.ithome.com.tw/articles/10199515?sc=rss.iron 文章的一開頭還是要不免俗的說一些前言

    接下來連續30天我將帶領大家探索Django這個基於Python的網頁框架

    在開始寫文章前,我...]]> 文章的一開頭還是要不免俗的說一些前言

    接下來連續30天我將帶領大家探索Django這個基於Python的網頁框架

    在開始寫文章前,我覺得傳達這篇文章適合什麼樣的人食用是一件非常重要的事情!!

    From Django 1.11 to Django 2.1 30天系列適合什麼樣的人參考,依照適合程度來排序

    1. 你有基礎的Python撰寫能力,又剛開始要學Django的初學者,對網頁沒什麼底子的人
      那接下來篇文章非常適合你,因為我也是兩個禮拜前剛開始入門Django,對於網頁只有瞭解基本的HTML、CSS、JavaScript,就來硬幹Django了
    2. 你想要用Python寫網頁,又不知從何下手,那你一定要來試一帖Django(flask當然也可以!
      Top 10 Python Web Frameworks 2018
      透過這一個網站你可以知道目前廣為人們使用的Python框架就是Django、Flask及Tornado,至於要怎麼選擇,全要看自己的需求為何,畢竟每一種框架都有各自的優缺點
    3. 原本就有使用Django1.11,但是想要了解Django 2.1
      這一系列文章的文章多少會提到一些Django 1.11 到 2.1 的一些轉變,但是目前並不把重點放在這一個部分,主要內容都會透過 Django 2.1來做說明,然後穿插一些這些功能在 Django 1.11的形式,再加以說明,anyway,如果後期還有時間的話再來探討這個部分

    [正文開始]

    Django是什麼? Django能吃嗎?

    Django 是一個基於Python語言所寫出來的框架,跟React.js、Angular.js及Vue.js一樣,簡化了很多我們在寫網頁的流程。相較於後者的MVC(Model-View-Contorller)架構,Django也有屬於它的MTV(Model-Template-Views),它們之間的對應關係如下

    MTV MVC
    Model Model
    Template View
    Views Controller

    如果你對於MVC架構原先就不熟悉的人,其實用講的,這個MTV的概念很抽象,關於這部分在後面透過實作將會更詳細的跟大家做說明,如果用一段文字來形容的話
    Model : 描述你的資料類型
    Template : 使用者看到網頁的形式
    Views : 傳達資料(重點在於資料傳達的內容

    再者,Django它是免費開放原始碼,秉持著DRY(Don't Repeat Yourself)的原則,有許多的格式在Django都幫你完成! 是不是真的很棒?!

    有這麼棒的框架怎麼能不用呢! 歡迎大家一起來玩Django!!

    Day2 我們再來繼續介紹安裝的流程 ^_^

    探索一門技術實在是讓人熱血沸騰 From BY

    ]]> Percy 2018-10-01 23:56:27

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>