วันศุกร์ที่ ๒๑ สิงหาคม พ.ศ. ๒๕๕๒

LINQ To SQL Group by multiple columns

วันนี้จะเขียน Linq To SQL เพื่อให้ทำคำสั่งเหมือน

SELECT cocode, arbook, batchdate, batchno,count(*)
FROM arpendingbill
WHERE cocode = 'TGIP' AND arbook = 'NEWAR'
GROUP BY cocode, arbook, batchdate, batchno

โดยให้นำข้อมูลไปใส่ Class ชื่อ ARPendingBatch เขียนเป็น LINQ ดังนี้ครับ


Dim b = From pb In dc.ARPendingBills _
Where pb.COCode = coCode AndAlso pb.ARBook = arBook _
Group By key = New With {pb.COCode, pb.ARBook, pb.BatchDate, pb.BatchNo} _
Into pbGroup = Group _
Select New ARPendingBatch With {.ARBook = key.ARBook, _
.CoCode = key.COCode, .BatchDate = key.BatchDate, _
.BatchNo = key.BatchNo, .BillCount = pbGroup.Count( _
Function(c) c.BatchDate = key.BatchDate AndAlso _
c.BatchNo = key.BatchNo)}


พอลอง debug เพื่อดู SQLCommand ที่มันสร้างขึ้นมาได้แบบนี้ครับ

SELECT ( SELECT COUNT(*) FROM [dbo].[ARPendingBill] AS [t2] WHERE ([t2].[BatchDate] = [t1].[BatchDate]) AND ([t2].[BatchNo] = [t1].[BatchNo]) AND ([t1].[COCode] = [t2].[COCode]) AND ([t1].[ARBook] = [t2].[ARBook]) AND ([t1].[BatchDate] = [t2].[BatchDate]) AND ([t1].[BatchNo] = [t2].[BatchNo]) AND ([t2].[COCode] = @p0) AND ([t2].[ARBook] = @p1) ) AS [BillCount], [t1].[BatchDate], [t1].[BatchNo], [t1].[ARBook], [t1].[COCode] AS [CoCode] FROM ( SELECT [t0].[COCode], [t0].[ARBook], [t0].[BatchDate], [t0].[BatchNo] FROM [dbo].[ARPendingBill] AS [t0] WHERE ([t0].[COCode] = @p0) AND ([t0].[ARBook] = @p1) GROUP BY [t0].[COCode], [t0].[ARBook], [t0].[BatchDate], [t0].[BatchNo] ) AS [t1]

รู้สึกว่ามันช่างเข้าใจยากพิกล ส่วนหนึ่งเป็นเพราะยังไม่ชินด้วยครับ และควรจะต้องไปจัด Layout ใหม่ให้อ่านง่ายๆก่อน แต่ผลลัพท์ได้ออกมาเหมือนกัน

ลองเขียนโดยใช้ Lambda Expression ครับ

Dim b = dc.ARPendingBills.Where( _
Function(a) a.COCode = coCode AndAlso a.ARBook = arBook).GroupBy( _
Function(g) New With {.coCode = g.COCode, .arBook = g.ARBook, _
.batchDate = g.BatchDate, .batchNo = g.BatchNo}).Select( _
Function(c) New ARPendingBatch With {.ARBook = c.Key.arBook, _
.CoCode = c.Key.coCode, .BatchDate = c.Key.batchDate, _
.BatchNo = c.Key.batchNo, .BillCount = c.Count( _
Function(k) k.BatchDate = c.Key.batchDate AndAlso _
k.BatchNo = c.Key.batchNo)})


ได้ผลลัพท์เหมือนกันครับ (ก็แหงละ ลอง debug ดู SQLCommand ก็เหมือนกับที่เขียนด้านบน)

รู้สึกขัดใจกับ SQLCommand ที่ Linq To SQL สร้างให้ ดังนั้นเลยเปลี่ยนมา ExecuteQuery ตรงๆแทน


Dim b = dc.ExecuteQuery(Of ARPendingBatch)( _
"SELECT cocode, arbook, batchdate, batchno, " & _
"BillCount=count(billRecId) " & _
"FROM arpendingbill & _
"WHERE cocode = '" & coCode & "' and arbook = '" & arBook & "' " & _
"GROUP BY cocode, arbook, batchdate, batchno")


