Короткий огляд, Windows Forms, ASP, статті

В даній статті пояснюється, як налаштувати зовнішній вигляд і поведінка елемента керування DataGrid. Похідний клас DataGridBoolColumn надає подія BoolValChanged, що викликається щоразу при зміні значення прапорця. Крім того, в похідному класі DataGridTextBoxColumn створено подія CellFormatting, що дозволяє управляти властивостями Font, ForeColor і BackColor кожної клітинки. DataGrid-подія CurrentCellChanged використовується, щоб перемістити фокус на будь стовпець незалежно від того, який стовпець рядки спочатку клацнув користувач. Додатково показується, як динамічно змінювати зовнішній вигляд осередків DataGrid в залежності від значень в ключових осередках. Під ключовою розуміється осередок, значення якої визначає зовнішній вигляд інших осередків. Далі пояснюється, як змусити стовпець з прапорцем реагувати на клацання в комірці зміною свого стану і як керувати порядком відображення і видимістю стовпців DataGrid незалежно від DataSource сітки. Нарешті, пояснюється, як за допомогою елемента управління ToolTip в Windows Forms виводити різні для кожного рядка в DataGrid підказки. Отриманий в результаті DataGrid наведено рис. 1.

Рис. 1. Настроєний DataGrid

Введення

У цієї статті дві мети. Перша – показати, наскільки легко реалізувати простий висновок табличних даних за допомогою DataGrid в Windows Forms. Друга – пояснити, як змінювати поведінку DataGrid за замовчуванням, що дозволяє використовувати його з більшою гнучкістю. Попутно розглядається, як створювати класи, які можна включати в проекти для забезпечення схожою функціональності в додатках.

Якщо вам відомо, як за допомогою дизайнера вивести DataGrid, пов’язаний з DataTable, пропустіть частина матеріалу до розділу
Налаштування сітки. Всі приклади написані на VB.NET. Проте вихідний код, який можна завантажити за раніше вказаним посиланням, включає проекти на VB.NET і на C #.

Основи

Для початку створіть в Visual Studio. NET проект Windows Forms Application, помістіть на форму DataGrid і додайте DataSource. Все це робиться за допомогою дизайнера. Щоб відобразити DataGrid, пов’язаний з джерелом даних ADO.NET, потрібно всього один рядок коду. Ще один рядок потрібно, щоб користувачі могли редагувати значення і зберігати їх у джерелі даних. Далі передбачається, що приклади баз даних, поставляються с. NET Framework, встановлені. Якщо це не так, встановіть їх, перейшовши за наступним посиланням (модифікуйте її в залежності від того, куди встановлений Visual Studio. NET):

C:Program FilesMicrosoft Visual
Studio.NETFrameworkSDKSamplesStartSamples.htm

Наступна процедура описує створення програми, що відображає таблицю Products з бази даних Northwind в DataGrid.

  1. У меню File виберіть New, потім Projects і створіть новий проект програми Windows Forms. Назвіть проект CustomDataGrid.

     

  2. Перетягніть DataGrid з Toolbox на форму. Змініть його розмір так, щоб елемент керування займав практично всю форму і настройте його властивість Anchor на прив’язку до всіх чотирьох краях. Отримана форма в Visual Studio наведена на рис. 2.

    Рис. 2. Форма в Visual Studio

     

  3. Потім в меню View виберіть Server Explorer Window. Під Data Connections відкрийте вузол Northwind зі списку Tables. Перетягніть таблицю Products в робочу область дизайнера. Після цього в секції Components в самому низу робочої області повинні з’явитися два компонента, SqlConnection1 і SqlDataAdapter1, як показано на рис.
    3.

    Рис. 3. Форма з компонентами SqlConnection і SqlDataAdapter

     

  4. Клацніть правою кнопкою миші компонент SqlDataAdapter1 і виберіть Generate DataSet. З’явиться діалогове вікно Generate DataSet (Рис. 4). Натисніть Enter і погодьтеся з операцією за замовчуванням, т. е. зі створенням типізованого набору даних і розміщенням його примірника в секції Components.

    Рис. 4. Створення DataSet

     

  5. У робочій області дизайнера клацніть DataGrid і встановіть його властивість DataSource рівним DataSet11Products.

     

  6. Додайте обробник події Load форми, двічі клацнувши порожній ділянка форми. В цей обробник помістіть єдиний рядок коду:
    Me.SqlDataAdapter1.Fill(Me.DataSet11)
    
  7. Нарешті, скомпілюйте і запустіть проект. Повинна з’явитися сітка на зразок наведеної на рис. 5.

    Рис. 5. DataGrid, створений за допомогою дизайнера і одного рядка коду

Налаштування сітки

1. Стовпці та порядок їх виводу

В елементі керування DataGrid необхідно управляти як виведеними стовпцями, так і порядком їх виведення. Стовпці та їх порядок в DataGrid, створюваному за замовчуванням, визначаються SQL-запитом, сформованим в процесі створення

SqlDataAdapter. З DataGrid, створеного за замовчуванням, потрібно видалити SupplierID і CategoryID. Крім того, потрібно перемістити стовпець Discontinued з останнього місця на перше.

