2014年9月18日 星期四

[ASP.NET]GridView中的控制項修改其他行控制項Part2

上一篇研究如何知道目前是哪一列的的控制項被觸發

好讓我們可以去修改同一列的內容

雖然提出了一個方式

但可以解決這問題的方式果然不只一種

論壇上topcat跟MIS2000提出用NamingContainer來取得我在哪個控制項中(就是GridViewRow)

protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
    DropDownList ddl = sender as DropDownList;
    GridViewRow gv = ddl.NamingContainer as GridViewRow;
    Label lb = gv.FindControl("Label1") as Label;
    lb.Text = ddl.SelectedValue;
}


不過讓我覺得神奇是Allen提出來的方式

protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
    var ddl = sender as DropDownList;
    var lb = ddl.FindControl("Label1") as Label;
    lb.Text = ddl.SelectedValue;
}

重點在var lb = ddl.FindControl("Label1") as Label;這段

我本來以為FindControl是專門用來找某個控制項中的子控制項(剛好跟NamingContainer相反)

但居然可以用同一階的控制項找到其他控制項

這可就神奇了~

因為依照MSDN上的解釋FindControl的功能「以指定的id去搜尋當前容器"內"的伺服器控制項。」

既然是內沒想到連隔壁的都可以找到

看來我對FindControl特性還不夠熟

有好好研究的必要!

參考文獻
使用 NamingContainer 屬性決定控制項的命名容器
Control.NamingContainer 屬性
HOW TO:存取 Web 伺服器控制項命名空間的成員
Control.FindControl 方法 (String)
GridView中取得目前使用的控制項所在的列

2014年9月17日 星期三

[ASP.NET]GridView中的控制項修改其他行控制項

在GridView中你可能有一行有DropDownList

你希望選擇這一行的DropDownList可以讓另外一行的Label帶出資料

像是選擇幣別然後同一列的其他行帶出匯率之類的

這個功能在SelectedIndexChanged就能辦到

但會有個問題

我怎麼知道是哪一列的DropDownList被異動?

如果我不知道是哪個DropDownList被異動

我又怎麼知道要去修改哪一列的內容

這個問題解法似乎很多

目前我只想到這種解法

protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            DataTable dt = new DataTable();
            dt.Columns.Add(new DataColumn("No", typeof(string)));

            for (int i = 0; i < 5; i++)
            {
                DataRow dr = dt.NewRow();
                dr["No"] = i.ToString();
                dt.Rows.Add(dr);
            }
            GridView1.DataSource = dt;
            GridView1.DataBind();
        }
    }
    protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
    {
        DropDownList ddl = sender as DropDownList;
        int index = Convert.ToInt32(ddl.Attributes["index"]);
        Label txt = GridView1.Rows[index].FindControl("Label1") as Label;
        txt.Text = ddl.SelectedValue;
    }
    protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            DropDownList ddl = e.Row.FindControl("DropDownList1") as DropDownList;
            ddl.Attributes.Add("index", e.Row.RowIndex.ToString());
        }
    }

簡單說就是利用每個控制項都有的Attributes

用Attributes取個編號到時候你再取出當下這個DropDownList的編號就可以知道是那一列了

如果是Button可以用RowCommand的事件知道是哪個按鈕被按

印象中應該還有其他解決方式

有研究出來再來分享吧~

2014年8月10日 星期日

[ASP.NET]如何取消Button控制項的PostBack

ASP.NET多數控制項都可以選擇是否觸發AutoPostBack

但偏偏Button控制項沒這東西讓你選,預設就是有

後來在ASP.NET的Button控制項上使用jQuery的toggle()

控制標籤顯示或隱藏

但是Button一按下去原本隱藏的標籤是有顯示了

但馬上又隱藏………

改CausesValidation="False"(是否觸發驗證)

跟UseSubmitBehavior="False"(是否為submit的按鈕)

都不行…

因為都是按下按鈕後畫面閃一下就收起來

而且在Page_Load()設定中斷點也有進到裡面

所以推斷應該是PostBack搞出來的問題

查了有無方法不讓Button控制項觸發PostBack

一開始有找到這個

Button.Attributes.Add("onclick", "event.returnValue=false;");

不過還是怪怪的…Chrome可以解決,IE跟FireFox一樣不行

可是到後來又不行了(是七月的關係嗎?)

後來終於找到在IE、FireFox、Chrome都可以取消PostBack的方法