มันไม่ Typed Save ครับ แต่ก็มีข้อดีคือ ผมไม่ต้องใช้เวลาเป็นชั่วโมงในการลองเขียน LINQ To SQl แล้ว debug ได้ SQLCommand ที่อ่านยาก และก็ต้อง Test เยอะเพื่อความมั่นใจ ดังนั้นช่วงนี้ถ้าเจอกรณีแบบนี้ก็จะใช้ ExecuteQuery ไปก่อน แล้วพอมีเวลาหัดให้คล่องๆแล้วค่อยกลับมาแก้ครับ (แต่มันก็อ่านยากเหมือนกันนะ programmer ที่ต้องมาแก้โค้ดนี้ภายหลัง ไม่แน่ใจว่าจะชอบแบบไหน ดังนั้นผมเลย Comment เก็บไว้ทั้ง 3 แบบเลย)

ว่าแต่โค้ด Linq To SQL ด้านบน สามารถเขียนได้ง่ายกว่านี้มั๊ยนะ

วันพุธที่ ๑๙ สิงหาคม พ.ศ. ๒๕๕๒

สร้าง DIV เพื่อบัง WebPage ขณะใช้งาน AJAX

ปกติเวลาพัฒนา Web Application ด้วย ASP.NET ผมมักจะใช้ ASP.NET AJAX ร่วมด้วยเสมอ และเมื่อมีการ PostBack หรือเรียกใช้ WebServices ก็จำเป็นต้องเขียน javascript เพื่อโชว์รูปว่ากำลัง Update และ disable controls ต่างๆ

พอดีได้เข้าไปดูเวบไหนจำไม่ได้แล้วครับ เค้าน่าจะสร้าง Div ที่กำหนด OPACITY ไว้เพื่อให้เป็นกรอบจางๆสำหรับบังหน้าเวบขณะ process งาน ก็สะดวกดีนะครับ เพราะว่าถ้าในหน้ามี control หลายๆตัว จะไปนั่งไล่ disable ทีละตัวก็จะถึกไปหน่อย ยิ่งถ้าใช้พวก asp:menu หรือ Master Page ด้วยแล้ว เสียเวลาน่าดูชม สร้าง Div มาบังมันซะเลย เร็วและได้ผลลัพท์ไม่แตกต่างกัน (อาจจะดีกว่าด้วยครับ) เลยมาลองทำดูบ้างครับ


<div id="divProgress">
<div id="divBackground"style="z-index:999; position:absolute;top:0px;left:0px; background-color:#eeeeee;filter:alpha(opacity=40); opacity:0.4;-moz-opacity:0.4"></div>
<div id="divImg" style="z-index:1000;position:absolute;border: 1px solid #800000; top:340px;left:200px;width:300px;text-align:center;background-color:#FFDECE">
<img alt="Processing" src="../Images/loading.gif" /><asp:label ID="lblLoading" CssClass ="textNormal" Text="Preparing reports..." runat="server"></asp:label>
</div>
<div>


โดยปกติก็จะกำหนด visibility เป็น hidden ไว้ก่อน พอมีการเรียกใช้ AJAX ก็จะเปลี่ยนเป็น visible มาดูโค้ดกันครับ


<script type="text/javascript" id="MSAJAX">
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_beginRequest(onBeginRequest);
prm.add_endRequest(onEndRequest);

function onBeginRequest(sender, e) {
ShowProgress(true);
}

function onEndRequest(sender, e) {
ShowProgress(false);
}
function ShowProgress(isShow) {
if(isShow == true){
$get("divProgress").style.visibility = "visible";
var dv = $get("divBackground")
dv.style.width = "1200px";
dv.style.height = "1000px";
}else{
$get("divProgress").style.visibility = "hidden";
}
}
</script>


เนื่องจากผมใช้ PageRequestManager ของ ASP.NET AJAX เป็นตัวสั่ง ShowProgress ดังนั้น Script นี้ต้องวางไว้ข้างล่าง ScriptManager control นะครับ

