如何使ListBox刷新其项目文本?

45

我正在为一个还没有意识到类似 ListBox 控件不必只包含字符串的人做一个示例;他一直在存储格式化字符串,并且通过复杂的解析过程来获取数据。我想向他展示有更好的方法。

我注意到,如果我在 ListBox 中存储了一个对象,然后更新一个影响 ToString 的值,ListBox 不会自动更新。我尝试在控件上调用 RefreshUpdate 方法,但都不起作用。这是我使用的示例代码,它需要你将一个 listbox 和一个 button 拖放到窗体上:

Public Class Form1

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        MyBase.OnLoad(e)

        For i As Integer = 1 To 3
            Dim tempInfo As New NumberInfo()
            tempInfo.Count = i
            tempInfo.Number = i * 100
            ListBox1.Items.Add(tempInfo)
        Next
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        For Each objItem As Object In ListBox1.Items
            Dim info As NumberInfo = DirectCast(objItem, NumberInfo)
            info.Count += 1
        Next
    End Sub
End Class

Public Class NumberInfo

    Public Count As Integer
    Public Number As Integer

    Public Overrides Function ToString() As String
        Return String.Format("{0}, {1}", Count, Number)
    End Function
End Class

我原本认为问题在于使用了字段,因此尝试实现INotifyPropertyChanged接口,但这并没有起作用。(之所以使用字段是因为这只是一个例子,我不想添加几十行与我正在演示的主题无关的代码。)

老实说,我以前从未尝试过像这样直接更新项目;过去我总是添加/删除项目,而不是编辑它们。所以我从来没有注意到我不知道如何使它正常工作。

那么我错过了什么?

13个回答

35

当我需要一个更新的列表框时,我使用这个类。

更新列表中的对象,然后根据是否有索引调用其中任意一个方法。如果要更新的对象包含在列表中但是你没有索引,你需要调用RefreshItems并更新所有项。

public class RefreshingListBox : ListBox
{
    public new void RefreshItem(int index)
    {
        base.RefreshItem(index);
    }

    public new void RefreshItems()
    {
        base.RefreshItems();
    }
}

1
请注意,仅当设置了“DisplayMember”属性时,“RefreshItem”才起作用。 - Olivier Jacot-Descombes

30
lstBox.Items[lstBox.SelectedIndex] = lstBox.SelectedItem;

5
这实际上是一个有效的答案! - nawfal
3
我支持这个!简单易行,无需添加任何新的类或代码段。这就是我所需要的。 - Rob
3
请注意,如果您通过“DataSource”属性设置项目,则此方法不起作用。 - Olivier Jacot-Descombes
愚蠢简单。非常好的答案。 - Tyler Montney

25

BindingList 能够自行处理绑定更新。

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace TestBindingList
{
    public class Employee
    {
        public string Name { get; set; }
        public int Id { get; set; }
    }

    public partial class Form1 : Form
    {
        private BindingList<Employee> _employees;

        private ListBox lstEmployees;
        private TextBox txtId;
        private TextBox txtName;
        private Button btnRemove;

        public Form1()
        {
            InitializeComponent();

            FlowLayoutPanel layout = new FlowLayoutPanel();
            layout.Dock = DockStyle.Fill;
            Controls.Add(layout);

            lstEmployees = new ListBox();
            layout.Controls.Add(lstEmployees);

            txtId = new TextBox();
            layout.Controls.Add(txtId);

            txtName = new TextBox();
            layout.Controls.Add(txtName);

            btnRemove = new Button();
            btnRemove.Click += btnRemove_Click;
            btnRemove.Text = "Remove";
            layout.Controls.Add(btnRemove);

            Load+=new EventHandler(Form1_Load);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            _employees = new BindingList<Employee>();
            for (int i = 0; i < 10; i++)
            {
                _employees.Add(new Employee() { Id = i, Name = "Employee " + i.ToString() }); 
            }

            lstEmployees.DisplayMember = "Name";
            lstEmployees.DataSource = _employees;

            txtId.DataBindings.Add("Text", _employees, "Id");
            txtName.DataBindings.Add("Text", _employees, "Name");
        }

        private void btnRemove_Click(object sender, EventArgs e)
        {
            Employee selectedEmployee = (Employee)lstEmployees.SelectedItem;
            if (selectedEmployee != null)
            {
                _employees.Remove(selectedEmployee);
            }
        }
    }
}

