带有 SuggestAppend 组合框列和动态单元格分配的 DataGridView

DataGridView with SuggestAppend comboBox columns and dynamic Cell assignment

我一直在做一些研究,但找不到这个问题的答案,可能我没有问正确的问题,或者我正在尝试的可能是不可能的。

那么,假设我将 DataTable 绑定到 DataGridView,是否可以在某些数据源列中使用 SuggestAppend

作为示例(下面的代码),我有一个 DataGridView,其中包含列 EmployeeStatusStatus 列是一个 DataGridViewComboBoxColumn,其中包含项目 ActiveWithdraw。网格有一个 DataTable 作为源,它具有相同的列( 员工和状态 ),我使用它是因为修改后导出数据更容易但是可能有更好的方法。

那么,是否可以让我的 DataTable 使用我为 DataGridView 创建的列?

测试代码:

$mainForm = New-Object System.Windows.Forms.Form
$mainForm.StartPosition = 'CenterScreen'
$mainForm.FormBorderStyle = 'Fixed3D'
$mainForm.Text = 'Test'
$mainForm.WindowState = 'Maximized'

$bounds = ($mainForm.CreateGraphics()).VisibleClipBounds.Size

$dataGrid = New-Object System.Windows.Forms.DataGridView
$dataGrid.Size = New-Object System.Drawing.Size(($bounds.Width-20),($bounds.Height-140))
$dataGrid.Location = New-Object System.Drawing.Size(10,60)
$dataGrid.AllowUserToAddRows = $true
$dataGrid.SelectionMode = 0
$dataGrid.MultiSelect = $true
$dataGrid.ReadOnly = $false
$dataGrid.RowHeadersVisible = $false
$dataGrid.ColumnHeadersBorderStyle = 2
$dataGrid.EnableHeadersVisualStyles = $true

$col1 = New-Object System.Windows.Forms.DataGridViewComboBoxColumn
$col1.Name = 'Status'
$col1.HeaderText = 'Status'
$col1.Items.AddRange('Active','Withdraw')

$dataGrid.Columns.Add([System.Windows.Forms.DataGridViewColumn],'Employee')
$dataGrid.Columns.Add($col1)

$dataGrid.Add_EditingControlShowing({
    
    $box = $_.Control -as [System.Windows.Forms.ComboBox]
    
    if($box)
    {
        $dataGrid.EditingControl.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDown
        $dataGrid.EditingControl.AutoCompleteMode = [System.Windows.Forms.AutoCompleteMode]::SuggestAppend
    }
})

$dataGrid.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill
#$dataGrid.Columns[-1].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill

$mainForm.Controls.Add($dataGrid)

#### Begin of DataTable as Source here

$source = @'
Employee,Status
user.example1,Active
user.example2,Withdraw
user.example3,Withdraw
user.example4,Active
'@ | ConvertFrom-Csv

$columns = $source[0].PSobject.Properties.Name

$table = New-Object System.Data.DataTable

foreach($column in $columns)
{
    $i = New-Object System.Data.DataColumn
    $i.DataType = [string]
    $i.ColumnName = $column
    $table.Columns.Add($i)
}

foreach($line in $source)
{
    $row = $table.NewRow()   
    foreach($column in $columns)
    {
        $row.$column = $line.$column
    }
    $table.Rows.Add($row)
}

$dataGrid.DataSource = $table

$mainForm.Add_Shown({ $mainForm.Activate() })
$mainForm.ShowDialog()

感谢 @JohnG's 指导,我得到了这个工作,如果这对你有用,谢谢他而不是我。

示例代码中包含什么?

  • DataTable 绑定到 DataGridView
  • DataGrid ColumnsDataTable 列配对
  • DataGridViewComboBoxColumnDropDownSuggestAppend
  • 动态单元格分配,理论上可以使用 DataColumn's 表达式完成,但我无法在 PowerShell 上获得 IIF 条件,所以我使用 .Add_CellEndEdit 事件来更新DataBound 项。即:$this.CurrentRow.DataBoundItem['Code'] = '1'

示例屏幕截图

代码片段

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName PresentationFramework

#### Begin of DataTable as Source here

$source = @'
Employee,Status,Code
user.example1,Active,1
user.example2,Withdraw,0
user.example3,Withdraw,0
user.example4,Active,1
user.example5,Withdraw,0
user.example6,Active,1
'@ | ConvertFrom-Csv

$columns = $source[0].PSobject.Properties.Name

$table = New-Object system.Data.DataTable

