ชุมชนคนรักภาษาเบสิค - Visual Basic Community

 ลืมรหัสผ่าน
 ลงทะเบียน
ค้นหา
ดู: 104|ตอบกลับ: 0

[VB.NET] การเคลื่อนที่ของเซลล์ในตารางกริดให้เลื่อนไปทางขวา เมื่อกด Enter

[คัดลอกลิงก์]

320

กระทู้

512

โพสต์

6495

เครดิต

ผู้ดูแลระบบ

ทองก้อน ทับทิมกรอบ

Rank: 9Rank: 9Rank: 9

เครดิต
6495




โดยปกติหากเรากด Enter ในเซลล์ของตารางกริด หรือตารางเอ็กเซลล์ ก็จะเกิดการเคลื่อนที่ลงไปในแถวถัดไปเสมอ (ยกเว้นว่ากดลูกศรขวา) แต่ถ้าหากเราอยากให้เลื่อนเซลล์ไปทางขวาล่ะ จะทำอย่างไร??? ... โค้ดชุดนี้จะเป็นการแก้ปัญหาในเรื่องนี้ด้วยการนำ ProcessCmdKey เข้ามาช่วย ...

ProcessCmdKey ไม่ใช่ Event ของ DataGridView แต่เป็น Method ของ Form (เราถึงใช้ Overrides) ที่ Windows เรียกก่อนส่ง Key ไปยัง Control ต่างๆ ดังนั้นจึงไม่ต้อง AddHandler หรือ Handles เลย ...


การทำงานของ ProcessCmdKey
1. ผู้ใช้กด Enter ที่ Cell Quantity (จำนวนสินค้า)
2. Windows ส่ง Message WM_KEYDOWN ไปยัง Form (คล้ายๆกับการเรียกใช้งาน WinAPI32)
3. Form.ProcessCmdKey ถูกเรียก
4. ตรวจสอบว่า dgvData มี Focus และกด Enter
5. เรียก dgvData.EndEdit() - บันทึกข้อมูล
6. คำนวณว่า Cell ถัดไปคือ UnitPrice
7. ใช้ BeginInvoke เพื่อเลื่อนไป UnitPrice
8. Return True (บอกว่าเราจัดการ Enter แล้ว)
9. DataGridView ไม่ได้รับ Event Enter  
10. หลังจาก ProcessCmdKey จบ BeginInvoke ทำงาน
11. CurrentCell เปลี่ยนไป UnitPrice
12. BeginEdit(True) - เปิดให้แก้ไขทันที


ความหมายของฟังค์ชั่น ProcessCmdKey ...
  1.     Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
คัดลอกไปที่คลิปบอร์ด
Protected Overrides: หมายความว่า "ขอเขียนทับ Method ที่แม่ (Form/Control) มีอยู่แล้ว"
Function ... As Boolean : ต้องคืนค่า True หรือ False
True = "จัดการคีย์นี้แล้ว ไม่ต้องส่งต่อ ให้ใครอีก"
False = "ไม่ได้จัดการ ส่งต่อไปให้ Control อื่นประมวลผลต่อ"
msg: ข้อความดิบจาก Windows OS (ระดับต่ำมาก ปกติไม่ต้องใช้)
keyData: คีย์ที่กด (เช่น Keys.Enter, Keys.Tab)

