【问题标题】:C# custom listbox GUIC# 自定义列表框 GUI
【发布时间】:2011-11-16 17:41:23
【问题描述】:

我有一个类列表,但是不同的孩子有不同的属性需要显示。

我想要实现的是在 gui 中有一个列表框类型的控件,它使每个孩子都能够以它想要的方式显示它的属性 - 所以不要为每个类使用相同的预定义列。

我设想类似传输界面(下图),每个类都可以绘制自己的条目,显示一些文本,进度条(如果相关)等。

如何在 C# 中实现这一点?

感谢您的帮助。

【问题讨论】:

  • Silverlight、WPF 还是 WinForms?
  • C# 不是 GUI,您使用什么 GUI 工具包?窗体? WPF?银光?

标签: c# winforms list user-interface


【解决方案1】:

让您的列表项实现一个接口,提供显示所需的一切:

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

传输对象不应显示自己。你不应该混合领域逻辑(业务逻辑)和显示逻辑。

自定义列表框: 为了以您自己的方式显示列表框项目,您必须从System.Windows.Forms.ListBox 派生您自己的列表框控件。在构造函数中将列表框的DrawMode 属性设置为DrawMode.OwnerDrawFixedDrawMode.OwnerDrawVariable(如果项目大小不同)。如果您使用OwnerDrawVariable,那么您还必须覆盖OnMeasureItem,以便告诉列表框每个项目的大小。

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();
    }
}

您可以让您的原始传输类实现IDisplayItem 或为此创建一个特殊类。您还可以在列表中包含不同类型的对象,只要它们实现接口即可。关键是,显示逻辑本身在控件中,传输类(或任何类)只提供所需的信息。

示例: 由于与 Mark 的持续讨论,我决定在此处包含一个完整的示例。让我们定义一个模型类:

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
}

这是一个自定义列表框:

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();
        }
    }
}

在一个表单上,我们放置了这个 AddressListBox 和一个按钮。在表单中,我们放置了一些初始化代码和一些按钮代码,它们会改变我们的地址。我们这样做是为了查看我们的列表框是否自动更新:

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";
    }
}

单击按钮时,AddressListBox 中的项目会自动更新。请注意,仅定义了列表框的 DataSource。 DataMember 和 ValueMember 保持为空。

【讨论】:

  • 我觉得我不太明白。那么您是否有一个单独的类来处理显示,绑定到列表项并可以添加到列表框控件中?那么是否有可能让不同的项目显示不同的属性?您是否有任何链接或示例可以进一步解释这一点?谢谢!
  • 感谢您的更新。我会试一试的!它肯定会给我一个学习的起点..
  • 在 WinForms 中,您通过调用 Invalidate() 方法(或重载之一)触发控件的重绘。 myListBox.Invalidate();IDisplayItem 接口可以有一个 float Progress { get; } 属性,它公开传输的当前状态。每次更改IDisplayItem 公开的属性时,您都必须使列表框无效。另一种方法是使用数据绑定。将您的列表框绑定到对象绑定源并在显示的项目中实现INotifyPropertyChanged。数据绑定的魔力将让列表框自动显示更改!
  • 我做了一些测试。如果您使用System.ComponentModel.BindingList&lt;T&gt;,它似乎效果更好。在我的测试中,我将 List 转换为绑定列表:var bindingList = new BindingList&lt;Address&gt;(addressList); addressBindingSource.DataSource = bindingList;。我没有设置 Display 或 ValueMember。
  • 好的,我之前的陈述基于一个代码,它也做了其他事情,这导致了混乱。我决定做一个完整的例子来创造清晰的事实(见编辑#2)。标记你是对的,如果使用BindingList&lt;T&gt;,则不需要BindingSource。该示例仅使用BindingList&lt;T&gt;
【解决方案2】:

是的,如果您使用 WPF,则很容易做到这一点。您所要做的就是为您的不同类型创建一个不同的DataTemplate

MSDN for data templates
Dr. WPF for Items Control & Data Templates

【讨论】:

  • 不幸的是,我在 WinForms 上。很抱歉没有在我的原帖中说。但是这些链接对于 WPF 来说看起来很棒——这正是我想要做的事情。
猜你喜欢
  • 1970-01-01
  • 2020-07-30
  • 1970-01-01
  • 2011-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多