2014年3月18日 星期二

[ASP.NET]如何知道目前按下的按鈕是哪一個?

常常我們在網頁中都需要按下按鈕來執行某些動作

ASP.NET也有提供Button、ImageButton、LinkButton三種按鈕

這三種控制項都是按鈕

只是長的不太一樣而已

假設現在有一種情況是三個按鈕都會觸發同一種方法時

那麼我們要怎麼知道目前的使用者按下去的是哪一個?

一般的作法是這樣

網頁部份我們會設定一個OnClick去呼叫這個方法
<asp:Button ID="Button1" runat="server" OnClick="CallMe_Click" Text="Button" />
CodeBehind的部份這樣寫
protected void CallMe_Click(object sender, EventArgs e)

在這邊特別提一下

當觸發OnClick的時候

他會傳入本身物件跟觸發事件給CallMe_Click

重點在object sender

你用Button btn=sender as Button就能取得觸發這個方法的Button object

這個方法以前在VB也有看過

不過一直不知道可以這樣用

後來看了MIS2000的書才知道還有這招

後來我遇到不同類型的按鈕也理所當然就想用這招解決

當然你不會知道目前是哪個按鈕被按下

所以取得sender自然要轉型成正確型態才能使用他

不然他對C#來講就是個object

你也只能用object才有的東西

所以我是這樣寫的

var btn = (sender.GetType().Name == "LinkButton") ? sender as LinkButton : sender as Button;

但一直有型態錯誤的訊息

後來上網問人才知道原來?:運算子的:左右兩邊的結果強制規定不能不同型態

又學到一個觀念!

而且就算是用if來做還是不行

LinkButton、Button也沒有父子類別的關係不能互轉

後來人家有提供一個方法就是用OnCommand

OnCommand根據MSDN的定義只要頁面上的任何按鍵按下都會觸發此方法

重點在於OnCommand提供的引數

protected void CallMe_Click(object sender, CommandEventArgs e)

其中的CommandEventArgs e它代表著包含資料的相關Command事件

他包含著Button本身的一些訊息像是CommandArgument或是CommandName之類的屬性

所以我們可以將一些訊息寫進CommandArgument中就可以用來判斷目前你按的按鍵要傳什麼訊息進來了

<asp:Button ID="Button1" runat="server" OnCommand="CallMe_Click" Text="Button1" CommandArgument="Button1" />
<asp:Button ID="Button2" runat="server" OnCommand="CallMe_Click" Text="Button2" CommandArgument="Button2" /><br />
<br />
<asp:ImageButton ID="ImageButton1" runat="server" OnCommand="CallMe_Click" CommandArgument="ImageButton1" />
<asp:ImageButton ID="ImageButton2" runat="server" OnCommand="CallMe_Click" CommandArgument="ImageButton2" />
<br />
<br />
<asp:LinkButton ID="LinkButton1" runat="server" OnCommand="CallMe_Click" CommandArgument="LinkButton1" >LinkButton1</asp:LinkButton>
<asp:LinkButton ID="LinkButton2" runat="server" OnCommand="CallMe_Click" CommandArgument="LinkButton2">LinkButton2</asp:LinkButton>

protected void CallMe_Click(object sender, CommandEventArgs e)
{
    Response.Write("<script>alert('你按下的是" + e.CommandArgument.ToString() + "');</script>");
}



問題是解決了不過還是有個小小的缺憾

沒想到不能直接用sender來轉型取得按下按鈕的物件

不過後來網路上的AllenKuo提供了一個方法

因為Button、LinkButton跟ImageButton都會implement IButtonControl這個interface

而CommandArgument、CommandName、PostBackUrl、Command()、Click()這些屬性跟方法也是從IButtonControl來的

所以我們可以把sender轉型成IButtonControl型態

解法如下

protected void CallMe_Click(object sender, CommandEventArgs e)
{
    var btn = sender as IButtonControl;
    Response.Write("<script>alert('你按下的是" + btn.CommandArgument+ "');</script>");
}

如此就能使用共同的界面來呼叫不同的屬性跟方法了

非常漂亮的解法!

2014年3月16日 星期日

[ASP.NET]ScriptManager同時存在dll的錯誤

前陣子compiler遇到下面的錯誤

'c:\WINDOWS\assembly\GAC_MSIL\System.Web.Extensions\1.0.61025.0__31bf3856ad364e35\System.Web.Extensions.dll' 和 'c:\WINDOWS\assembly\GAC_MSIL\System.Web.Extensions\3.5.0.0__31bf3856ad364e35\System.Web.Extensions.dll' 兩者中都有型別 'System.Web.UI.ScriptManager'

後來查了一下

原來是web.config的設定中有兩個System.Web.Extensions

Ajax的元件重複引用到兩個同名但不同版本的命名空間

因為用到的都是1.0.0的版本所以把web.config內下面的內容刪除就解決了
<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

[ASP.NET]如何只顯示自定義GridView的欄位

最近遇到一個需求

就是要使用GridView的某些欄位建立出新欄位的資料

但這些資料來源的欄位又不出現在畫面上

資料來源是DataSet

關於這個問題目前想到有三個解法

第一個就是在資料庫Select時就完成過濾產生新資料行的動作

第二個方法是在DataSet或DataTable的物件中就處裡table內容

然後再輸出給GridView

最後一個解法就是資料列顯示在GridView的時候處裡

之後我是選擇第三的方法

