Reputation:
So there are a few Answered questions but I still dont really understand what i'm meant to do. Like how can I basically make my Listview very customized.
What im trying to get it to look like is:
As you can see it's HEAVILLY customized - I made this in Photoshop if you were wondering and I dont EXACTLY need the Scrollbar customized or that Hover-Highlight effect as of right now but in the Future a way to do it would be needed.
Any ideas how I can achieve this?
EDIT: The closest thing I can get it to is: But theres a "FEW" issues - I cant get the Text alignment/position to look right as I need to put 2 spaces before the Text for it to actually look decent which "MIGHT" give issues for me in the future when trying to get actions on click. Next, There's for some reason no option to using Borders/Grids which remove the ability to do that slightly darker gray border around the Boxes in the first image. Any ideas guys?
(What I used to make it like this is: http://www.vbforums.com/showthread.php?599375-ListBox-with-custom-items-(colors-images-text-alignment))
Edit 2: Re-Edit: Ok I tought I got the Highlight Color sorted but it doesnt seem like it as im using FillRectangle but no matter how I put the Bounds (Which are correct) it seems to leave an extra like 1px border of White for about 1-2 seconds before its refreshed and gone. Anyway to fix this?
Edit 3: The http://www.vbforums.com/showthread.php?599375-ListBox-with-custom-items-(colors-images-text-alignment) ListBox I was using where I "ALMOST" got it done wont work for me due to it just not being stable - If I edit anything after making the ColorListBox in the Design mode it will just not work and give me errors which is annoying. It also has the "SelectedItem" parameter as no longer an Object which ruins half of my code. Other then that if those 2 are fixable I guess it would work but I have no idea how to fix it :(
So I reverted to a VERY basic ListBox for now with just text until you guys can help find a way to customize it like the first image above.
Upvotes: 2
Views: 2832
Reputation: 38875
It is not at all clear what you want exactly. Like how can I basically make my Listview very customized
is vague and very broad (and why you didn't get an answer you liked).
A picture is nice, but words describing the desired appearance would have been better. Given a picture to scrutinize, there is no way to know what is Important and what is just there...because. I assume every detail is important based on very customized
. Additionally:
Listview/Listbox
Pick one: they are very different controls.ListView
, so I scratched the LV1.ListBox
/ListView
items do not normally light up when the mouse passes over (the LV can when HotTracking
is true, but thats only when the mouse is over the Item
not subitem(s)).HotLight
and Selected
coloring is usually handled by the OS (==Operating System) respecting the theme and color choices of the user. Is this gadget supposed to ignore those, or is your theme using some shade of gray for Highlight ?I measured the elements in the first image carefully to get some metrics, then made guesses on the answers to the above.
Use a Button. Since the user will presumably click one of these to select a desired channel, a Button
makes more sense than a ListView
(much more). You can display an image on a Button
along with text, and use the FlatAppearance
properties to style it how you want. Use the Tag
to track the channel ID or the index of the related channel in the collection.
Finally, use the MouseHover
and MouseLeave
events to manipulate FlatAppearance.BorderSize
and FlatAppearance.BorderColor
to get very close to the first image in the question:
They reside in an scrolling Panel
. The panel width is just a bit wider than the control to avoid the horizontal scrollbar. As for the buttons, the HotLight
border (???) is around the entire control rather than just the text. Not what your picture shows you want, but on the other hand, there is nothing involved beyond some standard properties and a little event handling code (5-6 lines).
An ownerdraw ListBox will get you a bit closer to what you want, but ultimately this just makes it look like a ListBox
that has Buttons
in it.
Place a ListBox on the form, and set these properties:
- DrawMode = OwnerDrawFixed
- IntegralHeight = False
- Itemheight = 64 (this is based on the fact that the images are 60x60 in the first image)
- Set the BackColor to ControlLight
or {233,233,233}
for that exact shade of gray as desired.
As mentioned, ListBox
items do not normally light up when the mouse is over them, so we need some code to track that (like a Button
):
Private mouseItem As Int32 = -1
Private Sub lbChannels_MouseMove(sender As Object,
e As MouseEventArgs) Handles lbChannels.MouseMove
Dim ndx = lbChannels.IndexFromPoint(e.Location)
' test to avoid millions of paints
If ndx <> mouseItem Then
If mouseItem <> -1 Then
' invalidate/redraw the OLD itemrect
lbChannels.Invalidate(lbChannels.GetItemRectangle(mouseItem))
End If
mouseItem = ndx
' invalidate/redraw the NEW itemrect
lbChannels.Invalidate(lbChannels.GetItemRectangle(mouseItem))
End If
End Sub
Private Sub lbChannels_MouseLeave(sender As Object,
e As EventArgs) Handles lbChannels.MouseLeave
If mouseItem <> -1 Then
' get rect for what wont be the hot item in a tick
Dim rect = lbChannels.GetItemRectangle(mouseItem)
'lbChannels.Invalidate()
mouseItem = -1 ' no longer Hot
lbChannels.Invalidate(rect)
End If
End Sub
Updated to minimize flickering by redrawing just the items which changed.
I did not use the MouseHover
event because that fires later, resulting the small (but no where near a second) delay mentioned. MouseMove
also makes it easier to get the mouse location.
IImageItem
Based on comments, you want to auto-generate items based on some other data in the app. Rather than assume what that class looks like and to make this easy to implement and applicable to any class, this will use an interface:
Public Interface IImageItem
Property ID As String ' ???
Property ItemImage As Image
Property Text As String
End Interface
The draw code will require that interface be implemented so it can use those properties to draw your items. Id
is an extra one to allow a way to link which ListBox
item is selected or clicked from the collection (only needed if you add items to the ListBox
-- not recommended). It could be extended to include an Enabled
property to draw those differently, if needed. You'd implement this interface on your collection items class thusly:
Public Class ChannelItem
Implements IImageItem
Public Property ItemImage As Image Implements IImageItem.ItemImage
Public Property Text As String Implements IImageItem.Text
Public Property ID As String Implements IImageItem.ID
' + your existing properties
Public Sub New(txt As String, img As Image, key As String)
Text = txt
ItemImage = img
ID = key
End Sub
Public Overrides Function ToString() As String
Return Text
End Function
End Class
Note: ChannelItem
is my demo version of whatever class you are using to track the channels. Just type Implements IImageItem
on your class, press enter and the IDE will add the properties, set them before adding items to your collection.
DrawItem Code
Private Sub lbChannels_DrawItem(sender As Object,
e As DrawItemEventArgs) Handles lbChannels.DrawItem
Dim lb As ListBox = lbChannels
If e.Index < 0 Then
TextRenderer.DrawText(e.Graphics, "", lb.Font, e.Bounds, lb.ForeColor)
Return
End If
Dim iItem As IImageItem
If TypeOf (lb.Items(e.Index)) Is IImageItem Then
iItem = DirectCast(lb.Items(e.Index), IImageItem)
Else
TextRenderer.DrawText(e.Graphics, lb.Items(e.Index).ToString,
lb.Font, e.Bounds, lb.ForeColor)
Return
End If
Dim imgRect As Rectangle = Rectangle.Empty
Dim txtRect As Rectangle
' calc
If iItem.ItemImage IsNot Nothing Then
imgRect = New Rectangle(e.Bounds.X + 1, e.Bounds.Y + 1,
iItem.ItemImage.Width + 2, iItem.ItemImage.Height + 2)
End If
' GetTextExtent
Dim sz = TextRenderer.MeasureText(" " & iItem.Text, lb.Font)
txtRect = New Rectangle(iItem.ItemImage.Width + 4, e.Bounds.Y + 1,
(e.Bounds.Width - iItem.ItemImage.Width) - 8,
e.Bounds.Height - 2)
' Draw Big Box around the text portion
If e.Index = mouseItem Then
Using pR As New Pen(SystemColors.ControlDark, 2),
brB As New SolidBrush(SystemColors.Window)
e.Graphics.DrawRectangle(pR, txtRect)
txtRect.Inflate(-1, -1)
e.Graphics.FillRectangle(brB, txtRect)
End Using
ElseIf (e.State.HasFlag(DrawItemState.Selected)) Then
' ToDo: modify for whatever is desired for the selected item
' this is a guess/example
Using pR As New Pen(SystemColors.Highlight, 2),
brB As New SolidBrush(SystemColors.Window)
e.Graphics.DrawRectangle(pR, txtRect)
txtRect.Inflate(-1, -1)
e.Graphics.FillRectangle(brB, txtRect)
End Using
Else
' could use a channel specific color for each BG
' just extend IImageItem
e.DrawBackground()
End If
If iItem.ItemImage IsNot Nothing Then
e.Graphics.DrawImage(iItem.ItemImage, imgRect)
End If
' recalc TR for where the text really goes
txtRect = New Rectangle(iItem.ItemImage.Width + 4, e.Bounds.Y + 1,
sz.Width + 2, e.Bounds.Height - 0)
TextRenderer.DrawText(e.Graphics, iItem.Text, lb.Font, txtRect, lb.ForeColor)
End Sub
Again, it is not clear whether the darker gray box for "FOX" represents the SelectedItem,
HotLight`/Hover item or even disabled. The code shows how/where to box the text for the first 2 cases. Modify as needed.
Usage
' a collection of ChannelItem objects (which implement IImageItem)
Dim channels As New List(Of ChannelItem)
' my fake data
channels.Add(New ChannelItem("More 4", My.Resources.SO_LVImg01, "M4"))
channels.Add(New ChannelItem("Channel 4", My.Resources.SO_LVImg02, "4"))
channels.Add(New ChannelItem("RTE One", My.Resources.SO_LVImg03, "RET1"))
channels.Add(New ChannelItem("FOX", My.Resources.SO_LVImg04, "Fox"))
...
It is all pretty straightforward, just substitute your class for ChannelItem
(it must implement IImageItem
). Rather than copy items into the items collection, you can use that collection as the DataSource
:
lbChannels.DataSource = channels
SelectedValue
will be a ChannelItem
object boxed as System.Object
, cast it back to get at all the related data such as a URL or Now Playing text.
The result:
In this case, the selected item has a SystemColor.Hightlight
box (blue in my case), and the item the mouse is over has a gray box. There even appears to be the little 2px gap between each item as in the original image. This is the result of the image height being a bit less than the ItemHeight
used.
Putting a border on the things would elide the need to do mouse over highlighting.
If you want (nearly) total control over how these pseudo buttons appear, you should probably build a UserControl
. Using a Label
and PictureBox
and just the normal events you could get it to do almost anything you want. You can slap one together in about 20 mins:
The Channel
UserControls are contained in an autoscrolling Panel
. They are basically a custom button, but allows you a great deal of control over the layout, behavior and appearance. Best of all, each would have its own distinct Click
event.
1 The reason there are no gaps between items in the LV or LB is to make it easier for users to select an item. A gutter or gap opens the chance that the user could click there and get no result. Or depending on the implementation, your code would crash using a bad index.
Upvotes: 4