Link to home
Start Free TrialLog in
Avatar of TheRoyalFalcon
TheRoyalFalcon

asked on

Context Menu - Flat & Color Change

In my little VB.net application I have a few context menus. I would like to make them flat and change the background color from system gray to white. I need as simple of a solution as possible. Thanks in advance for your assistance.
 
Avatar of Howard Cantrell
Howard Cantrell
Flag of United States of America image

FYI.... makes great looking menu's....

check here for free XP software - like ... menus, XP format, etc.

http://www.divil.co.uk/net/
Avatar of gregasm
gregasm

You need to use Owner Drawn menus.

You set the Owner Draw property of the menu and then you control the way it is painted. I don't think it's very simple, but it's the way you accomplish what you want to do.
Avatar of TheRoyalFalcon

ASKER

Planocz . . . this site is really nice and what this guy has looks really good (I mean really good). But I need something a little more simple right now. I need to avoid adding third party controls at the moment (a stipulation of my employer).

gregasm . . . sounds like a plan. can you give me an example?


Thanks 2 both of you.
Have a look at this great and informative article on Owner Drawing::

http://msdn.microsoft.com/msdnmag/issues/04/02/CuttingEdge/
I am interested in the question. work has me swapped (i'm working this Saturday and planning to work sunday even - yuck). please do not close this question yet. i have been trying to get this working in between other projects and am hoping to post some questions next week related to the last posting by one of the experts.

would like to do it sooner but i'm swampped.

thanks

planocz, you must not have seen that I posted that exact link already.
Sorry I missed it. :)
Hi Bob,

I thought the link I provided does help towards
" I would like to make them flat and change the background color from system gray to white". I've used that article as the basis of writing some code that does exactly that.
Working 70 hr weeks. This is my 8th in a row. Just hard to get back to this. Working every day accept every other sunday.

I was trying to hold out for my boss to come up with a new version of VB.net. I cannot open the example at the reference link. He's choking at this point on getting it for me. So I need to ask that you please provide a 1.0 example. Also is there anyway to make it actually flat. The example seems to only do this for XP (can't tell for sure because of the vb.net version issue).

You mentioned in the last comment you've used the article as the basis for some code that does exactly this. Can you post that?

Thanks for your help, I appreciate it.
absolutely. I'll search for it and post it here asap.
The key is to set the OwnerDraw property to TRUE for each menuitem, and then wire the item to handle these two events:

        Private Sub MakeItemOwnerDraw(ByVal item As MenuItem)
            item.OwnerDraw = True

            AddHandler item.DrawItem, AddressOf StdDrawItem
            AddHandler item.MeasureItem, AddressOf StdMeasureItem
        End Sub

