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

时间:2016-07-06 12:43:34

标签: c# .net winforms listbox updates

我正在使用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:

GUI

2 个答案:

答案 0 :(得分:2)

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

为了让控件看到列表项的更改而不是匿名类型,您需要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<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];
        }
    }
}

结果:

enter image description here

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

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

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

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

// 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更改时自行更新。

最后,您可能需要考虑使用simple UserControl用户界面,而不是看似所有者绘制的Listbox

答案 1 :(得分:0)

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

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

根据In WPF, what is the equivalent of Suspend/ResumeLayout() and BackgroundWorker() from Windows Forms

的建议