views:

447

answers:

1

I am experimenting with owner drawn list box. I am adding a text box to a specific item within list box. However, when I start scrolling, the text box does not display in the right location. What is the right way to do this? Here's the code I am using.

Form1.cs

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Diagnostics;

namespace ListBoxControlScrollIssue { public partial class Form1 : Form { private const Int32 cellHeight = 40; private ListBox listBox1; private Font displayFont = new Font(FontFamily.GenericSerif, 8, FontStyle.Regular); private Brush displayBrush = Brushes.Black; private Pen displayPen = Pens.Black; private Button separateDebug; private TextBox item3Text;
public Form1() { SetupListBox(); }

    private void SetupListBox()
    {
        this.listBox1 = new System.Windows.Forms.ListBox();
        this.SuspendLayout();

        // 
        // listBox1
        // 
        this.listBox1.FormattingEnabled = true;
        this.listBox1.Location = new System.Drawing.Point(40, 40);
        this.listBox1.Name = "listBox1";
        this.listBox1.Size = new System.Drawing.Size(300, 100);
        this.listBox1.TabIndex = 0;
        this.listBox1.DrawMode = DrawMode.OwnerDrawVariable;
        this.listBox1.MeasureItem += new MeasureItemEventHandler(listBox1_MeasureItem);
        this.listBox1.DrawItem += new DrawItemEventHandler(listBox1_DrawItem);

        //
        // Add items to list box.
        //
        this.listBox1.Items.Add("Item0");
        this.listBox1.Items.Add("Item1");
        this.listBox1.Items.Add("Item2");
        this.listBox1.Items.Add("Item3");
        this.listBox1.Items.Add("Item4");

        //
        // Add button.
        //
        separateDebug = new Button();
        separateDebug.Name = "SeperateDebug";
        separateDebug.Text = "Seperator";
        separateDebug.Location = new Point(400, 100);
        separateDebug.Click += new EventHandler(separateDebug_Click);

        //
        // Create the text box. However, don't add it to anything.
        //
        item3Text = new TextBox();
        item3Text.Name = "item3Text";
        item3Text.Multiline = false;

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

    void separateDebug_Click(object sender, EventArgs e)
    {
        Debug.Print("\n=========================\n");
    }

    void listBox1_DrawItem(object sender, DrawItemEventArgs e)
    {
        PointF      displayLocation = new PointF(20, e.Index * cellHeight + 3);

        //
        // Display text names of the items here.
        //
        //
        Debug.Print("DrawItem:: Index = {0}, location = {1}", e.Index, displayLocation);
        e.Graphics.DrawString(this.listBox1.Items[e.Index].ToString(), displayFont, displayBrush, e.Bounds);
        //
        // Draw rectangle around the border to show boundary of cell.
        //
        e.Graphics.DrawRectangle(displayPen, e.Bounds);
    }

    void listBox1_MeasureItem(object sender, MeasureItemEventArgs e)
    {
        e.ItemHeight = cellHeight;
        e.ItemWidth = this.listBox1.Width;
        if (e.Index == 3)
        {
            //
            // Set the location of item3Text to the location of bounds.
            //
            item3Text.Location = new Point(21, e.Index * cellHeight + 21);
            listBox1.Controls.Add(item3Text);
        }
    }

}

}

Program.cs

using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms;

namespace ListBoxControlScrollIssue { static class Program { /// /// The main entry point for the application. /// [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } }

Form.Designer.cs

namespace ListBoxControlScrollIssue { partial class Form1 { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
    }

    #endregion

}

}

+1  A: 

Edit I re-wrote my answer in order to be clearer.

The ListBox below displays a TextBox with along its SelectedItem.

GetItemRectangle(int index)

is used to retrieve an item's current location in the ListBox. It seems to be the key method you're looking for.

public class ListBoxEx : ListBox
{
    public ListBoxEx()
    {
        TextBox.Visible = false;
        Controls.Add(TextBox);
    }

    private readonly Size TextBoxOffset = new Size(16, 16);
    private const Int32 CellHeight = 40;
    private readonly TextBox TextBox = new TextBox();

    protected override void OnSelectedIndexChanged(EventArgs e)
    {
        base.OnSelectedIndexChanged(e);
        TextBox.Visible = SelectedIndex != -1;
    }

    protected override void OnDrawItem(DrawItemEventArgs e)
    {
        base.OnDrawItem(e);

        // Somehow necessary
        e.Graphics.FillRectangle(new SolidBrush(BackColor), e.Bounds);
        // Drawing the item's text
        e.Graphics.DrawString(Items[e.Index].ToString(), Font, new SolidBrush(ForeColor), e.Bounds);
        // Drawing the item's borders
        e.Graphics.DrawRectangle(new Pen(ForeColor), e.Bounds);

        // Drawing updating the TextBox location 
        if (SelectedIndex != -1)
            TextBox.Location = Point.Add(GetItemRectangle(SelectedIndex).Location, TextBoxOffset);

        // Because clicking the scrollbar sometimes cause the ListBox to hide the TextBox
        TextBox.BringToFront();
        // Making sure the TextBox is redrawn ASAP
        TextBox.Invalidate();
    }

    protected override void OnMeasureItem(MeasureItemEventArgs e)
    {
        base.OnMeasureItem(e);
        e.ItemHeight = CellHeight;
    }
}
Bryan Menard
I agree that over-riding is better.However, my original question still remains. What should be new location of textbox control? If i calculate it using e.Bounds, then I get textbox placement issues. If I calculate using absolute value, then the result is that the textbox does not get displayed (or is not rendered?).Code I have is in next comment
Chetan
void listBox1_DrawItem(object sender, DrawItemEventArgs e) { PointF displayLocation = new PointF(20, e.Index * cellHeight + 3); Debug.Print("DrawItem:: Index = {0}, bounds = {1}", e.Index, e.Bounds); e.Graphics.DrawString(this.listBox1.Items[e.Index].ToString(), displayFont, displayBrush, e.Bounds); e.Graphics.DrawRectangle(displayPen, e.Bounds); if (e.Index == 3) { listBox1.Controls.Clear();// item3Text.Location = new Point(10, e.Bounds.Y + 18);
Chetan
item3Text.Location = new Point(10, e.Index * cellHeight + 18); Debug.Print("DrawItem:: location = {0}", item3Text.Location); item3Text.Focus(); item3Text.BringToFront(); } }
Chetan
I believe the method you're looking for is ListBox.GetItemRectangle (see my edited answer).
Bryan Menard
Yes. This worked :) Thanks for the answer.
Chetan