任何方式对于每行2个值的组合框? [英] Any way for a combo box with 2 values per line?

查看:177
本文介绍了任何方式对于每行2个值的组合框?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找一个选项,我可以使用组合框的功能和
列表视图。



这样做的原因是因为我有SQL查询不能有超过1个输出,在某些情况下我可以得到。



例如我的SQL表看起来有点像这样



Unique_ID - 名称

123456789 - Simon

987654321 - Simon



基本上,相同的名称可以在数据库中多次,每个条目都有自己的ID。



为了用户的缘故,我不能让他们根据ID选择要编辑的记录,而是让他们根据名称选择记录。



这里是有问题的MySQL查询:

SELECT worldspace from survivor where is_dead ='0'_ and survivor.unique_id =(select unique_id from profile where name ='&'')



然后,该查询的输出将用于另一个 UPDATE 查询中。



所以,我的组合框有可能有ID&作为值,它们之间有明确的分隔,所以整个值保持良好的可读性,因为它会在一个listview元素?

解决方案

我看到你已经得到了关于WPF中的一切是多么容易和WinForms吸引力的HighCore治疗。但你可能有兴趣知道,你可以在WinForms这样做。你只是做一点点不同。标准设计习语在WinForms和WPF中有所不同,这是不足为奇的;这不是证明一个是更好比另一个,它只是意味着你需要学习如何使用一个你正在使用。 (尽管,虽然,不可否认,一些鸽友的东西使用一个UI框架是一个更难以实现的,这是20年前发明的Windows本身,它的力量有非常惊人的。) p>

有两种基本的格式化信息的方法:一行上的所有内容(我相信是你在问题中提出的)或两行的信息每个项目基本上是一个两行单元(这是HighCore的WPF解决方案演示)。



单行格式



简单方法



我们先看看把每一行放在一行上,这真的很简单。您不需要用于分隔的列,当您将项目添加到组合框(例如垂直管道( | )时,您可以使用某种特殊的分隔符)



这个工作原理很好,因为<$ c $

c> ComboBox.Items.Add 方法接受类型 Object 的参数,它只是调用 ToString 获取控件中显示的值。

  myComboBox.BeginUpdate()
对于每个记录在myRecordSet
myComboBox.Items.Add(String.Format({0} | {1},record.UniqueID,record.Name))
'或甚至...
myComboBox.Items .Add(String.Format({0}({1}),record.UniqueID,record.Name))
下一条记录
myComboBox.EndUpdate()

   ;               OR                 < img src =https://i.stack.imgur.com/cN06N。 pngalt =>



通过OOP进行增量改进



您甚至可以将自定义类添加方法,跟踪唯一的ID和名称属性(以及任何其他您想要的),并覆盖 ToString 方法用于显示。

 公共类记录
公共属性UniqueID As Long'也许这应该是一个字符串太b $ b公共属性名称作为字符串

公共覆盖函数ToString()As String
'生成将显示在此
'记录的组合框中的字符串,就像我们在将它直接添加到组合框
'时,除了在这种情况下,它将动态生成,
'允许您还跟踪状态信息以及每个项目。
返回String.Format({0} | {1},Me.UniqueID,Me.Name)
结束函数
结束类

' 。

'(在别的地方,当你添加项到组合框)
myComboBox.BeginUpdate()
对于每个r在myRecordSet
'创建记录对象表示此项目,并设置其属性。
Dim newRecord As New Record
newRecord.UniqueID = r.UniqueID
newRecord.Name = r.Name
'... etc。

'然后,将该对象添加到组合框。
myComboBox.Items.Add(newRecord)
下一页r
myComboBox.EndUpdate()


b $ b

修复Jaggies



授予,如果每个集合中的第一个项目的长度可变,并且您使用的是可变宽度字体,一个不是等宽的像地球上的每个UI,除了代码编辑器),分隔符不会排队,你不会得到两个格式很好的列。





这将是很好的ComboBox控件支持的选项卡字符,将处理排队一切为我们自动,但不幸的是它不。这是遗憾的是,底层Win32控件的一个硬限制。



修复这个崎岖边缘的问题是可能的,但它确实有点复杂。