วันอาทิตย์ที่ ๑๖ สิงหาคม พ.ศ. ๒๕๕๒

VB.NET สร้างรายงานด้วย .rdlc (Client Report Definition File)

ช่วงนี้ดึกๆหามรุ่งหามค่ำนั่งโม่โปรแกรมอยู่ (เล่นปังยา 2 ชั่วโมง เขียนโปรแกรม ครึ่งชั่วโมง) พอจะสร้างรายงาน ปรากฏว่าตัว Visual Studio 2008 ที่ลงไว้ไม่มี Crystal Report มาด้วย (กรรม หาแผ่นก็ไม่เจอ) ก่อนหน้านี้เคยใช้ Print Document รู้สึกว่ามันถึกมาก (เหมือนใช้ VSPrint ใน VB6 เลย) มานั่งทบทวนจำได้ว่าเคยอ่านบทความเรื่อง rdlc กับ Report Viewer ผ่านตา เลยไปลองใช้บริการอากู๋ดูครับ

ได้ข้อมูลมาเยอะมาก อ่านผ่านๆไม่ค่อยรู้เรื่อง ก็ลงมือลองเขียนเลยครับ ปรากฏว่าไม่ยากเท่าไหร่ ติดที่ว่าไม่ถนัดครับ แต่ก็พอถูๆไถๆไปได้ ลองเขียน Daily Report ใช้เวลาคลำประมาณครึ่งชั่วโมงก็เสร็จครับ ไว้ถ้ามีเวลาจะศึกษาเพิ่ม แล้วจะมาลงวิธีการสร้างรายงานอีกที

พอสร้างรายงานเสร็จก็เขียนโค้ดสำหรับ Preview Report ครับ ก็ใช้ ReportViewer Control นั่นแหละ ก่อนอื่นสร้าง form สำหรับ query ข้อมูลก่อน แล้วพอ user กด print ก็ไปเปิด form ที่มี ReportViewer ขึ้นมาแสดง


Private Sub btnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPreview.Click

Dim reports As List(Of Entity.ReportDailySummary) = ReportManager.GetDailySummaryReport(Me.dtpWorkingDate.Value)

If reports Is Nothing OrElse reports.Count = 0 Then
MsgBox("ไม่พบข้อมูล", MsgBoxStyle.Information + MsgBoxStyle.OkOnly, My.Application.Info.Description)
Else
Dim ds As New Microsoft.Reporting.WinForms.ReportDataSource("ReportDailySummary", reports)
Dim params As New List(Of Microsoft.Reporting.WinForms.ReportParameter)
params.Add(New Microsoft.Reporting.WinForms.ReportParameter("WorkingDate", Me.dtpWorkingDate.Value.ToString("dd MMMM yyyy")))
frmPreview.ReportViewer1.LocalReport.ReportPath = "rptDailySummary.rdlc"
frmPreview.ReportViewer1.LocalReport.DataSources.Add(ds)
frmPreview.ReportViewer1.LocalReport.SetParameters(params)
frmPreview.ReportViewer1.RefreshReport()
frmPreview.Text = "รายงานสรุปยอดขายประจำวัน"
frmPreview.ShowDialog()
End If

End Sub


เนื่องจากขี้เกียจ และต้องรีบไปออกรอบ ดังนั้นลงแค่ code พอ เผื่อว่าวันหลังลืมวิธี Preview Report จะได้มาค้นดูได้

ปล. วันนี้ตอนอยู่ที่ทำงานจะเขียนคำสั่งสำหรับ sort list ว่าจะใช้ Lamda Expression แต่นึกไม่ออก ลองเขียนแล้วก็รันไม่ผ่าน ยังดีที่ได้เอาโค้ดมาลง blog ไว้ เลยมาเปิดหาดู สะดวกดีครับ ดีกว่าไปเปิดโปรเจคเก่าๆมาหามากมายก่ายกอง

วันศุกร์ที่ ๑๔ สิงหาคม พ.ศ. ๒๕๕๒

VB.NET อ่านข้อมูลภาษาไทยจากไฟล์

