Hello!
A cross-hair is a set two lines crossing(cross) each other on a perpendicular manner, usually very fine(hair) and it is an useful help for locating or aligning objects. It could be used on submarine or shooting games or mapping applications. The problem is that you don't want it to be part of the image, you want it to be floating on top it, but without leaving any permanent mark.
You want the crosshair to move, usually with the cursor, but it is a good feature to hide the cursor(mouse), so it is not in the way. And, how do you do that? Well, more complicated that it sounds. The net framework has these two methods: "cursor.hide" and "cursor.show".
They allow you to hide and to show a mouse pointer when it is on the form or on a control, but they are not too reliable, at least not on windows 7, you hide the mouse and it works ok, and suddenly when you go do something else and come back, the mouse pointer is there and refuses to go away. I tried several ways and couldn't trust that it wouldn't come back. The events "Hover", "Enter", "MouseMove", "MouseLeave" didn't work as I wanted. Some times was invisible in the wrong place and I couldn't click on the controls, because I didn't see it.
One note here. You need the default cursor showing on other parts of the program, so you need to go back and forth and something goes screwy there. Maybe you could experiment a little bit and find a solution that I didn't. My solution was to load a blank cursor from the program resources. A blank cursor is a cursor that has no image. I found one in the NET and I am including it with this project, in case
that you find another use for it. I loaded it from resources instead of loading it from a file because it gives less flickering and it goes with the program if you move it to another place.
Other problem is that you want the cross-hair or whatever image, to show on top of your image, not under it, but it can't become part of the image. If you are using a drawing program, you don't want a few lines polluting your picture, but at the same time you want other changes made to your image to stay.
Still more, you want the crosshair to move without leaving a mark. So you need to delete the old one when creating a new one on another location. How do you delete it? Depends. If you made it on a bitmap you have to replace the bitmap. If you made it on a graphics object you can clear or refresh your form. But, because it goes from pixel to pixel, you get a lot of flickering. What will happen with your other drawings? Will go to the drain too. So you can't have them on the same graphics. Besides, to clear a graphics object, you need a color, maybe the backcolor or white, but even a transparent color is not transparent; you won't see what is behind. So what to do? I decided to use two graphics objects for the same image object (bitmap,picturebox)
I was working on this electronics schematics drawing program and needed to align the components when stamping them there so, instead of a small croshair I needed a couple of full lines going across the image and from top to bottom and with a hole where they crossed each other. That way I could visually know where my new image should be located. Also the connecting wires needed to be aligned. It looks neater if the lines are parallel, horizontal and vertical with square corners instead of on an angle.
But from the thinking to the fact there is a long road. I found several problems and side effects. What you want to do is one thing, but it is a completely different thing how you get there. Having the same graphics object handing the bitmap and the crosshair didn't work. Two graphics on the same bitmap and handler also didn't work because when moving the mouse one graphics would cover the drawings on the other, and without clearing you had too many crosshairs drawn. Making it transparent would show just a black screen, So what I did I created few branches for the same mouse move event and manipulated each graphics object on a different one.
This is what I have now:
2 Mouse cursors: Blank, Eraser and default.
Graphics: Two for the same object, one for drawing and erasing, the other for the crosshair.
Here is my program. It is an electronics Schematics drawing program called "Circuits", but it could be for any other use, like computer programming flow diagram, doors or windows on a house floor plan or "Dress The Dolls" game. Here I load electronic components for disk and display them on a grid where you can select any of them and apply it at different places on the base image. I have 4 lines, two on salmon and two on cyan that extend from side to side of the screen, enclosing a rectangle where the component will be located. There is a small circle on the left-top corner of this rectangle, where you can see through to the image to find a precise point. The distance between the lines change according to the size of the thumbnail we will draw, and will change size with the size of the image selected and with the settings of a track bar that has given the size of the image.