因為最直接我掌控度也比較高

不過用這方法有遇到我預料之外的事

範例程式碼如下

這個範例的總平均欄位是由國文成績跟數學成績計算出來非原來table的內容

<asp:gridview autogeneratecolumns="true" id="GridView1" onrowdatabound="GridView1_RowDataBound" runat="server">
        <columns>
            <asp:boundfield datafield="student_id" headertext="學號" sortexpression="student_id">
            <asp:boundfield datafield="city" headertext="居住地" sortexpression="city">
            <asp:boundfield datafield="chinese" headertext="國文成績" sortexpression="chinese">
            <asp:boundfield datafield="math" headertext="數學成績" sortexpression="math">
            <asp:templatefield headertext="總平均">
                <itemtemplate>
                    <asp:label id="AVG" runat="server" text=""></asp:label>
                </itemtemplate>
            </asp:templatefield>
        </asp:boundfield></asp:boundfield></asp:boundfield></asp:boundfield></columns>
</asp:gridview>

顯示出來的結果是這樣


本來我預期撰寫上面程式碼的內容應該只有出現紅色框框外的自訂義內容

但是原來table中的內容還是跟著一起出現

後來找到不到問題在哪裡時我是先用Visible將欄位隱藏來解決

protected void Rate_RowDataBound(object sender, GridViewRowEventArgs e)
{
    e.Row.Cells[5].Visible = false;
    e.Row.Cells[6].Visible = false;
    e.Row.Cells[7].Visible = false;
    e.Row.Cells[8].Visible = false;
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        Label AVG = e.Row.FindControl("AVG") as Label;
        AVG.Text = ((Convert.ToDouble(e.Row.Cells[2].Text) + Convert.ToDouble(e.Row.Cells[3].Text)) / 2).ToString();
    }
}

當然這個方法看起來並不是很理想應該有更簡明的方式

後來回來仔細想了網路上人家提供的一個方法

就是將GridView的AutoGenerateColumns屬性設成false

依據MSDN上的敘述

取得或設定值,指出是否自動建立資料來源中每個欄位的繫結欄位。

簡單說就是你把DataSet、DataTable指派給GridView的DataSource後

他就不會自動幫你在頁面上顯示出Table內容

你設成false之後就只會顯示你自己定義的欄位內容了

2014年3月2日 星期日

[C#]將陣列切割成數個小陣列

最近又遇到一個奇妙的問題

就是對方提供的刪除資料的方法給我們

刪除小筆的資料(陣列)沒什麼問題

但是要刪除大筆資料的話就會出問題

在這種情況下最直觀的解決方法就是把原來的陣列切成數個小陣列

在一一丟進去給該方法刪除直到全部的內容刪除完畢

解法如下

int[] arr = new int[43];
Random rand=new Random(47);
for (int i = 0; i < arr.Length; i++)
{
    arr[i] = rand.Next()%100;
    Response.Write(arr[i]+" ");
}
Response.Write("<br>");

Array[] arr2 = null;

Response.Write("arr.Length=" + arr.Length + "<br>");

if (arr.Length % 20 == 0)
    arr2 = new Array[(arr.Length / 20)];
else
    arr2 = new Array[(arr.Length / 20) + 1];

for (int i = 0, index = 0; i < arr2.Length; i++)
{
    if (arr.Length - index >= 20)
    {
        arr2[i] = Array.CreateInstance(typeof(Int32), 20);
        Array.Copy(arr, index, arr2[i], 0, 20);
        index += 20;
    }
    else if (arr.Length - index > 0)
    {
        arr2[i] = Array.CreateInstance(typeof(Int32), arr.Length - index);
        Array.Copy(arr, index, arr2[i], 0, arr.Length - index);
    }
}
foreach (Array intArr in arr2)
{
    foreach (int delObj in intArr)
    {
        Response.Write(delObj+" ");
    }
    Response.Write("<br>");
}

程式在一開始亂數產生一個int陣列紀錄到arr陣列

arr2代表著新的陣列,使用Array類別當型別,代表著切割之後的每個reference指向一個一維陣列

每個新的陣列都有個長度(代表你每次傳進去刪除方法中的陣列有多長),這邊範例是20

當陣列在產生時會有兩種情況

第一你的陣列長度剛好可以被20整除,那表示你需要長度為n/20的陣列

第二你的陣列長度無法被20整除,那表示你需要長度為(n/20)+1的陣列

之後的迴圈代表將每筆資料copy至新陣列中

index代表目前指向舊陣列哪個索引

如果舊陣列長度減掉index大於等於20

那麼就用CreateInstance(typeof(Int32), 20)產生一個型態為Int32,長度為20的陣列(此時內容為空)給arr2[i]

之後用Array.Copy(arr, index, arr2[i], 0, 20);

複製arr陣列中從索引index開始到arr2[i] 中,長度為20的內容

完成後再將index加上20代表就陣列的索引往後移動20指到新的開頭

持續這個動作一直到arr.Length - index沒有大於等於20

則利用arr.Length - index判斷目前還剩下幾個元素未copy

測試範例如下


用這方法是真的有把問題解決了

但這其實不是最好的解法

其實我想的是能否直接將reference指到新的陣列而非用copy

畢竟用copy表示系統中會出現兩筆一模一樣的資料

copy的陣列越大消耗的系統資源也越大

也許是我對C#還不夠熟悉

如果有想到更好的解法再貼上來吧~