วันก่อนมีโอกาสเขียนโปรแกรมสำหรับทำการอ่านข้อมูลจาก text file แล้วตัดเอาข้อมูลไปเก็บใน database เพื่อประมวลผล จริงๆก็ไม่ยากครับ แต่ปรากฏว่าข้อมูลภาษาไทยมันกลายเป็นอักษรต่างดาว สาเหตุก็คือ text file ที่ว่าเป็น daily report ที่ได้จากการ export มาจากโปรแกรม POS (Point of Sale) และมันถูก encode ด้วย ANSI (ASCII) ไม่ใช่เป็น UNICODE พอเขียน VB.NET สั่งอ่านไฟล์ตรงๆมันเลย encode ไม่ถูกครับ เพราะว่า .NET ใช้ Unicode (UTF-16) เป็น default encoder

ดังนั้นตอนใช้ StreamReader ก็กำหนด Encode ไปด้วยแค่นี้เอง (งมตั้งนาน - จริงๆแล้วพอคิดไม่ออก เลยไปเล่นเกมส์ พอเล่นเสร็จ ง่วงแล้วก็เลยรีบนอน 5555) ทีนี้ก็มีให้เลือกว่าจะใช้ Windows-874 หรือว่า TIS-620 ผมก็เลือกใช้ Windows-874 เพราะว่า text file โดน export มาจากโปรแกรมที่เขียนบน Windows ซึ่งเขียนมานานพอสมควร ดังนั้นก็เลยเลือกใช้ Windows-874 ซะเลย (ยังไม่ได้ลอง TIS-620 เลย เพราะขี้เกียจ)

ลองดูโค้ดกัน ผมสร้าง Singleton Class ชื่อ DBImportManager ขึ้นมา มี Sub อยู่ 1 ตัว สังเกตุว่ามี BackGroundWorker เป็นพารามิเตอร์ด้วย


Public Sub ImportData(ByVal bk As System.ComponentModel.BackgroundWorker, ByVal fileName As String, ByVal fileImporter As IFileImporter)

If Not FileIO.FileSystem.FileExists(fileName) Then Exit Sub
Dim iLines As Integer
Try
Dim fs As New System.IO.StreamReader(fileName, System.Text.Encoding.GetEncoding(874))
Do While Not fs.EndOfStream
fileImporter.ParseLine(fs.ReadLine)
bk.ReportProgress(10, "Reading: " & iLines)
Loop
fileImporter.Update()
Catch exIO As IO.IOException
bk.ReportProgress(0, exIO.Message)
Catch ex As Exception
bk.ReportProgress(0, ex.Message)
End Try

End Sub


และพารามิเตอร์ตัวสุดท้ายก็เป็น IFlieImporter เนื่องจากว่าเรามีไฟล์หลายตัวที่ต้องทำการ import ข้อมูล ซึ่งแต่ละไฟล์ก็จะมีเงื่อนไขในการตัดไม่เหมือนกัน ผมเลยสร้าง Interface ตัวนี้ขึ้นมาครับ และก็ไปสร้าง FileImporter สำหรับไฟล์แต่ละแบบ เช่น รายงานประจำวัน รายงานลูกค้าใหม่ รายงานรับสินค้าเข้า ฯลฯ ดังนั้นเวลาเปลี่ยน logic ในการตัดไฟล์ เราก็ไปทำการแก้ไขเฉพาะ FileImporter Class ที่เราสร้างขึ้น โดยจะไม่กระทบกับ Class ตัวนี้เลย

ลองรันทดสอบดู เรียบร้อยครับ ผลลัพธ์ถูกต้อง

วันอาทิตย์ที่ ๙ สิงหาคม พ.ศ. ๒๕๕๒

วิเคราะห์ WebSite ด้วย Google Analytics

หลังจากเขียน Blog มาซักพัก เริ่มอยากรู้ว่ามีคนเข้าชมเยอะสักแค่ไหนครับ ก่อนหน้านี้เขียนที่ Bloggang.com จะมีตัวนับสถิติมาให้ (แต่ข้อมูลผิดบ่อยๆนะ) เลยลอง search ในเนตหาตัวนับสถิติฟรี (เน้น) มาลองใช้ครับ ก็เลยมาสะดุดที่ Google Analytics ครับ รู้สึกว่าเครื่องมือทันสมัยแถมมีเยอะมากมายครับ