Button.Attributes.Add("onclick", "return false;");

就很乾脆的回傳false就不會觸發PostBack

但要注意的是如果這樣寫的話Button就真的完全不會執行所Binding的事件

我是因為只拿來當開關所以沒差

參考文獻
[1] http://stackoverflow.com/questions/7547945/disable-postback-at-click-on-a-button
[2] http://stackoverflow.com/questions/683746/how-to-disable-postback-on-an-asp-button
[3] http://www.dotblogs.com.tw/yc421206/archive/2009/04/22/8125.aspx
[4] http://www.blueshop.com.tw/board/show.asp?subcde=BRD20080729150920PJW
[5] http://cate-taiwan.blogspot.tw/2008/08/aspnet-button-postback.html

2014年7月31日 星期四

[ASP.NET]Global.asax功能筆記

在ASP.NET專案底下有個Global.asax的檔案

該檔案可以在系統發生某些事件的時候被執行

個人用到的是捕捉例外的功能

在此做個筆記

<%@ Application Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Security" %>
<script RunAt="server">

    void Application_Start(object sender, EventArgs e)
    {
        // 應用程式啟動時執行的程式碼
    }

    void Application_End(object sender, EventArgs e)
    {
        //  應用程式關閉時執行的程式碼

    }

    void Application_Error(object sender, EventArgs e)
    {
        // 發生未處理錯誤時執行的程式碼
        HttpServerUtility server = HttpContext.Current.Server;
        HttpRequest request = HttpContext.Current.Request;
        HttpSessionState session = HttpContext.Current.Session;
        HttpBrowserCapabilities brower = HttpContext.Current.Request.Browser;
        Exception ex = server.GetLastError();
        System.Net.IPAddress ServerIP = new System.Net.IPAddress(System.Net.Dns.GetHostByName(System.Net.Dns.GetHostName()).AddressList[0].Address);

        StreamWriter sw = null;

        try
        {
            if (ex.InnerException != null)
            {
                ex = ex.InnerException;
            }

            string filePath = string.Format(@"{0}Log\err_log_" + DateTime.Now.ToString("yyyyMMdd") + ".txt", server.MapPath("~/"));

            sw = File.Exists(filePath) ? new StreamWriter(filePath, true) : File.CreateText(filePath);

            sw.WriteLine("----------Strat----------");
            sw.WriteLine("----------[目前時間:" + DateTime.Now.ToString() + "]----------");
            sw.WriteLine("事件發生網頁網址:" + request.Url);
            sw.WriteLine("事件發生路徑:" + request.Path);
            sw.WriteLine("例外狀況訊息:" + ex.Message);
            //事件發生網頁網址

            sw.WriteLine("例外堆疊:" + ex.StackTrace);

            sw.WriteLine("造成錯誤的程式名稱:" + ex.Source);

            //使用者名稱
            if (session != null)
                sw.WriteLine("使用者名稱:" + Session["UserID"].ToString());

            sw.WriteLine("識別系統別:" + request.UserHostAddress);

            sw.WriteLine("IP:" + ServerIP.ToString());

            sw.WriteLine("使用瀏覽器:" + brower.Type);

            sw.WriteLine("是否支援Cookie:" + (brower.Cookies ? "是" : "否"));

            sw.WriteLine("伺服器名稱:" + request.ServerVariables["SERVER_NAME"]);

            sw.WriteLine("----------END----------");

        }
        catch (HttpException HE)
        {
            throw HE;
        }
        catch (ArgumentNullException ANE)
        {
            throw ANE;
        }
        catch (ArgumentException AE)
        {
            throw AE;
        }
        catch (UnauthorizedAccessException UAE)
        {
            throw UAE;
        }
        catch (DirectoryNotFoundException DNFE)
        {
            throw DNFE;
        }
        catch (IOException IOE)
        {
            throw IOE;
        }
        catch (SecurityException SE)
        {
            throw SE;
        }
        finally
        {
            sw.Close();
            sw.Dispose();
        }
    }
</script>


Application_Error可以捕捉網站中的例外

但是如果該例外在網頁中就已經被捕捉

就不會被Application_Error給catch到

所以簡單說Application_Error專門接漏網之魚

這很適合用在你在本機執行沒問題

丟到IIS莫名其妙掛掉的情況

還有一些例外情況但被忽略掉的情形

上面範例就是當發生例外時

會取得該例外物件

然後描述該例外的情形

