2010/1/25

運用 CSS 讓過長文字以省略符號取代

在某種情況下, 我們可能需要把一段過長的文字從中截斷, 並在截斷處以省略符號 (...) 取代, 表示這段文字其實尚未結束, 只是被省略。如果你的文字是從資格庫中取得, 那麼你可以從資料庫取出時就載斷字串並加上簡略字元後傳回; 然而, 如果你不想把原始資料真的截斷 (若依上文, 你取出的資料是已經被截斷的, 除非重新讀取, 否則勢必拿不到完整的版本), 那麼你可以嘗試我現在要提供的方法, 那就是以 CSS 設定, 達到相同的效果。

請先來看看程式:

<style>
   .AutoSkip {
      overflow:hidden;
      text-overflow:ellipsis;
      white-space: nowrap;
   }
</style>
<html>
<body>
   <div class="AutoSkip" style='width:600px;'>
     在某種情況下, 我們可能需要把一段過長的文字從中截斷, 並在截斷處以省略符號 (...) 取代,
     表示這段文字其實尚未結束, 只是被省略。如果你的文字是從資格庫中取得, 那麼你可以參考我寫的
     「[SQL Puzzle] 載斷字串並加上簡略字元後傳回」一文, 當你取出資料時, 就把文字截斷。
   </div>
</body>
</html>

雖然在 <div> 段落中夾著一段很長的文字, 但如果你實際執行上述程式, 你會發現在 600px 處文字確實被截斷, 而且自動加上了省略符號, 變成了如下的樣子:

在某種情況下, 我們可能需要把一段過長的文字從中截斷...

當然, 這個結果和在資料庫中取出時就截斷文字看來有一樣的效果, 但實際上, 採用 CSS 的方法其實並沒有改動到文字; 它只是讓你覺得文字看起來好像被截斷而已。

運用這種方法, 無疑是給了程式設計師更大的彈性, 讓你在組版面時具有更靈活的空間。比方說, 由於資料並沒有被真的截斷, 所以, 假設你打算把資料繫結到 GridView 容器中, 限於版面, 你必須把過長的文字截斷, 但是你另一方面卻又可以把完整的文字繫結到 tooltip 裡面。如果採用第一種做法 (在讀取資料時即予截斷), 就無法做到這一點。

在上述程式中, 我所示範的技巧全部都在 AutoSkip 這個樣式裡了, 所以老實說也沒有什麼太大的學問; 照著寫就對了!

不過經過測試, IE8 與 Chrome 4.0 都可以正常顯示, 在 FireFox 上卻不會出現簡略字元, 而是硬生生把字串剪斷了, 如果那個地方是中文字, 有可能只顯示一半, 不甚美觀。

[SQL] 使用 Stored Procedure 動態組成 SQL 查詢指令

以下示範一個可以動態組成查詢子句的 SQL 指令, 以 MS SQL 的 Stored Procedure 寫成。其中, 如果任一個參數(或全部)傳入 NULL 值, 即代表不限制那個欄位的條件。

別讓網頁圖片降低網站效能 - 談談網頁圖片處理新趨勢

如果你的網頁只是設計來自己玩玩, 或者它很明確的只有少數使用者會上來, 那麼你大可以不必用到我在這裡要介紹的方法。但是, 如果你的網站是供作大量使用者(例如超過上千個 current session), 那麼你一定不得不開始想方設法的提升網站的效能

2010/1/20

多功能 FileUpload 使用者控制項

我寫了一個可以處理檔案上傳的多功能使用者控制項,除了可以處理一般的檔案上傳之外,還可以動態修改上傳路徑、讓使用者決定是否覆蓋舊檔,並動態決定是否限制使用者只能接受圖型檔案

善用擴充方法

雖然我個人認為自從 .Net 2.0 以後引進的 Lambda 運算式在某種程度上破壞了 C# 的嚴謹度, 但是對於一個程式設計師而言, 我們也不能否定它對於程式撰寫所能提供的方便性。擴充方法 (Extension Method) 也是一樣; 如果你還不知道擴充方法是什麼, 那麼當你看完本文之後, 我相信你會感謝微軟提供了這麼好用的功能。

擴充方法是 .Net 3.5 才提供的, 所以在之前的版本中並沒有這東西。它的實作很簡單 (相對於其功能), 絕對超乎所有人的想像。請先看看以下的程式碼:

public static class cls
{
    public static string keep10chars(this string str)
    {
        return (str.Length > 10) ? str.Substring(0, 10) : str;
    }
}