$col = New-Object System.Data.DataColumn
$col.DataType = [string]
$col.ColumnName = 'Employee'
$table.Columns.Add($col)

$col = New-Object System.Data.DataColumn
$col.DataType = [string]
$col.ColumnName = 'Status'
$table.Columns.Add($col)

$col = New-Object System.Data.DataColumn
$col.DataType = [string]
$col.ColumnName = 'Code'
$table.Columns.Add($col)

foreach($line in $source)
{
    $row = $table.NewRow()
    foreach($column in $columns)
    {
        $row.$column = $line.$column
    }
    $table.Rows.Add($row)
}

##########

$mainForm = New-Object System.Windows.Forms.Form
$mainForm.StartPosition = 'CenterScreen'
$mainForm.FormBorderStyle = 'Fixed3D'
$mainForm.Text = 'Test'
$mainForm.WindowState = 'Maximized'

$bounds = ($mainForm.CreateGraphics()).VisibleClipBounds.Size

$dataGrid = New-Object System.Windows.Forms.DataGridView
$dataGrid.Size = New-Object System.Drawing.Size(($bounds.Width-20),($bounds.Height-140))
$dataGrid.Location = New-Object System.Drawing.Size(10,60)
$dataGrid.AllowUserToAddRows = $false
$dataGrid.SelectionMode = 4
$dataGrid.MultiSelect = $true
$dataGrid.ReadOnly = $false
$dataGrid.RowHeadersVisible = $false
$dataGrid.ColumnHeadersBorderStyle = 2
$dataGrid.EnableHeadersVisualStyles = $true

# Pairing DataPropertyName with DataTable's column Names
$col0 = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
$col0.HeaderText = 'Employee'
$col0.DataPropertyName = 'Employee'
$col0.SortMode = 'NotSortable'
$dataGrid.Columns.Add($col0)

$col1=New-Object System.Windows.Forms.DataGridViewComboBoxColumn
$col1.HeaderText = 'Status'
$col1.DataPropertyName = 'Status'
# Using DataTable 'Status' column unique values as DataSource
$col1.DataSource = $table.Status | Select-Object -Unique
$col1.SortMode = 'NotSortable'
$dataGrid.Columns.Add($col1)

$col2 = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
$col2.HeaderText = 'Code'
$col2.DataPropertyName = 'Code'
$col2.ReadOnly = $True
$col2.SortMode = 'NotSortable'
$dataGrid.Columns.Add($col2)

$dataGrid.Add_EditingControlShowing({
    
    # Not entirely sure how this works but it works lol
    # Basically the only column that can behave as WinForms ComboBox is
    # DataGridViewComboBoxColumn. When this is True we can enable DropDown and SuggestAppend
    # to the EditingControl. This code is emulated from C# and not sure if it's the right approach
    # but still it works.

    if($_.Control -as [System.Windows.Forms.ComboBox])
    {
        $this.EditingControl.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDown
        $this.EditingControl.AutoCompleteMode = [System.Windows.Forms.AutoCompleteMode]::SuggestAppend
    }
})

$dataGrid.Add_CellEndEdit({

    $row = $this.CurrentRow.DataBoundItem

    # I tried this to work in both ways, meaning, the value of 'Status' would
    # dynamically update the value of 'Code' and vice versa but the DGV gets super buggy
    # Seems like you need 'INotifyPropertyChanged' on the bound DataTable but couldn't
    # figure out how to make it work in PowerShell yet.
    # Source: 

    if($row.Status -eq 'Active')
    {
        $this.CurrentRow.DataBoundItem['Code'] = '1'
    }
    else
    {
        $this.CurrentRow.DataBoundItem['Code'] = '0'
    }
})

$dataGrid.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill

$dataGrid.Add_DataError({

    # Error handling here
    # https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridview.dataerror?view=net-5.0
    $_.Cancel = $true
})

$mainForm.Controls.Add($dataGrid)

$dataGrid.DataSource = $table

$bulkUpdateBtn = New-Object System.Windows.Forms.Button
$bulkUpdateBtn.Size = New-Object System.Drawing.Size(85,30)
$bulkUpdateBtn.Location = New-Object System.Drawing.Size(($dataGrid.Width-74),($dataGrid.Height+70))
$bulkUpdateBtn.Text = "Bulk Update"
$bulkUpdateBtn.Add_Click({
    
    # TO DO

})
$mainForm.Controls.Add($bulkUpdateBtn)

$mainForm.Add_Shown({ $mainForm.Activate() })
$mainForm.ShowDialog()