ลองเข้าไปลงทะเบียนที่ http://www.google.co.th/analytics

ข้อมูลเพิ่มเติม ละเอียดน่าสนใจครับ (ภาษาไทย)
Google Analytics คืออะไร, Tung's Blog
มารู้จัก Google Analytics กันเถิด [ตอนที่ 1], โลกของคนไอที

ตอนนี้ยังใช้งานไม่ค่อยเป็น ต้องลองศึกษาอีกซักพักครับ หวังว่าคงมีคนเข้ามาชม blog เยอะๆ จะได้มีข้อมูลมาลองศึกษาเยอะๆตามไปด้วยครับ

วันพฤหัสบดีที่ ๖ สิงหาคม พ.ศ. ๒๕๕๒

jQuery ลอง Selectors หลายๆแบบ

วันก่อนคุณ Nine ได้ post ถามเรื่องการใช้ jQuery สำหรับค้นหาข้อมูลเพื่อซ่อน table โดยหน้า html ได้มาจาก MS SharePoint ครับ ทีแรกผมเข้าใจว่า html จะมี form เป็น pattern เดียวกัน คือมีจำนวนแถวและคอลัมภ์เท่ากันเสมอ ดังนั้นผมจึงเขียนโค้ดดังนี้ครับ


var isCompleted = $("table.ms-formtable tr:eq(11) td:eq(1)").html();
if (isCompleted!=null&&isCompleted.indexOf("1 ")>-1) {
$('table.ms-toolbar').hide();
}


คือใช้ Selectors แบบ ancestor descendant ร่วมกับ Filters หรืออธิบายคือ สั่งให้ jQuery ไปหา element ที่ class =table.ms-formtable (ซึ่งเป็น table) จากนั้นก็ไปที่ row ที่ 12 (index=11) ของตารางนี้ และไปที่ column ที่ 2 (index=1) แล้วไปดึงเอา innerHTML ออกมาซะ เพื่อตรวจสอบเงื่อนไขสำหรับ hide ตาราง

แต่ปรากฎว่าแต่ละหน้า อาจมี row ไม่เท่ากันก็ได้ ดังนั้นคุณ Nine จึงสั่งไปที่ table เดียวกัน (class =table.ms-formtable) แล้วใช้ jQuery Object Accessors ที่ชื่อว่า each ในการวนลูปทีละแถว เพื่อตรวจสอบข้อมูลทุกแถวครับ


$(document).ready(function($) {
var rows = $('table.ms-formtable tr').each(function() {
var row = $(this);
var columns = row.children('td');
if(columns.eq(1).html().indexOf('ProcessCompleted')>-1 && columns.eq(1).html().indexOf('1 ')>-1)
$('table.ms-toolbar').hide();
});
});


จริงๆแล้ว เราควรจะใช้ ID เป็น Selector จะได้ง่าย แต่เพราะว่า MS SharePoint มันดันสร้าง ID ซ้ำกันเยอะแยะนั่นสิ ทำให้เวลาเราใช้ ID Selector แล้วมันจะได้เฉพาะ element แรกเท่านั้นนั่นเอง (เพราะ ID ควรมีแค่ 1 ID ต่อ 1 page)
ใครสนใจจะดูโค้ดเต็มๆลองไปดูที่

jQuery ค้นหาค่า และซ่อน table ช่วยหน่อยคร้าบ
http://greatfriends.biz/?110667

วันจันทร์ที่ ๓ สิงหาคม พ.ศ. ๒๕๕๒

VB.NET Coding Convention

