如果元素已更改,如何更新列表框 C#

How to update a ListBox if an element was changed c#

嗨,

我在使用 ListBox.DataSource 和 INotifyPropertyChanged 接口时遇到了一些困难。我已经检查了几个关于这个问题的帖子,但我无法弄清楚,如果绑定的 BindingList 的元素发生更改,如何更新 ListBox 的视图。

我基本上想在解析内容后更改 IndexItem 的颜色。

这里是我表格中的相关调用:

btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);

private void btn_indexAddItem_Click(object sender, EventArgs e)
{
    Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
    IndexItem item = lst_index.Items[e.Index] as IndexItem;
    if (item != null)
    {
        e.DrawBackground();
        SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
        e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
        e.DrawFocusRectangle();
    }
}

Indexer.cs:

class Indexer
{
    public BindingList<IndexItem> Items { get; }
    private object SyncItems = new object();

    public Indexer()
    {
        Items = new BindingList<IndexItem>();
    }

    public void AddSingleURL(string url)
    {
        IndexItem item = new IndexItem(url);
        if (!Items.Contains(item))
        {
            lock (SyncItems)
            {
                Items.Add(item);
            }

            new Thread(new ThreadStart(() =>
            {
                // time consuming parsing
                Thread.Sleep(5000);
                string content = item.Url;

                lock (SyncItems)
                {
                    Items[Items.IndexOf(item)].Content = content;
                }
            }
            )).Start();
        }
    }
}

IndexItem.cs

class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
    public int Key { get; }
    public string Url { get; }
    public bool hasContent { get { return (_content != null); } }

    private string _content;
    public string Content {
        get
        {
            return (hasContent) ? _content : "empty";
        }
        set
        {
            _content = value;
            ContentChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void ContentChanged()
    {
        if (this.PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("Content"));
        }
    }

    public IndexItem(string url)
    {
        this.Key = url.GetHashCode();
        this.Url = url;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as IndexItem);
    }
    public override int GetHashCode()
    {
        return Key;
    }
    public bool Equals(IndexItem other)
    {
        if (other == null) return false;
        return (this.Key.Equals(other.Key)) ||
            ((hasContent || other.hasContent) && (this._content.Equals(other._content)));
    }
    public override string ToString()
    {
        return Url;
    }
}

知道哪里出了问题以及如何解决吗?我会很感激任何提示...

在我看来,控件在引发该项目的 ListChanged 事件时应该重绘。这将迫使它这样做:

lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;

private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
    lst_index.Invalidate(); // Force the control to redraw when any elements change
}

那么它为什么不这样做呢?好吧,如果 DisplayMember 都发生了变化,并且如果 INotifyPropertyChanged 事件是从 UI 线程引发的,那么列表框似乎只会调用 DrawItem。所以这也有效:

lock (SyncItems)
{
    // Hacky way to do an Invoke
    Application.OpenForms[0].Invoke((Action)(() =>
    {
        Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
        Items[Items.IndexOf(item)].Content = content;
    }));
}

请注意,在 Url 上调用 PropertyChanged 是不够的。该值必须实际更改。这告诉我列表框正在缓存这些值。 :-(

(使用 VS2015 REL 测试)