[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1007
  • Last Modified:

GDI+ draw image and keep it topmost

I have a form where i draw a box

gInst.DrawRectangle(New Pen(Color.White, 2), New Rectangle(myPointArray(1), New Size(130, 50)))

on this form i have a couple of controls (mostly pictureboxes)

now my dilema is, is when i draw this rectange, i want it to appear over the controls, but it appears under them.  in fact i want the rectangle topmost all the time

thanks!

~b

0
bramsquad
Asked:
bramsquad
  • 6
  • 6
  • 3
2 Solutions
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Hi bramsquad,

I think you are going to have go old school on this one and use APIs to draw your rectangle directly onto the desktop DC.  This is the only way I know of to get a line to appear above multiple controls.  The nasty colors on the form are just to make it easier to see the white rectangle.

Obviously, the white rectangle is not persistent.  If you minimize the form or pass another window over it, the rectangle will not be redrawn.  You can't place the code in the form paint event because the form is painted first and then the controls are painted afterwards.  The box will be drawn on top of the form and then just be painted over when the controls get their own paint messages.

Hope it helps or sparks some ideas,

Idle_Mind

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 Button1 As System.Windows.Forms.Button
    Friend WithEvents PictureBox1 As System.Windows.Forms.PictureBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.Button1 = New System.Windows.Forms.Button
        Me.PictureBox1 = New System.Windows.Forms.PictureBox
        Me.SuspendLayout()
        '
        'Button1
        '
        Me.Button1.BackColor = System.Drawing.Color.LawnGreen
        Me.Button1.Location = New System.Drawing.Point(8, 16)
        Me.Button1.Name = "Button1"
        Me.Button1.Size = New System.Drawing.Size(88, 32)
        Me.Button1.TabIndex = 0
        Me.Button1.Text = "Button1"
        '
        'PictureBox1
        '
        Me.PictureBox1.BackColor = System.Drawing.Color.Red
        Me.PictureBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
        Me.PictureBox1.Location = New System.Drawing.Point(8, 64)
        Me.PictureBox1.Name = "PictureBox1"
        Me.PictureBox1.Size = New System.Drawing.Size(120, 80)
        Me.PictureBox1.TabIndex = 1
        Me.PictureBox1.TabStop = False
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.BackColor = System.Drawing.Color.SkyBlue
        Me.ClientSize = New System.Drawing.Size(292, 266)
        Me.Controls.Add(Me.PictureBox1)
        Me.Controls.Add(Me.Button1)
        Me.Name = "Form1"
        Me.Text = "Form1"
        Me.ResumeLayout(False)

    End Sub

#End Region

    Private Structure PointAPI
        Public x As Integer
        Public y As Integer
    End Structure

    Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Integer) As Integer
    Private Declare Function GetDesktopWindow Lib "user32" () As Integer

    Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Integer, ByVal nWidth As Integer, ByVal crColor As Integer) As Integer
    Private Declare Function SelectObject Lib "gdi32" (ByVal hDC As Integer, ByVal hObject As Integer) As Integer
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Integer) As Integer

    Private Declare Function MoveToEx Lib "gdi32" (ByVal hdc As Integer, ByVal x As Integer, ByVal y As Integer, ByRef lpPoint As PointAPI) As Integer
    Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Integer, ByVal x As Integer, ByVal y As Integer) As Integer

    Private Const PS_SOLID = 0

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        ' start at (12,12) in Button1 Client Coords
        ' draw a 50x50 recttangle that will overlap
        ' the button, form and picturebox1

        ' get a pointer to the desktop DC
        Dim desktopDC As Integer = GetWindowDC(GetDesktopWindow())

        ' create a white pen 2 pixels thick
        Dim pen As Integer = CreatePen(PS_SOLID, 2, Microsoft.VisualBasic.RGB(255, 255, 255))

        ' set the current desktop pen to our white one
        ' saving the previous desktop pen in prevPen
        Dim prevPen = SelectObject(desktopDC, pen)

        ' convert (12,12) in button client coords to screen coords
        Dim p As Point = Button1.PointToScreen(New Point(12, 12))

        ' move to the point and draw the 50x50 rectangle
        MoveToEx(desktopDC, p.X, p.Y, Nothing)
        LineTo(desktopDC, p.X + 50, p.Y)
        LineTo(desktopDC, p.X + 50, p.Y + 50)
        LineTo(desktopDC, p.X, p.Y + 50)
        LineTo(desktopDC, p.X, p.Y)

        ' put prevPen back and delete our white pen
        ' forgetting to do these steps will result in a memory leak!!!
        SelectObject(desktopDC, prevPen)
        DeleteObject(pen)
    End Sub

