【问题标题】:How to update list box items with a timer如何使用计时器更新列表框项
【发布时间】:2016-07-06 12:43:34
【问题描述】:

我正在开发一个信使程序,我有一个计时器,它不断删除和添加新的列表框项目,因此列表框一直闪烁。我试图让闪烁停止。我不断删除和添加新列表框项目的原因是,如果朋友登录,它会将那里的状态从离线更改为在线。

定时器代码:

private void Requests_Tick(object sender, EventArgs e)
{
      LoadData();
}

LoadData() 代码:

FriendsLb.BeginUpdate();
_S = new Status();
Image Status = null;
FriendsLb.Items.Clear();
try
{
    var query = from o in Globals.DB.Friends
                where o.UserEmail == Properties.Settings.Default.Email
                select new
                {
                    FirstName = o.FirstName,
                    LastName = o.LastName,
                    Email = o.Email,
                    Status = o.Status,
                    Display = string.Format("{0} {1} - ({2})", o.FirstName, o.LastName, o.Email)
                };
    newFriendsLb.DataSource = query.ToList();
    newFriendsLb.ClearSelected();
    FriendsLb.DrawMode = DrawMode.OwnerDrawVariable;

    foreach (object contact in query.ToList())
    {
        string details = contact.GetType().GetProperty("Display").GetValue(contact, null).ToString();
        string email = contact.GetType().GetProperty("Email").GetValue(contact, null).ToString();
        string status = _S.LoadStatus(email);

        if (status == "Online")
        {
            Status = Properties.Resources.online;
        }
        else if (status == "Away")
        {
            Status = Properties.Resources.busy;
        }
        else if (status == "Busy")
        {
            Status = Properties.Resources.away;
        }
        else if (status == "Offline")
        {
            Status = Properties.Resources.offline;
        }
        FriendsLb.Items.Add(new Listbox(_A.LoadFriendAvatar(email), Status, details));
    }
    contact = query.ToList();
    FriendsLb.MeasureItem += FriendsLb_MeasureItem;
    FriendsLb.DrawItem += FriendsLb_DrawItem;
    FriendsLb.EndUpdate();

有没有办法不断更新当前列表框项而不是不断删除和添加新项?

这是图形用户界面:

【问题讨论】:

  • 为什么不对Status 属性使用事件处理程序。这样只有在发生变化时才会调用更新?
  • 您不只是添加和删除 - 您正在清除列表并每隔一段时间完全重新填充它。
  • 您可以检测更改并仅更新受影响的项目,而不是完全重绘查询。为此,您需要一些关键字段(例如电子邮件?它是唯一的吗?)。不要Clear(),而是检查项目并更新它们。
  • 这就是问题所在,除了删除和重新填充它们之外,我真的不知道如何更新它们:/
  • a) 将 BindingList 与具体类一起使用。对列表内容的更改将显示在控件中。 B)也许实现 INotifyPropertyChanged C)然后只更新列表 D)我认为您想将这些状态指示器图像加载到数组中 - 您的应用程序可能会泄漏

标签: c# .net winforms listbox updates


【解决方案1】:

有几种消除闪烁的方法 - 基本上都涉及不完全重新填充每次列表。为此,您希望获取用户的当前状态并简单地更新现有列表。

为了让控件看到对列表项的更改,而不是匿名类型,您需要一个User 类,以便您可以实现INotifyPropertyChanged。这“广播”了属性值已更改的通知。您还需要使用BindingList<T>,以便将这些消息转发到控件。这也将允许反映列表中的添加/删除。

您还需要一种具体的方法来查找每个用户,因此该类将需要某种 ID。

public enum UserStatus { Unknown, Online, Offline, Away, Busy }

class User : INotifyPropertyChanged 
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Image StatusImage;

    private UserStatus status = UserStatus.Unknown;
    public UserStatus Status 
    { 
        get{return status;}
        set{
            if (value != status)
            {
                status=value;
                PropertyChanged(this, new PropertyChangedEventArgs("Status"));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public override string ToString()
    {
         return string.Format("{0}, {1}: {2}", LastName, FirstName, Status);
    }

}

然后是集合:

private BindingList<User> Users;
private Image[] StatusImgs;          // See notes

BindingList 然后用作控件的数据源:

Users = GetUserList();

// display the list contents in the listbox:
lbUsers.DataSource = Users;
timer1.Enabled = true;

更新用户状态只涉及重置每个已更改用户的状态。然后BindingList&lt;User&gt;会通知控件更新显示:

private void UpdateUserStatus()
{
    // get current list of user and status
    var newStatus = GetCurrentStatus();
    User thisUser;

    // find the changed user and update
    foreach (User u in newStatus)
    {          
        thisUser = Users.FirstOrDefault(q => q.Id == u.Id);
        // ToDo: If null, there is a new user in the list: add them.
        if (thisUser != null && thisUser.Status != u.Status)
        { 
            thisUser.Status = u.Status;
            thisUser.StatusImage = StatusImgs[(int)u.Status];
        }
    }
}

结果:


请注意,您的应用可能存在泄漏。如果您深入代码以从Resources 获取图像,您将看到:

internal static System.Drawing.Bitmap ball_green {
    get {
        object obj = ResourceManager.GetObject("ball_green", resourceCulture);
        return ((System.Drawing.Bitmap)(obj));
    }
}

GetObject() 每次调用它时都会创建一个新的对象/图像,您的代码不会显示旧的对象/图像是 Disposed() 因此,它可能会泄漏资源。

由于每个在线用户不需要自己的唯一实例(或状态变化时的新实例),因此将它们加载到列表或数组中,以便重复使用:

// storage:
private Image[] StatusImgs;
...
// populate:
StatusImgs = new Image[] {Resources.ball_black, Resources.ball_green, 
            Resources.ball_red, Resources.ball_yellow, Resources.ball_delete};
...
// usage:
thisUser.StatusImage = StatusImgs[(int)u.Status];

您也可以更改它,以便 User 类在 Status 更改时自行更新。

最后,您可能需要考虑为 UI 使用 simple UserControl,而不是看起来像是所有者绘制的 Listbox

【讨论】:

  • @richardj97 答案正是您所需要的,它为您提供了许多新的有用的东西,这将帮助您朝着正确的方向前进。当您接受答案时,也可以为答案投票。这是合理的和推荐的。看看这个帖子:Accepting Answers: How does it work?
  • 一切似乎都在工作,但我不知道要在计时器中放什么,因为当我尝试它时它不会更新列表框,它只有在我重新打开应用程序时才会更新。
  • 从字面上看,我在演示中所做的只是从计时器事件中调用UpdateUserStatus();。就像我说的。我认为您需要将其实施到ToDo - 一开始列表中可能没有用户,因此需要添加它们。这也允许显示添加到列表中
【解决方案2】:

如果您不想更改代码结构以消除重复的清除/重新加载循环,则应在重建列表时暂停 UI 绘制;

using(var d = Dispatcher.DisableProcessing())
{
    /* your work...  */
}

这里建议In WPF, what is the equivalent of Suspend/ResumeLayout() and BackgroundWorker() from Windows Forms

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-02
    相关资源
    最近更新 更多