我有一个类列表,但不同的子代有不同的属性需要显示。
我想要实现的是在gui中有一个listbox类型的控件,它允许每个子节点以它想要的方式显示它的属性 - 所以不要为每个类使用相同的预定义列。
我设想类似传输接口(下面),每个类可以绘制它自己的条目,显示一些文本,相关的进度条等等。
如何在C#中实现这一目标?
感谢您的帮助。
答案 0 :(得分:13)
让您的列表项实现一个界面,提供显示所需的一切:
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.OwnerDrawFixed
或DrawMode.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
}
这是一个自定义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();
}
}
}
在表单上,我们放置此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保持为空。
答案 1 :(得分:1)
是的,如果你使用WPF,那么这很容易实现。您所要做的就是为不同的类型制作不同的DataTemplate
。
MSDN for data templates
Dr. WPF for Items Control & Data Templates