最好用的就是HttpContext.Current.Request的Url跟Path屬性

他可以指出當前的Request是來自於哪個網址跟路徑

這在IIS上執行有bug時又不曉得bug從何而來很好用

其餘的HttpContext.Current.Server、HttpContext.Current.Session、HttpContext.Current.Request.Browser

都可以讓你取得當下相關資訊並且在Application_Error寫入txt或是資料庫

方便你事後分析問題點

參考文獻
[1] http://www.dotblogs.com.tw/mis2000lab/archive/2008/04/28/3526.aspx
[2] http://sharebody.com/list.asp?id=50684
[3] http://msdn.microsoft.com/zh-tw/library/vstudio/2027ewzw%28v=vs.100%29.aspx
[4] http://www.webjx.com/htmldata/2006-08-14/1155516831.html
[5] http://myprogramlog.blogspot.tw/2013/09/globalasax-applicationbeginrequest.html

2014年7月2日 星期三

[C#]Debugger會呼叫ToString()的問題

為了紀念一下我逝去的青春小鳥(浪費了我一整個下午)

來紀錄一下這次遇到的事件

簡單說我寫了一個組合SQL字串的class

其中裡面有InsertString、DeleteString、UpdateString、WhereString等StringBuilder的物件紀錄執行的動作

當中我override ToString()這個來自System.Object的方法

來輸出組合並輸出的字串

詭異的事情就此發生

在Visual Studio偵錯模式底下執行看程式變化時

有用過的人應該知道可以按F10或F11一步一步執行

其中你游標指到變數還可以看到變數的內容

後來我發現每指向變數一次變數(StringBuilder)的內容就會改變一次

而且用ToString()輸出後的確是有改變

之後我嘗試在ToString()中回傳前指派InsertString、DeleteString、UpdateString、WhereString都為null

沒想到居然會發生參照未指定物件的錯誤

但問題是我根本還沒呼叫ToString()這個方法

怎麼「看起來」好像有去呼叫ToString()

難道是我學藝不精?還是C#有什麼神奇魔力…?

找了半天也跟別人討論

最後才發現這個不曉得算不算bug的問題來源

一般偵錯模式底下執行時我們游標指到變數會顯示的是這個物件完整的class name

但其實這個內容是呼叫ToString()來的

可是ToString()又已經被我Override過了

所以就會發生我遇到明明沒呼叫ToString()卻會執行ToString()內容的靈異事件了

這個問題好像一直到Visual Studio 2012都還有

難道微軟不認為這是bug嗎?

後來我有嘗試方法用new但還是不行

他看來就是一定會去執行這個物件的ToString()不管你是override還是new

看起來他並非去copy出一個新物件

再去呼叫這個新物件的ToString()

所以要解決這個問題你只能取別的名字避免他莫名其妙偷偷去呼叫ToString()

可以參考以下兩篇也是苦主的文章

http://mocheng.wordpress.com/2009/06/06/%E5%88%AB%E8%AE%A9tostring%E5%87%BD%E6%95%B0%E6%94%B9%E5%8F%98%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%8A%B6%E6%80%81/
http://www.xuebuyuan.com/130963.html

2014年7月1日 星期二

[Oracle SQL]使用update指定修改日期欄位

最近遇到日期轉換錯亂的問題

因為程式中在還沒完全改成民國年的情況下就執行

會導致儲存到Oracle上之後

出現3925年(1911+2014)這種結果

但要把時間改回2014年資料有上千筆當然不可能一個一個去改

當然還是要靠update的指令

但問題來了

如果你直接去用to_date改日期

最後的結果可能是變成2014/07/01 00:00:00

原本的時分秒可能因為沒指定被蓋掉

那麼要如何只改年不動到月日時分秒呢?

後來我問到的方法如下

update Login k set k.date=ADD_MONTHS(k.date,-1911*12)
where to_char(k.date,'yyyy')='3925';

這段的意思是說我將現有的時間減掉1911*12個月

至於where的部份必須要用to_char取得年

因為如果是用to_date的話會變成3925/07/01 00:00:00這種結果

當然你的時分秒不會剛好是00:00:00

所以也不會找到符合的結果

2014年6月16日 星期一

[Oracle SQL]如何看到某個時間點的資料內容

ㄜ…因為上禮拜不小心用delete刪了某位大嬸測試資料

搞的他老人家暴怒~

還好強者我同事最後還是幫我把資料復原回來

最後強者同事跟我分享如何還原的方式在此做個紀錄

SELECT * 「你的table」 AS OF TIMESTAMP TO_DATE('13-06-14 09:00:00','DD-MM-YY HH24:MI:SS')


在PL/SQL上輸入以上內容可以看到該table在某個時間點的資料內容

你就可以幫當下的資料記錄下來存回資料庫

但是你能看到多久之前的資料要看系統設定

最後還是感謝強者同事讓我免於大嬸的碎唸

2014年5月18日 星期日

[ASP.NET]如何在calendar控制項指定年月

ASP.NET中有個calendar控制項

能夠讓使用者點選日期

這部份jQuery UI也有套件可以做到

但calendar控制項最大的缺點是不能直接指定年為單位調整日曆

頂多只能一個月一個月切換

如果要跳到1911年不是按到手抽筋就是你滑鼠壞掉吧…

不過當然還有辦法辦到

在此做個紀錄

protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            for (int i = year; i >= year - 100; i--)
            {
                DDLYear.Items.Add(new ListItem(i.ToString()));
            }

            for (int i = 1; i < 13; i++)
            {
                DDLMonth.Items.Add(new ListItem(i.ToString()));
            }
        }
    }

    protected void DropDownList2_SelectedIndexChanged(object sender, EventArgs e)
    {
        Calendar1.TodaysDate = new DateTime(int.Parse(DDLYear.SelectedValue), int.Parse(DDLMonth.SelectedValue), 1);
        //Calendar1.VisibleDate = new DateTime(int.Parse(DDLYear.SelectedValue), int.Parse(DDLMonth.SelectedValue), 1);
       // Calendar1.SelectedDate = new DateTime(int.Parse(DDLYear.SelectedValue), int.Parse(DDLMonth.SelectedValue), 1);
    }
    protected void DropDownList3_SelectedIndexChanged(object sender, EventArgs e)
    {
        Calendar1.TodaysDate = new DateTime(int.Parse(DDLYear.SelectedValue), int.Parse(DDLMonth.SelectedValue), 1);
        //Calendar1.VisibleDate = new DateTime(int.Parse(DDLYear.SelectedValue), int.Parse(DDLMonth.SelectedValue), 1);
        //Calendar1.SelectedDate = new DateTime(int.Parse(DDLYear.SelectedValue), int.Parse(DDLMonth.SelectedValue), 1);
    }