And now you'll need to handle the measureItem event to provide the minimum menu drawing functionality:

        Private Sub StdDrawItem(ByVal sender As Object, ByVal e As DrawItemEventArgs)

            ' Grab a reference to the item being drawn
            Dim item As MenuItem = CType(sender, MenuItem)

            ' Saves helper objects for easier reference
            Dim g As Graphics = e.Graphics
            Dim bounds As RectangleF = MakeRectangleF(e.Bounds)
            Dim itemText As String = item.Text
            Dim itemState As DrawItemState = e.State

            ' Define bounding rectangles to use later
            CreateLayout(bounds)

            ' Draw the menu item background and text
            DrawBackground(g, itemState)  'Here is where you set the background color

            ' Draw the text
            DrawText(g, item, itemState)
        End Sub

        Private Sub CreateLayout(ByVal bounds As RectangleF)

            ' Define the overall menu item area
            MenuItemBounds = bounds

            ' Define the Bitmap area
            BitmapBounds = MenuItemBounds
            BitmapBounds.Width = BitmapWidth + 2

            ' Define the Client area (everything right of the bitmap)
            ItemBounds = bounds
            ItemBounds.X = BitmapWidth

            ' Define the Text area (including text offset)
            ItemTextBounds.X = CType(BitmapWidth + HorizontalTextOffset, Single)
            ItemTextBounds.Y = CType(bounds.Y + VerticalTextOffset, Single)
            ItemTextBounds.Width = CType(bounds.Width, Single)
            ItemTextBounds.Height = CType(bounds.Height, Single)
        End Sub

        Private Sub DrawBackground(ByVal g As Graphics, ByVal itemState As DrawItemState)

            ' Declare some helper variables
            Dim backBrush, bitmapBrush As Brush
            Dim borderPen As Pen
            Dim selected, disabled, paintBitmapArea As Boolean
            Dim rectToPaint As Rectangle

            ' Determine the state of the item
            selected = (itemState And DrawItemState.Selected) = DrawItemState.Selected
            disabled = (itemState And DrawItemState.Disabled) = DrawItemState.Disabled

            ' Determine whether the bitmap vertical strip must be created
            paintBitmapArea = Not BitmapBackColor.Equals(Color.Empty)
            If paintBitmapArea Then
                rectToPaint = Rectangle.Round(ItemBounds)
            Else
                rectToPaint = Rectangle.Round(MenuItemBounds)
            End If

            ' Determine the brushes to use based on the state
            If selected And Not disabled Then
                backBrush = New SolidBrush(MenuItemBackColorSelected)
                borderPen = New Pen(MenuItemBorderSelected)
            Else
                If MenuItemDithered Then
                    backBrush = New LinearGradientBrush(rectToPaint, _
                        MenuItemBackColorStart, _
                        MenuItemBackColorEnd, _
                        LinearGradientMode.Horizontal)
                    borderPen = Nothing
                Else
                    backBrush = New SolidBrush(MenuItemBackColorStart)
                End If
            End If

            ' Fill the area
            ' NOTE:
            '    When you fill an area larger than the linear gradient, the
            '    end color is used to fill it. This ensures that we also have
            '    the bitmap area painted with the end color of the gradient.
            '    This is for free

            If (selected And Not disabled) Then
                rectToPaint = Rectangle.Round(MenuItemBounds)
                g.FillRectangle(backBrush, rectToPaint)

                ' Draw border
                rectToPaint.Width -= 1
                rectToPaint.Height -= 1
                g.DrawRectangle(borderPen, rectToPaint)
            Else
                g.FillRectangle(backBrush, rectToPaint)
                If paintBitmapArea Then
                    bitmapBrush = New SolidBrush(BitmapBackColor)
                    g.FillRectangle(bitmapBrush, BitmapBounds)
                End If
            End If

            ' Cleanup objects
            If Not (bitmapBrush Is Nothing) Then
                bitmapBrush.Dispose()
            End If
            backBrush.Dispose()
            If Not (borderPen Is Nothing) Then
                borderPen.Dispose()
            End If
        End Sub

 Private Sub DrawText(ByVal g As Graphics, ByVal item As MenuItem, ByVal itemState As DrawItemState)
            ' Declare the foreground brush
            Dim foreBrush As Brush

            ' Handle the separator as a special case; then return
            If item.Text = "-" Then
                DrawSeparator(g)
                Return
            End If

            ' Determine the foreground brush to use based on the state
            ' NOTE: you could use gradients too
            If Not item.Enabled Then
                foreBrush = New SolidBrush(MenuItemForeColorDisabled)
            Else
                foreBrush = New SolidBrush(MenuItemForeColor)
            End If

            ' If default item, use bold
            Dim tmpFont As Font
            Dim defaultItem As Boolean
            defaultItem = (itemState And DrawItemState.Default) = DrawItemState.Default
            If (defaultItem) Then
                tmpFont = New Font(ItemFont, FontStyle.Bold)
            Else
                tmpFont = ItemFont
            End If

            ' Get text and keyboard shortcut info to paint
            Dim textToPaint As String = GetEffectiveText(item)

            ' Text and shortcut are null-separated. Split the string in two parts
            Dim parts() As String = textToPaint.Split(Chr(0))

            ' Format the string(s) to render
            Dim strFormat As New StringFormat
            strFormat.HotkeyPrefix = HotkeyPrefix.Show
            strFormat.LineAlignment = StringAlignment.Center

            ' Paint text
            If parts.Length = 1 Then
                ' Paint when no shortcut info is found
                g.DrawString(textToPaint, tmpFont, foreBrush, ItemTextBounds, strFormat)
            Else
                ' Paint text when shortcut info is found
                g.DrawString(parts(0), tmpFont, foreBrush, ItemTextBounds, strFormat)

                ' Paint right-aligned shortcut info
                Dim rect As New RectangleF(ItemBounds.X, ItemBounds.Y, ItemBounds.Width, ItemBounds.Height)
                rect.Width -= BitmapWidth + HorizontalTextOffset + RightOffset
                strFormat.FormatFlags = StringFormatFlags.DirectionRightToLeft
                g.DrawString(parts(1), tmpFont, foreBrush, rect, strFormat)
            End If

            ' Cleanup resources
            foreBrush.Dispose()
        End Sub


TheRoyalFalcon:

This is not a trivial exercise... as you can see. It takes some knowledge of GDI+. Just let me know if there is anything else I can help you with.

-Gerg
Hi Greg

Sorry for the delay in responding. With all that’s going on here I’ve been hard pressed to leave before 10PM (in at 7:30 to 8AM).

Thanks so much for passing along the code. I really appreciate it. I need to ask a quick question. When I place these in the forms class quite a few things flag out as undeclared. For example:

~With the line that reads:

        AddHandler item.MeasureItem, AddressOf StdMeasureItem

StdMeasureItem shows up as undeclared.

