Короткий огляд

У даній статті пояснюється, як налаштувати зовнішній вигляд і поведінка
елемента управління 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>

*

*