Calendar1.TodaysDate跟Calendar1.VisibleDate最大的差別是 Calendar1.TodaysDate切換到該年月日曆後還是會有個目前選擇的日期 但Calendar1.VisibleDate不會有目前選擇的日期這是最大差別 Calendar1.SelectedDate嘗試是的結果是沒反應,這個有空再研究看看 最後這個範例應該是可以指定日的 只是需要用月份判斷日要產生幾天還有是否為閏年 流程大概是選擇完年後(月是固定的) 日的dropdownlist需要取得目前該月的天數 再由此天數去產生list的內容 不過這部份有遇到再寫吧…
參考文獻
MIS2000時光跳躍
Calendar.TodaysDate
Calendar.VisibleDate

2014年4月20日 星期日

[ASP.NET]動態產生網頁表格內容

有的時候網頁可能會隨著資料不同需求不同

來呈現不同的內容

像是表格就是其中一例

在這裡寫一個可以依照使用者設定產生他所需要的表格大小

類似的方法也可以應用在像是填寫某些人員資料

但你又不曉得使用者會填寫幾個人的情況

只要隨使用者的需求動態產生即可

<body>
    <form id="form1" runat="server">
<div>
列:<asp:DropDownList ID="Row" runat="server" AutoPostBack="True">
                <asp:ListItem>1</asp:ListItem>
                <asp:ListItem>2</asp:ListItem>
                <asp:ListItem>3</asp:ListItem>
            </asp:DropDownList>
行:<asp:DropDownList ID="Column" runat="server" AutoPostBack="True">
                <asp:ListItem>1</asp:ListItem>
                <asp:ListItem>2</asp:ListItem>
                <asp:ListItem>3</asp:ListItem>
            </asp:DropDownList
            <asp:Table ID="tableExample" runat="server" BorderColor="#FFFFFF" BorderStyle="Solid"
                BorderWidth="1px" CellPadding="0" CellSpacing="0" GridLines="Both">
            </asp:Table>
        </div>
</form>
</body>