End Class
0
 
Bob LearnedCommented:
You might want to create a borderless user control that has the rectangle shape that you are looking for.  Then you would have control over the z-order, and bring it to the top.

Bob
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Bob,

Wouldn't the user control interfere with normal interaction of controls directly underneath it though?  bramsquad stated that most of the controls underneath would be pictureboxes (which probably wouldn't need interaction) but what if you had a textbox or button underneath the user control?

Also, can you make a user control truly transparent so that other controls show thru it, and not just the background colors/images of the usercontrol parent containter?  If the user control box overlapped several pictureboxes, would the images in those pictureboxes show thru properly?

I haven't played with user controls too much...

~IM
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
Bob LearnedCommented:
Actually, Mike, what is really interesting is what is the purpose of the rectangle?  Drawing a rectangle using GDI+ has no handle, so you can't change the order, and will always be at the bottom of the z-order.  That's how light-weight controls were accomplished in VB6.  Really, if you want a rectangle, then use a PictureBox with no border.  I didn't hear anything here that hinted to having transparency, but maybe I missed something.

Bob
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
>> Actually, Mike, what is really interesting is what is the purpose of the rectangle?

That's a great question.  =)

>> Drawing a rectangle using GDI+ has no handle, so you can't change the order, and will always be at the bottom of the z-order.

By drawing the the rectangle on the Desktop DC I have forced it to the top of the Z order in a roundabout way since drawing on the desktop always draws on top of all other windows.  Try the example out.  You can use the code to draw over any window, not just your own.

>> I didn't hear anything here that hinted to having transparency, but maybe I missed something.

Your right, "Transparent Rectangle" isn't directly in the description.  I'm trying to read between the lines in bramsquads description.  He is using DrawRectangle() which draws the border of a rectangle...thus leaving the middle "transparent".  A solid rectangle would use FillRectangle().  He says he has several pictureboxes and he wants the rectangle to appear over the controls and be topmost all the time, implying that he is attempting to draw the outline of a rectangle over several pictureboxes while still being able to see the rest of the controls.

...or maybe I'm the one missing the boat here...wouldn't be the first time.  ;)

Can you give us some more info on what you are trying to do here bramsquad?

~IM
0
 
bramsquadAuthor Commented:
ill explain in a little better detail what i want.  

whoever guessed at the transparency was exactly right.  i have two pictureboxes (so no events will occur), and on top of those would be a transparent box.  within the transparent box is some (non transparent) text....im using this mostly as a label

the only problem with your code IdleMind is that I do want it redrawn under the paint method.  Im trying some stuff with it now, if you guys have any ideas though please let me know.

thanks,

~b
0
 
Bob LearnedCommented:
Very perceptive logic :)  You can draw a user control with transparency:

SetStyle(ControlStyles.SupportsTransparentBackColor, True)
BackColor = Color.FromArgb(0, 0, 0, 0)

Bob
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Bob,

I'll have to try it out.  My (limited) experience with setting backcolors to transparent is that when the control is over another control the transparency doesn't work.  The controls seem to pull the color/image from the parent container which usually doesn't coincide with what the control is actually over.  This is even more apparent when the control is over three controls at once, one being the container and overlapping two other controls contained within the same parent container.

bramsquad,

One way of avoiding the problem of drawing a line that is above several different controls is to make sure it only has to be drawn over one control instead.  This allows us to draw in the paint event for that control and use that controls graphics context.

Is it possible to use one PictureBox and draw all the images into using DrawImage()?  This would allow you to modify the image directly.

~IM

0
 