The program has 4 radiobuttons for 4 different operations.:
. "Components" to stamp the image thumbnails on the page.
. "Titles" to write text notes at selected points.
. "Connections" to draw terminals and wires.
. "Erase" to delete areas of the image.
I didn't include an "Undo" or a "Cut-Paste" option, so you have to use the eraser to correct if you made a mistake. This is just a basic version to show you the cross-hair programming and function, and maybe you could add a crop and save routine to make an utility for circuits or other drawings and save a section only, instead of the full image.
I called the 4 radio buttons Rb1, Rb2, Rb3 and Rb4.
The procedure is like this: At form load the name of the files in the folder "Components" are added to the List "ImgArray" and the images are displayed on the datagridview cells. Also a thumbnail is created from the first image. After the program has finished loading you would look at the images and click on the one you want to use. This sets the index(position) on the list that contains the names of the images, depending on the cell clicked. The image is resized according to the trackbar value. and creates a new thumbnail image and stores it on MyThumb using the name stored on the list and reading a file from disk. After selecting the component you are ready to paste it on the diagram (PictureBox).
The First operation is to paste the thumbnail and it is done with the default radiobutton selected(Rb1); when you enter the picturebox area, the mouse disappears and the guide lines (crosshair) appear. You select a position and click. There you get the image pasted. If you click on another point, you will paste a clone of the first image and so on. Let's say, you selected a resistor and pasted it there; now you align the cross hair at the same level and besides it and click again, you have 2 resistors aligned side by side. Now you select another component, lets say a battery, you find a proper spot and click. Now a transistor or a capacitor... and so on.
When you have some components there, you would select Rb3, this sets the option for drawing the connections You align the small circle at the end of a terminal and click. This will draw a small ring there and will set a point on an array of points that will be converted to a graphics path to draw some lines. Now you find another terminal that should be connected to this one and click. You get another ring and another point is added to the array of points. When you have enough points (without going back and forth), you will press the "Draw" button. It will convert the points into a graphics path and will draw the lines from point to point. Next, you will align again with another terminal, and the next and the next and click draw and so on. For all the connections.
If you made a mistake, you click on "Erase" and go to the picturebox. An eraser cursor appears and if you click and drag you will enclose an area surrounded by a magenta trace. When you release the cursor, the enclosed area is cleared. If you type something on the textbox, select "Titles" and click the mouse (normal mouse) on the picturebox, and then press "Write" it will write the title at that point.