protected void Page_Load(object sender, EventArgs e)
{
    TableCell TCell;
    TableRow TRow;
    int RowNumber = int.Parse(Row.SelectedValue);
    int ColumnNumber = int.Parse(Column.SelectedValue);

    for (int i = 0; i < RowNumber; i++)
    {
        TRow = new TableRow();
        TRow.ForeColor = Color.Yellow;
        TRow.Width = Unit.Pixel(300);
        TRow.Height = Unit.Pixel(30);

        if (i % 2 == 0)
            TRow.BackColor = Color.BlueViolet;
        else
            TRow.BackColor = Color.Green;

        for (int j = 0; j < ColumnNumber; j++)
        {
            TCell = new TableCell();             
            Label lb = new Label();
            lb.ID = "exampleLabe" + RowNumber * i + j;
            lb.Text = "範例" + (RowNumber * i + j).ToString();
            lb.ForeColor = Color.Yellow;

            TCell.Controls.Add(lb);
            TCell.Width = Unit.Pixel(int.Parse(TRow.Width.Value.ToString()) / ColumnNumber);
            TCell.HorizontalAlign = HorizontalAlign.Center;

            TRow.Cells.Add(TCell);
        }
        tableExample.Rows.Add(TRow);
    }
}
TableRow代表的是每列的物件

而TableCell代表的是每列的每一格(就是行)

這邊是以使用者選擇的行列數來決定產生幾行幾列

每列寬300px高30px

在TableCell中塞入Label物件顯示內容

如果需要設定CSS可以使用CssClass的屬性

跨欄或跨列可以使用ColumnSpane跟RowSpan設定

假設使用者要輸入十筆資料

那麼你只需要用迴圈findControl十次

而你要find的ID可以用"exampleLabe" + RowNumber * i;之類的方式產生

如此就能用字串組合的方式找到你要的ID

以下是執行結果

2014年4月16日 星期三

[SQL]如何在OracleSQL中實現top的功能

最近在用Oracle SQL才發現原來Oracle SQL沒有top這個功能

一般在SQL Server或是MySQL中都有top的指令讓使用者挑出前n筆資料

像是這樣
select top 10 * from test

表示從test這個table挑出前十筆資料

連AS400都有這個功能top這個功能沒想到Oracle SQL居然沒有?

不過這也不是什麼問題當然還是有解決方式

在Oracle SQL要這樣寫
select * from test where rownum<=10
這樣會列出最前面的十筆 如果需要排序後的結果再取前十筆要這樣寫 例如要以id的欄位來排序
select * from (select * from test order by id desc) where rownum<=10

因為rownum在Oracle SQL執行的順序會早於 order by或group by

所以要以排序完的結果再取前十筆才會正確顯示

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#還不夠熟悉

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

2014年2月20日 星期四

[ASP.NET]如何抓取大型控制項中每列資料的內容

最近想寫一個程式當作自我測驗的工具

利用GridView撈出資料庫的資料當題目

然後在每一列設一行TextBox作為輸入

答案行則預設Visible=false隱藏答案

最後填完後按下外部button檢查全部TextBox跟答案是否相同

當然這不是一種好的設計方式

因為使用者可以用看網頁原始碼的方式看到答案

有人提供比較好的解決方式是把出入的內容去跟資料庫裡面的內容做比較

不過這個程式只有我自己會用到所以暫時沒這問題


其中有遇到一個問題

我要怎麼用外部button讓每列的資料進行比較?

如果是在資料繫結階段結束後GridView會觸發rowDataBound()事件

ListView則是觸發itemDataBound()事件

都可以在這些事件中抓取網頁中的資料進行比對

但現在資料都已經在頁面上我要怎麼「主動」讓每列重新作出類似itemDataBound()的動作

後來看了人家的意見發現自己有點想太多了

就直接用迴圈去撈每列資料不就好了(是的,其實很簡單,我在想啥Orz)

最後用ListView解法如下

protected void Button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i < ListView1.Items.Count; i++)
    {
        TextBox tb = ListView1.Items[i].FindControl("TextBox1") as TextBox;
        HtmlTableCell ans = ListView1.Items[i].FindControl("answer") as HtmlTableCell;
        if (ans != null)
        {
            if (tb.Text.Trim().Equals(ans.InnerHtml.Trim()))
            {
                Response.Write("答對<br>");
            }
            else
            {
                Response.Write("答錯<br>");
            }
        }
    }
}

ListView有items GridView有rows