Bob LearnedCommented:
Mike,

   You might be right about that.  I haven't tried it lately.  I should probably do that before opening my mouth, huh?  I do have a vague impression of problems.

Bob
0
 
Bob LearnedCommented:
Also, look at a GraphicsPath, since it can easily draw/redraw complex shapes.  The GraphicsPath class has similar methods to the Graphics class, so you can add paths, and then redraw in a few steps.

Bob
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Hi Josh,

Just wondering how your project was going.  What direction did you decide to go in?

There are no other interesting questions open at the moment...   =\

~IM
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Hi Bob,

Regarding this statement:

    SetStyle(ControlStyles.SupportsTransparentBackColor, True)

From the help file:

    If the SupportsTransparentBackColor bit is set to true, and the BackColor is set to a color whose alpha component is less than 255, OnPaintBackground will simulate transparency by asking its parent control to paint the background. This is not true transparency.

    Note:  If there is another control between the control and its parent, the current control will not show the control in the middle.

Mike
0
 
Bob LearnedCommented:
A very salient point, my friend.  Like I said, I hadn't done this in a while, and couldn't remember all the pitfalls.  

Thanks for pointing that out for me.  BTW, are we alone here.  I thought that there was someone else, but I haven't heard neither hide nor hair from bramsquad.  Did we scare Josh off with our off-topic comments?

Bob
0
 
bramsquadAuthor Commented:
hey guys,

absolutely not did you scare me off with off topic comments.  honestly its kinda fun learning a little more about some experts i revere on this site.  on a side note to that, i actually just lost my job recently too (i was a temp, and they made cutbacks) so i kinda wish the comments werent taken off, becuase they were very interesting and kinda hit home i guess.

anyways, i dont know if this is the best way, but it was the only way i could *tweak* it to do.  i ended up creating an extra large picturebox.  from there, i made the picturebox have a transparent background and used this function to place a bitmap ontop of one another (previously i just had two pictureboxes places on one another)

    Public Function BitmapOnTop(ByVal OnBottom As String, ByRef OnTop As String) As Bitmap
        Dim bmpPictureBox As Bitmap = New Bitmap(131, 118)
        Dim bmpOnBottom As Bitmap = New Bitmap(CreateImage(OnBottom))
        Dim bmpOnTop As Bitmap = New Bitmap(CreateImage(OnTop))

        'create bitmap placing one ontop the other
        Dim g As Graphics = Graphics.FromImage(bmpOnBottom)
        bmpOnTop.MakeTransparent(Color.Blue)
        g.DrawImage(bmpOnTop, 24, 8)
        g.Dispose()

        'create bitmap placing overall
        Dim g2 As Graphics = Graphics.FromImage(bmpPictureBox)
        g2.DrawImage(bmpOnBottom, 50, 50)
        g2.Dispose()

        Return bmpPictureBox
    End Function

this is a very specific function, as you can see i use a lot of "magic numbers" instead of making it portable, but i dont know if ill be using it again anytime soon.

btw, the CreateImage function is also self made, ill post that here

    Public Function CreateImage(ByVal vName As String) As Bitmap
        'get assembly
        Dim executingAssembly As System.Reflection.Assembly = Me.GetType.Assembly.GetAssembly(Me.GetType)
        Dim myNamespace As String = executingAssembly.GetName().Name.ToString()

        'get bitmap
        Dim myBitmap As New Bitmap(Me.GetType.Assembly.GetAssembly(Me.GetType).GetManifestResourceStream(myNamespace + "." + vName))

        'set transparency and return
        myBitmap.MakeTransparent(Color.Blue)
        Return myBitmap
    End Function

i have all my bitmaps included in the solution explorer, and this function finds them there.

lastly, how i got the gdi functions to put a transparent box on top of these, is to put that code in the picturebox.paint event, instead of the form.paint event.

i dont think i left out any steps, Mike I tried to mess with your code, but conceptually i found this easier.  

I also wanted to apologize for taking so long on this, i did find some things i needed to attend to, and had to put this on the back burner.

thanks again.

~b
0
 
bramsquadAuthor Commented:
btw, i just gave you guys equal points

you both pointed me in the right direction
0

Featured Post

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 6
  • 6
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now