如何使用计时器更新列表框项目

How to update list box items with a timer

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

定时器代码:

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();

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

这是 GUI:

如果您不想更改代码结构来消除重复的 Clear/Reload 循环,您应该在重建列表时暂停 UI 绘制;

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

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

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

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

您还需要找到每个用户的具体方法,因此 class 需要某种 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);
    }

}

然后collection:

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<User> 然后会通知控件更新显示:

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() 每次调用它时都会创建一个新的 object/image,您的代码不会显示旧的 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 class 在 Status 更改时自行更新。

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