Можна повернутися назад і вручну змінити SQL-запит, щоб задати, які стовпці повинні з’явитися в DataGrid і який повинен бути порядок їх прямування. Але замість цього я поясню, як додати до DataGrid

DataGridTableStyle. Додавши DataGridTableStyle, списком стовпців і порядком їх проходження можна управляти на основі стилів, що включаються в

DataGridTableStyle-Набір GridColumnStyles. Стилі, які використовуються DataGrid, визначаються в момент додавання DataGridTableStyle до набору DataGrid.TableStyle. Якщо TableStyle.GridColumnStyle на цьому етапі порожній, створюється використовуваний DataGrid набір ColumnStyles за замовчуванням. Однак, якщо DataGridColumns явно додані до DataGridTableStyle до додавання DataGridTableStyle до набору DataGrid.TableStyles, в DataGrid відображаються лише стовпці, що містяться в зазначеному наборі DataGridTableStyle.GridColumnStyles, причому порядок їх появи відповідає порядку, в якому вони розміщені в наборі.

Перш ніж вивчати фрагменти коду, що задають стовпці і порядок їх прямування, розглянемо клас DataGridColumnStyle. Це абстрактний клас. Зазвичай застосовують або клас DataGridTextBoxColumn, або клас DataGridBoolColumn (обидва вони є похідними від DataGridColumnStyle і поставляються с. NET Framework). Основне призначення цих класів – управління зовнішнім виглядом стовпця в DataGrid. Наприклад, DataGridBoolColumn змушує стовпець виглядати і функціонувати подібно прапорця. Пізніше я створю від цієї пари додаткові похідні класи, що дозволяють проводити подальші зміни зовнішнього вигляду і поведінки стовпців.

Відповідність між конкретним стовпцем в DataTable і конкретним об’єктом DataGridColumnStyle задається у властивості DataGridColumnStyle.MappingName. Це єдине обов’язкове властивість, яке необхідно задати при створення DataGridColumnStyle. До інших цікавих властивостей DataGridColumnStyle відносяться

Header,

ReadOnly і

Width.

DataGrid за замовчуванням містить наступні 10 стовпців в такому порядку:
ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel і Discontinued. Змінений DataGrid включає тільки вісім шпальт в наступному порядку:
Discontinued, ProductID, ProductName, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder і ReorderLevel.

