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.
Number of downloads: 867