มาดูโค้ดฉบับเต็มกันเถอะ ...

  1. Public Class frmGridView

  2.     ' กำหนดค่าคงที่สำหรับดัชนีคอลัมน์ (ป้องกัน Magic Numbers)
  3.     Private Const colPK As Integer = 0
  4.     Private Const colProductID As Integer = 1
  5.     Private Const colProductName As Integer = 2
  6.     Private Const colQuantity As Integer = 3
  7.     Private Const colPrice As Integer = 4
  8.     Private Const colTotal As Integer = 5

  9.     Public Sub New()
  10.         ' This call is required by the designer.
  11.         InitializeComponent()
  12.         ' Add any initialization after the InitializeComponent() call.
  13.         InitializeGrid()
  14.     End Sub

  15.     Private Sub frmGridView_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  16.         Me.KeyPreview = True
  17.         Me.CenterToScreen()
  18.         txtSumTotal.ReadOnly = True
  19.         txtSumTotal.Text = "0.00"
  20.     End Sub

  21.     Private Sub frmGridView_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
  22.         ' ตรวจสอบว่าโฟกัสไม่ได้อยู่ในโหมดแก้ไขข้อความ เพื่อป้องกันการทำงานซ้อน
  23.         If Not dgvData.IsCurrentCellInEditMode Then
  24.             Select Case e.KeyCode
  25.                 Case Keys.F7
  26.                     btnAdd.PerformClick()
  27.                 Case Keys.F8
  28.                     btnRemove.PerformClick()
  29.             End Select
  30.         End If
  31.     End Sub

  32.     ' / --------------------------------------------------------------------------------
  33.     ' / Grid Initialization & Styling
  34.     ' / --------------------------------------------------------------------------------
  35.     Private Sub InitializeGrid()
  36.         With dgvData
  37.             .RowHeadersVisible = False
  38.             .AllowUserToAddRows = False
  39.             .AllowUserToDeleteRows = False
  40.             .AllowUserToResizeRows = False
  41.             .MultiSelect = False
  42.             .ReadOnly = False
  43.             .RowTemplate.Height = 27
  44.             .Font = New Font("Tahoma", 10.0!)
  45.             .DefaultCellStyle.Font = New Font("Tahoma", 10.0!, FontStyle.Regular)
  46.             .RowsDefaultCellStyle.Font = New Font("Tahoma", 10, FontStyle.Regular)
  47.             .AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
  48.             ' ปิดการปรับขนาดโดยผู้ใช้ (ถ้าต้องการให้คงที่)
  49.             .ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing
  50.             ' กำหนดความสูงของ Header
  51.             .ColumnHeadersHeight = 38
  52.             ' จัดรูปแบบ Header
  53.             With .ColumnHeadersDefaultCellStyle
  54.                 .BackColor = Color.Navy
  55.                 .ForeColor = Color.White
  56.                 .Font = New Font("Tahoma", 10.0F, FontStyle.Bold)
  57.                 .Alignment = DataGridViewContentAlignment.MiddleCenter
  58.             End With
  59.         End With

  60.         With dgvData
  61.             ' กำหนดโครงสร้างคอลัมน์
  62.             .Columns.Add("PK", "Primary Key")
  63.             .Columns.Add("ProductID", "Product ID")
  64.             .Columns.Add("ProductName", "Product Name")

  65.             .Columns.Add("Quantity", "Quantity")
  66.             .Columns(colQuantity).ValueType = GetType(Decimal) ' ใช้ Decimal เพื่อความแม่นยำของตัวเลข

  67.             .Columns.Add("UnitPrice", "Unit Price")
  68.             .Columns(colPrice).ValueType = GetType(Decimal)

  69.             .Columns(colPrice).DefaultCellStyle.Format = "N2"

  70.             .Columns.Add("Total", "Total")
  71.             .Columns(colTotal).ValueType = GetType(Decimal)

  72.             ' จัดรูปแบบคอลัมน์ Total
  73.             With .Columns(colTotal)
  74.                 .ReadOnly = True
  75.                 .DefaultCellStyle.BackColor = Color.LightGoldenrodYellow
  76.                 .DefaultCellStyle.ForeColor = Color.DarkRed
  77.                 .DefaultCellStyle.Font = New Font(dgvData.Font, FontStyle.Bold)
  78.                 .DefaultCellStyle.Format = "N2" ' จัดรูปแบบเป็นทศนิยม 2 ตำแหน่งอัตโนมัติ
  79.             End With

  80.             ' จัดชิดขวาสำหรับคอลัมน์ตัวเลข (3, 4, 5)
  81.             For i As Integer = colQuantity To colTotal
  82.                 .Columns(i).HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight
  83.                 .Columns(i).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
  84.             Next
  85.         End With
  86.     End Sub

  87.     ' / --------------------------------------------------------------------------------
  88.     ' / Row Management
  89.     ' / --------------------------------------------------------------------------------
  90.     Private Sub btnAddRow_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
  91.         Dim newPK As Integer = 1
  92.         If dgvData.RowCount > 0 Then
  93.             ' ดึงค่า PK จากแถวสุดท้ายแล้วบวกเพิ่ม
  94.             Dim lastPKObj As Object = dgvData.Rows(dgvData.RowCount - 1).Cells(colPK).Value
  95.             If lastPKObj IsNot Nothing AndAlso IsNumeric(lastPKObj) Then newPK = Convert.ToInt32(lastPKObj) + 1
  96.         End If

  97.         ' เพิ่มแถวใหม่
  98.         Dim newRow As String() = {
  99.             newPK.ToString(),
  100.             String.Format("PRO{0:D6}", newPK),
  101.             "Product " & newPK.ToString(),
  102.             "1",
  103.             "0.00",
  104.             "0.00"
  105.         }
  106.         dgvData.Rows.Add(newRow)

  107.         ' ย้ายโฟกัสไปที่ช่อง Quantity ของแถวใหม่ และเริ่มแก้ไขทันที
  108.         Dim lastRowIndex As Integer = dgvData.RowCount - 1
  109.         dgvData.CurrentCell = dgvData.Rows(lastRowIndex).Cells(colQuantity)
  110.         dgvData.BeginEdit(True)

  111.         RecalculateGrandTotal()
  112.     End Sub

  113.     Private Sub btnRemoveRow_Click(sender As Object, e As EventArgs) Handles btnRemove.Click
  114.         If dgvData.CurrentRow IsNot Nothing AndAlso Not dgvData.CurrentRow.IsNewRow Then
  115.             dgvData.Rows.Remove(dgvData.CurrentRow)
  116.             RecalculateGrandTotal()
  117.         End If
  118.     End Sub

  119.     ' / --------------------------------------------------------------------------------
  120.     ' / Calculation (แยกส่วนคำนวณให้ชัดเจน)
  121.     ' / --------------------------------------------------------------------------------
  122.     Private Sub RecalculateRow(rowIndex As Integer)
  123.         Dim qty As Decimal = 0D
  124.         Dim price As Decimal = 0D

  125.         ' ใช้ Convert.ToString จัดการ Nothing และ DBNull ได้โดยคืนค่า ""
  126.         Dim qtyStr As String = Convert.ToString(dgvData.Rows(rowIndex).Cells(colQuantity).Value)
  127.         Dim priceStr As String = Convert.ToString(dgvData.Rows(rowIndex).Cells(colPrice).Value)

  128.         ' TryParse จะคืนค่า False และตัวแปรยังคงเป็น 0 หากสตริงว่างหรือไม่ใช่ตัวเลข
  129.         Decimal.TryParse(qtyStr, qty)
  130.         Decimal.TryParse(priceStr, price)

  131.         ' บังคับให้เป็นค่าบวกหรือศูนย์
  132.         If qty < 0 Then qty = 0
  133.         If price < 0 Then price = 0

  134.         dgvData.Rows(rowIndex).Cells(colQuantity).Value = qty
  135.         dgvData.Rows(rowIndex).Cells(colPrice).Value = price
  136.         dgvData.Rows(rowIndex).Cells(colTotal).Value = qty * price
  137.     End Sub

  138.     Private Sub RecalculateGrandTotal()
  139.         Dim grandTotal As Decimal = 0D
  140.         For Each row As DataGridViewRow In dgvData.Rows
  141.             If Not row.IsNewRow Then
  142.                 Dim rowTotalStr As String = Convert.ToString(row.Cells(colTotal).Value)
  143.                 Dim rowTotal As Decimal = 0D
  144.                 Decimal.TryParse(rowTotalStr, rowTotal)
  145.                 grandTotal += rowTotal
  146.             End If
  147.         Next
  148.         txtSumTotal.Text = grandTotal.ToString("N2")
  149.     End Sub

  150.     ' / --------------------------------------------------------------------------------
  151.     ' / Data Validation (หัวใจหลักของการป้องกันข้อมูลผิด)
  152.     ' / --------------------------------------------------------------------------------
  153.     ' ป้องกันการพิมพ์ตัวอักษรผิดตั้งแต่กดปุ่ม (Real-time)
  154.     Private Sub dgvData_EditingControlShowing(sender As Object, e As DataGridViewEditingControlShowingEventArgs) Handles dgvData.EditingControlShowing
  155.         ' ลบ Event เก่าออกก่อนเพื่อป้องกันการใช้งานซ้ำซ้อน (Multiple Handlers)
  156.         RemoveHandler e.Control.KeyPress, AddressOf Cell_KeyPress

  157.         Dim currentColName As String = dgvData.Columns(dgvData.CurrentCell.ColumnIndex).Name
  158.         If currentColName = "Quantity" OrElse currentColName = "UnitPrice" Then AddHandler e.Control.KeyPress, AddressOf Cell_KeyPress
  159.     End Sub

  160.     Private Sub Cell_KeyPress(sender As Object, e As KeyPressEventArgs)
  161.         Dim tb As TextBox = CType(sender, TextBox)
  162.         Dim colIndex As Integer = dgvData.CurrentCell.ColumnIndex

  163.         Select Case colIndex
  164.             Case colQuantity ' อนุญาตเฉพาะตัวเลข 0-9 และ Backspace
  165.                 If Not Char.IsControl(e.KeyChar) AndAlso Not Char.IsDigit(e.KeyChar) Then
  166.                     e.Handled = True
  167.                 End If

  168.             Case colPrice ' อนุญาตตัวเลข 0-9, จุดทศนิยม (ได้เพียง 1 ตัว), และ Backspace
  169.                 If Not Char.IsControl(e.KeyChar) AndAlso Not Char.IsDigit(e.KeyChar) AndAlso e.KeyChar <> "."c Then
  170.                     e.Handled = True
  171.                 End If
  172.                 ' ป้องกันการใส่จุดทศนิยมมากกว่า 1 ตัว
  173.                 If e.KeyChar = "."c AndAlso tb.Text.Contains("."c) Then
  174.                     e.Handled = True
  175.                 End If
  176.         End Select
  177.     End Sub

  178.     ' ตรวจสอบความถูกต้องเมื่อผู้ใช้พยายามออกจากเซลล์ (Catch Copy-Paste or Blank)
  179.     Private Sub dgvData_CellValidating(sender As Object, e As DataGridViewCellValidatingEventArgs) Handles dgvData.CellValidating
  180.         ' ข้ามการตรวจสอบแถวใหม่ที่ยังไม่ได้กรอก
  181.         If dgvData.Rows(e.RowIndex).IsNewRow Then Return

  182.         Dim formattedValue As String = Convert.ToString(e.FormattedValue)

  183.         Select Case e.ColumnIndex
  184.             Case colQuantity
  185.                 Dim qty As Integer
  186.                 If Not Integer.TryParse(formattedValue, qty) OrElse qty <= 0 Then
  187.                     e.Cancel = True ' ห้ามออกจากเซลล์
  188.                     MessageBox.Show("จำนวนต้องเป็นตัวเลขที่มากกว่า 0 เท่านั้น", "ข้อมูลไม่ถูกต้อง", MessageBoxButtons.OK, MessageBoxIcon.Warning)
  189.                 End If

  190.             Case colPrice
  191.                 Dim price As Decimal
  192.                 If Not Decimal.TryParse(formattedValue, price) OrElse price < 0 Then
  193.                     e.Cancel = True
  194.                     MessageBox.Show("ราคาต้องเป็นตัวเลขที่ถูกต้องและไม่ติดลบ", "ข้อมูลไม่ถูกต้อง", MessageBoxButtons.OK, MessageBoxIcon.Warning)
  195.                 End If
  196.         End Select
  197.     End Sub

  198.     ' เมื่อข้อมูลผ่านการตรวจสอบแล้ว ให้ทำการคำนวณ
  199.     Private Sub dgvData_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgvData.CellValueChanged
  200.         If e.RowIndex >= 0 AndAlso (e.ColumnIndex = colQuantity OrElse e.ColumnIndex = colPrice) Then
  201.             RecalculateRow(e.RowIndex)
  202.             RecalculateGrandTotal()
  203.         End If
  204.     End Sub

  205.     ' / --------------------------------------------------------------------------------
  206.     ' / Navigation Enhancement (กด Enter เพื่อไปทางขวา)
  207.     ' / --------------------------------------------------------------------------------
  208.     ' / ProcessCmdKey ไม่ใช่ Event ของ DataGridView แต่เป็น Method ของ Form (เราถึงใช้ Overrides)
  209.     ' / ที่ Windows เรียกก่อนส่ง Key ไปยัง Control ต่าง ๆ ดังนั้นจึงไม่ต้อง AddHandler หรือ Handles เลย
  210.     ' / --------------------------------------------------------------------------------
  211.     Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
  212.         If dgvData.ContainsFocus AndAlso dgvData.CurrentCell IsNot Nothing Then
  213.             If keyData = System.Windows.Forms.Keys.Enter Then

  214.                 ' EndEdit เป็น Method ของ DataGridView ที่ใช้บังคับให้ DataGridView บันทึกค่าที่กำลังแก้ไขอยู่ใน Cell
  215.                 Dim isEditCommitted As Boolean = dgvData.EndEdit()

  216.                 ' ถ้า EndEdit ไม่สำเร็จ (ข้อมูลผิด/ว่าง) ให้หยุดทันที
  217.                 If Not isEditCommitted Then Return True

  218.                 ' คำนวณตำแหน่งเซลล์ถัดไป
  219.                 Dim currentRow As Integer = dgvData.CurrentCell.RowIndex
  220.                 Dim currentCol As Integer = dgvData.CurrentCell.ColumnIndex
  221.                 Dim nextCol As Integer = currentCol + 1
  222.                 Dim targetRow As Integer = currentRow
  223.                 Dim targetCol As Integer = nextCol

  224.                 ' ตรวจสอบขอบเขต
  225.                 If nextCol >= dgvData.ColumnCount Then
  226.                     If currentRow < dgvData.RowCount - 1 Then
  227.                         targetRow = currentRow + 1
  228.                         targetCol = 0
  229.                     Else
  230.                         ' อย่าลืม Return True เมื่อจัดการแล้ว
  231.                         Return True ' อยู่แถวสุดท้าย คอลัมน์สุดท้ายแล้ว
  232.                     End If
  233.                 End If

  234.                 ' ใช้ BeginInvoke เลื่อนการย้ายเซลล์ออกไป (ให้ Grid จัดการสถานะภายในให้เสร็จก่อน)
  235.                 ' BeginInvoke หมายถึง ยังไม่ต้องทำตอนนี้ รอให้ DataGridView จบการทำงานก่อน
  236.                 ' แล้วค่อยเรียก Sub นี้
  237.                 Me.BeginInvoke(Sub()
  238.                                    Try
  239.                                        dgvData.CurrentCell = dgvData.Rows(targetRow).Cells(targetCol)
  240.                                        dgvData.BeginEdit(True)
  241.                                    Catch ex As Exception
  242.                                        ' หากยัง Error อีก ให้ข้ามไปก่อน (เหนื่อยจะแก้แล้ว ... 5555+)
  243.                                    End Try
  244.                                End Sub)

  245.                 Return True
  246.             End If
  247.         End If
  248.         ' อย่าลืมส่งต่อเมื่อไม่จัดการ
  249.         Return MyBase.ProcessCmdKey(msg, keyData)
  250.     End Function

  251.     ' / --------------------------------------------------------------------------------
  252.     Private Sub frmGridView_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
  253.         Me.Dispose()
  254.         GC.SuppressFinalize(Me)
  255.         Application.Exit()
  256.     End Sub

  257. End Class