Далі наведено змінений обробник події Load форми, який створює спочатку DataGridTableStyle (крок 1), потім – об’єкти DataGridColumnStyle, що додаються до набору GridColumnStyles (крок 2), і, нарешті, додає DataGridTableStyle до властивості DataGrid.TableStyles (Крок 3).

    Private Sub Form1_Load(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles MyBase.Load
        Me.SqlDataAdapter1.Fill(Me.DataSet11)
 ? Крок 1: створити DataGridTableStyle ? і привласнити MappingName таблицю.
        Dim tableStyle As New DataGridTableStyle()
        tableStyle.MappingName = "Products"
 ? Крок 2: створити DataGridColumnStyle для кожного стовпця ? виведеного в сітці в порядку їх відображення.
        ?Discontinued
        Dim discontinuedCol As New DataGridBoolColumn()
        discontinuedCol.MappingName = "Discontinued"
        discontinuedCol.HeaderText = ""
        discontinuedCol.Width = 30 ? Відключити третій стан прапорця
        discontinuedCol.AllowNull = False 
        tableStyle.GridColumnStyles.Add(discontinuedCol)
 ? Крок 2: ProductID
        Dim column As New DataGridTextBoxColumn()
        column.MappingName = "ProductID"
        column.HeaderText = "ID"
        column.Width = 30
        tableStyle.GridColumnStyles.Add(column)
 ? Крок 2: ProductName
        column = New DataGridTextBoxColumn()
        column.MappingName = "ProductName"
        column.HeaderText = "Name"
        column.Width = 140
        tableStyle.GridColumnStyles.Add(column)
        ? Крок 2: QuantityPerUnit
        column = New DataGridTextBoxColumn()
        column.MappingName = "QuantityPerUnit"
        column.HeaderText = "QuantityPerUnit"
        tableStyle.GridColumnStyles.Add(column)
 ? Крок 2: UnitPrice
        column = New DataGridTextBoxColumn()
        column.MappingName = "UnitPrice"
        column.HeaderText = "UnitPrice"
        tableStyle.GridColumnStyles.Add(column)
 ? Крок 2: UnitsInStock
        column = New DataGridTextBoxColumn()
        column.MappingName = "UnitsInStock"
        column.HeaderText = "UnitsInStock"
        tableStyle.GridColumnStyles.Add(column)
         ? Крок 2: UnitsOnOrder
        column = New DataGridTextBoxColumn()
        column.MappingName = "UnitsOnOrder"
        column.HeaderText = "UnitsOnOrder"
        tableStyle.GridColumnStyles.Add(column)
         ? Крок 2: ReorderLevel
        column = New DataGridTextBoxColumn()
        column.MappingName = "ReorderLevel"
        column.HeaderText = "ReorderLevel"
        tableStyle.GridColumnStyles.Add(column)
 ? Крок 3: додати tablestyle до datagrid
        Me.DataGrid1.TableStyles.Add(tableStyle)
    End Sub

На рис. 6 показаний DataGrid після додавання коду, наведеного вище. Стовпці збігаються з раніше зазначеними, а Discontinued з’являється першим.

Рис. 6. DataGrid з певними стовпцями в необхідному порядку

2. Зміна реакції на клацання у рядку з відомостями про товар, випуск якого припинений

Припустимо, користувач не повинен редагувати дані про товар, випуск якого припинено (discontinued product), поки не скине прапорець Discontinued. Тобто, якщо користувач клацне будь-яку клітинку в рядку з таким товаром, фокус перекладається з цього осередку на перший стовпець –
Discontinued.

Щоб створити таку функціональність, додайте обробник події CurrentCellChanged. У цьому обробнику, якщо значення стовпця Discontinued одно true, явно встановлюйте поточної клітинку в стовпці Discontinued. Ось приклад коду:

Private Sub dataGrid1_CurrentCellChanged( _
            ByVal sender As System.Object, _
            ByVal e As System.EventArgs) _
         Handles DataGrid1.CurrentCellChanged ? Якщо користувач клацнув у рядку з товаром, випуск ? якого припинено, встановити CurrentCell рівним CheckBox
    Dim discontinuedColumn As Integer = 0
    Dim val As Object = Me.DataGrid1( _
        Me.DataGrid1.CurrentRowIndex, _
        discontinuedColumn)
    Dim productDiscontinued As Boolean = CBool(val)
    If productDiscontinued Then
         Me.DataGrid1.CurrentCell = _
            New DataGridCell( _
                Me.DataGrid1.CurrentRowIndex, _
                discontinuedColumn)
    End If
End Sub

3. Прапорець, що реагує на єдиний клацання

Щоб змінити значення прапорця в DataGrid за замовчуванням потрібно два клацання. Перший переводить фокус введення на клітинку, а другий змінює стан прапорця. У нашому прикладі ми зробимо так, щоб значення прапорця змінювалося при першому клацанні. Для цього треба захоплювати подія DataGrid.Click і перевіряти, чи перебуває курсор миші в комірці з прапорцем. Якщо це перший клацання після того, як осередок стала поточної, слід явно змінити булево значення в сітці. Щоб відстежити Перше клацання після того, як осередок стала поточної, додайте до форми змінну і присвоюйте їй відповідне значення в обробнику
CurrentCellChanged.

Далі наведено фрагменти коду, що реалізує таку поведінку. В обробнику Click визначається позиція курсора миші, яка передається DataGrid-методу HitTest, щоб з’ясувати, якою осередку стався клацання. Завдання – зреагувати на перший клацання в осередку після зміни поточної комірки. Щоб уникнути проблем, що виникають при клацанні на рядку AddNew, що знаходиться внизу DataGrid, проводиться додаткова перевірка, т. е. з’ясовується, чи є рядок реальної (пов’язаної з джерелом даних). Для цього номер рядка порівнюється зі значенням властивості Count об’єкта BindingManagerBase.

Private afterCurrentCellChanged As Boolean = False
Private Sub dataGrid1_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles DataGrid1.Click
   Dim discontinuedColumn As Integer = 0
   Dim pt As Point = Me.dataGrid1.PointToClient( _
       Control.MousePosition)
   Dim hti As DataGrid.HitTestInfo = _
       Me.dataGrid1.HitTest(pt)
   Dim bmb As BindingManagerBase = _
       Me.BindingContext(Me.dataGrid1.DataSource, _
       Me.dataGrid1.DataMember)
   If afterCurrentCellChanged _
      AndAlso hti.Row < bmb.Count _
      AndAlso hti.Type = DataGrid.HitTestType.Cell _
      AndAlso hti.Column = discontinuedColumn Then
        Me.DataGrid1(hti.Row, discontinuedColumn) = _
           Not CBool(Me.DataGrid1(hti.Row, _
                 discontinuedColumn))
    End If
    afterCurrentCellChanged = False
End Sub ?dataGrid1_Click
 ? Додати рядок до цього (існуючого) обробникові
Private Sub dataGrid1_CurrentCellChanged( _
            ByVal sender As System.Object, _
            ByVal e As System.EventArgs) _
        Handles DataGrid1.CurrentCellChanged ? Якщо клацання стався в рядку з товаром, випуск якого припинено, ? зробити поточної клітинку з прапорцем
    Dim discontinuedColumn As Integer = 0
    Dim val As Object = Me.DataGrid1( _
         Me.DataGrid1.CurrentRowIndex, _
         discontinuedColumn)
    Dim productDiscontinued As Boolean = CBool(val)
    If productDiscontinued Then
         Me.DataGrid1.CurrentCell = _
           New DataGridCell( Me.DataGrid1.CurrentRowIndex, _
                             discontinuedColumn)
    End If ? Додайте цей рядок
    afterCurrentCellChanged = True 
End Sub ?dataGrid1_CurrentCellChanged

4. Зміна властивостей осередку BackColor, ForeColor і Font

Для зміни способу відтворення осередку DataGrid потрібно перевизначити метод Paint в похідному класі DataGridTextBoxColumn. Коли елементу DataGrid, з яким пов’язаний DataGridTableStyle, потрібно намалювати клітинку, він викликає метод Paint класу DataGridTextBoxColumn. Аргументи викликається методу включають ForeBrush і BackBrush, що керують кольором комірки. Таким чином, щоб керувати квітами кожної окремої клітинки, можна перевизначити віртуальний метод Paint класу DataGridTextBoxColumn і змінювати кисті для кожної мальованої осередки.

Суть в тому, щоб додати подію до похідному класу DataGridTextBoxColumn. У похідному методі Paint цю подію спрацьовує, і всі його приймачі можуть вказати BackColor і ForeColor для мальованої осередки. Після того як ця подія створено, для управління кольором клітинки потрібно лише створити обробник події. Крім того, спеціальні EventArgs забезпечують приймач події інформацією про номерах рядка і стовпця, а також дозволяють йому налаштувати властивості кольору.

Створення класу аргументів події

У Solution Explorer двічі клацніть проект і додайте новий клас DataGridFormatCellEventArgs, похідний від EventArgs. Далі наведено список його відкритих властивостей з описом їх застосування. Три перших властивості передаються в обробник події у вигляді аргументу; приймач події встановлює сім останніх властивостей, вказуючи, як малювати клітинку.

Dispose-властивості DataGridFormatCellEventArgs встановлюються приймачем події в тому випадку, якщо перевизначення версія Paint повинна викликати метод Dispose об’єктів, що реалізують

IDisposable. Наприклад, якщо кожен раз при відображенні осередку динамічно створюється BackBrush, то по закінченні відтворення викликайте Dispose кисті з методу Paint. З іншого боку, якщо створюється і кешується єдина кисть, що надає BackBrush для декількох осередків, для неї викликати Dispose не слід. Dispose-властивості дозволяють приймачу події повідомити перевизначення методів Paint, що робити з викликами Dispose.

Ось як має виглядати код класу. Перед визначенням класу доданий делегат, що визначає сигнатуру обробника події, необхідну подією SetCellFormat і певну як частина похідного класу
DataGridTextBoxColumn.

Public Delegate Sub FormatCellEventHandler( _
     ByVal sender As Object, _
     ByVal e As DataGridFormatCellEventArgs)
Public Class DataGridFormatCellEventArgs
    Inherits EventArgs
    Public Sub New(ByVal row As Integer, _
       ByVal col As Integer, _
       ByVal cellValue As Object)
    End Sub ?New
End Class

Створення похідного класу DataGridTextBoxColumn

Тепер потрібно додати код в похідний клас DataGridTextBoxColumn. Для цього клацніть проект правою кнопкою миші в Solution Explorer і додайте клас FormattableTextBoxColumn. Щоб додати заглушку (stub) для перевизначення версії Paint, виберіть (Overrides) у спадному списку в лівій верхній частині вікна редагування коду, а в аналогічному списку в правій верхній частині – другий метод Paint (з сімома аргументами). Відповідний код показаний нижче.

Примітка Зауважте, що додано і оголошення події SetCellFormat; при цьому використовується делегат FormatCellEventHandler, визначений у файлі попереднього класу.

Public Class FormattableTextBoxColumn
    Inherits DataGridTextBoxColumn
    Public Event SetCellFormat As FormatCellEventHandler
 Protected Overloads Overrides Sub Paint(………)
        
    End Sub ?Paint    
End Class

Реалізація перевантаженої версії Paint

Перше, що потрібно зробити в перевантаженій версії Paint, – викликати подія SetCellFormat. Саме через цю подію його приймач вказує формат поточної комірки. Для цього слід створити DataGridFormatCellEventArgs, ініціалізувати рядок, стовпець і поточне значення, а потім викликати подію з цим аргументом. Нарешті, після генерації події необхідно проаналізувати аргумент події та з’ясувати, які форматуються операції потрібно виконати.

Виклик події

Нижче наведено фрагмент коду, який створює аргумент і генерує подія. Змінні source і rowNum передаються методу Paint.

Dim e As DataGridFormatCellEventArgs = Nothing
 ? Отримати номер стовпця
    Dim col As Integer = _
     Me.DataGridTableStyle.GridColumnStyles.IndexOf(Me)
     ? Створити об'єкт EventArgs
 e = New DataGridFormatCellEventArgs( _
       rowNum, col, _
       Me.GetColumnValueAtRow([source], rowNum))
 ? Викликати подія форматування
   RaiseEvent SetCellFormat(Me, e)

Кольорове оформлення

Для завдання кольору і фону осередку перевірте властивості об’єкта DataGridFormatCellEventArgs після того, як обробник події повернув управління. Дві кисті (foreBrush і backBrush), що використовуються для відтворення осередку, передаються як аргументи методу Paint. Для зміни кольору і фону осередку змініть старі кисті на нові до виклику базової реалізації методу Paint. Далі наведено фрагмент коду, що ілюструє цей підхід:

? припустимо, ми викликаємо BaseClass
Dim callBaseClass As Boolean = True 
 ? Перевірити кисті, повернені подією
   If Not (e.BackBrush Is Nothing) Then
        backBrush = e.BackBrush
   End If
   If Not (e.ForeBrush Is Nothing) Then
        foreBrush = e.ForeBrush
   End If
 ? Перевірити властивість UseBaseClassDrawing
   If Not e.UseBaseClassDrawing Then
            callBaseClass = False
   End If
   
   If callBaseClass Then
        MyBase.Paint(g, bounds, _
          [source], rowNum, backBrush, _
          foreBrush, alignToRight)
   End If

У попередньому фрагменті кисті налаштовувалися на основі властивостей об’єкта DataGridFormatCellEventArgs. В залежності від значення UseBaseClassDrawing викликається базовий клас. А виклик базового класу з новими кистями і змінює колір комірки. Не забудьте, що цей метод викликається для кожного осередку в стовпці.

Обробка властивості TextFont

Зміна шрифту осередку істотно ускладнює перевантажену версію Paint. Справа в тому, що шрифт, який використовується для виведення тексту, – не просто параметр, переданий як аргумент методу Paint. Навпаки, спочатку шрифт витягується з властивості DataGrid.Font. При цьому значення шрифту кешується і не зчитується знову для кожного осередку. Саме тому перше, що спадає на думку, – динамічна зміна DataGrid.Font для кожного осередку – не спрацює. Тому, щоб змінити шрифт комірки, необхідно самостійно перетворювати рядок. Для точної імітації виведення стандартного DataGrid доведеться потрудитися. У прикладі цього не приділяється надмірно багато уваги – задовольнимося схожістю замість повної ідентичності. Ось фрагмент коду, що виводить рядок новим шрифтом.

? Якщо шрифт тексту (TextFont) заданий, вивести рядок
If Not (e.TextFont Is Nothing) Then
    Try
        Dim charWidth As Integer = _
            Fix(Math.Ceiling(g.MeasureString("c", _
                e.TextFont, 20, _
                StringFormat.GenericTypographic).Width))
        Dim s As String = _
           Me.GetColumnValueAtRow([source], _
                 rowNum).ToString()
        Dim maxChars As Integer = _
           Math.Min(s.Length, bounds.Width / charWidth)
        Try
            g.FillRectangle(backBrush, bounds)
            g.DrawString(s.Substring(0, maxChars), _
                e.TextFont, foreBrush, _
                bounds.X, bounds.Y + 2)
        Catch ex As Exception
              Console.WriteLine(ex.Message.ToString())
        End Try Catch? порожній catch
    End Try
    callBaseClass = False
End If

У першій частині коду оцінюється, скільки символів можна вивести з урахуванням ширини поточної комірки. Для цього ширина осередку ділиться на середній розмір символів. Більш точно кількість символів можна обчислити, послідовно викликаючи MeasureString. При цьому ви отримаєте точне число символів, але тоді висновок буде істотно відрізнятися від висновку стандартного DataGrid. Спосіб, заснований на середньому розмірі символу, набагато швидше, а його результати набагато ближче до стандартного DataGrid.

Отримавши число символів, ми малюємо фон і викликаємо Graphics.DrawString з передачею потрібних шрифту і кисті. Нарешті, прапор callBaseClass задається так, щоб метод Paint базового класу згодом не викликався, знищуючи всі наші попередні зусилля.

Застосування нового класу FormattableTextBoxColumn

Щоб використовувати новий стовпець, поверніться в код форми і замініть всі входження DataGridTextBoxColumn на FormattableTextBoxColumn. Тепер для кожного стовпця підключіть приймач події SetCellFormat, генерується похідним класом. Можна вказати свій обробник для кожного стовпця. Але нам для фарбування рядків досить одного обробника для всіх стовпців. Нижче наведено код для типового стовпця після цих змін.

? ProductName
column = New FormattableTextBoxColumn()
column.MappingName = "ProductName"
column.HeaderText = "Name"
column.Width = 140
AddHandler column.SetCellFormat, AddressOf FormatGridRow
tableStyle.GridColumnStyles.Add(column)

Зверніть увагу, що тепер створюється екземпляр похідного класу, FormattableTextBoxColumn. Крім того, додається FormatGridRow – Обробник для події SetCellFormat.

Обробник події SetCellFormat

В обробнику SetCellFormat кисті задають фон і колір тексту в комірці, номери рядка і стовпця якій передаються в аргументах події. Якщо осередок знаходиться в поточному рядку сітки, використовується один набір квітів і шрифту, а якщо вона знаходиться в рядку, булево значення Discontinued в якої дорівнює true, застосовується інший набір кольорів. Далі наведено фрагмент коду, що оголошує і визначає ці Кешована об’єкти GDI +.

? Змінні форми, кешуючий об'єкти GDI +
Private disabledBackBrush As Brush
Private disabledTextBrush As Brush
Private currentRowFont As Font
Private currentRowBackBrush As Brush
 ? В Form1_Load створюються і кешуються об'єкти GDI +
Me.disabledBackBrush = _
  New SolidBrush(SystemColors.InactiveCaptionText)
Me.disabledTextBrush = _
  New SolidBrush(SystemColors.GrayText)
Me.currentRowFont = _
  New Font(Me.DataGrid1.Font.Name, _
           Me.DataGrid1.Font.Size, _
           FontStyle.Bold)
Me.currentRowBackBrush = Brushes.DodgerBlue

У коді обробника події налаштовуються члени DataGridFormatCellEventArgs, передані для виведення конкретних рядків у тому чи іншому форматі. Рядки з відомостями про товари, випуск яких припинено, виводяться сірими, а поточна рядок виділяється на тлі DodgerBlue напівжирним шрифтом. Ось обробник, який реалізує цю логіку:

Private Sub FormatGridRow(ByVal sender As Object, _
        ByVal e As DataGridFormatCellEventArgs)
    Dim discontinuedColumn As Integer = 0 ? Встановити властивості e в залежності від e.Row і e.Col
    Dim discontinued As Boolean = CBool( _
       IIf(e.Column <> discontinuedColumn, _
       Me.DataGrid1(e.Row, discontinuedColumn), _
       e.CurrentCellValue))  ? Рядок з відомостями про товар, випуск якого припинено?
    If e.Column > discontinuedColumn AndAlso _
CBool(Me.DataGrid1(e.Row, discontinuedColumn)) Then 
         e.BackBrush = Me.disabledBackBrush
         e.ForeBrush = Me.disabledTextBrush ? Поточна рядок?
    ElseIf e.Column > discontinuedColumn AndAlso _
           e.Row = Me.DataGrid1.CurrentRowIndex Then 
         e.BackBrush = Me.currentRowBackBrush
         e.TextFont = Me.currentRowFont
    End If
End Sub

Пам’ятайте, що насправді в наборі DataGrid.TableStyle (0). GridColumnStyles існує кілька об’єктів FormattableTextBoxColumn. По суті кожен стовпець (за винятком булевого) використовує ці об’єкти. Крім того, кожен з цих об’єктів приєднується до свого події SetCellFormat. Проте всі ці події використовують один і той же обробник FormatGridRow, наведений раніше. Те є всі клітинки в рядку (крім булевих) форматуються за одним і тим же правилам. Таким чином, встановлюються стилі рядків, і всі комірки рядки мають однаковий формат незважаючи на те, що кожна клітинка форматується окремо.

Подивимося, що вийшло

Нижче показана сітка, яка вийшла у нас до цього моменту. Зауважте, що рядки зі встановленими прапорцями відображаються сірим, а поточний рядок виділяється напівжирним шрифтом на тлі DodgerBlue.

Рис. 7. DataGrid з кольоровими рядками

На перший погляд все добре. Але є маленька проблема, яку треба вирішити. Справа в тому, що клацання раніше позначеного першого стовпця призводить до скидання прапорця, але рядок не перемальовується. А рядок потрібно оновлювати при кожній зміні значення прапорця. Щоб вирішити цю проблему, створимо клас, похідний від DataGridBooleanColumn, і додамо обробник події, що викликає подія всякий раз, коли булево значення змінюється. Тоді ми отримаємо можливість реагувати на відповідний клацання і перемальовувати рядок відповідно до нового значенням.

5. Додавання стовпця типу bool з подією ValueChanged

Аргументи події

Почнемо зі створення класу, похідного від EventArgs і зберігає необхідні нам відомості про подію BoolValueChanged, а саме: стовпець і рядок змінною осередку, а також нове булево значення. Ніжк показаний клас BoolValueChangedEventArgs. В відсутньої реалізації ініціалізіруемих в конструкторі закриті поля використовуються тільки як копії відкритих властивостей. Крім того, на початку визначається делегат для цієї події – BoolValueChangedEventHandler.

Public Delegate Sub BoolValueChangedEventHandler( _
       ByVal sender As Object, _
       ByVal e As BoolValueChangedEventArgs)
Public Class BoolValueChangedEventArgs
    Inherits EventArgs
    
    Public Sub New(ByVal row As Integer, _
                   ByVal col As Integer, _
                   ByVal val As Boolean)
           . . .
    End Sub ?New
    Public Property Column() As Integer
           . . .
 End Property
    Public Property Row() As Integer
         . . .
    End Property
    Public ReadOnly Property BoolValue() As Boolean
           . . .
    End Property
End Class ?BoolValueChangedEventArgs

Похідний клас DataGridBoolColumn

У похідному класі ClickableBooleanColumn перевантажуються три методи, відповідають за різні аспекти роботи з булевим значенням у клітинці. Перший з них – Edit. Він викликається, коли осередок повинна перейти в режим редагування. У перевантаженої версії деякі закриті поля настроюються відповідно до поточному булевим станом і поточним режимом (редагування). Другий – такий же метод Paint, що і раніше використаний в FormattableTextBoxColumn. Тут замість зміни зовнішнього вигляду осередку відслідковуються зміни булевого значення і, якщо воно змінилося, викликається подія BoolValueChanged. Третій – метод Commit. Він викликається при завершенні редагування, коли значення необхідно зберегти у джерелі даних. У цьому методі обнуляються поля, заповнюються в перевантаженій версії методу Edit.

Є кілька технічних завдань, які потрібно вирішити коректно. Булево значення може змінитися в результаті клацання клітинки або натискання клавіші-пробілу, коли фокус уведення знаходиться в комірці. Для відстеження цих подій використовується допоміжний метод ManageBoolValueChanging. При зміні значення він викликає подія BoolValueChanged. Щоб відстежити клацання, ManageBoolValueChanging перевіряє загальні (статичні) властивості Control.MousePosition і Control.MouseButtons. Однак перевірка KeyState для клавіші-пропусків – операція проблематична; тут доведеться вдатися до Interop-викликом Win32-функції GetKeyState.

Ось як виглядає код класу:

Public Class ClickableBooleanColumn
    Inherits DataGridBoolColumn
 ? Подія зміни
    Public Event BoolValueChanged _
         As BoolValueChangedEventHandler
 ? Налаштування змінних для відстеження змін
    Protected Overloads Overrides Sub Edit(. . .)
        Me.lockValue = True
        Me.beingEdited = True
        Me.saveRow = rowNum
        Me.saveValue = CBool( _
           MyBase.GetColumnValueAtRow( _
                    [source], rowNum))
        MyBase.Edit(. . .)
    End Sub ?Edit
 ? Перевантажений для обробки події BoolChange
    Protected Overloads Overrides Sub Paint(. . .)
        Dim colNum As Integer = _
           Me.DataGridTableStyle.GridColumnStyles.IndexOf(Me)
 ? Використовується для обробки зміни значення
        ManageBoolValueChanging(rowNum, colNum)
        MyBase.Paint(. . .)
    End Sub ?Paint 
 ? Відключити відстеження змін
    Protected Overrides Function Commit(. . .) As Boolean
        Me.lockValue = True
        Me.beingEdited = False
        Return MyBase.Commit(dataSource, rowNum)
    End Function ?Commit
 ? Поля, необхідні для відстеження значення
    Private saveValue As Boolean = False
    Private saveRow As Integer = -1
    Private lockValue As Boolean = False
    Private beingEdited As Boolean = False
    Public Const VK_SPACE As Integer = 32
 ? Необхідно, щоб пробіл зраджував булево значення
    <System.Runtime.InteropServices.DllImport("user32.dll")> _
    Shared Function GetKeyState( _
           ByVal nVirtKey As Integer) As Short
    End Function
 ? Викликати подія при зміні значення
    Private Sub ManageBoolValueChanging( _
                ByVal rowNum As Integer, _
                ByVal colNum As Integer)
        Dim mousePos _
            As Point = Me.DataGridTableStyle.DataGrid.PointToClient( _
                       Control.MousePosition)
        Dim dg As DataGrid = Me.DataGridTableStyle.DataGrid
        Dim isClickInCell As Boolean = _
            Control.MouseButtons = MouseButtons.Left AndAlso _
            dg.GetCellBounds(dg.CurrentCell).Contains(mousePos)
        Dim changing As Boolean = _
            dg.Focused AndAlso isClickInCell _
            OrElse GetKeyState(VK_SPACE) < 0 ? Або пробіл
        If Not lockValue AndAlso _
                  beingEdited AndAlso _
                  changing AndAlso _
                  saveRow = rowNum Then
            saveValue = Not saveValue
            lockValue = False
 ? Викликати подія
            Dim e As New BoolValueChangedEventArgs( _
                          rowNum, colNum, saveValue)
            RaiseEvent BoolValueChanged(Me, e)
        End If
        If saveRow = rowNum Then
            lockValue = False
        End If
    End Sub ?ManageBoolValueChanging
End Class

Використання ClickableBooleanColumn

Щоб застосовувати спеціальний стиль стовпця, створіть його примірник при налаштування columnstyle для discontinuedCol. Також додайте обробник нового події BoolValueChanged.

? Рядок з відомостями про товар, випуск якого припинений
Dim discontinuedCol As New ClickableBooleanColumn()
discontinuedCol.MappingName = "Discontinued"
discontinuedCol.HeaderText = ""
discontinuedCol.Width = 30 ? Відключити підтримку третього стану прапорця
discontinuedCol.AllowNull = False 
AddHandler discontinuedCol.BoolValueChanged, _
AddressOf BoolCellClicked
tableStyle.GridColumnStyles.Add(discontinuedCol)

Обробник події BoolValueChanged

У цьому обробнику події ініціюється перемальовування рядки з урахуванням нового значення в комірці. При кожній зміні булева значення обробник події оголошує недійсним прямокутник, пов’язаний з рядком. Щоб перемалювати рядок, використовується допоміжний метод RefreshRow. В обробнику події збереження булева значення ініціюється закінченням редагування. Це дозволяє методу FormatGridRow отримати правильне значення булевої осередки. Без цього виклику в індексованому DataGrid доступно тільки первісне значення комірки.

Private Sub BoolCellClicked(ByVal sender As Object, _
            ByVal e As BoolValueChangedEventArgs)
    Dim g As DataGrid = Me.DataGrid1
    Dim discontinuedCol As New _
         ClickableBooleanColumn = _ 
         g.TableStyles(0).GridColumnStyles("Discontinued")  
    g.EndEdit(discontinuedCol, e.Row, False)
    RefreshRow(e.Row)
    g.BeginEdit(discontinuedCol, e.Row)
End Sub 
 ? Ініціює перемальовування цього рядка
Private Sub RefreshRow(ByVal row As Integer)
    Dim r As Rectangle = _
        Me.dataGrid1.GetCellBounds(row, 0)
    r = New Rectangle(r.Right, r.Top, _
        Me.dataGrid1.Width, r.Height)
   Me.dataGrid1.Invalidate(r)
End Sub ?RefreshRow

Залишилася остання проблема, після рішення якої осередок буде реагувати на клацання як годиться, а рядок міняти колір. При натисканні на невідміченим прапорці в рядку, відмінною від поточної, рядок набуває колір поточної замість кольору, яким виділяються комірки з товаром, випуск якого припинений. Більше того, схоже, що проблема пов’язана з оновленням, так як при перетягуванні сітки по екрану рядок перемальовується так, як і повинно бути, – сірим кольором. Вирішити цю проблему можна, ініціюючи перемальовування рядки в існуючому обробнику dataGrid1_Click. У цій події виконується код, що змінює булево значення і оновлюючий рядок.

If afterCurrentCellChanged AndAlso _
   hti.Row < bmb.Count AndAlso _
   hti.Type = DataGrid.HitTestType.Cell AndAlso _
   hti.Column = 0 Then
        Me.DataGrid1(hti.Row, 0) = _
            Not CBool(Me.DataGrid1(hti.Row, 0))
        RefreshRow(hti.Row)
End If

6. Додавання спливаючих підказок, зміст яких залежить від конкретної рядки

За допомогою Windows Forms-класу ToolTip можна додати спливаючі підказки до всього DataGrid. Для цього перетягніть компонент ToolTip на форму і настройте властивість ToolTip, яке додається до кожного елементу управління. Однак подібна реалізація спливаючих підказок не дозволяє змінювати підказку при переміщенні від рядка до рядка (або від осередку до осередку) в DataGrid. Наша задача – виводити підказку, різну для різних рядків. Тим же способом можна було б реалізувати підказки, різні для різних осередків або стовпців.

Почнемо з додавання до класу форми двох полів: hitRow і ToolTip1. Ці поля ініціалізувалися в Form1_Load і динамічно змінюються в доданому обробнику події dataGrid1_MouseMove. В MouseMove перевіряється, чи знаходиться курсор миші над новим рядком, і, якщо так, в ToolTip записується інший текст. Див наступний приклад коду:

    Private hitRow As Integer
    Private toolTip1 As System.Windows.Forms.ToolTip
 ? В Form1_Load
    Me.hitRow = -1
    Me.toolTip1 = New ToolTip()
    Me.toolTip1.InitialDelay = 300
    AddHandler Me.DataGrid1.MouseMove, _
          AddressOf dataGrid1_MouseMove
Private Sub dataGrid1_MouseMove( _
        ByVal sender As Object, _
        ByVal e As MouseEventArgs)
   Dim g As DataGrid = Me.dataGrid1
   Dim hti As DataGrid.HitTestInfo = _
              g.HitTest(New Point(e.X, e.Y))
   Dim bmb As BindingManagerBase = _
            Me.BindingContext(g.DataSource, g.DataMember)
   If hti.Row < bmb.Count AndAlso _
      hti.Type = DataGrid.HitTestType.Cell AndAlso _
      hti.Row <> hitRow Then
       If Not (Me.toolTip1 Is Nothing) AndAlso _
            Me.toolTip1.Active Then Me.toolTip1.Active = False? вимкнути
       End If
       Dim tipText As String = ""
       If CBool(Me.DataGrid1(hti.Row, 0)) Then
           tipText = Me.DataGrid1(hti.Row, 2).ToString() & _
                " discontinued"
           If tipText <> "" Then ? Новий рядок
                hitRow = hti.Row
                Me.toolTip1.SetToolTip(Me.DataGrid1, tipText) ? Зробити підказку активної, щоб вона могла показуватися
                Me.toolTip1.Active = True 
            Else
                hitRow = -1
            End If
        Else
            hitRow = -1
        End If
    End If
End Sub ?dataGrid1_MouseMove

Рис. 8. DataGrid з спливаючими підказками, різними для різних рядків

Висновок

В кінцевому DataGrid для форматування комірки і відстеження змін булевого значення використовуються два похідних DataGridColumnStyles. Форматування здійснюється за допомогою перевизначення методу Paint похідного класу і виклику події, дозволяє приймачу передавати інформацію про форматування в Залежно від номерів рядка та стовпця. Похідний клас, керуючий булевими змінними, також перевантажує метод Paint і викликає подія при зміні значення на цьому етапі. Крім того, для вирішення цієї завдання потрібно перевизначити методи Edit і Commit.

Для управління поведінкою DataGrid використовуються та події рівня форми. За допомогою події DataGrid.CurrentCellChanged поточної осередком стає конкретна осередок в рядку незалежно від того, на який осередку клацнув користувач. Завдяки події DataGrid.Clicked стовпець CheckBox реагує на перший клацання; щоб змінити його значення, другий клацання не потрібно. Нарешті, подія DataGrid.MouseMove використовується для динамічної зміни спливаючих підказок від рядка до рядка.

У матеріалах для скачування (див. посилання на початку статті) містяться два приклади. Перший, CustomGrid, включає проект VB.NET, фрагменти з якої наведені в цій статті. Другий, CustomGridA, містить два проекту: один – на VB.NET, інший – на C #. Останній складніше, ніж обговорюваний тут, але в основному реалізує той же підхід.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*