如何引用 ListBox 项

How do I reference a ListBox item

有时事情(PowerShell?)对我来说聪明...

在这种情况下,我想更改 WinForms ListBox item selected by its index (not the SelectedItems). The point here is that $ListBox.Items[$CurrentIndex] appears to be a [string] type rather than an [Item] 类型的 Text 属性...
为此,我从我的大项目中创建了一个 mcve(带有上下文菜单):

using namespace System.Windows.Forms
$Form = [Form]@{ StartPosition = 'CenterScreen' }
$ListBox = [ListBox]@{}
@('one', 'two', 'three').ForEach{ $Null = $ListBox.Items.Add($_) }
$Form.Controls.Add($ListBox)
$ListBox.ContextMenuStrip = [ContextMenuStrip]@{}
$Context = $ListBox.ContextMenuStrip.Items.Add('ToUpper')
$ListBox.Add_MouseDown({ param($s, $e) 
    if ($e.Button -eq 'Right') { $Script:CurrentIndex = $ListBox.IndexFromPoint($e.Location) }
})
$Context.Add_Click({ param($s, $e)
    if ($Null -ne $CurrentIndex) {
        $Text = $ListBox.Items[$CurrentIndex]
        Write-Host "I want to change text ""$Text"" of the item #$CurrentIndex to: ""$($Text.ToUpper())"""
        # $ListBox.Items.Item[$CurrentIndex] = $Text.ToUpper()
    }
})
$Form.ShowDialog()

如何更改例如$ListBox.Items[1] ("two") 例如“TWO”?
我实际上不确定这是否是与 PowerShell 相关的问题(类似于 #16878 Decorate dot selected Xml strings (leaves) with XmlElement methods where I would have an option to use the SelectNodes() method) or related to WinForms itself: The answer might be in Gwt Listbox item reference to an Object 在这种情况下我不知道如何将其转换为 PowerShell。

可以 使用 $ListBox.Items.Item($CurrentIndex) = $Text.ToUpper() 修改存储的项目值 - 但就地修改 collection 项目不会触发 re-drawing ListBox 控件,所以看起来没有发生任何变化。

相反,修改 collection 本身 - 删除旧条目并在相同位置插入新条目:

$Context.Add_Click({ param($s, $e)
    if ($Null -ne $CurrentIndex) {
        $Text = $ListBox.Items[$CurrentIndex]
        # Remove clicked item
        $ListBox.Items.RemoveAt($CurrentIndex)
        # Insert uppercased string value as new item at the same index
        $ListBox.Items.Insert($CurrentIndex, $Text.ToUpper())
    }
})

这将导致拥有的控件刷新并且更改将立即反映在 GUI 中

补充

确实,问题不在于修改 .Items 集合本身 - $ListBox.Items[$CurrentIndex] = $Text.ToUpper() 工作正常。[1]

解决真正的问题 - ListBox 没有刷新以响应修改基础 .Items 集合 中的 现有项目 到位 (即使您调用 $ListBox.Refresh() 也不行),还有一种方法可以删除和 re-adding 项目:

您可以使用 数据绑定,方法是将 System.ComponentModel.BindingList`1 实例分配给列表框的 .DataSource 属性。

修改项目到位后,可以调用.ResetBindings()方法让列表框自行刷新,如下图

注:

  • 还有一个.ResetItem(int index)方法用来执行item-specific刷新,但我无法让它工作;关于原因的指示表示赞赏。

  • .ResetBindings()可以改变listbox的滚动状态,这也是下面代码显式保存和恢复的原因。但是,它会自动保留列表框的选择状态(删除和re-adding方法也必须手动管理)。

using namespace System.Windows.Forms
using namespace System.ComponentModel

Add-Type -AssemblyName System.Windows.Forms

$Form = [Form]@{ StartPosition = 'CenterScreen' }
$ListBox = [ListBox]@{}

# Define the items as a strongly typed array of item strings.
[string[]] $items = @('one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight')
# Wrap the array in a [BindingList[string]] instance
# and make it the listbox's data source.
$ListBox.DataSource = [BindingList[string]]::new($items)

$Form.Controls.Add($ListBox)
$ListBox.ContextMenuStrip = [ContextMenuStrip]@{}
$Context = $ListBox.ContextMenuStrip.Items.Add('ToUpper')
$ListBox.Add_MouseDown({ param($s, $e) 
    if ($e.Button -eq 'Right') { $Script:CurrentIndex = $ListBox.IndexFromPoint($e.Location) }
})
$Context.Add_Click({ param($s, $e)
    if ($null -ne $CurrentIndex) {
        # Modify the underlying array.
        $items[$CurrentIndex] = $items[$CurrentIndex].ToUpper()
        # To prevent unwanted scrolling during the refresh below, 
        # save and restore what item is currently shown at the top 
        # of the visible list portion.
        $prevTopIndex = $ListBox.TopIndex 
        # Notify the listbox via the BindingList[string] instance that
        # the items have changed.
        # !! The following *item-specific* call does NOT work.
        # !!   $ListBox.DataSource.ResetItem($CurrentIndex)
        $ListBox.DataSource.ResetBindings()
        $ListBox.TopIndex = $prevTopIndex # Restore the scrolling state.
    }
})
$Form.ShowDialog()

[1] 一般语法观察:冗长的替代方法是显式调用实现参数化属性 提供 index-based 访问权限:$ListBox.Items.Item($CurrentIndex)。这适用于任何 IList-实现集合类型,包括数组。但是,通常不需要这样做,因为 PowerShell 通过 直接索引 显示此类参数化属性。例如,给定数组 $a = 'foo', 'bar'$a.Item(1) 更自然地表示为 $a[1]