撰寫擴充方法就是這麼簡單而已, 以下幾個重點要記住:

  1. 它必須包在一個宣告為 public static 的類別裡面 (如上例中的 public static class cls); 類別名稱可以隨便取, 並不影響執行及引用。 
  2. 方法必須以 public static [type] 宣告 (如上例中的 public static string)。
  3. 方法不能沒有參數, 而且其型別必須以 this [type] 宣告 (如上例中的 this string)。

如果你寫在網頁裡, 那麼完整的網頁程式如下:

using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {    
        Response.Write("12345678901234567890".keep10chars());
    }
}

public static class cls
{
    public static string keep10chars(this string str)
    {
        return (str.Length > 10) ? str.Substring(0, 10) : str;
    }

如上例, 當你把這個擴充方法 keep10chars 寫好之後, 你的字串物件就自動多了一個 .keep10chars() 方法可以使用了。在這個例子中, "12345678901234567890".keep10chars() 的輸出是 "1234567890"。意思就是說, 你已經為 string 型別建立了一個擴充方法 keep10chars(), 可以隨時引用。

擴充方法的用法就是這麼簡單。你不需要另外建立一個繼承自 string 的新型別, 而是直接套用在現有的系統型別上面; 也沒有什麼繁瑣的宣告或語法。你說, 這是不是非常方便呢?

擴充方法對於 ASP.NET 程式而言, 還有另一個極為友善的功能, 那就是它可以毫無問題的用在繫結控制項的 tag 裡面。舉個例子, 假設你的 GridView 中有一個 TemplateField, 你可以把擴充方法直接運用在從 Eval 函式撈出來的資料上:

<asp:TemplateField HeaderText="OperatorName" SortExpression="OperatorName">
    <EditItemTemplate>
        <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("OperatorName") %>'></asp:TextBox>
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Label ID="Label1" runat="server" Text='<%# Eval("OperatorName").ToString().keep10chars() %>'></asp:Label>
    </ItemTemplate>
</asp:TemplateField>

運用擴充方法, 我們就可以對所繫結的資料進行後處理, 而且是用非常簡單而輕鬆的做法。

不過, 請注意, 我發現我在 .Net 4.5 上建立的 ASP.NET 專案上建立的擴充方法, 總是發生未定義的錯誤。這個網頁有套用 Master Page, 我不知道這是否是引發錯誤的原因; 不過解法很簡單。

我的擴充方法是這樣定的:

namespace Johnny
{
    public static class abc
    {
        public static string Gender(this string isMale)
        {
            return (1)? "男" : "女";
        }
    }

    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
                ...

換句話說, 這個擴充方法 Gender() 是定義在和網頁相同的命名空間之下的。但是, 我還是必須以手動方式在網頁上加上明確的 IMPORT 指示詞, 這個擴充方法才能被找到:

<%@ Page Title="" Language="C#" MasterPageFile="~/..." %>
<%@ Import Namespace="Johnny" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <h2>...
    ...
<asp:TemplateField ...>
    <ItemTemplate>
        <asp:Label ID="lbIsMale" runat="server" Text='<%# Eval("IsMale").Gender() %>' />
    </ItemTemplate>
</asp:TemplateField>

我不知道為什麼這種問題會發生, 我只知道加上 IMPORT 指示詞就能解決這個問題。

如果你想更進一步了解擴充方法, 你可以參考 C# 程式設計手冊 (也有 VB 的版本)。

2010/1/19

[UserControl] 在使用者控制項中建立事件處理函式

當你在設計 ASP.NET 網站時, 使用者控制項 (User Controls) 是非常好用的,假設我們把一個 DropDownList 製作成使用者控制項,那麼我們就等於把整個相關機制 (外觀、資料存取及邏輯判斷等) 都封裝在一個可以重複使用的物件裡面,可以在網站的任何地方使用。所以我十分推薦大家適當地使用使用者控制項

2010/1/18

ASP.NET 的電子郵件傳輸工具程式

以下是我已經寫好的 Email 傳送程式, 稍為改一下就可以使用了:

using System;
using System.Net.Mail;
using System.Text;
using System.Web;
using System.Web.Configuration;

public class Mails
{
    private string _sendFrom = "YourId@YourDomain";
    private string _sendBy = "系統管理員";

    /// 

    /// 指定 SMTP Server; 預設值為 msa.hinet.net
    /// 

    public string Host = "msa.hinet.net";

    /// 

    /// 指定 SMTP Server Port; 預設值為 25
    /// 

    public int HostPort = 25;

    /// 

    /// 指定寄件人的 Email 位址
    /// 