คัดลอกไปที่คลิปบอร์ด



ดาวน์โหลดโค้ดฉบับเต็ม VB.NET (2010) ได้ที่นี่ ...

ขออภัย! โพสต์นี้มีไฟล์แนบหรือรูปภาพที่ไม่ได้รับอนุญาตให้คุณเข้าถึง

คุณจำเป็นต้อง ลงชื่อเข้าใช้ เพื่อดาวน์โหลดหรือดูไฟล์แนบนี้ คุณยังไม่มีบัญชีใช่ไหม? ลงทะเบียน

x
สิ่งที่ดีกว่าการให้ คือการให้แบบไม่มีที่สิ้นสุด
ขออภัย! คุณไม่ได้รับสิทธิ์ในการดำเนินการในส่วนนี้ กรุณาเลือกอย่างใดอย่างหนึ่ง ลงชื่อเข้าใช้ | ลงทะเบียน

รายละเอียดเครดิต

ข้อความล้วน|อุปกรณ์พกพา|ประวัติการแบน|G2GNet.com  

GMT+7, 2026-8-14 17:21 , Processed in 0.264072 second(s), 4 queries , File On.

Powered by Discuz! X3.4, Rev.62

Copyright © 2001-2020 Tencent Cloud.

ตอบกระทู้ ขึ้นไปด้านบน ไปที่หน้ารายการกระทู้