vb.net 每行有 2 个值的组合框的任何方式?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15514698/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Any way for a combo box with 2 values per line?
提问by Yorrick
I'm looking for an option with which I can use the functionality of a combobox together with a listview.
我正在寻找一种可以将组合框的功能与列表视图一起使用的选项。
The reason for this is because I have SQL queries which can't have more than 1 output, which I can get in some cases.
这样做的原因是因为我的 SQL 查询不能有超过 1 个输出,在某些情况下我可以获得。
E.g. my SQL table looks somewhat like this
例如我的 SQL 表看起来有点像这样
Unique_ID - Name
123456789 - Simon
987654321 - Simon
Unique_ID - Name
123456789 - Simon
987654321 - Simon
Basically the same name can be in the database multiple times, each entry with it's own ID.
基本上相同的名称可以在数据库中多次出现,每个条目都有自己的 ID。
For the user's sake, I can't have them choose what records to edit based on the ID, instead I have them base the chosen record on the name. When there's more than 1 record resulting from my query though, I get a MySQL exception.
为了用户,我不能让他们根据 ID 选择要编辑的记录,而是让他们根据名称选择记录。但是,当我的查询产生超过 1 条记录时,我会收到 MySQL 异常。
Here's the MySQL query in question:"SELECT worldspace from survivor where is_dead = '0' _ and survivor.unique_id = (select unique_id from profile where name = '" & target & "')"
这是有问题的 MySQL 查询:"SELECT worldspace from survivor where is_dead = '0' _ and survivor.unique_id = (select unique_id from profile where name = '" & target & "')"
The output from that query is then used in another UPDATEquery.
然后在另一个UPDATE查询中使用该查询的输出。
So, is it possible for my combobox to have both the ID & the name as values, with a clear seperation between them so the entire value stays well readable, as it would do in a listview element?
那么,我的组合框是否有可能将 ID 和名称都作为值,并且它们之间有明确的分隔,因此整个值保持良好的可读性,就像在列表视图元素中所做的那样?
回答by Cody Gray
I see you've already gotten the HighCore treatment about how easy everything is in WPF and how much WinForms sucks. But you might be interested to know that you can do this in WinForms, too. You just do it a little bit differently. It should come as no surprise that the standard design idioms differ in WinForms and WPF; that doesn't justify one being "better" than the other, it just means you need to learn how to use the one you're using. (Although, admittedly, some of the fancier stuff is a bit more difficult to achieve using a UI framework that was invented 20 years ago with Windows itself. The power it doeshave is rather remarkable.)
我看到你已经得到了 HighCore 处理,关于 WPF 中的一切都很简单,WinForms 有多糟糕。但是您可能有兴趣知道您也可以在 WinForms 中执行此操作。你只是做的有点不同。WinForms 和 WPF 中的标准设计习惯用法不同也就不足为奇了;这并不能证明一个比另一个“更好”,这只是意味着您需要学习如何使用您正在使用的那个。(虽然,不可否认,使用 20 年前与 Windows 本身一起发明的 UI 框架来实现一些更高级的东西有点困难。它确实具有的功能相当出色。)
There are two basic ways of formatting the information: everything on a single line (which I believe is what you asked for in the question) or the pieces of information on two lines where each item is basically a two-line unit (which is what HighCore's WPF solution demonstrates).
格式化信息有两种基本方式:一行上的所有内容(我相信这是您在问题中要求的内容)或两行上的信息片段,其中每个项目基本上是一个两行单位(这就是HighCore 的 WPF 解决方案演示)。
Single-Line Format
单行格式
The Simplistic Approach
简单化的方法
We'll look at putting everything on a single line first, which really is simple. You don't need columns for separation, you can just use some kind of distinctive separator character when you add the items to the combobox, such as a vertical pipe (|) or a dash (-) like you used in the question.
我们将首先考虑将所有内容放在一行中,这确实很简单。您不需要用于分隔的列,您可以在将项目添加到组合框时使用某种独特的分隔符,例如垂直管道 ( |) 或-问题中使用的破折号 ( )。
This works so well because the ComboBox.Items.Addmethod accepts a parameter of type Object, on which it just calls ToStringto get the value displayed in the control. If you pass it a string, it displays that string.
这很有效,因为该ComboBox.Items.Add方法接受一个 type 参数Object,它只是调用它ToString来获取控件中显示的值。如果传递给它一个字符串,它会显示该字符串。
myComboBox.BeginUpdate()
For Each record In myRecordSet
myComboBox.Items.Add(String.Format("{0} | {1}", record.UniqueID, record.Name))
' or even...
myComboBox.Items.Add(String.Format("{0} ({1})", record.UniqueID, record.Name))
Next record
myComboBox.EndUpdate()
OR 
或者 
An Incremental Improvement Through OOP
通过 OOP 的增量改进
You can even pass a custom class to the Addmethod that keeps track of the unique ID and name properties (and anything else you want) and overrides the ToStringmethod for display purposes.
您甚至可以将自定义类传递给Add跟踪唯一 ID 和名称属性(以及您想要的任何其他内容)的ToString方法,并出于显示目的覆盖该方法。
Public Class Record
Public Property UniqueID As Long ' maybe this should be a string too
Public Property Name As String
Public Overrides Function ToString() As String
' Generate the string that will be displayed in the combobox for this
' record, just like we did above when adding it directly to the combobox,
' except that in this case, it will be dynamically generated on the fly,
' allowing you to also track state information along with each item.
Return String.Format("{0} | {1}", Me.UniqueID, Me.Name)
End Function
End Class
' ...
' (somewhere else, when you add the items to the combobox:)
myComboBox.BeginUpdate()
For Each r In myRecordSet
' Create a Record object representing this item, and set its properties.
Dim newRecord As New Record
newRecord.UniqueID = r.UniqueID
newRecord.Name = r.Name
' ...etc.
' Then, add that object to the combobox.
myComboBox.Items.Add(newRecord)
Next r
myComboBox.EndUpdate()
Fixing the Jaggies
修复锯齿
Granted, if the first item in each set can be of variable length and you're using a variable-width font (i.e., one that is not monospaced like every UI on the planet does except code editors), the separators won't line up and you won't get two nicely-formatted columns. Instead, it looks all jumbled and ugly.
当然,如果每组中的第一项可以是可变长度的,并且您使用的是可变宽度字体(即,不是像地球上除代码编辑器之外的每个 UI 那样等宽的字体),则分隔符不会排成一行向上,你不会得到两个格式很好的列。相反,它看起来很混乱和丑陋。


It would be nice of the ComboBox control supported tab characters that would handle lining everything up for us automatically, but unfortunately it does not. This is, regrettably, a hard limitation of the underlying Win32 control.
ComboBox 控件支持制表符会很好,它可以自动为我们处理所有内容,但不幸的是它没有。遗憾的是,这是底层 Win32 控件的硬限制。
Fixing this ragged-edge problem is possible, but it does get a bit complicated. It requires taking over the drawing of the items in the combobox, referred to as "owner-draw".
解决这个边缘参差不齐的问题是可能的,但它确实变得有点复杂。它需要接管组合框中项目的绘制,称为“所有者绘制”。
To do this, you set its DrawModeproperty to OwnerDrawFixedand handle the DrawItemevent to manually draw the text. You'll use the TextRenderer.DrawTextmethod to draw the caption string (because that matches what WinForms uses internally; avoid using Graphics.DrawString), and TextRenderer.MeasureTextif necessary to get the spacing right. The drawing code can (and should) use all of the default properties provided by the DrawItemEventArgspassed as e. You don't need OwnerDrawVariablemode or to handle the MeasureItemevent because the width and height of each item cannot vary in this case.
为此,您将其DrawMode属性设置为OwnerDrawFixed并处理DrawItem事件以手动绘制文本。您将使用该TextRenderer.DrawText方法绘制标题字符串(因为它与 WinForms 内部使用的内容相匹配;避免使用Graphics.DrawString),并TextRenderer.MeasureText在必要时获得正确的间距。绘图代码可以(并且应该)使用DrawItemEventArgs传递的 as提供的所有默认属性e。您不需要OwnerDrawVariablemode 或处理MeasureItem事件,因为在这种情况下每个项目的宽度和高度不能变化。
Just to give you an idea, here's a quick-and-dirty implementation that simply divides the drop-down in half vertically:
只是为了给您一个想法,这里有一个快速而肮脏的实现,它只是将下拉菜单垂直分成两半:
Private Sub myComboBox_DrawItem(sender As Object, e As DrawItemEventArgs) Handles myComboBox.DrawItem
' Fill the background.
e.DrawBackground()
' Extract the Record object corresponding to the combobox item to be drawn.
Dim record As Record = DirectCast(myComboBox.Items(e.Index), Record)
Dim id As String = record.UniqueID.ToString()
Dim name As String = record.Name
' Calculate important positions based on the area of the drop-down box.
Dim xLeft As Integer = e.Bounds.Location.X
Dim xRight As Integer = xLeft + e.Bounds.Width
Dim xMid As Integer = (xRight - xLeft) / 2
Dim yTop As Integer = e.Bounds.Location.Y
Dim yBottom As Integer = yTop + e.Bounds.Height
' Draw the first (Unique ID) string in the first half.
TextRenderer.DrawText(e.Graphics, id, e.Font, New Point(xLeft, yTop), e.ForeColor)
' Draw the column separator line right down the middle.
e.Graphics.DrawLine(SystemPens.ButtonFace, xMid, yTop, xMid, yBottom)
' Draw the second (Name) string in the second half, adding a bit of padding.
TextRenderer.DrawText(e.Graphics, name, e.Font, New Point(xMid + 5, yTop), e.ForeColor, TextFormatFlags.Left)
' Finally, draw the focus rectangle.
e.DrawFocusRectangle()
End Sub


Now, this is looking pretty good. You can certainly improve on the technique used by the DrawItemevent handler method, but it works out pretty well as is, so long as the combobox is made the right size for the values it will be displaying.
现在,这看起来很不错。您当然可以改进DrawItem事件处理程序方法使用的技术,但只要组合框的大小适合它将显示的值,它就可以很好地工作。
Multiple-Line Format
多行格式
Defining a Custom ComboBox Class
定义自定义 ComboBox 类
The second method, where each item is a two-line group like HighCore's WPF example, is best done by subclassing the built-in ComboBox control and taking complete control its drawing routines. But that's nothing to be afraid of, subclassing a control is a standard WinForms idiom to gain extra control over the UI. (You could, of course, implement all of this by handling events like I did above, but I think subclassing is a much cleaner approach and also promotes reuse if you want to have multiple comboboxes that all behave in a similar fashion.)
第二种方法,其中每个项目都是像 HighCore 的 WPF 示例一样的两行组,最好通过继承内置 ComboBox 控件并完全控制其绘图例程来完成。但这没什么好害怕的,子类化一个控件是一个标准的 WinForms 习惯用法,用于获得对 UI 的额外控制。(当然,你可以像我上面那样通过处理事件来实现所有这些,但我认为子类化是一种更简洁的方法,如果你想要多个组合框都以类似的方式运行,还可以促进重用。)
Again, you don't need OwnerDrawVariablebecause the height of the items is not going to change. You'll always have two lines, so a fixed height works fine. You just need to make sure that you set the ItemHeightproperty to double of its normal value because you're going to have two lines. You could do this the complicated way using TextRenderer.MeasureText, or you could do it the easy way by just multiplying the default value by 2. I chose the latter for this demo.
同样,您不需要,OwnerDrawVariable因为项目的高度不会改变。你总是有两条线,所以固定的高度工作正常。您只需要确保将ItemHeight属性设置为其正常值的两倍,因为您将有两行。您可以使用 以复杂的方式完成此操作TextRenderer.MeasureText,或者您可以通过将默认值乘以 2 来轻松完成此操作。我在本演示中选择了后者。
Add this class into your project, and then use the MultiLineComboBoxcontrol instead of the built-in System.Windows.Forms.ComboBox. All of the properties and methods work the same.
将此类添加到您的项目中,然后使用MultiLineComboBox控件而不是内置的System.Windows.Forms.ComboBox. 所有的属性和方法都一样。
Public Class MultiLineComboBox : Inherits ComboBox
Public Sub New()
' Call the base class.
MyBase.New()
' Typing a value into this combobox won't make sense, so make it impossible.
Me.DropDownStyle = ComboBoxStyle.DropDownList
' Set the height of each item to be twice its normal value
' (because we have two lines instead of one).
Me.ItemHeight *= 2
End Sub
Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
' Call the base class.
MyBase.OnDrawItem(e)
' Fill the background.
e.DrawBackground()
' Extract the Record object corresponding to the combobox item to be drawn.
If (e.Index >= 0) Then
Dim record As Record = DirectCast(Me.Items(e.Index), Record)
' Format the item's caption string.
Dim caption As String = String.Format("ID: {0}{1}Name: {2}", record.UniqueID.ToString(), Environment.NewLine, record.Name)
' And then draw that string, left-aligned and vertically centered.
TextRenderer.DrawText(e.Graphics, caption, e.Font, e.Bounds, e.ForeColor, TextFormatFlags.Left Or TextFormatFlags.VerticalCenter)
End If
' Finally, draw the focus rectangle.
e.DrawFocusRectangle()
End Sub
End Class


Adding Fancies and Flourishes
添加幻想和繁荣
What we've got now isn't bad, but by lavishing a bit more effort on the drawing code in OnDrawItem, we can add some extra visual fancies and flourishes.
我们现在得到的还不错,但是通过在 中的绘图代码上付出更多的努力OnDrawItem,我们可以添加一些额外的视觉幻想和繁荣。
For example, without the selection rectangle, it would be pretty hard to tell that these are actually two-line units. That's unusual for a combobox control, so for usability reasons your application should go out of its way to make this abundantly clear. One way we might do that is by indenting the second line. You'll recall that I said that the built-in combobox control doesn't support tabs? Well that doesn't apply anymore, since we're doing the drawing ourselves now. We can emulate tabs by adding some extra padding to the beginning of the second line.
例如,如果没有选择矩形,就很难判断这些实际上是两行单位。这对于组合框控件来说是不寻常的,因此出于可用性原因,您的应用程序应该竭尽全力使这一点非常清楚。我们可以这样做的一种方法是缩进第二行。您还记得我说过内置组合框控件不支持选项卡吗?好吧,这不再适用,因为我们现在自己在画图。我们可以通过在第二行的开头添加一些额外的填充来模拟选项卡。
If you wanted the labels ("ID:" and "Name:") to be set apart from the actual values, you could do that, too. Perhaps you'd make the labels bold and lighten the text color.
如果您希望标签(“ID:”和“名称:”)与实际值分开设置,您也可以这样做。也许您可以将标签加粗并淡化文本颜色。
So you see that just by playing with the drawing code, you can create almost any effect you want. We have complete control, and by wrapping it all up in a MultiLineComboBoxclass that can be reused all over the place, the rest of your code doesn't even have to know that anything special is happening. Cool, right?
所以您会看到,只需使用绘图代码,您就可以创建几乎任何您想要的效果。我们拥有完全的控制权,并且通过将其全部包装在一个MultiLineComboBox可以在任何地方重复使用的类中,您的其余代码甚至不必知道正在发生任何特殊的事情。酷,对吧?
Avoiding All the Work
避免所有的工作
And finally, I would be remiss if I didn't point out that you could skip doing all of this work and take your pickof the variety of custom multi-line combobox controls that have already been written.
最后,如果我没有指出您可以跳过所有这些工作并选择已经编写的各种自定义多行组合框控件,我将是失职。
This oneis pretty nifty. It actually just displays a ListView control when you click the drop-down arrow on the combobox. In doing so, you get all of the formatting niceties of the ListView control for free, but it behaves just like a regular ComboBox.
这个很漂亮。当您单击组合框上的下拉箭头时,它实际上只是显示一个 ListView 控件。通过这样做,您可以免费获得 ListView 控件的所有格式细节,但它的行为就像一个普通的 ComboBox。
回答by Federico Berasategui
Im posting this as an answer because the OP asked it:
我将此作为答案发布,因为 OP 询问了它:
This would be a WPF ComboBox with multiple Lines:
这将是一个带有多行的 WPF ComboBox:
<ComboBox ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding ID, StringFormat='{}ID: {0}'}"/>
<TextBlock Text="{Binding Name, StringFormat='{}Name: {0}'}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Data Item:
数据项:
public class ComboItem
{
public int ID { get; set; }
public string Name { get; set; }
}
Result:
结果:


To all the hackforms zombies: technical superiority speaks by itself.
对于所有黑客僵尸:技术优势不言而喻。

