Tengo una lista de clases, pero los niños son distintos, tienen propiedades diferentes de las que se deben mostrar.

Lo que quiero lograr es tener un cuadro de lista tipo de control en la interfaz gráfica de usuario que permite a cada niño para mostrar las propiedades de la forma en que se quiere – para no usar el mismo pre-columnas definidas para cada clase.

Imagino algo así como la transmisión de la interfaz (más abajo), donde cada clase puede pintar su propia entrada, mostrando algo de texto, barra de progreso, si son relevantes, etc.

C# personalizado listbox GUI

Cómo se consigue esto en C#?

Gracias por la ayuda.

  • Silverlight, WPF o WinForms?
  • C# no es una interfaz gráfica de usuario, lo que GUI toolkit se utilizan? Winforms? WPF? SilverLight?
  • Lo Siento, Ganar Formas.
InformationsquelleAutor Mark | 2011-11-16

2 Comentarios

  1. 13

    Deje que sus elementos de la lista de implementar una interfaz que proporciona todo lo necesario para la pantalla:

    public interface IDisplayItem
    {
        event System.ComponentModel.ProgressChangedEventHandler ProgressChanged;
        string Subject { get; }
        string Description { get; }
        //Provide everything you need for the display here
    }

    La transmisión de los objetos no se deben mostrar a sí mismos. No se debe mezclar la lógica del dominio (lógica de negocio) y la lógica de presentación.

    Personalizado ListBox:
    Con el fin de hacer de visualización de cuadro de lista de los elementos de tu propio camino, tendrá que derivar su propio control de cuadro de lista de System.Windows.Forms.ListBox. Establecer el DrawMode propiedad de su listbox a DrawMode.OwnerDrawFixed o DrawMode.OwnerDrawVariable (si los elementos no son del mismo tamaño) en el constructor. Si utiliza OwnerDrawVariable entonces usted tendrá que reemplazar OnMeasureItem así, con el fin de contar el listbox el tamaño de cada elemento.

    public class TransmissionListBox : ListBox
    {
        public TransmissionListBox()
        {
            this.DrawMode = DrawMode.OwnerDrawFixed;
        }
    
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            e.DrawBackground();
            if (e.Index >= 0 && e.Index < Items.Count) {
                var displayItem = Items[e.Index] as IDisplayItem;
                TextRenderer.DrawText(e.Graphics,displayItem.Subject,e.Font,...);
                e.Graphics.DrawIcon(...);
                //and so on
            }
            e.DrawFocusRectangle();
        }
    }

    Usted puede dejar que su transmisión original de la clase de implementar IDisplayItem o crear una clase especial para este propósito. Usted también puede tener diferentes tipos de objetos en la lista, siempre y cuando se implemente la interfaz. El punto es, que la lógica de presentación en sí está en el control, la clase de transmisión (o de cualquier clase) sólo se proporciona la información necesaria.

    Ejemplo:
    Debido a la continua discusión con Mark, he decidido incluir un ejemplo completo aquí. Vamos a definir una clase de modelo:

    public class Address : INotifyPropertyChanged
    {
    private string _Name;
    public string Name
    {
    get { return _Name; }
    set
    {
    if (_Name != value) {
    _Name = value;
    OnPropertyChanged("Name");
    }
    }
    }
    private string _City;
    public string City
    {
    get { return _City; }
    set
    {
    if (_City != value) {
    _City = value;
    OnPropertyChanged("City");
    OnPropertyChanged("CityZip");
    }
    }
    }
    private int? _Zip;
    public int? Zip
    {
    get { return _Zip; }
    set
    {
    if (_Zip != value) {
    _Zip = value;
    OnPropertyChanged("Zip");
    OnPropertyChanged("CityZip");
    }
    }
    }
    public string CityZip { get { return Zip.ToString() + " " + City; } }
    public override string ToString()
    {
    return Name + "," + CityZip;
    }
    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
    var handler = PropertyChanged;
    if (handler != null) {
    handler(this, new PropertyChangedEventArgs(propertyName));
    }
    }
    #endregion
    }

    Aquí es una costumbre ListBox:

    public class AddressListBox : ListBox
    {
    public AddressListBox()
    {
    DrawMode = DrawMode.OwnerDrawFixed;
    ItemHeight = 18;
    }
    protected override void OnDrawItem(DrawItemEventArgs e)
    {
    const TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
    if (e.Index >= 0) {
    e.DrawBackground();
    e.Graphics.DrawRectangle(Pens.Red, 2, e.Bounds.Y + 2, 14, 14); //Simulate an icon.
    var textRect = e.Bounds;
    textRect.X += 20;
    textRect.Width -= 20;
    string itemText = DesignMode ? "AddressListBox" : Items[e.Index].ToString();
    TextRenderer.DrawText(e.Graphics, itemText, e.Font, textRect, e.ForeColor, flags);
    e.DrawFocusRectangle();
    }
    }
    }

    En un formulario, ponemos este AddressListBox y un botón. En el formulario, nosotros hemos puesto algunos inicialización código y algunas código del botón, que cambia nuestras direcciones. Hacemos esto con el fin de ver si nuestro cuadro de lista se actualiza automáticamente:

    public partial class frmAddress : Form
    {
    BindingList<Address> _addressBindingList;
    public frmAddress()
    {
    InitializeComponent();
    _addressBindingList = new BindingList<Address>();
    _addressBindingList.Add(new Address { Name = "Müller" });
    _addressBindingList.Add(new Address { Name = "Aebi" });
    lstAddress.DataSource = _addressBindingList;
    }
    private void btnChangeCity_Click(object sender, EventArgs e)
    {
    _addressBindingList[0].City = "Zürich";
    _addressBindingList[1].City = "Burgdorf";
    }
    }

    Cuando se pulsa el botón, los elementos de la AddressListBox se actualizan automáticamente. Tenga en cuenta que sólo el origen de datos del cuadro de lista está definido. El DataMember y ValueMember permanecen vacías.

    • No creo que me entiendo muy bien. Así tendría una clase independiente que se encarga de la visualización, está enlazado a un elemento de la lista y puede ser añadido para el control listbox? Entonces, ¿sería posible tener diferentes elementos que muestran diferentes propiedades? ¿Tiene algún vínculo o ejemplos para explicar esto más a fondo? Gracias!
    • Gracias por las actualizaciones. Voy a darle una oportunidad! Eso sin duda me dará un punto de partida para aprender..
    • Al revisar mi código de ejemplo, me di cuenta de que es incorrecta. Ningún bucle, ya que OnDrawItem se llama una vez para cada elemento. He editado el ejemplo y se sustituye el bucle por medio de un índice de verificación.
    • Gracias por la actualización. Estoy casi allí. Puedo preguntarte, ¿cómo soy yo mejor dibujar la barra de progreso en la entrada?
    • En WinForms desencadenar el Repaint de un control llamando al método Invalidate () (o una de las sobrecargas). myListBox.Invalidate();. El IDisplayItem interfaz podría tener un float Progress { get; } propiedad a las que se expone el estado actual de la transmisión. En cada cambio de una propiedad expuesta por IDisplayItem deberá invalidar el listbox. Otra manera es utilizar el enlace de datos. Enlazar tu listbox a un objeto de unión de la fuente y aplicar INotifyPropertyChanged en sus elementos mostrados. La magia de enlace de datos, a continuación, permitirá el cuadro de lista mostrar los cambios de forma automática!
    • Gracias por el apoyo constante! Tengo 2 preguntas más, si eso es ok.. 1) Si puedo implementar INotifyPropertyChanged, el cuadro de lista sólo se actualiza si he puesto DisplayMember/ValueMember para el nombre de la propiedad que está siendo actualizado en lo que si estoy pintando el uso de varias propiedades, entonces sólo se actualizará cuando el obligado propiedad se cambia, no de los demás. Cómo es la mejor manera de superar esto? Quizás también llamada PropertyChanged("RePaint") o similar para todas las propiedades y se unen a eso?
    • 2) En OnDrawItem/DrawItem, me acaba de comprobación para el tipo de objeto para ver cómo lo pintan. Hay una forma más elegante de hacer esto? Yo sé que usted debe mantener la lógica de presentación por separado, pero cuando cada uno son tan diferentes, parece que debe haber una mejor manera.. También, para la barra de progreso, actualmente estoy creando un Bitmap y un ProgressBar, la configuración de la barra de progreso de las propiedades del objeto que se está actualizado y, a continuación, utilizando ProgressBar.DrawToBitmap(..) & e.Graphics.DrawImage(bitmap, location). Es esta la mejor manera de hacer esto?
    • He hecho algunas pruebas. Parece que funciona mejor si se utiliza un System.ComponentModel.BindingList<T>. En mi prueba me convertí en la Lista de<T> en una lista de enlace: var bindingList = new BindingList<Address>(addressList); addressBindingSource.DataSource = bindingList;. Yo no configure la Pantalla o ValueMember.
    • Ya tengo un BindingList<T>como el cuadro de lista origen de datos, pero no BindingSource – ¿cuál es el beneficio de tener dos etapas de unión? La adición de la BindingSource todavía no funciona bien. el ListChanged evento que está siendo elevado, pero BindingComplete no lo es. Hay otro paso que tengo que realizar?
    • OK, he basado mis declaraciones anteriores en un código, el cual hizo otras cosas así, lo que causó confusión. Me decidí a hacer un ejemplo completo en el fin de crear hechos claros (véase la EDICIÓN #2). Marcos, tienes razón, si un BindingList<T> se utiliza, no BindingSource es necesario. En el ejemplo se utiliza únicamente un BindingList<T>.
    • Muchas gracias por los detalles adicionales!
    • Wow, no puedo creer que Mark nunca le dio un voto! Cosas muy útiles.

Dejar respuesta

Please enter your comment!
Please enter your name here