~With the line that reads:

        Dim bounds As RectangleF = MakeRectangleF(e.Bounds)

MakeRectangleF shows up as undeclared.

There are a few more thereafter. I imagine that I need to import certain name spaces. I tried a few but nothing seemed to work. Do you have any ideas what I need to do next?

Thanks for the help with this. I do appreciate it.

Well, I am trying to point you in the right direction. the code that I provided above is not meant to compile.

If the pattern is not clear, then I would need to perhaps provide some complete code that compiles, but alas, I am busy also..
@Well, I am trying to point you in the right direction. the
@code that I provided above is not meant to compile.

Sorry for the confusion, I thought you were posting the actual code referenced above.

@If the pattern is not clear, then I would need to perhaps
@provide some complete code that compiles,

I’m trying to make my way through it. I think I’m following the idea so I will try working with it some more today and tomorrow.

If you have access to the code you put together that you mentioned above, please post it if you get a chance.

I will let you know if I can get something working from the code above. If I do I will also post it back to the site.

@but alas, I am busy also..

I understand. I hear a lot about there being less IT work than there was a few years back. I "sometimes" think the amount of work didn’t change "that" much. Employers just laid some folks off and placed that work on those they kept.

I understand if you’re busy (can sympathize greatly). If I get this working for me I will post it back (thanks for the direction). But if you get a little time and can turn up your working example please post it. I’ll even bump up the question 100 points (anything to get home a little early one night). I entirely understand if you can’t though.

Thanks again, I do appreciate it.

Well back to the salt mine.
Hi Bob,

As long as TheRoyalFalcon does not have any objections, please leave this question open. I am really meaning to post some compile-able code here soon.

Sorry it is taking so long. He's probably already got it licked by now though.
Hi Bob,

We’ve been without power for over a week here in Ohio due to the snow and ice. Just got it back yesterday. Please leave this question open for a little while longer (I appreciate it).

Hi Gregasm,

@Sorry it is taking so long. He's probably already got it licked by now though.

I wish. I can see now this was way more than I anticipated. Would it have been too much for MS to include these things on their own? :-)

Thanks for putting something together for me as I do appreciate it.
The code below should illustrate the concept of Owner Drawing menus. Just cut and paste the whole thing into a new form.

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
   Friend WithEvents ContextMenu1 As System.Windows.Forms.ContextMenu
   Friend WithEvents MenuItem1 As System.Windows.Forms.MenuItem
   Friend WithEvents MenuItem2 As System.Windows.Forms.MenuItem
   Friend WithEvents MenuItem3 As System.Windows.Forms.MenuItem
   Friend WithEvents MenuItem4 As System.Windows.Forms.MenuItem
   Friend WithEvents MenuItem5 As System.Windows.Forms.MenuItem
   Friend WithEvents MenuItem8 As System.Windows.Forms.MenuItem
   <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
      Me.ContextMenu1 = New System.Windows.Forms.ContextMenu
      Me.MenuItem1 = New System.Windows.Forms.MenuItem
      Me.MenuItem2 = New System.Windows.Forms.MenuItem
      Me.MenuItem3 = New System.Windows.Forms.MenuItem
      Me.MenuItem4 = New System.Windows.Forms.MenuItem
      Me.MenuItem5 = New System.Windows.Forms.MenuItem
      Me.MenuItem8 = New System.Windows.Forms.MenuItem
      '
      'ContextMenu1
      '
      Me.ContextMenu1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.MenuItem1, Me.MenuItem2, Me.MenuItem3, Me.MenuItem4, Me.MenuItem5, Me.MenuItem8})
      '
      'MenuItem1
      '
      Me.MenuItem1.Index = 0
      Me.MenuItem1.Text = "Undo"
      '
      'MenuItem2
      '
      Me.MenuItem2.Index = 1
      Me.MenuItem2.Text = "Cut"
      '
      'MenuItem3
      '
      Me.MenuItem3.Index = 2
      Me.MenuItem3.Text = "Copy"
      '
      'MenuItem4
      '
      Me.MenuItem4.Index = 3
      Me.MenuItem4.Text = "Paste"
      '
      'MenuItem5
      '
      Me.MenuItem5.Index = 4
      Me.MenuItem5.Text = "Delete"
      '
      'MenuItem8
      '
      Me.MenuItem8.Index = 5
      Me.MenuItem8.Text = "Select All"
      '
      'Form1
      '
      Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
      Me.ClientSize = New System.Drawing.Size(292, 266)
      Me.ContextMenu = Me.ContextMenu1
      Me.Name = "Form1"
      Me.Text = "Form1"

   End Sub