都能取得整個資料的集合

所以只要用陣列的方式讀出每一列再用FindControl()就能抓到你要的資料了

2014年2月18日 星期二

[ASP.NET]如何刪除重複的值(物件)

昨天遇到一個問題

就是下拉選單的資料是從現有的方法來的

下拉選單的資料不應該有重複

但問題現有的方法沒辦法做distinct過濾重複的資料

又不能自己寫ADO.NET連資料庫

所以你還是只能用他給你的資料做處理

比較土法煉鋼的方式就是寫一個陣列判斷陣列中有無該值

有就繼續下一個沒有就存進陣列

但這種方式的時間複雜度應該也是最差的

其實類似的問題之前在Java已經有解過-大樂透程式

在C#上我找到的方法是使用Enumerable的Distinct方法

最後解法如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

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

    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        List<distinctexample> list = new List<distinctexample>();
        list.Add(new distinctExample(45));
        list.Add(new distinctExample(32));
        list.Add(new distinctExample(9));
        list.Add(new distinctExample(32));
        list.Add(new distinctExample(66));

        foreach (distinctExample x in list)
        {
            Response.Write(x.number+" ");
        }
        Response.Write("
");
        IEnumerable<distinctexample> distinctList = list.Distinct();
        foreach (distinctExample distinctResult in distinctList)
        {
            Response.Write(distinctResult.number+" ");
        }
    }
}
public class distinctExample : IEquatable<distinctexample>
{
    public bool Equals(distinctExample anyNumber)
    {
        if(Object.ReferenceEquals(anyNumber,null))
            return false;
        if (Object.ReferenceEquals(this, anyNumber))
            return true;
        return number.Equals(anyNumber.number);
    }

    public override int GetHashCode()
    {
        int hashdistinctExample = number == null ? 0 : number.GetHashCode();
        return hashdistinctExample;
    }

    public distinctExample(int number)
    {
        this.number = number;
    }
    public int number
    {
        get;
        set;
    }
}

記得實作Equals跟GetHashCode這兩個方法才能進行distinct

Enumerable也有提供不少常用的方法像是OrderBy、Max、Min、Reverse...

參考資料:Enumerable.Distinct 方法
Enumerable 方法
C# 3.0 泛型集合 Generic Collection 中的 List

2014年2月15日 星期六

[ASP.NET]如何抓到頁面中不是ASP.NET控制項的HTML資料

最近碰到一個超奇怪的程式

裡面一堆奇奇怪怪的問題跟限制

這次遇到的其中一個問題是請你用現有的搜尋找出資料後

判斷資料欄位中是否全部都是"未繳款"的狀態

如果全部都是"未繳款"才可以刪除否則不給使用者刪

一開始想到的解法是用ADO.NET下SQL指令

select count(status) as count from bill where status!='0'

計算status不等於0的欄位有多少

如果不等於0就表示有其中一筆資料已繳款用這樣的方式做判斷

結果後來才知道禁止直接跟對方資料庫做連結

只能用現有的東西Orz

後來想到之前寫的人資料呈現在網頁上是用ListView

ListView可以直接帶出資料來

我直接抓網頁上的資料來判斷就行(題外話,為啥有人要用ListView做出一個很像GridView的東西,你直接用GridView不就好了......)

而在每筆資料進行databound時都會觸發ItemDataBound()這個方法

我就在這方法抓每筆資料裡面我要的資料就行

結果看他網頁原始檔發現他的資料並沒有指派給ASP.NET的控制項

一般如果是用ASP.NET幫你建起來的表單裡面每列資料大致上都會長的類似這樣

<asp:Label> ID="co_vidLabel" runat="server" Text='<%# Eval("co_vid") %>' />

Eval("co_vid")代表進行DataBind的資料庫欄位

另一種方式是Bind("co_vid")

前者是單向繫結後者是雙向繫結

就是前者只能讀後者如果你網頁上會觸發Edit的功能那麼該欄位除了讀也能寫

看到的網頁只有<td><%# Eval("co_vid") %></td>

並沒有像我上面寫的還有個<asp:Label>可以接住Eval("co_vid")的值

因為如果有ASP.NET的控制項那我只要用下面方法就可以抓到控制項的值

protected void ListView1_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    //方法一 有asp.net控制項的抓法
    Label covid = e.Item.FindControl("co_vidLabel") as Label;
    if (covid != null)
    Response.Write(covid.Text + "<br>");
}

