views:

167

answers:

2

I'm trying to build a simple music player with a ListBox playlist. When adding audio files to the playlist, it first fills the ListBox with the filenames and then (on a separate thread) extracts the ID3 data and overwrites the filenames with the correct Artist - Title information (much like Winamp).

But while the ListBox is being updated, it's unscrollable, as it always jumps to the top on every item overwrite.

Any way to prevent this?

EDIT:
The code:

public Form1()
{
    //Some initialization code omitted here

    BindingList<TAG_INFO> trackList = new BindingList<TAG_INFO>();

    // The Playlist
    this.playlist = new System.Windows.Forms.ListBox();
    this.playlist.Location = new System.Drawing.Point(12, 12);
    this.playlist.Name = "playlist";
    this.playlist.Size = new System.Drawing.Size(229, 316);
    this.playlist.DataSource = trackList;
}

private void playlist_add_Click(object sender, EventArgs e)
{
    //Initialize OpenFileDialog
    OpenFileDialog opd = new OpenFileDialog();
    opd.Filter = "Music (*.WAV; *.MP3; *.FLAC)|*.WAV;*.MP3;*.FLAC|All files (*.*)|*.*";
    opd.Title = "Select Music";
    opd.Multiselect = true;

    //Open OpenFileDialog
    if (DialogResult.OK == opd.ShowDialog())
    {

        //Add opened files to playlist
        for (int i = 0; opd.FileNames.Length > i; ++i)
        {
            if (File.Exists(opd.FileNames[i]))
            {
                trackList.Add(new TAG_INFO(opd.FileNames[i]));
            }
        }

        //Initialize BackgroundWorker
        BackgroundWorker _bw = new BackgroundWorker();
        _bw.WorkerReportsProgress = true;
        _bw.DoWork += new DoWorkEventHandler(thread_trackparser_DoWork);
        _bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged);

        //Start ID3 extraction
        _bw.RunWorkerAsync();
    }

}

void thread_trackparser_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker _bw = sender as BackgroundWorker;

    for (int i = 0; i < trackList.Count; ++i)
    {
        //Pass extracted tag info to _bw_ProgressChanged for thread-safe playlist entry update
        _bw.ReportProgress(0,new object[2] {i, BassTags.BASS_TAG_GetFromFile(trackList[i].filename)});
    }
}

void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    object[] unboxed = e.UserState as object[];

    trackList[(int)unboxed[0]] = (unboxed[1] as TAG_INFO);
}

EDIT2:
Much simpler test case:
Try scrolling down without selecting an item. The changing ListBox will scroll to the top again.

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class Form1 : Form
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.listBox1 = new System.Windows.Forms.ListBox();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.SuspendLayout();

            // listBox1
            this.listBox1.FormattingEnabled = true;
            this.listBox1.Location = new System.Drawing.Point(0, 0);
            this.listBox1.Name = "listBox1";
            this.listBox1.Size = new System.Drawing.Size(200, 290);

            // timer1
            this.timer1.Enabled = true;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);

            // Form1
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(200, 290);
            this.Controls.Add(this.listBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
        }

        private System.Windows.Forms.ListBox listBox1;
        private System.Windows.Forms.Timer timer1;

        public Form1()
        {
            InitializeComponent();

            for (int i = 0; i < 45; i++)
                listBox1.Items.Add(i);
        }

        int tickCounter = -1;

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (++tickCounter > 44) tickCounter = 0;
            listBox1.Items[tickCounter] = ((int)listBox1.Items[tickCounter])+1;
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
A: 

It might be prudent to disable any painting of the Listbox when doing the updates. Use ListBox.BeginUpdate() and then update the entries, when done, invoke ListBox.EndUpdate(). This should ensure that no refreshes occurs during the update.

You should also ensure that you do not refresh the ListBox from a thread either as cross-threading is dis-allowed, and the runtime will barf with a message saying that 'Cross Threading Exception' has occurred.

Use the ListBox's BeginInvoke(...) method to get around the cross threading issue. See here on the MSDN on how this is done.

tommieb75
A: 

Nice repro code, I had no trouble diagnosing the source of the problem. It is a feature, not a bug. Press the arrow down key several times, then scroll the list. Note that it doesn't jump back now.

What's going on here is that list box automatically scrolls the item with the focus back into view when it gets updated. This is normally desirable behavior, you cannot turn it off. Workarounds, like selecting the item you're updating, isn't going to be pretty when you update the list like this, it is going to flicker badly. Maybe virtual mode, I didn't try it.

ListView doesn't have this behavior, consider using it instead. Use View = List or Details.

Hans Passant
Nice exhaustive answer. Thanks!
WDZ
Works perfectly! Also used http://stackoverflow.com/questions/442817 to get rid of the flickering during update.
WDZ