#End Region

   Private m_horizontalOffset As Integer = 15
   Private m_font As Font = New Font("Arial", 12)
   Private m_backColor As Color = Color.Violet
   Private m_foreColor As Color = Color.Tomato

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

      'Initialize the context menu
      For Each mnuItem As MenuItem In ContextMenu1.MenuItems
         MakeMenuOwnerDraw(mnuItem)
      Next mnuItem

   End Sub

   Private Sub MakeMenuOwnerDraw(ByVal ParentMenu As MenuItem)
      ' Let separators draw themselves
      If String.Compare(ParentMenu.Text, "-") = 0 Then Exit Sub

      ParentMenu.OwnerDraw = True
      AddHandler ParentMenu.DrawItem, AddressOf Menu_DrawItem
      AddHandler ParentMenu.MeasureItem, AddressOf Menu_MeasureItem

      If ParentMenu.MenuItems.Count > 0 Then
         For Each mnuItem As MenuItem In ParentMenu.MenuItems
            Call MakeMenuOwnerDraw(mnuItem)
         Next mnuItem
      End If

   End Sub

   Private Sub Menu_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs)

      Dim foreBrush, backBrush As Brush
      Try
         Dim item As MenuItem = DirectCast(sender, MenuItem)
         foreBrush = New SolidBrush(m_foreColor)
         backBrush = New SolidBrush(m_backColor)
         e.Graphics.FillRectangle(backBrush, e.Bounds)
         e.Graphics.DrawString(item.Text, Font, foreBrush, e.Bounds.X + m_horizontalOffset, e.Bounds.Y)
      Finally
         foreBrush.Dispose()
         backBrush.Dispose()
      End Try
   End Sub

   Private Sub Menu_MeasureItem(ByVal sender As Object, ByVal e As System.Windows.Forms.MeasureItemEventArgs)
      Dim item As MenuItem = DirectCast(sender, MenuItem)
      Dim stringSize As SizeF = e.Graphics.MeasureString(Font.Name, Font)

      ' Set the height and width of the item
      e.ItemHeight = CInt(stringSize.Height)
      e.ItemWidth = CInt(stringSize.Width) + m_horizontalOffset

   End Sub

End Class
Gregasm

Thank you for posting what you have. I have seen a few examples out on the web for changing the color of the menus and its text. This is very nice and I would use this over what I’ve seen. With that in mind though is there any way to make the menu flat. Of the two things I listed [Context Menu - Flat & Color Change ] this is the bigger of the two. Without making the menu flat changing the color looks really odd and I’d probably want to stay away from that. Thanks for any ideas you can supply. :-)
Flat huh?

I take it you mean, without the 3d borders. I didn't see any such property, like FlatStyle, but I'll poke around some and get back to you on this.
@I take it you mean, without the 3d borders.

yes.

@I didn't see any such property, like FlatStyle, but I'll poke around
@some and get back to you on this.

thank you, i REALLY appreciate it. ~trf
Hi Gregasm,

just wanted to touch base with you and see if you found anything on the flat border? hope things are going well. thanks.
Oh, I am glad you reminded me. Thanks for being so patient.
ASKER CERTIFIED SOLUTION
Avatar of gregasm
gregasm

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Please do not close this question. Got tasked with 3 NT4 server conversions in regional areas (different states) along with a couple here. Been swampped for weeks.  Next week I get back to programming. I will try this out next week. Thanks.
TheLeatnedOne,

My job sucks. Getting much time to work through this had been next to impossible. When I posted this I had hoped to get a copy paste class, function, or something. I didn't expect to have to work through all this with a hope of possible completing code to make it do what I needed. But without a direct solution post that is all I could plan on doing. This is a complicated question and I need time to work through this. All I could do was squeeze a little here and there. I work 60hrs a week, live an hour from work, and my wife is pregenant (so as you can tell I have alot of time on my hands).

I appreciate gregasm's try at this. But with that in mind I needed a flat menu (which was in the posting). But at this point i just don't care any more. I can't work through what I was given with a hope of making it flat and posting it back. So at this point I give up. And I don't want to have to go through the hassle of trying to break out points or determine a score although I never got what I really needed.

Anyway, you appeare to need this closed for some reason so badly that I'm just closing it without really ever getting what I needed.

If I ever get this to work the way I needed I will post back what I have to those who may read this post and too need a flat menu.

Hope everyone is happy now that the posting is closed. :-(

Hi,

If you need a cut and paste class, I suggest looking into third party tools. There is no easy way to achieve a flat menu appearance with the standard .NET menu class. To achieve the flat menu look is also too much for me to take on at this time as well (although I am not going to say I have as much going on in my life).. =]

I know you don't have any time to think about this, but other than looking at the link posted in a previous posting (the article written by Dino Esposito about context menu programming), third party tools is the way to save your time.

Good luck, and I wish I could have helped you more.
Thanks gregasm, I do appreciate your attempts (alot). Just didn't have enough time to work through all this. Oh well, if I ever get the time to work through this, I will post back anything I workout. Thanks again.