如果是用GridView也有類似的RowDataBound方法

以上方法行不通只能另想辦法了

一開始在網路上看到這個方法二抓取資料來源控制項的資料

但我試了之後...自己電腦上可以伺服器上行不通

這個程式一堆地方都是謎

更別說知道他資料是不是DataSet或DataTable來的

後來翻到其他程式突然看到某個寫法

ListViewItem item = e.Item;
HtmlTableCell td = item.FindControl("tdCancelFlag") as HtmlTableCell;

看了之後上MSDN查才知道ASP.NET除了提供抓取控制項的方法也有提供抓取HTML ID元件的方法

最後解法如下

protected void ListView1_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    //方法三,使用HtmlTableCell的InnerText屬性跟InnerText屬性
    ListViewItem item = e.Item;
    HtmlTableCell td = item.FindControl("tdCancelFlag") as HtmlTableCell;

    if (!td.InnerText.Trim().Equals("未繳款"))
    {
        DelButton.Enabled = false;
    }
}

其中HtmlTableCell類別有提供InnerText跟InnerHtml這兩個屬性

依照MSDN上面HtmlTableCell類別的對於兩個屬性的定義

InnerHtml 取得或設定在指定的 HTML 伺服器控制項的開始和結尾標記之間的內容。
InnerText 取得或設定在指定的 HTML 伺服器控制項的開始和結尾標記之間的文字。

字面上的意思來看應該InnerHtml會回傳整個Html標籤包含內容InnerText則是純內文

不過看回傳值都一樣

我還怕是不是顯示在網頁所以被網頁給解碼

所以輸出成txt文字檔

可是看起來兩個輸出完全一樣

InnerHtml並沒有如我預期出現HTML標籤

不曉得有哪位高手知道這兩者到底差在哪裡嗎Orz


一開始用這方法有遇到一個bug

Equals("未繳款")這邊怎樣都是false

回家後才想到該不會是回傳值中間有空白...

所以用Trim()去頭去尾就解決這個問題了

另外注意用這個方法你要抓的HTML標籤必須加上runat="server"他才能按圖索驥抓到該ID

<td id="tdCancelFlag" runat="server">
    <%# cancel_flagToCh(Eval("cancel_flag").ToString()) %>
</td>

2014年1月14日 星期二

[ASP.NET]GridView的行轉成template跟沒轉template抓取控制項的差別


今天晚上遇到一個Bug

我在GridView裡面的RowUpdating撰寫當你觸發編輯時的程式流程

TextBox TextBox_Name, TextBox_Price, TextBox_Intro;
TextBox_Name = GridView1.Rows[e.RowIndex].Cells[3].Controls[0] as TextBox;
TextBox_Price = GridView1.Rows[e.RowIndex].Cells[4].Controls[0] as TextBox;
TextBox_Intro = GridView1.Rows[e.RowIndex].Cells[5].Controls[0] as TextBox;

編輯時會先抓GridView第4、5、6行的控制項轉型成TextBox

可是奇怪的是這樣寫後面程式會出現TextBox_Name的值是null的錯誤

TextBox_Name = (TextBox)GridView1.Rows[e.RowIndex].Cells[3].Controls[0];

這樣寫又會出現Unable to cast object of type 'System.Web.UI.LiteralControl' to type 'System.Web.UI.WebControls.TextBox'.轉型失敗的錯誤

結果這個問題搞了一整晚

最後程式重寫一遍-正常

但我發現當我把某行轉成Template時相同的錯誤又出現了

這下就確定是因為Template造成的錯誤

後來突然想到用FindControl()來抓看看

TextBox TextBox_Name, TextBox_Price, TextBox_Intro;
TextBox_Name = GridView1.Rows[e.RowIndex].FindControl("TextBox1") as TextBox;
TextBox_Price = GridView1.Rows[e.RowIndex].Cells[4].Controls[0] as TextBox;
TextBox_Intro = GridView1.Rows[e.RowIndex].FindControl("TextBox2") as TextBox;

改成這樣就對了

原因是當你GridView的行轉成Template(樣板)之後

你必須要用GridView1.Rows[e.RowIndex].FindControl("TextBox1")這個方式才能抓到該行的控制項

像是TextBox_Price因為沒有轉成樣板

所以用GridView1.Rows[e.RowIndex].Cells[4].Controls[0] as TextBox這種方法就能夠抓到

在FindControl()跟Cell的使用區別還不夠熟練阿~