Datagridview-Sorting. Error on selected row

Ask questions about creating Graphical User Interfaces (GUI) in PowerShell and using WinForms controls.
Forum rules
Do not post any licensing information in this forum.

Any code longer than three lines should be added as code using the 'Select Code' dropdown menu or attached as a file.
This topic is 6 years and 5 months old and has exceeded the time allowed for comments. Please begin a new topic or use the search feature to find a similar but newer topic.
Locked
User avatar
Pharal
Posts: 1
Last visit: Fri Nov 17, 2017 12:57 am

Datagridview-Sorting. Error on selected row

Post by Pharal »

Greetings,
I have recently made a search function for some users and while it works, it throws an error when sorting the columns in the datagridview.
It is a DataTable source I am using.
The problem seems to be that the SelectionChanged event fires off in response to the sorting. The selected row stays in the same place on the datagrid which means that the item in the same place will be selected after the sorting and for some reason this isn't handled well.
Is there a way to avoid this error? If there is not, is there then a way to deselect the Row? I have tried different methods and can't seem to make it work when inserting into the $datagridviewResults_ColumnHeaderMouseClick.

The error thrown is:

Code: Select all

ERROR: Cannot index into a null array.
test_datagrid.psf (329, 3): ERROR: At Line: 329 char: 3
ERROR: +             $cell = $datagridviewCreateUserResults.CurrentRow.Cells[1 ...
ERROR: +             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ERROR:     + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
ERROR:     + FullyQualifiedErrorId : NullArray
ERROR:
This should be the relevant code:

Code: Select all

$datagridviewResults_ColumnHeaderMouseClick = [System.Windows.Forms.DataGridViewCellMouseEventHandler]{
if($datagridviewCreateUserResults.DataSource -is [System.Data.DataTable])
	{
		
		$column = $datagridviewCreateUserResults.Columns[$_.ColumnIndex]
		$direction = [System.ComponentModel.ListSortDirection]::Ascending
		
		if($column.HeaderCell.SortGlyphDirection -eq 'Descending')
		{
			$direction = [System.ComponentModel.ListSortDirection]::Descending
		}

		$datagridviewCreateUserResults.Sort($datagridviewCreateUserResults.Columns[$_.ColumnIndex], $direction)
	}
}


$datagridviewResults_SelectionChanged = {

		$cell = $datagridviewCreateUserResults.CurrentRow.Cells[1].Value
		$textbox2.Text = $cell
	
	
}
If you need the entire thing, here is the whole code (Not the form code, only the extras):

Code: Select all


$domaincontroller = "domainController01"
$AdDomain = "Domain"
#region Control Helper Functions
function Load-DataGridView
{
	<#
	.SYNOPSIS
		This functions helps you load items into a DataGridView.

	.DESCRIPTION
		Use this function to dynamically load items into the DataGridView control.

	.PARAMETER  DataGridView
		The DataGridView control you want to add items to.

	.PARAMETER  Item
		The object or objects you wish to load into the DataGridView's items collection.
	
	.PARAMETER  DataMember
		Sets the name of the list or table in the data source for which the DataGridView is displaying data.

	.PARAMETER AutoSizeColumns
	    Resizes DataGridView control's columns after loading the items.
	#>
	Param (
			[ValidateNotNull()]
			[Parameter(Mandatory = $true)]
			[System.Windows.Forms.DataGridView]$DataGridView,
			[ValidateNotNull()]
			[Parameter(Mandatory = $true)]
			$Item,
			[Parameter(Mandatory = $false)]
			[string]$DataMember,
			[System.Windows.Forms.DataGridViewAutoSizeColumnMode]$AutoSizeColumns = 'None'
	)
	$DataGridView.SuspendLayout()
	$DataGridView.DataMember = $DataMember
	
	if ($Item -is [System.Data.DataSet] -and $Item.Tables.Count -gt 0)
	{
		$DataGridView.DataSource = $Item.Tables[0]
	}
	elseif ($Item -is [System.ComponentModel.IListSource]`
			-or $Item -is [System.ComponentModel.IBindingList] -or $Item -is [System.ComponentModel.IBindingListView])
	{
		$DataGridView.DataSource = $Item
	}
	else
	{
		$array = New-Object System.Collections.ArrayList
		
		if ($Item -is [System.Collections.IList])
		{
			$array.AddRange($Item)
		}
		else
		{
			$array.Add($Item)
		}
		$DataGridView.DataSource = $array
	}
	
	if ($AutoSizeColumns -ne 'None')
	{
		$DataGridView.AutoResizeColumns($AutoSizeColumns)
	}
	
	$DataGridView.ResumeLayout()
}

function ConvertTo-DataTable
{
	<#
		.SYNOPSIS
			Converts objects into a DataTable.
	
		.DESCRIPTION
			Converts objects into a DataTable, which are used for DataBinding.
	
		.PARAMETER  InputObject
			The input to convert into a DataTable.
	
		.PARAMETER  Table
			The DataTable you wish to load the input into.
	
		.PARAMETER RetainColumns
			This switch tells the function to keep the DataTable's existing columns.
		
		.PARAMETER FilterWMIProperties
			This switch removes WMI properties that start with an underline.
	
		.EXAMPLE
			$DataTable = ConvertTo-DataTable -InputObject (Get-Process)
	#>
	[OutputType([System.Data.DataTable])]
	param (
			[ValidateNotNull()]
			$InputObject,
			[ValidateNotNull()]
			[System.Data.DataTable]$Table,
			[switch]$RetainColumns,
			[switch]$FilterWMIProperties)
	
	if ($null -eq $Table)
	{
		$Table = New-Object System.Data.DataTable
	}
	
	if ($InputObject -is [System.Data.DataTable])
	{
		$Table = $InputObject
	}
	elseif ($InputObject -is [System.Data.DataSet] -and $InputObject.Tables.Count -gt 0)
	{
		$Table = $InputObject.Tables[0]
	}
	else
	{
		if (-not $RetainColumns -or $Table.Columns.Count -eq 0)
		{
			#Clear out the Table Contents
			$Table.Clear()
			
			if ($null -eq $InputObject) { return } #Empty Data
			
			$object = $null
			#find the first non null value
			foreach ($item in $InputObject)
			{
				if ($null -ne $item)
				{
					$object = $item
					break
				}
			}
			
			if ($null -eq $object) { return } #All null then empty
			
			#Get all the properties in order to create the columns
			foreach ($prop in $object.PSObject.Get_Properties())
			{
				if (-not $FilterWMIProperties -or -not $prop.Name.StartsWith('__')) #filter out WMI properties
				{
					#Get the type from the Definition string
					$type = $null
					
					if ($null -ne $prop.Value)
					{
						try { $type = $prop.Value.GetType() }
						catch { }
					}
					
					if ($null -ne $type) # -and [System.Type]::GetTypeCode($type) -ne 'Object')
					{
						[void]$table.Columns.Add($prop.Name, $type)
					}
					else #Type info not found
					{
						[void]$table.Columns.Add($prop.Name)
					}
				}
			}
			
			if ($object -is [System.Data.DataRow])
			{
				foreach ($item in $InputObject)
				{
					$Table.Rows.Add($item)
				}
				return @( ,$Table)
			}
		}
		else
		{
			$Table.Rows.Clear()
		}
		
		foreach ($item in $InputObject)
		{
			$row = $table.NewRow()
			
			if ($item)
			{
				foreach ($prop in $item.PSObject.Get_Properties())
				{
					if ($table.Columns.Contains($prop.Name))
					{
						$row.Item($prop.Name) = $prop.Value
					}
				}
			}
			[void]$table.Rows.Add($row)
		}
	}
	
	return @( ,$Table)
}
#endregion

$formMain_Load={
	do
	{
		
		$script:credential1 = get-credential -credential "$AdDomain\"
		
		Add-Type -AssemblyName System.DirectoryServices.AccountManagement
		if ($script:Credential1 -eq $null) { exit }
		$UserName = $script:Credential1.GetNetworkCredential().username
		$Password = $script:Credential1.GetNetworkCredential().Password
		$Domain = $env:USERDOMAIN
		
		if ($Domain -ne $AdDomain) { exit }
		
		$ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain
		$pc = New-Object System.DirectoryServices.AccountManagement.PrincipalContext $ct, $Domain
		$credentialtest = $pc.ValidateCredentials($UserName, $Password)
		if ($credentialtest -eq $false)
		{
			$i++
			if ($i -eq 3) { exit }
		}
	}
	
	while ($credentialtest -eq $false)
	#add the timer control
	
	$script:timer1 = New-Object System.windows.Forms.Timer
	
	#enable it
	$script:timer1.Interval = 500
	$script:timer1.Enabled = $True
	
	$Mainsession = New-PSSession -ComputerName $domaincontroller -Credential $script:Credential1
	Export-PSSession -Session $Mainsession -module activedirectory -outputmodule AD -force -AllowClobber
	import-module AD
	
    # Make the search field take foreign letters into account with simple wildcard replace
	$SearchName = $SearchName -replace "e", "?" -replace "i", "?" -replace "a", "?" -replace "o", "?" -replace "ae", "?" -replace "n", "?" -replace "æ", "?" -replace "u", "?"
	$script:result = get-aduser -Server $domaincontroller -Credential $script:Credential1 -Filter * -properties displayname, samaccountname, office | select displayname, samaccountname, office 
	
	$dgresult = $result
	$createUserTable = ConvertTo-DataTable -InputObject $dgresult
	Load-DataGridView -DataGridView $datagridviewCreateUserResults -Item $createUserTable -AutoSizeColumns DisplayedCells
	
	$timer1.add_Tick($UpdateUserDatagrid)
}

$UpdateUserDatagrid =
{
	$script:timer1.Stop()
	if ($textbox1.Text -eq "")
	{
		$dgresult = $result
	}
	else
	{
		$SearchName = $textbox1.Text -replace "e", "?" -replace "i", "?" -replace "a", "?" -replace "o", "?" -replace "ae", "?" -replace "n", "?" -replace "æ", "?" -replace "u", "?"
		$dgresult = $script:result | Where-Object { $_.displayname -like "$($textbox1.Text)*" }
		$tempResult = $script:result | Where-Object { $_.displayname -like "$searchname*" }
		if ($tempResult.count -gt 0) { $dgresult += $tempResult }
		$dgresult = $dgresult | select-object * -Unique
		
		if ($dgresult.count -eq 0)
		{ $dgresult = New-Object PSObject -Property @{ Invalid = "No users found with that search" } }
	}
	$createUserTable = ConvertTo-DataTable -InputObject $dgresult
	Load-DataGridView -DataGridView $datagridviewCreateUserResults -Item $createUserTable -AutoSizeColumns DisplayedCells
	$datagridviewCreateUserResults.Refresh()
	
}
$buttonExit_Click={
	#TODO: Place custom script here
	$formMain.Close()
}


$datagridviewResults_ColumnHeaderMouseClick = [System.Windows.Forms.DataGridViewCellMouseEventHandler]{
	
#Event Argument: $_ = [System.Windows.Forms.DataGridViewCellMouseEventArgs]
	if($datagridviewCreateUserResults.DataSource -is [System.Data.DataTable])
	{
		
		$column = $datagridviewCreateUserResults.Columns[$_.ColumnIndex]
		$direction = [System.ComponentModel.ListSortDirection]::Ascending
		
		if($column.HeaderCell.SortGlyphDirection -eq 'Descending')
		{
			$direction = [System.ComponentModel.ListSortDirection]::Descending
		}

		$datagridviewCreateUserResults.Sort($datagridviewCreateUserResults.Columns[$_.ColumnIndex], $direction)
	}
}

$datagridviewResults_SelectionChanged = {

		$cell = $datagridviewCreateUserResults.CurrentRow.Cells[1].Value
		$textbox2.Text = $cell
}

$textbox1_TextChanged={ 
	# Timer to prevent search on every single change to textbox which makes the program unstable and slow. 
    # Will only update if stop typing for 500 ms
	$script:timer1.Stop()
	$script:timer1.Start()
	
}

$datagridview1_CellFormatting=[System.Windows.Forms.DataGridViewCellFormattingEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.DataGridViewCellFormattingEventArgs]
	#TODO: Place custom script here
	
}

User avatar
davidc
Posts: 5913
Last visit: Mon Jul 08, 2019 8:55 am
Been upvoted: 2 times

Re: Datagridview-Sorting. Error on selected row

Post by davidc »

CurrentRow property can be null if there is no selection. The event was triggered because there was a selection and now there is none. Try modifying your code as follows:

Code: Select all

	
if ($datagridviewCreateUserResults.CurrentRow)
{
	$cell = $datagridviewCreateUserResults.CurrentRow.Cells[1].Value
	$textbox2.Text = $cell
}
else
{
	$textbox2.Text = ""
}
David
SAPIEN Technologies, Inc.
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: Datagridview-Sorting. Error on selected row

Post by jvierra »

When posting your code example please post the PSF file as an attachment. What you posted is not useful.
This topic is 6 years and 5 months old and has exceeded the time allowed for comments. Please begin a new topic or use the search feature to find a similar but newer topic.
Locked