ผมได้เข้าไปค้นหาข้อมูลใน msdn ก็เลยเจอหัวข้อ VB.NET Coding Convention อยู่ภายใน VS2008 พออ่านๆดูก็พบว่ามีหลายอย่างที่ไม่ได้ทำตาม Convention ของ MS ครับ ก่อนอื่นมาดูก่อนว่าถ้าเราเขียนโค้ดตาม guide line นี้จะมีข้อดีอะไรบ้าง

  • Coding conventions create a consistent look to the code, so that readers can focus on content, not layout.

  • Conventions let the readers understand the code more quickly, because it allows them to make assumptions based on previous experience.

  • Conventions make copying, changing, and maintaining the code easier.

  • Conventions demonstrate Visual Basic "best practices."


  • ทีนี้มาลองดูหัวข้อที่น่าสนใจครับ

    Naming Convention

  • Do not use "My" or "my" as part of a variable name. This creates confusion with the My objects. ข้อนี้ผิดบ่อยครับ บางทีคิดไม่ออกชอบตั้งชื่อตัวแปรมี My นำหน้า


  • Layout Convention
  • Use only one statement per line. Do not use the Visual Basic line continuation character (:). ข้อนี้ก็ทำบ้าง แต่ส่วนใหญ่จะใช้กับ 2 statements สั้นๆ โดยเฉพาะอย่างยิ่งเขียนใน SELECT CASE หรือ IF THEN STATEMENT และพอทำแล้วผมว่าโค้ดอ่านง่ายนะ

  • Use only one declaration per line.

  • Add at least one blank line between method and property definitions. ผมทำตามนี้อยู่แล้ว เห็นโค้ดบางคนไม่เว้นบรรทัด อ่านแล้วลายตาครับ


  • Language Guidelines
    String Data Type
  • Use & to concatenate strings: เมื่อก่อนก็เขียนแบบนี้ แต่ตอนหลังเปลี่ยนมาใช้ String.Concat() นะ ว่าแต่ใช้แบบไหนดีเนี่ย

  • For appending strings in loops, use the StringBuilder object: อันนี้แน่นอน ใช้ StringBuilder ดีกว่าเห็นๆ


  • Type Inference
  • Take advantage of type inference for local variables: สำหรับผมกำหนด Type ไปเลยอ่านง่ายกว่า Type inference นะ อันนี้ขอไม่เปลี่ยน

  • Use Type Inference for Loop Variables in For or For Each Statements
    Allow type inference to determine the type of the loop range variable. เหมือนข้อเมื่อกี้ ส่วนตัวไม่ชอบ Type inference (แต่ก็ใช้บ้าง) ถ้าเป็นไปได้จะหลีกเลี่ยง มันเหมือนคนพูดสั้นๆให้เข้าใจเอาเอง


  • Unsigned Data Type
  • Use Integer rather than unsigned types unless memory is at a premium. อันนี้รวมถึงไม่ควรใช้ Int16, Int64, Short, Long ด้วย


  • Use the MsgBox Function
  • Use MsgBox instead of MessageBox.Show or Console.WriteLine. In environments that do not support the MsgBox function, such as Silverlight, use an appropriate alternative. อืม เคยอ่านมาว่าให้ใช้ MessageBox.Show แต่จริงๆชอบ MsgBox มากกว่า แบบนี้ก็กลับมาใช้แบบเดิมดีกว่าครับ


  • New Keyword
  • Use short instantiation:

  • Use object initializers for new objects instead of the parameterless constructor:


  • ยังมีอีกพอสมควรครับ สำหรับคนที่สนใจเข้าไปศึกษาได้ที่ http://msdn.microsoft.com/en-us/library/h63fsef3.aspx ครับ

    วันเสาร์ที่ ๑ สิงหาคม พ.ศ. ๒๕๕๒

    jQuery กับการกำหนด Mouse Hover ใน GridView

    อ้างอิงจากคำถามที่ GreatFriends ครับ
    ขอคำแนะนำเรื่อง GridView กับ Jquey จ้า
    http://greatfriends.biz?110946

    เจ้าของกระทู้ต้องการทำ Highlight สีของแถวใน GridView เมื่อเอา mouse ไปชี้ที่แถวนั้นๆครับ และพอกด Select ใน GridView ก็ให้นำข้อมูลมาใส่ textbox

    จริงๆแล้วถ้าข้อมูลที่จะใส่ TextBox อยู่ใน GridView ทั้งหมด เราสามารถใช้ jQuery ดึงข้อมูลของแถวมาใช้ได้เลย แต่ผมสมมติว่าเราต้องการข้อมูลอื่นๆ โดยอ้างจาก Id ของข้อมูลที่เลือกละกันครับ ดังนั้นผมก็ต้อง Post back กลับไปเพื่อหาค่ามาใส่ใน textbox ก็เลยทำหน้า page มาลองทดสอบดูครับ

    โค้ดส่วน Head ครับ Style ผม copy มาจากเจ้าของกระทู้เลย ส่วน jQuery ผมใช้ที่อาจารย์สุเทพแก้ไขให้ นำมาแยกออกจาก $document.ready() เป็น LoadHandle function ครับ

    <head runat="server">
    <title>Test</title>
    <style>
    .trMouseClick {
    background-color: Transparent;
    color: #A4D1A1 !important;
    text-decoration: none;
    }

    .trMouseOver {
    background-color: #F2FEF2 !important;
    color: #A4D1A1 !important;
    text-decoration: none;
    }

    .trMouseOver a {
    background-color: #F2FEF2 !important;
    color: #A4D1A1 !important;
    }
    </style>
    <script type="text/javascript" src="../Scripts/jquery-1.3.2-vsdoc.js"></script>
    <script type="text/jscript">

    $(document).ready(LoadHandle);

    function LoadHandle() {
    $("tr:gt{0}").hover(function() {
    $(this).addClass("trMouseOver");
    }, function() {
    $(this).removeClass("trMouseOver");
    });
    $("tr:gt{0}").click(function() {
    $(this).addClass("trMouseClick").siblings().removeClass("trMouseClick");
    });
    }

    </script>
    </head>


    โค้ดส่วน Form tag (ใน Body)

    <asp:ScriptManager ID="ScriptManager1" runat="server">:
    </asp:ScriptManager>:
    <div id = "main" style="height:700px;z-index:-10">:
    <div id="text" style="position:absolute">:
    <asp:UpdatePanel ID ="UpdatePanel1" runat="server" UpdateMode="Conditional" RenderMode="Block">:
    <ContentTemplate>:
    <div id="Update" style="position:absolute;top:50px; width:700px">:
    <asp:GridView ID="gvInvoice" runat="server" AllowPaging="True"
    AutoGenerateColumns="False" BorderColor="Gainsboro" BorderStyle="None"
    BorderWidth="1px" CellPadding="0" GridLines="Horizontal"
    AutoGenerateSelectButton="True" DataKeyNames="ItemId">:
    <Columns>:
    <asp:TemplateField SortExpression="ITEMID">:
    <ItemStyle Width="0px">:</ItemStyle>:
    <ItemTemplate>:
    <asp:HiddenField id="hdnRecId" runat="server" Value='<%# Bind("ITEMID") %>:'>:</asp:HiddenField>:
    </ItemTemplate>:
    </asp:TemplateField>:
    <asp:TemplateField HeaderText="Item No">:
    <HeaderStyle Width="40px" HorizontalAlign="center">:</HeaderStyle>:
    <ItemStyle HorizontalAlign="center" />:
    <ItemTemplate>:
    <asp:Label id="lblItemNo" runat="server" Text='<%# Eval("ORDERNO") %>:' >:</asp:Label>:
    </ItemTemplate>:
    </asp:TemplateField>:
    <asp:TemplateField HeaderText="Invoice Date">:
    <ItemStyle Width="100px">:</ItemStyle>:
    <HeaderStyle HorizontalAlign="Center">:</HeaderStyle>:
    <ItemTemplate>:
    <asp:Label style="TEXT-ALIGN: center" id="Label2" runat="server" Width="80px" Text='<%# Bind("InvoiceDate", "{0:dd/MM/yyyy}") %>:'>:</asp:Label>:
    </ItemTemplate>:
    </asp:TemplateField>:
    <asp:TemplateField HeaderText="Product Code">:
    <ItemStyle Width="60px">:</ItemStyle>:
    <HeaderStyle HorizontalAlign="Center">:</HeaderStyle>:
    <ItemTemplate>:
    <asp:Label style="TEXT-ALIGN: center" id="Label3" runat="server" Width="50px" Text='<%# Bind("ProductCode") %>:'>:</asp:Label>:
    </ItemTemplate>:
    </asp:TemplateField>:
    <asp:TemplateField HeaderText="Amount (THB)">:
    <ItemStyle Width="80px">:</ItemStyle>:
    <HeaderStyle HorizontalAlign="Center">:</HeaderStyle>:
    <ItemTemplate>:
    <asp:Label style="TEXT-ALIGN: right" id="Label5" runat="server" Width="70px" Text='<%# Bind("Amount","{0:#,##0.00}") %>:'>:</asp:Label>:
    </ItemTemplate>:
    </asp:TemplateField>:
    <asp:TemplateField ShowHeader="False">:
    <ItemStyle Width="40px">:</ItemStyle>:
    <ItemTemplate>:
    <asp:ImageButton ID="btnEdit" runat="server" CommandName="Select" ImageUrl="~/Images/icon_edit.gif" ToolTip="Edit" />:
    </ItemTemplate>:
    </asp:TemplateField>:
    </Columns>:
    <RowStyle >:</RowStyle>:
    <SelectedRowStyle>:</SelectedRowStyle>:
    <PagerStyle HorizontalAlign="Right" VerticalAlign="Top">:</PagerStyle>:
    <HeaderStyle >:</HeaderStyle>:
    <AlternatingRowStyle >:</AlternatingRowStyle>:
    </asp:GridView>:
    </div>:
    <div id="dvContent" style="position:absolute;top:400px">:
    <asp:TextBox ID="txtId" runat="server" style="width:100px" MaxLength="6">:</asp:TextBox>:
    <asp:TextBox ID="txtDescription" runat="server" style="width:250px">:</asp:TextBox>:
    <asp:TextBox ID="txtAmount" runat="server" style="width:250px">:</asp:TextBox>:
    </div>:
    </ContentTemplate>:
    </asp:UpdatePanel>:
    </div>:
    </div>:


    โค้ดส่วน Code Behind ครับ

    Imports jnithi.Class.Invoice

    Partial Class InvoiceTest
    Inherits System.Web.UI.Page
    #Region "Local fields"

    Private _inv As List(Of Entity.Invoice) = Nothing

    #End Region

    Private Sub GetInvoiceByInvoiceNo()
    If _inv IsNot Nothing Then
    _inv.Clear()
    _inv = Nothing
    End If
    _inv = Manager.Invoice.GetInvoiceByInvoiceNo("Test1")
    Session("Invoice") = _inv
    End Sub

    Private Sub BindGridView()
    gvInvoice.DataSource = _inv
    gvInvoice.DataBind()
    End Sub

    Protected Sub form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles form1.Load

    If Not IsPostBack Then
    GetInvoiceByInvoiceNo()
    Else
    _inv = Session("Invoice")
    End If
    BindGridView()
    End Sub

    Protected Sub gvInvoice_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles gvInvoice.SelectedIndexChanged
    Dim id As Integer = CType(gvInvoice.SelectedValue, Integer)
    Dim item As Entity.Invoice = _inv.Find(Function(i) i.ItemID = id)
    Me.txtId.Text = id
    Me.txtDescription.Text = item.ItemWorkDesc
    Me.txtAmount.Text = item.LCCost.ToString("#,##0.00")
    ScriptManager.RegisterStartupScript(Page, Page.GetType, "GVDataBound", "LoadHandle();", True)
    End Sub


    ผลการทดสอบครับ


    สรุป
    1. ผมแยก LoadHandle Function ออกมาจาก $document.ready() เพื่อที่ว่าให้ function นี้เป็น Event Handler เมื่อทำการ Load ข้อมูลฝั่ง client เสร็จครับ
    2. ใน GridView_SelectedIndexChanged ผมทำการ register script เพื่อให้เรียก LoadHandle functionให้ทำงาน เมื่อ UpdatePanel ทำการ Update ข้อมูลเสร็จแล้ว
    3. ถ้าไม่ใช้ ScriptManager.RegisterStartupScript ซึ่งเป็นการสั่งที่ฝั่ง Server เราสามารถใช้ PageRequestManager (เขียน javascript) แทนได้ครับ