为此,您需要设置 DrawMode 属性到 OwnerDrawFixed 并处理属性的正常值的两倍,因为你会有两行。你可以使用 TextRenderer.MeasureText 这个复杂的方法来做到这一点,或者你可以通过简单的方法只是将默认值乘以2.我选择后者为这个演示。



将此类添加到您的项目中,然后使用 MultiLineComboBox 控件而不是内置的 System.Windows.Forms.ComboBox

 公共类MultiLineComboBox:Inherits ComboBox 
Public Sub New()
'调用基类。
MyBase.New()

在这个组合框中键入一个值是没有意义的,所以不可能。
Me.DropDownStyle = ComboBoxStyle.DropDownList

'将每个项目的高度设置为其正常值的两倍
'(因为我们有两行而不是一行)。
Me.ItemHeight * = 2
End Sub

受保护的覆盖Sub OnDrawItem(e As DrawItemEventArgs)
'调用基类。
MyBase.OnDrawItem(e)

'填充背景。
e.DrawBackground()

'提取与要绘制的组合框项目对应的Record对象。
If(e.Index> = 0)Then
Dim record As Record = DirectCast(Me.Items(e.Index),Record)

'格式化项目的标题串。
Dim caption As String = String.Format(ID:{0} {1} Name:{2},record.UniqueID.ToString(),Environment.NewLine,record.Name)

'然后绘制该字符串,左对齐和垂直居中。
TextRenderer.DrawText(e.Graphics,caption,e.Font,e.Bounds,e.ForeColor,TextFormatFlags.Left或TextFormatFlags.VerticalCenter)
如果

结束,绘制焦点矩形。
e.DrawFocusRectangle()
End Sub
结束类别



添加幻灯片和繁荣



我们现在已经不坏了,但是通过在 OnDrawItem 中的绘图代码上花费更多的精力,我们可以添加一些额外的视觉幻想和繁荣。



例如,没有选择矩形,很难说这些实际上是两行单位。这是不寻常的组合框控件,所以为了可用性的原因,你的应用程序应该走出去的方式,使这个充分清楚。我们可以这样做的一种方法是缩进第二行。你会记得,我说内置的组合框控件不支持制表符?好了,不再适用了,因为我们现在正在做绘图。我们可以通过在第二行的开头添加一些额外的填充来模拟制表符。



如果您希望标签(ID:和Name:)除了实际价值,你也可以这样做。



所以你看到,只要玩绘图代码,你几乎可以创建任何你想要的效果。我们有完全的控制,并通过将它包装在一个 MultiLineComboBox 类中,可以重复使用的地方,其余的代码甚至不必知道任何特别的事情发生。很酷,对吧?



避免所有的工作



最后,如果我没有指出您可以跳过执行所有这些工作,并选择您的选择已经编写的各种自定义多行组合框控件。



这一个是很漂亮的。它实际上只是显示一个ListView控件,当您单击下拉箭头在组合框。这样做,您可以免费获得所有格式化的ListView控件,但它的行为就像一个常规的ComboBox。


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.

E.g. my SQL table looks somewhat like this

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.

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.

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 & "')"

The output from that query is then used in another UPDATE query.

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?

解决方案

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 does have is rather remarkable.)

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).

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.Add method accepts a parameter of type Object, on which it just calls ToString to get the value displayed in the control. If you pass it a string, it displays that string.

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

You can even pass a custom class to the Add method that keeps track of the unique ID and name properties (and anything else you want) and overrides the ToString method for display purposes.

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.

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.

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 DrawMode property to OwnerDrawFixed and handle the DrawItem event to manually draw the text. You'll use the TextRenderer.DrawText method to draw the caption string (because that matches what WinForms uses internally; avoid using Graphics.DrawString), and TextRenderer.MeasureText if necessary to get the spacing right. The drawing code can (and should) use all of the default properties provided by the DrawItemEventArgs passed as e. You don't need OwnerDrawVariable mode or to handle the MeasureItem event because the width and height of each item cannot vary in this case.

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 DrawItem event 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.

Multiple-Line Format

Defining a Custom ComboBox Class

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.)

Again, you don't need OwnerDrawVariable because 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 ItemHeight property 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.

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.

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.

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.

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 MultiLineComboBox class 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?

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 pick of the variety of custom multi-line combobox controls that have already been written.

This one is 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.

这篇关于任何方式对于每行2个值的组合框?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