Listbox horizontal scrollbar not updating properly

I have a form which contains a listbox control with many other controls which determine the listbox contents. Adding items to the listbox correctly sets the scrollbar's range, but updating items on the listbox (via this.lstResources.Items[index] = myUri) causes the scrollbar's range to decrease past the largest item's width, cutting off the last few characters. The scrollbar still works, but updating the listbox in this fashion causes an unexpected, and unacceptable, scroll range for the item(s) in the list. Here is how I am implementing my listbox:

public System.Windows.Forms.ListBox lstResources;
this.lstResources = new System.Windows.Forms.ListBox();

this.lstResources.FormattingEnabled = true;
this.lstResources.HorizontalScrollbar = true;
this.lstResources.Location = new System.Drawing.Point(331, 122);
this.lstResources.Name = "lstResources";
this.lstResources.Size = new System.Drawing.Size(307, 316);
this.lstResources.TabIndex = 8;
this.lstResources.Click += new System.EventHandler(this.ResourcesPage.LstResources_Click);

The altering operations that I perform on this listbox are as follows:

this.parentClassReference.lstResources.Items.Add(myUri);
this.parentClassReference.lstResources.Items[index] = myUri;
this.parentClassReference.lstResources.Items.RemoveAt(index);

I have tried Refresh() and Update() on both the form and the listbox control, but neither have any effect. I've looked all over for listbox scrollbar issues, but none seem to have this peculiar redrawing issue.

This is what it should look like:

This is what is actually happening:

I have a feeling I'm missing something obvious or perhaps changing the items incorrectly, but I'm fresh out of ideas for this one. I'm not entirely new to C# and UI design, but I can't say I know all the nitty gritty control operations either. Any assistance would be much appreciated.

EDIT 1: This is a sample form which should reproduce the problem. I'm starting to have a suspicion that StringBuilder might have something to do with the update part, but it is also used in the btnAdd_Click code as well...

public partial class SampleListBoxForm : Form
{
    public Dictionary<string, string> CurrentUriQuery { get; set; }

    public SampleListBoxForm()
    {
        this.CurrentUriQuery = new Dictionary<string, string>();
        this.InitializeComponent();
    }

    private void btnAdd_Click(object sender, EventArgs e)
    {
        this.UpdateParams("sampleApp");
        this.listBox1.Items.Add(this.GenerateURI());
        this.listBox1.Refresh();
    }

    private void btnUpdate_Click(object sender, EventArgs e)
    {
        int index = 0;
        this.UpdateParams("sampleApp2");
        for (; index < this.listBox1.Items.Count; index++)
        {
            this.listBox1.Items[index] = this.GenerateURI();
        }
    }

    private void UpdateParams(string filename)
    {
        this.CurrentUriQuery = new Dictionary<string, string>();
        this.CurrentUriQuery["filename"] = filename;
        this.CurrentUriQuery["url"] = @"C:\Users\me\Desktop\" + filename;
        this.CurrentUriQuery["type"] = "dynamicType";
        this.CurrentUriQuery["p1"] = "foo";
        this.CurrentUriQuery["p2"] = "bar";
        this.CurrentUriQuery["p3"] = "stuff";
        this.CurrentUriQuery["p4"] = "test";
    }

    private string GenerateURI()
    {
        StringBuilder currentUri = new StringBuilder();
        bool firstParam = true;
        currentUri.Append(this.CurrentUriQuery["filename"]);
        foreach (KeyValuePair<string, string> pair in this.CurrentUriQuery)
        {
            if (pair.Key != "url" && pair.Key != "filename" && pair.Value != string.Empty && pair.Value != "0")
            {
                if (firstParam)
                {
                    currentUri.Append("?");
                    firstParam = false;
                }
                else
                {
                    currentUri.Append("&");
                }

                currentUri.Append(pair.Key);
                currentUri.Append("=");
                currentUri.Append(pair.Value.Replace(" ", ""));
            }
        }

        return currentUri.ToString();
    }

    public string[] ExtractPathAndQueryFromUriString(string uriString)
    {
        string[] pathAndQuery = new string[2];
        if (uriString.Contains('?'))
        {
            pathAndQuery[0] = uriString.Split('?')[0];
            pathAndQuery[1] = uriString.Split('?')[1];
        }
        else if (uriString.Contains("%3F"))
        {
            string[] stringSeparator = new string[] { "%3F" };
            pathAndQuery[0] = uriString.Split(stringSeparator, StringSplitOptions.None)[0];
            pathAndQuery[1] = uriString.Split(stringSeparator, StringSplitOptions.None)[1];
        }
        else
        {
            pathAndQuery[0] = uriString;
            pathAndQuery[1] = string.Empty;
        }

        return pathAndQuery;
    }
}

Answers


The problem seems to be with the ampersand characters. When you add the item, it seems to measure correctly. When you update the item, it doesn't.

This ugly hack seems to be: record the index, remove the item, insert the item back into that recorded index.

Or switch to DrawMode = OwnerDrawFixed and calculate the HorizontalExtent value for every item that you add or change.

private void ResizeListBox() {
  int maxWidth = 0;

  for (int i = 0; i < listBox1.Items.Count; i++) {
    int testWidth = TextRenderer.MeasureText(listBox1.Items[i].ToString(), 
                                             listBox1.Font, listBox1.ClientSize,
                                             TextFormatFlags.NoPrefix).Width;
    if (testWidth > maxWidth)
      maxWidth = testWidth;
  }

  listBox1.HorizontalExtent = maxWidth;
}

Unfortunately, you would have to call it for every change:

private void btnAdd_Click(object sender, EventArgs e) {
  this.UpdateParams("sampleApp");
  this.listBox1.Items.Add(this.GenerateURI());
  ResizeListBox();
}

private void btnUpdate_Click(object sender, EventArgs e) {
  this.UpdateParams("sampleApp");
  for (int index = 0; index < this.listBox1.Items.Count; index++) {
    this.listBox1.Items[index] = this.GenerateURI();
  }
  ResizeListBox();
}

Here is a quick and dirty DrawItem version:

private void listBox1_DrawItem(object sender, DrawItemEventArgs e) {
  e.DrawBackground();
  if (e.Index > -1) {
    Color textColor = SystemColors.WindowText;
    if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) {
      textColor = SystemColors.HighlightText;
    }
    TextRenderer.DrawText(e.Graphics, listBox1.Items[e.Index].ToString(), 
                          listBox1.Font, e.Bounds, 
                          textColor, Color.Empty, TextFormatFlags.NoPrefix);
  }
}

Need Your Help

How do I write a correct micro-benchmark in Java?

java jvm benchmarking jvm-hotspot microbenchmark

How do you write (and run) a correct micro-benchmark in Java?