I tried making the sample component images with little margins. That will help to align them side by side, close together. They can also be resized with the trackbar, so you will have a new size aligning box on the cross hair.
From the menu, you can save your image, you can flip and rotate the whole image and you can clear it. There are also 4 buttons to rotate and flip the thumbnail, this way you can have less components on disk and still be able to adjust size and orientation. Also there is an image of the component to use.
Let's see the code:
Libraries, Global Variables and Initial Values for the variables and conditions:
#Region "Imports"
Imports System.Drawing.Imaging
Imports System.Drawing.Drawing2D
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Collections
Imports System.Collections.Specialized
Imports System.Windows
#End Region
Public Class Form1
#Region "Global"
Dim ImgArray As New List(Of String)
Dim Position As Integer = 0
Private imgSize As Integer = 25
Dim Pictures As Integer = 0
Dim MyPath As New GraphicsPath
Dim BendPoints As New List(Of Point)
Dim MyThumb As Image
Dim Reload As Image
Dim bmx As Bitmap
Dim resized As Bitmap
Dim grCross As Graphics
'arrays of points
Private ThePts1(2) As Point
Private TracedPath As New GraphicsPath(Drawing2D.FillMode.Winding)
Private ThePoints() As Point
Private NumPoints As Integer
Dim Zero As Point
'create a pen object to draw the lines
Dim ErasePen As New Pen(Color.Magenta, 2)
#End Region
#Region "Initial Values"
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Reload = Work.Image.Clone
ToolTip1.SetToolTip(Rb1, "Will Apply Selected Image From Chart" & vbNewLine & "At Last Mouse Up Position")
ToolTip1.SetToolTip(Rb4, "Will Erase Area Around Mouse Path")
DataVImages.DefaultCellStyle.BackColor = Color.White 'RosyBrown
scan()
If ImgArray.Count > 0 Then
LoadImages()
End If
' The default operation is to draw components, so we set the cursor to blank for the picturebox
' And generate an initial thumbnail to avoid error if clicked the picturebox without selecting a component
'Load a mouse from resources
If Rb1.Checked = True Then
Dim ms As New System.IO.MemoryStream(My.Resources.Blank)
Work.Cursor = New Cursor(ms)
End If
'Create a thumbnail image from the component
MyThumb = generateThumbnail(Image.FromFile(Environment.CurrentDirectory & "\Components\" & ImgArray(0)), tbSize.Value)
pbThumb.Image = MyThumb.Clone
' PictureBox1.Cursor = New Cursor(Application.StartupPath & "\Experiment.cur")
End Sub
#End Region
When we start an operation it is good to clear the arrays, we don't want values there that don't exist or that are duplicated. Here is not necessary but on some instances you would like to reload or rescan a directory. I have set a subdirectory called "Components" under the application directory. This way if we save any file with ".jpg" extension, it won't be loaded as a component. Our components are jpeg files cropped from charts and images. If you need, I can modify an utility I have; that would help you to crop smaller sections of a picture and put it here to use. Let me know, and I will make it and post it here as an attachment.
The following block of code scans the directory, fills the list with component names, creates images from the files, re-sizes them and displays them on the datagridview cells.
Here is a tip that you may find useful. When you don't want the last row of cells on a datagridview with a red "X" on them you could set the datagridview property "AllowUserToAddRows" to false.
#Region "Find and load Components"
Private Sub scan()
ImgArray.Clear()
Try
'Scan directory for images
Dim di As New IO.DirectoryInfo(Environment.CurrentDirectory & "\Components")
Dim aryFi As IO.FileInfo() = di.GetFiles("*.jpg")
' How Many?
Pictures = aryFi.Count
Dim fi As IO.FileInfo
Dim aryFi2 As IO.FileInfo() = di.GetFiles("*.jpg")
'Add them to the work array
For Each fi In aryFi2
ImgArray.Add(fi.Name) 'Original jpg Files
Next
If ImgArray.Count = 0 Then MessageBox.Show("Didn't Find Any Jpg Fles In Directory, Sorry")
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try
End Sub
After finding the files and filling the arrays and list we calculate the columns and rows of the datagridview and re-size the display images to fit there. In this program the operation is not repeated, but still is good idea to clear the datagridview of any former data. Erase rows and columns and create them again. The balance between rows x columns minus elements is the number of empty cells at the last row. We "BLANK" them so they don't show an "X".
Private Sub LoadImages()
Try
If ImgArray Is Nothing Then
Return
End If
If Me.WindowState = FormWindowState.Minimized Then
Return
End If
' If everything is fine then create columns and rows is the datagridview, set the sizes of the images and load them.
' In case it takes long we use the wait cursor
Me.Cursor = Cursors.WaitCursor
DataVImages.Rows.Clear()
DataVImages.Columns.Clear()
' And start again
Dim ColumnsThatFit As Integer = (DataVImages.Width - 5) \ (imgSize + 15)
Dim numRows As Integer = 0
' using the size and margins around picture.
Dim ImagesToDisplay As Integer = ImgArray.Count
'Then create enough cells for them
numRows = CInt(Math.Ceiling(CDbl(ImgArray.Count) / CDbl(ColumnsThatFit)))
Dim CellsForPictureNumber As Integer = numRows * ColumnsThatFit
' Dynamically create the columns
For index As Integer = 0 To ColumnsThatFit - 1
Dim dataGridViewColumn As New DataGridViewImageColumn()
DataVImages.Columns.Add(dataGridViewColumn)
DataVImages.Columns(index).Width = imgSize + 15
Next
' Create the rows
For index As Integer = 0 To numRows - 1
DataVImages.Rows.Add()
DataVImages.Rows(index).Height = imgSize + 8
Next
' Create the indexes for the rows and columns, so we can locate our images
Dim columnIndex As Integer = 0
Dim rowIndex As Integer = 0
For index As Integer = 0 To (ImagesToDisplay - 1)
' Load the image from the file and add to the DataGridView
Dim img As Image = ResizeTheImage(Image.FromFile(Environment.CurrentDirectory & "\Components\" & ImgArray(index)), 25, 25,
False)
DataVImages.Rows(rowIndex).Cells(columnIndex).Value = img
' Have we reached the end column? if so then start on the next row
If columnIndex = ColumnsThatFit - 1 Then
rowIndex += 1
columnIndex = 0
Else
columnIndex += 1
End If
Next
' Blank the unused cells
If CellsForPictureNumber > ImagesToDisplay Then
For index As Integer = 0 To CellsForPictureNumber - ImagesToDisplay - 1
Dim dataGridViewCellStyle As New DataGridViewCellStyle()
dataGridViewCellStyle.NullValue = Nothing
dataGridViewCellStyle.Tag = "BLANK"
DataVImages.Rows(rowIndex).Cells(columnIndex + index).Style = dataGridViewCellStyle
Next
End If
Catch ex As Exception
MessageBox.Show(ex.ToString)
Me.Cursor = Cursors.Default
End Try
Me.Cursor = Cursors.Default
End Sub
Create the images for display. The images that we will display are generated from the images on disk. They are thumbnails of the other
images. And we re-size them using the width and height to decide which one to use.
Private Function ResizeTheImage(ByVal Imgx As Image, ByVal width As Integer, ByVal height As Integer, ByVal onlyResizeIfWider As
Boolean) As Image
Using image1 As Image = Imgx
' Prevent using images internal thumbnail
image1.RotateFlip(RotateFlipType.Rotate180FlipNone)
image1.RotateFlip(RotateFlipType.Rotate180FlipNone)
Dim W1 As Integer = Imgx.Width
Dim H1 As Integer = Imgx.Height
If onlyResizeIfWider = True Then
If image1.Width <= width Then
width = image1.Width
End If
End If
Dim newHeight As Integer = image1.Height * width \ image1.Width
If newHeight > height Then
' Resize with height instead
width = image1.Width * height \ image1.Height
newHeight = height
End If
Dim NewImage As Image = image1.GetThumbnailImage(width, newHeight, Nothing, IntPtr.Zero)
Return NewImage
End Using
End Function
When we finish loading, then we are ready to start drawing. We can select a component image to stamp on the large image. When we click on
it we know which image to use, because we have the column and row indexes.
Private Sub dataVImages_CellClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles
DataVImages.CellClick
Dim i, j As Integer
i = e.RowIndex
j = e.ColumnIndex
Position = i * DataVImages.Columns.Count + j ' + 1 'index of imagearray
MyThumb = generateThumbnail(Image.FromFile(Environment.CurrentDirectory & "\Components\" & ImgArray(Position)), tbSize.Value)
pbThumb.Image = MyThumb.Clone
'We work on a copy not the original
End Sub
#End Region
When we press the mouse button on the drawing area we save a point to an structure. This value is to keep the position where the mouse was clicked and is the point where we will apply our thumbnails. This point is also used for drawing the text and to erase a section. To erase we enclose an area with the mouse and flood it with white. This point is updated as we move (On mousemove) and a new point created, so we draw anohter point from here to the next and so on. When we have all the points accounted for, we convert them on a graphics path and clear the area inside it.
Private Sub Work_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Work.MouseUp
If Rb4.Checked = False Then Exit Sub
Dim MyGraphics As Graphics
'Create a white brush to paint our area on white
Dim TransBrush As New SolidBrush(Color.White)
' Exit if we're not selecting an Area.
If ThePoints Is Nothing Then Exit Sub
' Close the region.
If (ThePoints(0).X <> ThePoints(NumPoints).X) Or _
(ThePoints(0).Y <> ThePoints(NumPoints).Y) _
Then
' Save next point to the array that defines our area.
NumPoints += 1
ReDim Preserve ThePoints(NumPoints) 'And change the dimension of the array as we add new points
ThePoints(NumPoints).X = ThePoints(0).X
ThePoints(NumPoints).Y = ThePoints(0).Y
End If
' Set points into a Path.
TracedPath.AddLines(ThePoints)
Try
Dim bm As New Bitmap(Work.Image)
MyGraphics = Graphics.FromImage(bm)
MyGraphics.FillPolygon(TransBrush, ThePoints) ' We flood the enclosed region on white
Work.Image = bm
ThePoints = Nothing
GC.Collect()
Catch
MessageBox.Show("Error")
ThePoints = Nothing
Exit Sub
End Try
End Sub
Basically our program has 4 operations. We select them by checking 4 radiobuttons. We already saw the erase function that clears an area when the mouse button is released. The other operations are: Draw (stamp) "components", Trace the wiring and connection terminals ("Connections") and print notes or legends "Titles". On the next block of code we print our components on the page. The mouseDown event handler has few if conditions to decide what operation to perform and what mouse cursor to show.
When we press the left button inside the image area we record the position where it happened by setting the values for x and y for a variable called zero. This point will be a future reference point and is where our operation takes place. If we have the radiobutton one checked, then we stamp an image thumbnail on the larger image. If we had the connections checkbox checked (Rb3) we draw an small circle where the mouse when down, and add a point to an array to later create a path for tracing lines, these are connection terminals or on due case the last point tell us where to print a string.
#Region "Draw"
Private Sub Work_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Work.MouseDown
'Our erase (rb4 checked)routine draws a magenta line to enclose an area, so we erase it before continuing
If Rb4.Checked = True Then Refresh()
' And register our points
ThePts1(0).X = e.X
ThePts1(0).Y = e.Y
Zero.X = e.X
Zero.Y = e.Y
Try
bmx = New Bitmap(Work.Image)
' If we selected components
If Rb1.Checked = True Then
' Call the function DrawWatermark withwhith the parameters that tell it what to print and where
DrawWatermark(MyThumb, Work.Image, ThePts1(0).X, ThePts1(0).Y)
Refresh()
End If
'If we selected connections, we draw a small circle where we clicked
If Rb3.Checked = True Then
Dim gf As Graphics = Graphics.FromImage(Work.Image)
gf.DrawArc(Pens.Black, e.X - 2, e.Y - 2, 4, 4, 0, 360)
BendPoints.Add(New Point(e.X, e.Y))
End If
' If we selected erase, set the path for erasing
If Rb4.Checked = True Then
TracedPath.Reset()
' Erase any previous drawing and Save the starting point.
NumPoints = 0
ReDim ThePoints(NumPoints)
ThePoints(NumPoints).X = e.X
ThePoints(NumPoints).Y = e.Y
End If
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try
End Sub
The datagridview shows all the available icons to stamp. I have here electronic symbols, but I could have windows, doors, toilets, sofas, or I could have hasts, scarves, glasses, wigs, shirts, or I could have shapes like rectangles, ellipses, rhombus, arrows for programming flux sketches. I can select from the available ones by clicking on it. If I press ok besides the component radiobutton, the program uses the last mouseup position to print the thumbnail. If instead I press on the large image, then the new point will be used when I press OK.

#Region "Stamp WaterMark"
Function generateThumbnail(ByVal bmp As Bitmap, ByVal newWidth As Integer) As Image
Dim newHeight As Integer
Dim W1 As Integer = bmp.Width
Dim H1 As Integer = bmp.Height
If W1 >= H1 Then
newHeight = (bmp.Height * newWidth) / bmp.Width
resized = New Bitmap(newWidth, newHeight)
Else
newHeight = newWidth
newWidth = (bmp.Width * newHeight) / bmp.Height
End If
resized = New Bitmap(newWidth, newHeight)
Dim gx As Graphics = Graphics.FromImage(resized)
gx.SmoothingMode = SmoothingMode.HighQuality
gx.CompositingQuality = CompositingQuality.HighQuality
gx.InterpolationMode = InterpolationMode.High
gx.DrawImage(bmp, New Rectangle(0, 0, resized.Width, resized.Height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel)
gx.Dispose()
Return resized
' bmp.Dispose()
End Function
' Copy the watermark image over the result image.
Private Sub DrawWatermark(ByVal watermark_bm As Bitmap, _
ByVal result_bm As Bitmap, ByVal x As Integer, ByVal y As Integer)
Dim ALPHA As Byte = 255
' Set the watermark's pixels' Alpha components.
Dim clr As Color
For py As Integer = 0 To watermark_bm.Height - 1
For px As Integer = 0 To watermark_bm.Width - 1
clr = watermark_bm.GetPixel(px, py)
watermark_bm.SetPixel(px, py, Color.FromArgb(ALPHA, clr.R, clr.G, clr.B)/>)
Next px
Next py
' watermark_bm.MakeTransparent(watermark_bm.GetPixel(2, 2))
' Copy onto the result image.
Dim gr As Graphics = Graphics.FromImage(result_bm)
gr.DrawImage(watermark_bm, x, y)
End Sub
#End Region
I have supplied few functions to modify our thumbnail, this way we don't need to have extra images for horizontal and vertical or for mirror image, or upside down. Also we have a resize function for the thumbnail. We can also modify the whole page:
#Region "Rotate"
'Rotate or Flip the Whole page
Private Sub FlipRotate(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles FlipHorizontal.Click, FlipVertical.Click,
Rotate90R.Click, Rotate90L.Click
' resulting bitmap.
Dim bmrotate As New Bitmap(Work.Image)
If sender Is FlipHorizontal Then bmrotate.RotateFlip(RotateFlipType.RotateNoneFlipX)
If sender Is FlipVertical Then bmrotate.RotateFlip(RotateFlipType.RotateNoneFlipY)
If sender Is Rotate90R Then bmrotate.RotateFlip(RotateFlipType.Rotate90FlipNone)
If sender Is Rotate90L Then bmrotate.RotateFlip(RotateFlipType.Rotate270FlipNone)
Work.Image = bmrotate
End Sub
'Rotate Component
Private Sub btnL_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnL.Click, btnR.Click, btnV.Click,
btnH.Click
Dim bmrotate As New Bitmap(MyThumb)
If sender Is btnH Then bmrotate.RotateFlip(RotateFlipType.RotateNoneFlipX)
If sender Is btnV Then bmrotate.RotateFlip(RotateFlipType.RotateNoneFlipY)
If sender Is btnR Then bmrotate.RotateFlip(RotateFlipType.Rotate90FlipNone)
If sender Is btnL Then bmrotate.RotateFlip(RotateFlipType.Rotate270FlipNone)
pbThumb.Image = bmrotate
MyThumb = bmrotate
End Sub
#End Region
Private Sub ThumbSize(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tbSize.Scroll
Label11.Text = "Size:" & tbSize.Value.ToString & " PX"
End Sub
Private Sub tbSize_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles tbSize.MouseUp
MyThumb = generateThumbnail(Image.FromFile(Environment.CurrentDirectory & "\Components\" & ImgArray(Position)), tbSize.Value)
pbThumb.Image = MyThumb.Clone
End Sub
OK, now lets say that we already have some resistors, coils, batteries and transistors stamped at several places. We need now to connect them to complete the circuit. So we will do this by sections, wherever we can without branching. WE select the radiobutton "Connections" and we click on the free end of the terminal of the elements. After clicking on some places and creating an array of points we can press the button OK besides "Connections". The program will draw lines from point to point. You should be careful here because if you click on a point across and on top of a former component drawn, you will have an undesired line and you will have to erase it and the component underneath in order to fix it. The parallel lines look nicer and in good order. So do it properly. The "Cross hair" is there for this purpose, to align and locate the components and the wiring. You have have two of them, so, NO EXCUSE!
Private Sub btnConnections_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConnections.Click
If Rb3.Checked = False Then Exit Sub
If BendPoints.Count = 0 Then 'If we cleared or forgot to click on the terminals, we get a message
MessageBox.Show("Please Select Some Points")
Exit Sub
End If
Try
' Otherwise we draw the lines
Dim gfr As Graphics = Graphics.FromImage(Work.Image)
For m As Integer = 0 To BendPoints.Count - 2
gfr.DrawLine(Pens.Black, BendPoints(m), BendPoints(m + 1))
Refresh()
Next
Catch ex As Exception
End Try
BendPoints.Clear()
End Sub
Now we click on some more points and draw new lines, and repeat until done. We can also add more components or add some titles, names or legends for the circuit: There is a multiline textbox that accept our notes and we can print them on the image. We do this by clicking before or after typing, but before clicking OK. The text will come on blue letters wherever was our last mouseup point.
For draw the titles we use the function DrawString. We have the point already saved and if we have typed something on the textbox, that string will appear on blue at the selected point.
Private Sub btnText_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnText.Click
If Rb2.Checked = False Then Exit Sub
Dim Afont As New Font("Comic Sans MS", 16, FontStyle.Bold, GraphicsUnit.Pixel)
Dim gfr As Graphics = Graphics.FromImage(Work.Image)
Dim pt As New PointF(ThePts1(0).X - 3, ThePts1(0).Y - 6)
gfr.DrawString(TextBox1.Text, Afont, Brushes.MediumBlue, pt)
Refresh()
End Sub
When we switch from one operation we need to set some conditions, those are mostly on the checked changed events. I also included a couple of "bells and whistles". I have two extra cursors on my resources. One of them is a invisible cursor, it is set when we have the crosshairs moving, so it doesn't interfere. The other is an eraser, to use when clearing an area:
#Region "Operation"
'To start fresh.
Private Sub ClearToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Clear.Click
Work.Image = Reload.Clone
End Sub
'Set invisible mouse from resources
Private Sub Rb1_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Rb1.CheckedChanged
If Rb1.Checked = True Then
Dim ms As New System.IO.MemoryStream(My.Resources.Blank)
Work.Cursor = New Cursor(ms)
End If
End Sub
' Return to normal cursor.
Private Sub Rb2_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Rb2.CheckedChanged
If Rb2.Checked = True Then Work.Cursor = Cursors.Default
End Sub
Private Sub Rb3_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Rb3.CheckedChanged
If Rb3.Checked = True Then
Dim ms As New System.IO.MemoryStream(My.Resources.Blank)
Work.Cursor = New Cursor(ms)
BendPoints.Clear()
End If
End Sub
' Or the eraser cursor.
Private Sub Rb4_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Rb4.CheckedChanged
If Rb4.Checked = True Then ' Work.Cursor = Cursors.Default
Dim ms As New System.IO.MemoryStream(My.Resources.EraserLite)
Work.Cursor = New Cursor(ms)
End If
End Sub
#End Region
Let's go by sections:
First we set the mouse pointers depending on the operation. The one from resources is the blank one that we use for drawing. The other is the eraser that we use when erasing and the default. Then we set the crosshair lines.
#Region "While Moving"
#Region "Operation"
' To clear image and set appropriate cursors. The default and the Blank one.
Private Sub ClearToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Clear.Click
Work.Image = Reload.Clone
End Sub
Private Sub Rb2_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Rb2.CheckedChanged
If Rb2.Checked = True Then Work.Cursor = Cursors.Default
End Sub
Private Sub Rb1_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Rb1.CheckedChanged
If Rb1.Checked = True Then
Dim ms As New System.IO.MemoryStream(My.Resources.Blank)
Work.Cursor = New Cursor(ms)
End If
End Sub
Private Sub Rb3_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Rb3.CheckedChanged
If Rb3.Checked = True Then
Dim ms As New System.IO.MemoryStream(My.Resources.Blank)
Work.Cursor = New Cursor(ms)
BendPoints.Clear()
End If
End Sub
#End Region
The two croshairs are produced on the mousemove handler. But the mousemove subroutine has several functions assigned. The mouse could just be hovering or could be dragging. Besides, we have different cursors for different operations set. So, what we do is to branch out for each operation. A group of "If" according to the radiobuttons settings. most of them are empty, but we need them to weed out situations. The changes of cursors are already taken care of on the checked changed events.
Private Sub Work_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Work.MouseMove
If (e.Button = MouseButtons.Left) Then
'Set our arrays of points
ThePts1(1).X = e.X
ThePts1(1).Y = e.Y
NumPoints += 1
ReDim Preserve ThePoints(NumPoints)
ThePoints(NumPoints).X = e.X
ThePoints(NumPoints).Y = e.Y
If Rb1.Checked = True Then
' Empty
ElseIf Rb2.Checked = True Then
' Empty
ElseIf Rb3.Checked = True Then
' Empty
ElseIf Rb4.Checked = True Then
' Create graphics for displaying eraser enclosed area
grCross = Work.CreateGraphics
grCross.DrawLine(ErasePen, ThePts1(0).X, ThePts1(0).Y, ThePts1(1).X, ThePts1(1).Y)
End If
'Make 2nd points first points
ThePts1(0) = ThePts1(1)
Else
Work.Refresh()
' And show our two cursors at a distance from each other according to the size of our thumbnail
If Rb2.Checked = False AndAlso Rb4.Checked = False Then
Try
grCross = Work.CreateGraphics
grCross.DrawLine(Pens.Salmon, e.X, 5, e.X, e.Y - 5)
grCross.DrawLine(Pens.Salmon, e.X, e.Y + 5, e.X, Work.Height - 5)
grCross.DrawLine(Pens.Cyan, 5, e.Y + MyThumb.Height, Work.Width - 5, e.Y + MyThumb.Height)
grCross.DrawLine(Pens.Cyan, e.X + MyThumb.Width, 5, e.X + MyThumb.Width, Work.Height - 5)
grCross.DrawLine(Pens.Salmon, e.X + 5, e.Y, Work.Width - 5, e.Y)
grCross.DrawLine(Pens.Salmon, 5, e.Y, e.X - 5, e.Y)
grCross.DrawArc(Pens.Salmon, e.X - 4, e.Y - 4, 8, 8, 0, 360)
grCross.FillEllipse(Brushes.Transparent, e.X - 3, e.Y - 3, 6, 6)
' grCross.DrawImage(MyThumb, e.X, e.Y)
Catch
End Try
End If
End If
End Sub
I cropped my images from other images. At the beginning I made them square because it was easier to re-size to a proper dimension, but depending on the shape of the element, some of them had too much white area around, so you couldn't set images too close because they would cover the former ones. Then I cut them closer and resized them differently using the longest side and comparing it with the trackbar value. Here we also generate a watermark but we don't want it transparent, so we use the alpha value set to 255 and them apply it.
I have included some accessory routines and functions:
#Region "Save And Exit"
' To save the schematics
Private Sub ToolStripMenuItem1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripMenuItem1.Click
Dim i As Integer
Dim str As String = "Dummy"
For i = 1 To 1000
If i < 10 Then str = "Schem_00" & i.ToString & ".jpg"
If i > 9 And i < 100 Then str = "Schem_0" & i.ToString & ".jpg"
If i > 99 Then str = "Schem_" & i.ToString & ".jpg"
If i > 900 Then MsgBox("You Have Over 900 Pictures, Please Check And Delete Unnecessary Ones")
If Not System.IO.File.Exists(str) Then
Try
Work.Image.Save(str, System.Drawing.Imaging.ImageFormat.Jpeg) 'Whole
Catch Ex As Exception
MsgBox("Could Not Write To Location")
End Try
Exit For
End If
Next
End Sub
' Exit Program
Private Sub ExitToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ExitToolStripMenuItem.Click
Application.Exit()
End Sub
'View output folder
Private Sub ToolStripMenuItem3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripMenuItem3.Click
Shell("Explorer " & AppDomain.CurrentDomain.BaseDirectory, AppWinStyle.NormalFocus)
End Sub
#End Region
Also, in order to allow for other uses, I have set 3 routines. One for drawing a set of Cartesian axis on the center of the panel. One for drawing rulers on inches at top and left of image, and one for drawing millimeter rulers on top and left. We are assuming 96 dpi for the ruler on inches, and 50 millimeters every two inches. The millimeters rulers are a little bit inaccurate, but it avoids using floats for calculating positions. It will miss 16 mm on a meter. The inches is more accurate, but assumes printer resolution. Nevertheless they are useful:

#Region "Axis"
Private Sub DrawAxisToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
DrawAxisToolStripMenuItem.Click
Dim zero As New Point
zero.X = Panel1.Width / 2
zero.Y = Panel1.Height / 2
Dim rx As Integer = Panel1.Width / 2 ' Need Code here for clicking
Dim ry As Integer = Panel1.Height / 2 ' Need Code here for clicking
Dim gfr As Graphics = Graphics.FromImage(Work.Image)
gfr.DrawLine(Pens.Black, 0, ry, Work.Width, ry)
gfr.DrawLine(Pens.Black, rx, 0, rx, Work.Height)
' Refresh()
For m As Integer = rx Mod 25 To Work.Width Step 25
If m Mod 50 = 0 Then
gfr.DrawLine(Pens.Black, m, ry - 4, m, ry + 4)
Else
gfr.DrawLine(Pens.Black, m, ry - 2, m, ry + 2)
End If
Next
For m As Integer = ry Mod 25 To Work.Height Step 25
If m Mod 50 = 0 Then
gfr.DrawLine(Pens.Black, rx - 4, m, rx + 4, m)
Else
gfr.DrawLine(Pens.Black, rx - 2, m, rx + 2, m)
End If
Next
Refresh()
End Sub
#End Region
#Region "Rulers"
Private Function Centimetres(ByVal img As Image) As Image
Dim Afont As New Font("Arial", 8, FontStyle.Regular, GraphicsUnit.Pixel)
Dim gfr As Graphics = Graphics.FromImage(img)
' Dim Cm As Single = 5.08
For m As Integer = 0 To Work.Image.Height - 1 Step 5
gfr.DrawLine(Pens.Black, 0, m, 2, m)
Select Case m Mod 40
Case 20
gfr.DrawLine(Pens.Black, 0, m, 5, m)
Case 0
gfr.DrawLine(Pens.Black, 0, m, 9, m)
If m > 6 Then gfr.DrawString(((m) \ 40).ToString, Afont, Brushes.Black, 11, (m) - 6)
End Select
Next
For m As Integer = 0 To Work.Image.Width - 1 Step 5
gfr.DrawLine(Pens.Black, m, 0, m, 2)
Select Case m Mod 40
Case 20
gfr.DrawLine(Pens.Black, m, 0, m, 5)
Case 0
gfr.DrawLine(Pens.Black, m, 0, m, 9)
If m > 3 Then gfr.DrawString(((m + 1) \ 40).ToString, Afont, Brushes.Black, m - 3, 11)
End Select
Next
gfr.DrawString("0", Afont, Brushes.Black, 2, 1)
Return img
End Function
Private Function Inches(ByVal img As Image) As Image
Dim Afont As New Font("Arial", 8, FontStyle.Regular, GraphicsUnit.Pixel)
Dim gfr As Graphics = Graphics.FromImage(img)
For m As Integer = 0 To Work.Image.Height - 1 Step 6
Select Case (m \ 6) Mod 16
Case 1, 3, 5, 7, 9, 11, 13, 15
gfr.DrawLine(Pens.Black, 0, m, 2, m)
Case 0, 2, 6, 10, 14
gfr.DrawLine(Pens.Black, 0, m, 4, m)
Case 4, 12
gfr.DrawLine(Pens.Black, 0, m, 6, m)
Case 8
gfr.DrawLine(Pens.Black, 0, m, 8, m)
End Select
If m > 0 Then
If m Mod 96 = 0 Then
gfr.DrawString(((m + 1) \ 96 Mod 96).ToString, Afont, Brushes.Black, 11, (m + 1) - 6)
gfr.DrawLine(Pens.Black, 0, m, 9, m)
End If
End If
Next
For m As Integer = 0 To Work.Image.Width - 1 Step 6
Select Case (m \ 6) Mod 16
Case 1, 3, 5, 7, 9, 11, 13, 15
gfr.DrawLine(Pens.Black, m, 0, m, 2)
Case 0, 2, 6, 10, 14
gfr.DrawLine(Pens.Black, m, 0, m, 4)
Case 4, 12
gfr.DrawLine(Pens.Black, m, 0, m, 6)
Case 8
gfr.DrawLine(Pens.Black, m, 0, m, 8)
End Select
If m > 0 Then
If m Mod 96 = 0 Then
gfr.DrawString(((m + 1) \ 96 Mod 96).ToString, Afont, Brushes.Black, m - 3, 11)
gfr.DrawLine(Pens.Black, m, 0, m, 9)
Else
End If
End If
Next
gfr.DrawString("0", Afont, Brushes.Black, 3, 3)
Return img
End Function
Private Sub DrawRulersToolStripMenuItem_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
DrawRulersToolStripMenuItem.Click
Work.Image = Inches(Work.Image)
End Sub
Private Sub ToolStripMenuItem2_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripMenuItem2.Click
Work.Image = Centimetres(Work.Image)
End Sub
#End Region
I hope this will be interesting for some of you and maybe useful.
Please see the attachment.
Thank you,
ricardosms.
Attached File(s)
-
Circuit.zip (277.99K)
Number of downloads: 322





MultiQuote



|