    public string SendFrom
    {
        get
        {
            return _sendFrom;
        }
        set
        {
            _sendFrom = value;
        }
    }

    /// 

    /// 指定寄件人的名字
    /// 

    public string SendBy
    {
        get
        {
            return _sendBy;
        }
        set
        {
            _sendBy = value;
        }
    }

    /// 

    /// 建立寄件人 MailAddress 物件
    /// 

    private MailAddress From
    {
        get
        {
            MailAddress _from = new MailAddress(SendFrom, SendBy);
            return _from;
        }
    }

    /// 

    /// 指定收件人地址; 可以同時指定多個 Email, 以逗號區隔
    /// 

    public string To;

    /// 指定 CC 地址; 由於可以同時指定多個 Email, 以逗號區隔
    /// 
    public string Cc;

    /// 指定 BCC 地址; 由於可以同時指定多個 Email, 以逗號區隔
    /// 
    public string Bcc;

    /// 

    /// 指定該郵件的標題
    /// 

    public string Subject = null;

    /// 

    /// 指定郵件標題的編碼方式, 預設值為 UTF8
    /// 

    public Encoding SubjectEncoding = Encoding.UTF8;

    private string _body = null;

    /// 

    /// 指定該郵件的內容
    /// 

    public string Body
    {
        get
        {
            return _body;
        }
        set
        {
            _body = (IsBodyHtml) ?
                string.Format("<div style='{0}'>{1}</div>", Style, value) :
                value;
        }
    }

    /// <summary>
    /// 指定郵件內容的編碼方式, 預設值為 UTF8
    /// </summary>
    public Encoding BodyEncoding = Encoding.UTF8;

    /// <summary>
    /// 指定該郵件是否為 HTML 格式或純文字格式; 預設值為 true
    /// </summary>
    public bool IsBodyHtml = true;

    /// <summary>
    /// 指定整體信件的樣式; 只有當信件格式為 HTML 時有效
    /// </summary>
    public string Style = "font-family:'微軟正黑體, Verdana,Arial'; width: 90%; font-size: 0.9em;";

    private MailMessage mail;

    public Mails() // Constructor
    {
        mail = new MailMessage();
    }

    /// <summary>
    /// Send mail directly with static data
    /// </summary>
    public static void QuickSend(string _Subject, string _Message, string _To, string _Cc = null, string _Bcc = null)
    {
        Mails m = new Mails();
        m.To = _To;
        m.Cc = _Cc;
        m.Bcc = _Bcc;
        m.Subject = _Subject;
        m.Body = _Message;
        m.Send();
    }

    /// <summary>
    /// 寄出信件
    /// </summary>
    public void Send()
    {
        if (To == null) // 防呆
            return;
        mail.From = this.From;
        string a = mail.From.Address;
        string u = mail.From.User;
        foreach (string to in this.To.Split(','))
            if (!string.IsNullOrEmpty(to))
                mail.To.Add(to);
        foreach (string cc in this.Cc.Split(','))
            if (!string.IsNullOrEmpty(cc))
                mail.To.Add(cc);
        foreach (string bcc in this.Bcc.Split(','))
            if (!string.IsNullOrEmpty(bcc))
                mail.To.Add(bcc);
        mail.IsBodyHtml = this.IsBodyHtml;
        mail.Subject = this.Subject;
        mail.SubjectEncoding = this.SubjectEncoding;
        mail.Body = this.Body;
        mail.BodyEncoding = this.BodyEncoding;

        SmtpClient client = new SmtpClient(this.Host, this.HostPort);
        client.Send(mail);
    }
}

要使用這個類別, 最簡單的方法如下:

string subject = txtMailSubject.Text.Trim();
string message = txtMailMessage.Text.Trim();
string to = txtMailTo.Text.Trim();
string cc = txtMailCc.Text.Trim();
string bcc = txtMailBcc.Text.Trim();
Mails.QuickSend(subject, message, to, cc, bcc);

在程式中, Mails.QuickSend 是一個靜態方法, 把相關字串填上去即可進行郵件傳送; 凡是未指定的欄位或屬性, 都採用預設值。如果你不想使用預設值, 則可以改用 Send() 方法, 但是可以自行修改各欄位或屬性的值。

[ADO.NET] 動態 SQL 指令與 SqlDataSource 的 QueryStringParameter 配合查詢

我在「[SQL] 使用 Stored Procedure 動態組成 SQL 查詢指令 」一文中介紹了如何在 Stored Procedure 中動態的組合出 SQL 指令並進行查詢。然而, 如果我們要配合 SqlDataSource 中以進行查詢, 該怎麼做呢?