1
我认为你可以进一步改进它。您可以将父子绑定应用于控件,这意味着您可以不使用_SelectedIndexChanged事件处理程序。但是我忘记了确切的代码..... :( - Quibblesome
我已经寻找这样的东西多年了。非常感谢你,给你很多Karma分! - redcalx
1
我一直在使用普通的List,但我认为那不是问题所在,一直在其他地方寻找问题。谢谢! - adam_0
这个例子对我不起作用,列表框没有显示更新的值(我只是复制/粘贴了你的代码)...发生了什么? - Francesco Belladonna
3
据我所知,这并没有解决实际更新所列项目文本的问题。 - JonC
显示剩余3条评论

16
typeof(ListBox).InvokeMember("RefreshItems", 
  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod,
  null, myListBox, new object[] { });

我发现这很有用:它可以用来使列表框动态刷新。 - John Kroetch

10

使用datasource属性和BindingSource对象,将数据源与列表框的datasource属性之间连接。然后刷新它。

更新:添加示例。

如下所示:

Public Class Form1

    Private datasource As New List(Of NumberInfo)
    Private bindingSource As New BindingSource

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        MyBase.OnLoad(e)

        For i As Integer = 1 To 3
            Dim tempInfo As New NumberInfo()
            tempInfo.Count = i
            tempInfo.Number = i * 100
            datasource.Add(tempInfo)
        Next
        bindingSource.DataSource = datasource
        ListBox1.DataSource = bindingSource
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        For Each objItem As Object In datasource
            Dim info As NumberInfo = DirectCast(objItem, NumberInfo)
            info.Count += 1
        Next
        bindingSource.ResetBindings(False)
    End Sub
End Class

Public Class NumberInfo

    Public Count As Integer
    Public Number As Integer

    Public Overrides Function ToString() As String
        Return String.Format("{0}, {1}", Count, Number)
    End Function
End Class

很好。不知为何,在WinForms中进行数据绑定时,无论我在WPF中使用多少次它,它似乎从未成为我的解决方案。 - OwenP
哈哈,以前比现在更有趣。类似这样:((CurrencyManager)this.BindingContext[ListBox1]).Refresh();从BindingContext中获取一个“隐藏”的对象,然后将其转换为货币管理器。 虽然这是C#,但我从未在VB.NET中这样做过。 - Quibblesome
这是一个很好的答案,但最终geno建议使用BindingList<T>会减少工作量。 - OwenP

2
如果您从ListBox派生,那么可以调用RefreshItem受保护方法。只需在自己的类型中重新公开此方法即可。
public class ListBox2 : ListBox {
    public void RefreshItem2(int index) {
        RefreshItem(index);
    }
}

然后修改你的设计文件,使用你自己的类型(在这种情况下,是ListBox2)。


0
如果您使用绘制方法,例如:
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
    e.DrawBackground();
    e.DrawFocusRectangle();

    Sensor toBeDrawn = (listBox1.Items[e.Index] as Sensor);
    e.Graphics.FillRectangle(new SolidBrush(toBeDrawn.ItemColor), e.Bounds);
    e.Graphics.DrawString(toBeDrawn.sensorName, new Font(FontFamily.GenericSansSerif, 14, FontStyle.Bold), new SolidBrush(Color.White),e.Bounds);
}

传感器是我的类。

所以,如果我在某个地方更改了类Color,您可以简单地将其更新为:

int temp = listBoxName.SelectedIndex;
listBoxName.SelectedIndex = -1;
listBoxName.SelectedIndex = temp;

而且颜色会更新,这只是另一种解决方案 :)


0
如果你正在进行数据绑定,请尝试这个:
private void CheckBox_Click(object sender, EventArgs e)
{
    // some kind of hack to make the ListBox refresh
    int currentPosition = bindingSource.Position;
    bindingSource.Position += 1;
    bindingSource.Position -= 1;
    bindingSource.Position = currentPosition;
}

在这种情况下,有一个复选框可以更新数据绑定的 ListBox 中的项目。来回切换绑定源的位置对我来说似乎是有效的。

0

我在VBnet中编写了一些代码来帮助实现这个功能。anObject类重写了ToString以显示对象的“标题/名称”。

Dim i = LstBox.SelectedIndex
LstBox.Items(i) = anObject
LstBox.Sorted = True

0

这可能有点不太专业,但它能用。 我只是删除并添加了该项(然后再次选择它)。 列表根据“显示和更改”属性进行排序,所以对我来说还好。副作用是会触发其他事件(索引已更改)。

if (objLstTypes.SelectedItem != null)
{
 PublisherTypeDescriptor objType = (PublisherTypeDescriptor)objLstTypes.SelectedItem;
 objLstTypes.Items.Remove(objType);
 objLstTypes.Items.Add(objType);
 objLstTypes.SelectedItem = objType;
}

这将始终将所选项目放置在ListBox的末尾! - Olivier Jacot-Descombes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接