我有一个 WinForms ListView,显然包含 ListViewItems。我希望能够将单击事件附加到每个项目,而不是整个 ListView (然后试图找出单击了哪个项目)。原因是我需要根据选择的项目执行不同的操作。ListViewItem 类在这方面似乎非常有限。有什么方法可以做我想做的事,还是我被迫使用 ListView.Click 事件?
7 回答
我仍然会使用 ListView Click 事件。我在这些情况下使用的一个技巧是使用 ListViewItem 的 Tag 属性。它非常适合存储每个项目的数据,您可以在其中放入任何内容。
子类化并使用虚拟调度来根据在适当事件中ListViewItem
的选择来选择适当的行为可能是有意义的。ListViewItem
ListView
例如(未编译)
public abstract class MyItems : ListViewItem
{
public abstract DoOperation();
}
public class MyItemA : MyItems
{
public override DoOperation()
{ /* whatever a */ }
}
public class MyItemB : MyItems
{
public override DoOperation()
{ /* whatever b */ }
}
// in ListView event
MyItems item = (MyItems)this.SelectedItem;
item.DoOperation();
正如其他人所提到的,使用适当的 Tag 属性也可能有意义。您采用哪种技术实际上取决于您的操作是什么(因此在架构上它属于哪里)。我认为子类更有意义,因为您正在寻找对列表视图项目的点击,并且(对我而言)似乎更有可能是表示层 b/c 您正在覆盖一些标准控制行为(通常只是选择一个项目)而不是根据行为做某事。
在大多数用例中,aListViewItem
是某个对象在 UI 中的表示,而您要做的是ListViewItem
在用户单击它时执行该对象的方法。为了简单和可维护性,您希望在用户单击鼠标和正在执行的实际方法之间保留最少的内容。
您可以将对象存储在ListViewItem
的Tag
属性中,然后在 Click 事件处理程序中引用它,但这会导致代码具有一些固有的弱点:
private void MyListView_Click(object sender, EventArgs e)
{
ListView l = (ListView)sender;
if (l.SelectedItem != null)
{
MyClass obj = l.SelectedItem.Tag as MyClass;
if (obj != null)
{
obj.Method();
}
}
}
这是很多强制转换和空引用检查。这段代码的真正弱点是,如果结果Tag
是空的,或者包含对象以外的东西MyClass
,你真的不知道去哪里寻找问题发生的地方。
将其与如下代码进行对比:
private void MyListView_Click(object sender, EventArgs e)
{
MyClass.ListViewClicked(sender as ListView);
}
当您维护此代码时,您不知道该ListViewClicked
方法是如何实现的,但至少您知道在哪里查找它 - 在MyClass
. 当你这样做时,你会看到这样的东西:
public static void ListViewClicked(ListView listView)
{
if (listView.SelectedItem == null)
{
return;
}
if (ListViewItemLookup.ContainsKey(listView.SelectedItem))
{
ListViewItemLookup[listView.SelectedItem].Execute();
}
}
嗯,这很有趣。按照线程,该字典是如何填充的?您在 MyClass 的另一个方法中发现了这一点:
private static Dictionary<ListViewItem, MyClass> ListViewItemLookup =
new Dictionary<ListViewItem, MyClass>();
public ListViewItem GetListViewItem()
{
ListViewItem item = new ListViewItem();
item.Text = SomeProperty;
// population of other ListViewItem columns goes here
ListViewItemLookup.Add(item, this);
return item;
}
(理性的人可能不同意将一个类与其在 UI 中的特定表示形式如此紧密地联系起来是否合适——有些人会将这些方法和这个字典隔离在一个辅助类中而不是MyClass
它本身,并且取决于问题的其余部分有多毛,我也可能会这样做。)
这种方法解决了许多问题:它为您提供了一种ListView
正确处理 Click 事件的简单方法,这正是您所要求的。但它也隔离了ListViewItem
最初创建的并非总是微不足道的过程。ListView
如果您重构表单并将其移动到另一个表单,它会减少您必须移动的代码量。它减少了表单类需要知道的事情的数量,这通常是一件好事。
此外,它是可测试的。通常,在 UI 事件处理程序中测试代码的唯一方法是通过 UI。这种方法使您可以将围绕这部分 UI 的所有逻辑隔离在可以单元测试的东西中;唯一不能编写单元测试的就是表单中的一行代码。
我应该指出,人们一直在建议的另一种方法 - 子类化ListViewItem
- 也非常好。你把我在GetListViewItem
方法中的逻辑放到类的构造函数中,让MyClass
实例成为类的私有属性,并暴露一个调用方法的 Click 方法MyClass
。几乎我不喜欢它的唯一原因是它仍然会在表单中留下大量无法真正进行单元测试的代码:
ListView l = (ListView)sender;
if (l.SelectedItem != null)
{
MyClassListViewItem item = l.SelectedItem as MyClassListViewItem;
if (item != null)
{
item.MyClass.Method();
}
}
但是,您可能会幸运地在标签字段中粘贴对委托或其他处理程序的引用(假设存在 ListViewItem 的标签属性)。您仍然必须确定单击了哪个 ListViewItem,但是您可以直接转到标签而不是另一个决策结构。
你想创建一个新的类(或者如果有各种类型的类),它继承自 ListViewItem,然后用这些对象填充你的 ListView(只要它们继承自 listview(甚至是几个级别的继承)ListView 控件就会接受它们)。
然后向您的自定义类添加一个单击方法,并在您的 listView 的 ItemClick 事件上,只需调用单击项的单击方法。(可能需要一些铸造)
实际上没有办法使用 ListViewItem。您必须使用 ListView 本身。通过使用 ListView 的“SelectedItems”属性,您可以访问选定的 ListViewItems。
一种选择是覆盖 ListViewItem 类并在其中实现特定的东西。然后,您可以将所选项目强制转换为被覆盖的项目并执行操作。
我真的不明白这样做的原因,而不仅仅是使用常规的 ListView Click 事件,但如果我按照你的建议做,我会为每个 ListViewItem 的 Tag 属性分配一个 EventHandler 委托,然后在 ListView Click 事件中处理程序我会检查 ListViewItem.Tag <> 是否为空,如果是,则调用委托。