控件代碼: /Files/zhuqil/HyperlinkFileList.zip
在我主持過的一個ASP.Net論壇上,一個用戶詢問如何在一個頁面上去通過hyperlink列出一個路徑下的很多文件名,能使用戶能點擊它們。我認為這是可能是經常執行的動作,所以決定建立一個服務器控件來封裝它。
起初,我嘗試了Web用戶控件,但我想能允許設置邊框,字體,背景顏色等。使用Web用戶控件,我要為每一個屬性手動設置。俗話說:“你寫的代碼越少,你的錯誤越少了”,所以我要研究一個更好的方法。
我決定創建自己首個自定義服務器控件。我試著去繼承一個label控件,但label控件不支持滾動條。 所以,我選擇繼承Panel控件。最終的控件具有Panel控件的所有屬性(顏色,邊框,滾動支持等),再加上一些我添加自定義屬性。使用Panel控件將會盡最小的努力。
第一部分:自定義服務器控件
初始化的服務器控制相對容易。下面是最終的代碼:
以下為引用的內容:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: TagPrefix("EndWell", "EW")]
namespace EndWell
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:HyperlinkFileList runat="server">")]
[ToolboxBitmap("HyperlinkFileList.ico")]
public class HyperlinkFileList : Panel
{
[Bindable(true)]
[Category("Files List")]
[Description("The Title of the list of files")]
public string FilesTitle {get; set;}
[Bindable(true)]
[Category("Files List")]
[Description("The directory of the files to list: (~/Files/)")]
// these two built in editors were lacking:
//[EditorAttribute(typeof(System.Web.UI.Design.UrlEditor), typeof(UITypeEditor))]
//[EditorAttribute(typeof(System.Windows.Forms.Design.FolderNameEditor), typeof(UITypeEditor))]
[EditorAttribute(typeof(EndWell.DualModeFolderEditor), typeof(UITypeEditor))]
public string FilesDirectory { get; set; }
[Bindable(true)]
[Category("Files List")]
[Description("The filter for the files to show: (*.*)")]
public string FilesFilter { get; set; }
[Bindable(true)]
[Category("Files List")]
[Description("Text to show when there are no files")]
public string NoFilesText { get; set; }
// ---- Private vars --------------------------
private String[] m_FilesArray; // cached for performance
// ---- Default constants-------------------
const String DEF_FILES_DIR = "~/XML/";
const String DEF_FILES_FILT = "*.XML";
const String DEF_FILES_TITLE = "XML Files:";
const String DEF_NOFILES_TEXT = "";
// ---- Constructor --------------------------
public HyperlinkFileList()
{
// set defaults for our propertIEs
FilesDirectory = DEF_FILES_DIR;
FilesFilter = DEF_FILES_FILT;
FilesTitle = DEF_FILES_TITLE;
NoFilesText = DEF_NOFILES_TEXT;
// Set defaults for panel propertIEs
// I don't like the default width to be full screen
// And a border looks better
Width = new Unit("300px");
BorderStyle = BorderStyle.Solid;
BorderWidth = 1;
BorderColor = Color.Black;
// If height is set, force scroll bars to keep list
// from spilling over the panel/div boundarIEs.
if ((Height != null) && (ScrollBars == ScrollBars.None))
ScrollBars = ScrollBars.Auto;
// Allow multiple controls to be placed horizontally
// (normally each div get's its own line)
Style["display"] = "inline-block";
// add spacing outside the control
Style["margin"] = "0.5em";
// add space inside the control
Style["padding-left"] = "0.5em";
Style["padding-right"] = "0.5em";
Style["padding-bottom"] = "0.5em";
// top space usually comes from the title...
if (String.IsNullOrEmpty(FilesTitle) == true)
Style["padding-top"] = "0.5em";
}
// ---- RenderContents ----------------------------
//
// Spit out the Html
protected override void RenderContents(HtmlTextWriter Output)
{
// output the title if one was set
if (String.IsNullOrEmpty(FilesTitle) == false)
{
Output.Write("<h3> "); // cosmetic spacing
Output.Write(FilesTitle);
Output.Write("</h3>");
}
GetFilesArray();
if (m_FilesArray.Length == 0)
{
Output.Write(HttpUtility.HtmlEncode(NoFilesText));
}
else
{
foreach (String OneFile in m_FilesArray)
{
HyperLink Link = new HyperLink();
Link.NavigateUrl = Path.Combine(FilesDirectory, Path.GetFileName(OneFile));
Link.Text = Path.GetFileNameWithoutExtension(OneFile);
Link.RenderControl(Output);
Output.WriteBreak();
}
}
}
// ---- GetFilesArray -------------------------
//
// Fill the m_FilesArray with a list of files
// either from disk or the cache
private void GetFilesArray()
{
// see if the file list is in the cache.
// use directory and filter as unique key
m_FilesArray = Page.Cache[FilesDirectory + FilesFilter] as String[];
if (m_FilesArray != null)
return;
// if no files filter set, use the default one.
if (String.IsNullOrEmpty(FilesFilter))
FilesFilter = DEF_FILES_FILT;
// if no files directory set, use the default one.
if (String.IsNullOrEmpty(FilesDirectory))
FilesDirectory = DEF_FILES_DIR;
// if a virtual path is detected, map to full path
String FullPath;
if (FilesDirectory.StartsWith("~"))
FullPath = Context.Server.MapPath(FilesDirectory);
else
FullPath = FilesDirectory;
// get the files
m_FilesArray = Directory.GetFiles(FullPath, FilesFilter, SearchOption.TopDirectoryOnly);
// put the list in the cache so we don't have to read the disk again
// use a dependency on the directory being read from for auto refreshing
Page.Cache.Insert(FilesDirectory + FilesFilter, // unique key
m_FilesArray, // list of files to store
new CacheDependency(FullPath)); // dependency on directory
}
}
}
注意:
控件名字:
去煩惱一個控件的名稱是非常愚蠢做法,真的是這樣嗎?錯誤的命名一個控件(或裡任何變量)就像結婚。由於你將要與它走很長一段時間,在將來改變它可能會非常痛苦。所以首先要用好你的命名。
我稱這個控件為: HyperlinkFileList.
溢出問題:
如果控件的高度一旦設置,文件的列表超出了控件的高度。就會使文件“溢出”控件的邊界。
為了解決這個問題,我說下面代碼到控件的構造函數之中:
以下為引用的內容:
if ((Height != null) && (ScrollBars == ScrollBars.None))
ScrollBars = ScrollBars.Auto;
CSS 布局:
因為控件是基本是一個div(panel渲染成一個div),然後,設置“display”屬性為“inline-block”,允許多個控件被並排的一起。
以下為引用的內容:
Style["display"] = "inline-block";
CSS框模型:
我不喜歡文本卡在控件的左側,所以我添加一些CSS來填充。我也在控件的周圍使用一些CSS樣式。使它不會與其它控件貼的很死。
以下為引用的內容:
// add spacing outside the control狀態管理:
在最初的測試的時候, 我發現每次控件都能運行,它都將重新讀取的文件目錄。文件輸入輸出代價是很昂貴的。我想去結合的服務器控件的“State”。但它使用的是VIEw State 類型,兩次發送文件列表到客戶端是效率非常低的 。一次作為Html列表,一次在VIEwState 。
所以我想使用 Session State, Application State 和Cache。
我決定將文件列表放在一個緩存對象之中。使列表能在session之間共享。如果內存在溢出,緩存中的列表將會丟失。
我將文件的目錄和文件filter連接在一起,作為緩存中索引鍵。這樣允許多個控件同時使用和共享文件列表。
開始,我添加了使開發人員可以強制重新讀取需要的文件的功能。但是,緩存對象可以使用依賴關系:任何從屬目錄更改會導致緩存過期。最後的代碼是非常的簡單:
以下為引用的內容:
// put the list in the cache so we don't have to read the disk again
// use a dependency on the directory being read from for auto refreshing
Page.Cache.Insert(FilesDirectory + FilesFilter, // unique key
m_FilesArray, // list of files to store
new CacheDependency(FullPath)); // dependency on directory
側注:當然,這只是一個代碼行,但做了幾個小時的研究,以決定這是最好的方法去處理狀態的管理的問題。有時需要很長的時間編寫很少的代碼。
Property Editor:
我將所有的自定義屬性分組到標題 “Files List”下面。他們都在一個地方與Panel的屬性分離開來。
下面是四個控件在一個頁面上的標簽
以下為引用的內容:
<EW:HyperlinkFileList ID="HyperlinkFileList5" runat="server" BackColor="#FFFF66"
Height="200px">
<EW:HyperlinkFileList ID="HyperlinkFileList6" runat="server" FilesTitle="The Same XML Files"
Height="200px">
<br >
<EW:HyperlinkFileList ID="HyperlinkFileList7" runat="server" BackColor="#66FFFF"
BorderColor="#FF3300" BorderWidth="3px" FilesDirectory="C:/Peachw/EndSofi/BAK/"
FilesFilter="*.Zip" FilesTitle="Whole lotta files!" ForeColor="#3333CC" Width="293px"
Height="156px">
<EW:HyperlinkFileList ID="HyperlinkFileList8" runat="server" BackColor="#66CCFF"
Height="156px" Width="198px" FilesDirectory="~/Images/" FilesFilter="*.jpg"
FilesTitle="Pretty Pictures">
下面是他們渲染之後的樣子:
第二部分:自定義服務器控件編輯器
選擇文件目錄:
我認為粘貼文件的目錄路徑是業余開發人員使用的法子,所以我決定添加一個目錄浏覽器。
開發服務器控件編輯器所用時間比開發實際控件用的時間更長。
我認為控件的文件的目錄,應在兩個方面可設置:
Absolute Path: C:\PublicData\ImageFiles\
Virtual Path: ~\XMLFiles\
我試著設計兩個內置浏覽來設置FilesDirectory屬性。
以下為引用的內容:
[EditorAttribute(typeof(System.Web.UI.Design.UrlEditor), typeof(UITypeEditor))]
我沒有使用UrlEditor,因為它不允許浏覽外部站點的主目錄。
以下為引用的內容:
[EditorAttribute(typeof(System.Windows.Forms.Design.FolderNameEditor), typeof(UITypeEditor))]
我沒有使用FolderNameEditor,因為它沒有提供虛擬路徑的選擇。此外,它迫使用戶選擇一個我不想選擇文件。
要創建自定義服務器控件編輯器,就要創建一個類自UITypeEditor繼承和覆蓋兩個功能。..其中一個啟動一個DialogBox。
下面是代碼:
以下為引用的內容:
using System;
using System.Collections.Generic;
using System.Drawing.Design;
using System.ComponentModel;
using System.Windows.Forms.Design;
using System.Text;
namespace EndWell
{
class DualModeFolderEditor : UITypeEditor
{
// ---- GetEditStyle --------------------------------
//
// tell designer what kind of UI we are (Dropdown or Modal DialogBox)
public override UITypeEditorEditStyle
GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
// ---- EditValue ----------------------------------------
//
// Called by IDE designer when user clicks the ... button
// A DialogBox is launched
public override object EditValue(ITypeDescriptorContext Context,
IServiceProvider Provider,
object Value)
{
IWindowsFormsEditorService EditorService = null;
if (Provider != null)
{
EditorService = (IWindowsFormsEditorService)
Provider.GetService(typeof(IWindowsFormsEditorService));
}
if (EditorService != null)
{
// launch the dialog box
DuaModeFolderEditorForm Editor =
new DuaModeFolderEditorForm(Value.ToString(), Context);
EditorService.ShowDialog(Editor);
return Editor.m_Value;
}
else
{
return Value;
}
}
}
}
以下是編輯的DialogBox,如下所示:
我將不會顯示DialogBox代碼,因為這是它比較長,涉及廣。由於缺乏文檔,在開發過程中進行了反復的試錯。但也有一些利益的東西。..
目錄分隔符(斜線):
反斜槓像 “\~”這樣被使用的時候,GetProjectItemFromUrl 函數將不能正常使用。它正斜槓:“/~”能正常工作。因此,我確保所有的目錄分隔符使用正斜槓。但是,從目錄浏覽器返回的目錄使用的是反斜槓。所以我們要確保一致性。..雖然它使代碼有點凌亂,但真的沒有我更喜歡其他選擇。
服務器控件開發提示:
一旦控件放置到頁面上,你能自動的更新bin文件路徑下面的DLL,通過右擊右擊控件,選擇“Refresh”。我不得不刪除從bin目錄下的控件,然後重新添加到網頁上,以獲取最新版本到該項目中。
調試編輯器:
調試控件是非常容易的。調試控件的編輯器卻非常的難。因為它運行在Visual Studio中。我在不同的地方添加下面代碼:
System.Diagnostics.Debugger.Break();
當運行到斷點,你得到下面這個令人爽快的畫面:
點擊 “Debug the program”,將創建一個新的 Visual Studio 實例。你就能夠調試控件的編輯器了。原來的運行Visual Studio將鎖定的(至少在我這裡是這樣的),你不得不去終止。由於欠缺自定義服務器控件編輯器的文檔,這是非常寶貴的去尋找和了解什麼被傳遞了,發生了什麼。
可能的改進:
•標題字體可設置(大小,顏色,背景顏色。..)
•將名稱放在一個固定的div中,文件列表在另一可調整大小或有scrollable的div中。
•添加一個布爾字段來選擇顯示在鏈接的文件擴展名。
希望這篇文章能夠幫助你。
歡迎討論,謝謝!
參考原文:http://www.codeproject.com/Articles/46264/A-Simple-ASP-NET-Custom-Server-Control.ASPx
作者:朱祁林
出處:http://zhuqil.cnblogs.com
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。