Download Image under the Mouse pointer via WebBrowser - vb.net

I'm navigating to Google Images using a WebBrowser control. The aim is to be able to right click on any image and download and populate a PictureBox background.
I have my own ContextMenuStrip with Copy on it and have disabled the built in context menu.
The issue I am having is that the coordinate returned from CurrentDocument.MouseMove are always relative to the first (top left) image.
So my code works correctly if the Image I want is the very first image on the page, however clicking on any other Images always returns the coordinates of the first image.
It would appear that the coordinates are relative to each Image rather than the page.
Private WithEvents CurrentDocument As HtmlDocument
Dim MousePoint As Point
Dim Ele As HtmlElement
Private Sub Google_covers_Load(sender As Object, e As EventArgs) Handles MyBase.Load
WebBrowser1.IsWebBrowserContextMenuEnabled = False
WebBrowser1.ContextMenuStrip = ContextMenuStrip1
End Sub
Private Sub WebBrowser1_Navigated(sender As Object, e As WebBrowserNavigatedEventArgs) Handles WebBrowser1.Navigated
CurrentDocument = WebBrowser1.Document
End Sub
Private Sub CurrentDocument_MouseMove(sender As Object, e As HtmlElementEventArgs) Handles CurrentDocument.MouseMove
MousePoint = New Point(e.MousePosition.X, e.MousePosition.Y)
Me.Text = e.MousePosition.X & " | " & e.MousePosition.Y
End Sub
Private Sub ContextMenuStrip1_Opening(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles ContextMenuStrip1.Opening
Ele = CurrentDocument.GetElementFromPoint(MousePoint)
If Ele.TagName = "IMG" Then
CopyToolStripMenuItem.Visible = True
Else
CopyToolStripMenuItem.Visible = False
End If
End Sub
Private Sub CopyToolStripMenuItem_Click(sender As System.Object, e As System.EventArgs) Handles CopyToolStripMenuItem.Click
Dim ToImg = Ele.GetAttribute("src")
mp3_row_edit.PictureBox1.BackgroundImage = New System.Drawing.Bitmap(New IO.MemoryStream(New System.Net.WebClient().DownloadData(ToImg)))
ToImg = Nothing
End Sub

This code allow to use a standard WebBrowser control to navigate to the Google Image search page and select/download an Image with a right-click of the Mouse.
To test it, drop a WebBrowser Control and a FlowLayoutPanel on a Form and navigate to a Google Image search page.
Things to know:
WebBrowser.DocumentCompleted: This event is raised each time one of the Sub-Documents inside a main HtmlDocument page is completed. Thus, it can be raised multiple times. We need to check whether the WebBrowser.ReadyState = WebBrowserReadyState.Complete.
Read these note about this: How to get an HtmlElement value inside Frames/IFrames?
The images in the Google search page can be inserted in the Document in 2 different manners: both using a Base64Encoded string and using the classic src=[URI] format. We need to be ready to get both.
The mouse click position can be espressed in either absolute or relative coordinates, referenced by the e.ClientMousePosition or e.OffsetMousePosition.
Read the notes about this feature here: Getting mouse click coordinates in a WebBrowser Document
The WebBrowser emulation mode can be important. We should use the most recent compatible mode available in the current machine.
Read this answer and apply the modifications needed to have the most recent Internet Explorer mode available: How can I get the WebBrowser control to show modern contents?.
Note that an event handler is wired up when the current Document is completed and is removed when the Browser navigates to another page. This prevents undesired calls to the DocumentCompleted event.
When the current Document is complete, clicking with the right button of the Mouse on an Image, creates a new PictureBox control that is added to a FlowLayouPanel for presentation.
The code in the Mouse click handler (Protected Sub OnHtmlDocumentClick()) detects whether the current image is a Base64Encoded string or an external source URI.
In the first case, it calls Convert.FromBase64String to convert the string into a Byte array, in the second case, it uses a WebClient class to download the Image as a Byte array.
In both cases, the array is then passed to another method (Private Function GetBitmapFromByteArray()) that returns an Image from the array, using Image.FromStream() and a MemoryStream initialized with the Byte array.
The code here is not performing null checks and similar fail-proof tests. It ought to, that's up to you.
Public Class frmBrowser
Private WebBrowserDocumentEventSet As Boolean = False
Private base64Pattern As String = "base64,"
Private Sub frmBrowser_Load(sender As Object, e As EventArgs) Handles MyBase.Load
WebBrowser1.ScriptErrorsSuppressed = True
WebBrowser1.IsWebBrowserContextMenuEnabled = False
End Sub
Private Sub WebBrowser1_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
If WebBrowser1.ReadyState = WebBrowserReadyState.Complete AndAlso WebBrowserDocumentEventSet = False Then
WebBrowserDocumentEventSet = True
AddHandler WebBrowser1.Document.MouseDown, AddressOf OnHtmlDocumentClick
End If
End Sub
Protected Sub OnHtmlDocumentClick(sender As Object, e As HtmlElementEventArgs)
Dim currentImage As Image = Nothing
If Not (e.MouseButtonsPressed = MouseButtons.Right) Then Return
Dim source As String = WebBrowser1.Document.GetElementFromPoint(e.ClientMousePosition).GetAttribute("src")
If source.Contains(base64Pattern) Then
Dim base64 As String = source.Substring(source.IndexOf(base64Pattern) + base64Pattern.Length)
currentImage = GetBitmapFromByteArray(Convert.FromBase64String(base64))
Else
Using wc As WebClient = New WebClient()
currentImage = GetBitmapFromByteArray(wc.DownloadData(source))
End Using
End If
Dim p As PictureBox = New PictureBox() With {
.Image = currentImage,
.Height = Math.Min(FlowLayoutPanel1.ClientRectangle.Height, FlowLayoutPanel1.ClientRectangle.Width)
.Width = .Height,
.SizeMode = PictureBoxSizeMode.Zoom
}
FlowLayoutPanel1.Controls.Add(p)
End Sub
Private Sub WebBrowser1_Navigating(sender As Object, e As WebBrowserNavigatingEventArgs) Handles WebBrowser1.Navigating
If WebBrowser1.Document IsNot Nothing Then
RemoveHandler WebBrowser1.Document.MouseDown, AddressOf OnHtmlDocumentClick
WebBrowserDocumentEventSet = False
End If
End Sub
Private Function GetBitmapFromByteArray(imageBytes As Byte()) As Image
Using ms As MemoryStream = New MemoryStream(imageBytes)
Return DirectCast(Image.FromStream(ms).Clone(), Image)
End Using
End Function
End Class

Related

How to degrade similar tool strip button procedures to a single in VB.NET

I'm using ToolStrip and it's buttons to change GUI structure with an example code how i change GUIs with tool strip buttons. Are there any methods to use them more easy way like when clicked event of ToolStripButton handled it can call a single procedure etc.? In current case it seems i'm coding in a bad way.
For example if user click the Home button it highlights the button as selected and hides other panel elements and make visible Home's panel.
Private Sub tsbHome_Click(sender As Object, e As EventArgs) Handles tsbHome.Click
tsbHome.Checked = True
tsbTools.Checked = False
tsbReport.Checked = False
tsbAnalyze.Checked = False
'... Tool Strip Button lists continues...
pnlHome.Visible = True
pnlTools.Visible = False
pnlReport.Visible = False
pnlAnalyze.Visible = False
' ... Panel lists continues...
End Sub
if user click the Tools button it highlights the button as selected and hides other panel elements and make visible Tool's panel.
Private Sub tsbTools_Click(sender As Object, e As EventArgs) Handles tsbTools.Click
tsbHome.Checked = False
tsbTools.Checked = True
tsbReport.Checked = False
tsbAnalyze.Checked = False
'... Tool Strip Button lists continues...
pnlHome.Visible = False
pnlTools.Visible = True
pnlReport.Visible = False
pnlAnalyze.Visible = False
' ... Panel lists continues...
End Sub
There are two tricks to making this code much simpler. The first is knowing you can have more than one item in the Handles clause of an event method declaration. (You can also omit that clause, and use AddHandler to set up event handlers for a lot of controls to one method.) The other trick is knowing how to use the sender argument to determine which of the several controls connected to this method was used.
Put those together, and you get one method that will work to change to any of your views.
Private Sub NavigationMenuItem_Click(sender As Object, e As EventArgs) Handles tsbHome.Click, tsbTools.Click, tsbReport.Click, tsbAnalyzer.Click
'First Suspend Layout, to avoid extra screen re-draws
Me.SuspendLayout()
'Set your checkboxes
tsbHome.Checked = sender Is tsbHome
tsbTools.Checked = sender Is tsbTools
tsbReport.Checked = sender Is tsbReport
tsbAnalyze.Checked = sender Is tsbAnalyze
'Then De-select EVERYTHING
pnlHome.Visible = sender Is tsbHome
pnlTools.Visible = sender Is tsbTools
pnlReport.Visible = sender Is tsbReport
pnlAnalyze.Visible = sender Is tsbAnalyze
' ... lists continues...
'Finally, resume layout so all changes draw to the screen at once
Me.ResumeLayout()
End Sub
We can make this simpler if you add code to the Form Load or InitializeComponent() method to put the panels and toolstrip buttons into lists:
Private ViewButtons As List(Of ToolStripButton)
Private ViewPanels As List(Of Panel)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ViewButtons = New List(Of ToolStripButton) From {tsbHome, tsbTools, tsbReport, tsbAnalyze}
ViewPanels = New List(Of Panel) From {pnlHome, pnlTools, pnlReport, pnlAnalyze}
For Each b In ViewButtons
AddHandler b.Click, AddressOf NavigationMenuItem_Click
Next
End Sub
Private Sub NavigationMenuItem_Click(sender As Object, e As EventArgs)
Me.SuspendLayout()
For i As Integer = 0 To ViewButtons.Length - 1
Dim selected As Boolean = ViewButtons(i) Is sender
ViewButtons(i).Checked = selected
ViewPanels(i).Visible = selected
Next
Me.ResumeLayout()
End Sub

How to turn over cards in card game?

I have created a card game where 2 x 12 different images will be loaded into 24 picture boxes in vb forms. My intention is for the user to turn over two cards at a time, trying to find pairs to match. Each time the game is loaded, there will be different pictures and they will be in different positions. So far I have loaded the image for the back of the card in the game successfully but I can’t turn them over to see if my images have loaded successfully.
I am not concerned about shuffling them yet, I just want to see if the images have loaded and to be able to have two cards turned over at a time. I’m really confused as I’m not used to using VB for such tasks so any help is appreciated. Here is my code:
Imports System.IO
Public Class Board
' as per stackoverflow Terms of Service
' this code comes from
' http://stackoverflow.com/a/40707688
'array of picture boxes
Private pBoxes As PictureBox()
'array of images
Private imgs As String() = {"1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg", "6.jpg", "7.jpg", "8.jpg", "9.jpg", "10.jpg", "11.jpg", "12.jpg", "13.jpg", "14.jpg", "15,jpg", "16.jpg", "17.jpg", "18.jpg", "19.jpg", "20.jpg", "21.jpg", "22.jpg", "23.jpg", "24.jpg"}
'random number generator
Private RNG = New Random
'cover image
Private coverImg As String = "bg.jpg"
'timer
Private dt As DateTime
'turns cards
Private pbFirst As PictureBox
Private pbSecond As PictureBox
Private matches As Int32 = 0
'Folder where images are held
Private ImgFolder As String
Private Sub Board1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
RNG = New Random()
'array of picture boxes
pBoxes = New PictureBox() {PictureBox1, PictureBox2, PictureBox3, PictureBox4,
PictureBox5, PictureBox6, PictureBox7, PictureBox8,
PictureBox9, PictureBox10, PictureBox11, PictureBox12, PictureBox13, PictureBox14, PictureBox15, PictureBox16, PictureBox17, PictureBox18, PictureBox19, PictureBox20, PictureBox21, PictureBox22, PictureBox23, PictureBox24}
'where images are located
ImgFolder = "F: \COMPUTER SCIENCE\Test images"
coverImg = Path.Combine(ImgFolder, coverImg)
For Each p As PictureBox In pBoxes
p.ImageLocation = coverImg
Next
NewGame()
End Sub
'Take images from file
Private Sub PickImages()
Dim nums = Enumerable.Range(1, 12).ToArray()
Dim pool = nums.Concat(nums).OrderBy(Function(r) RNG.Next).ToArray()
End Sub
Private Sub Shuffle()
End Sub
' reset everything
Private Sub NewGame()
matches = 0
pbFirst = Nothing
pbSecond = Nothing
' repick, reshuffle
PickImages()
Shuffle()
dt = DateTime.Now
'tmrMain.Enabled = True
End Sub
End Class
I dont have the points to comments however are you clicking on the pictures to turn them over? If so I think you would just need to have an event such as the below to load the back of the card image.
Private Sub PictureBox1_Click(sender As System.Object, e As System.EventArgs) Handles PictureBox1.Click
PictureBox1.ImageLocation = ("Path to Picture of back of the card")
PictureBox1.Load()
End Sub
Don't try and over-think this - forget about "turning them over" simply change the image:
Private Sub PictureBox1_Click(sender As System.Object, e As System.EventArgs) Handles PictureBox1.Click, PictureBox2.Click 'etc
dim index as short
' to do: get the index of the PictureCard
If sender.Image is coverImg then
sender.Image = imgs(index) ' in stead of 0, use the index of the picture card
Else
sender.Image = coverImage
End if
End Sub

How to hide tab selectors in tab control in visual basic

Basically i'm currently trying to create an ordering program in visual basic for an assignment and i want to know if it's possible to hide the tab controls at the top of the page and instead have users change tabs by pressing a button. I already know how to create buttons that change the page but i can't figure out how to hide the tab selectors.
An example of this would be after a user enters their details they would click next and then it would take them to the payment screen.
Please bear in mind i'm an absolute beginner so i may need a bit extra explaination
You can create a custom control where you override WndProc and trap the TCM_ADJUSTRECT message:
Public Class CustomTabControl
Inherits TabControl
Const TCM_ADJUSTRECT As Integer = &H1328
Protected Overrides Sub WndProc(ByRef message As Message)
If DesignMode = False AndAlso message.Msg = TCM_ADJUSTRECT Then
message.Result = New IntPtr(1) 'Always return 1.
Return
End If
MyBase.WndProc(message)
End Sub
End Class
Build your project via the Build > Build <your project name here> menu, then you will be able to add it from the Tool Box.
This is the dumest way to do it but it works:
Private Sub Form1_Load_2(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim pic As New PictureBox
pic.BackColor = Color.Transparent
pic.Width = TabControl1.Width
pic.Height = 21
pic.Location = TabControl1.Location
Me.Controls.Add(pic)
pic.BringToFront()
End Sub
This removes the up line of the TabControl. If you want it use:
Private Sub Form1_Load_2(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim pic1 As New PictureBox
Dim pic2 As New PictureBox
pic1.BackColor = Color.Transparent
pic1.Width = TabControl1.Width
pic1.Height = 21
pic1.Location = TabControl1.Location
Me.Controls.Add(pic1)
pic1.BringToFront()
pic2.BackColor = Color.Gray
pic2.Width = TabControl1.Width - 2
pic2.Height = 1
pic2.Location = New Point(TabControl1.Location.X, TabControl1.Location.Y + 20)
Me.Controls.Add(pic2)
pic2.BringToFront()
End Sub
Replace the TabControl1 with your tab control name.

Button Array - how to pass a parameter to shared handler

I have a bit of code where i have a dynamically created array or buttons with staff pictures on them, as well as the staff's name. I've added one handler to handle any button click from any of the buttons. where i am stuck is, if you look at the code below, it all works fine, and if you click any of the buttons you get the "aha" test message. but i want the name of the staff clicked on (so btnArray(i).Text) to be passed to the handler for further processing. I tried adding a ByVal parameter to the handler but that caused an error. what's the correct way to do this? As i said, the code below works for me, i just am at a loss as to how to add the extra functionality.
Dim btnArray(staffcount) As System.Windows.Forms.Button
For i As Integer = 1 To staffcount - 1
btnArray(i) = New System.Windows.Forms.Button
btnArray(i).Visible = True
btnArray(i).Width = 80
btnArray(i).Height = 101
btnArray(i).BackgroundImage = Image.FromFile(picloc(i))
btnArray(i).BackgroundImageLayout = ImageLayout.Stretch
btnArray(i).Text = staffname(i)
Dim who As String
who = btnArray(i).Text
AddHandler btnArray(i).Click, AddressOf Me.theButton_Click
btnArray(i).ForeColor = Color.White
btnArray(i).TextAlign = ContentAlignment.BottomCenter
Dim fnt As Font
fnt = btnArray(i).Font
btnArray(i).Font = New Font(fnt.Name, 10, FontStyle.Bold)
FlowLayoutPanel1.Controls.Add(btnArray(i))
Next i
End Sub
Private Sub theButton_Click()
MsgBox("aha")
End Sub
First, correct the signature of your shared handler.
Private Sub theButton_Click(sender As Object, e As EventArgs)
End Sub
Once that is done getting the text of the button clicked is a simple matter.
Private Sub theButton_Click(sender As Object, e As EventArgs)
Dim textOfButtonClicked As String = DirectCast(sender, Button).Text
MessageBox.Show(textOfButtonClicked)
End Sub
The sender is the button that was clicked. Since signatures use objects for the sender the DirectCast 'changes' it to button and you then can access the .Text property of the button.
If there are more manipulations you want to perform on the clicked button you could do it this way
Private Sub theButton_Click(sender As Object, e As EventArgs)
Dim whBtn As Button = DirectCast(sender, Button) ' get reference to button clicked
Dim textOfButtonClicked As String = whBtn.Text
MessageBox.Show(textOfButtonClicked)
'e.g. change the color
whBtn.BackColor = Color.LightYellow
End Sub

SelectionChanged event occurs more times than it should

I have grid and those grid is populate on Form's load event. At the end line of that event i am hooking method handler for my SelectionChanged event of this grid. I want to get current selected row's zero cell's 1 value. Unfortunately when i run program my SelectionChanged event method handler is called infinite times... And i have no idea why is that.
So its basically like this:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
some code which populating data to grid...
'here hooking up method after data is there already to not fire it up during grid population
AddHandler gridArtikels.SelectionChanged, AddressOf gridArtikels_SelectionChanged
End Sub
and this is event handler method itself:
Private Sub gridArtikels_SelectionChanged(sender As Object, e As GridEventArgs)
RemoveHandler gridArtikels.SelectionChanged, AddressOf gridArtikels_SelectionChanged
If gridArtikels.PrimaryGrid.Rows.Count > 0 Then
gridArtikels.PrimaryGrid.SetSelectedRows(0, 1, True)
ItemPanelImgs.Items.Clear()
'Dim images As New List(Of Article_Image)
Dim selectedNummer As String = String.Empty
selectedNummer = gridArtikels.PrimaryGrid.SelectedRows(0).Cells(1).Value.ToString()
'images = ArtikelsAndTheirVariationsFinal.GetImagesForArticle(selectedNummer)
'ItemPanelImgs.DataSource = images
End If
AddHandler gridArtikels.SelectionChanged, AddressOf gridArtikels_SelectionChanged
End Sub
P.S I am using to be concrete supergrid control from DotnetBar devcomponenets but it shouldn't be diffrent from ordinary controls behaviour.
What could be wrong here?
For those whom would like to debug here is sample app
EDIT:
I also tried this way but its still going to infinitive loop...
Public IgnoreSelectionChanged As Boolean = False
Private Sub gridArtikels_SelectionChanged(sender As Object, e As GridEventArgs) Handles gridArtikels.SelectionChanged
If IgnoreSelectionChanged Then Exit Sub
IgnoreSelectionChanged = True
If gridArtikels.PrimaryGrid.Rows.Count > 0 Then
gridArtikels.PrimaryGrid.SetSelectedRows(0, 1, True)
ItemPanelImgs.Items.Clear()
'Dim images As New List(Of Article_Image)
Dim selectedNummer As String = String.Empty
selectedNummer = gridArtikels.PrimaryGrid.SelectedRows(0).Cells(1).Value.ToString()
'images = ArtikelsAndTheirVariationsFinal.GetImagesForArticle(selectedNummer)
'ItemPanelImgs.DataSource = images
End If
IgnoreSelectionChanged = False
End Sub