FlowLayoutPanel not showing more than one Control after it's cleared - vb.net

My program automates a process on a website using selenium, HTTP requests, and an HTML parser known as HtmlAgilityPack.
There are parts of this process that require user input that can not be automatically filled out by hard-coded values. Allowing the user control over the website to fill in these inputs would be a mistake. It would be difficult to know when they are done inputting it and they would likely touch something they shouldn't which would cause errors when control of the website is handed back over to the program.
Instead, I download the page source of the website using selenium and parse the inputs out of the HTML with HtmlAgilityPack. Then controls matching those inputs are dynamically generated and added to a FlowLayoutPanel. The FlowLayoutPanel works correctly the first time. It displays all added controls. When the user is done they click on a button and the process continues.
Sometimes the process encounters errors that cause it to loop back to where the user needs to enter input again. The exact code that worked before is then run in the same subroutine again. It all runs without error and as expected. I have manually checked at runtime that the controls are added to the FlowLayoutPanel, that they have a size bigger than 0, 0, and that they are a visible color. Yet only one control ever shows up in the FlowLayoutPanel no matter how many should be visible.
The FlowLayoutPanel has it's AutoScroll property set to true and it has adequate space to add more controls. As I said this works fine the first time around just not the second. I've been stuck on this for a while and would appreciate some help. The code is going to be posted below for you to look at.
The code I am going to post has several FlowLayoutPanels. One that is on the Form permanently that was created in the designer and the others are generated dynamically to group up the controls.
The issue is with the permanent one. It shows one control as I said. This control is the first dynamically generated flow layout panel and all of it's children. It doesn't display any of the other FlowLayoutPanel controls that are added to it. Just whichever one is added first.
For those of you wondering about the line FLPOutOfStock.ImprovedClear() in the code bellow, The .Improved clear is an extension method I wrote. When Clearing the FlowLayoutPanel with the normal method, .Clear(), the controls get removed but they don't get disposed of. My method disposes of all the controls in the panel and then calls the Clear() method on the panel.
I realize that the Clear() method isn't necessary after they've been disposed of it's just a backup since nothing ever seems to work. I wanted to make sure the panel was fully reset.
Here is the code:
Private Sub UIOutOfStockState_Load()
Try
cmdOutOfStockDeleteAll.Enabled = False
cmdOutOfStockContinue.Enabled = False
FLPOutOfStock.ImprovedClear() 'The flowlayoutpanel that does not display all controls
With New WebDriverWait(ChromeDriver, TimeSpan.FromSeconds(20)).Until(Function(driver) CBool((CType(driver, IJavaScriptExecutor)).ExecuteScript("return jQuery.active == 0")))
End With
LBLOutOfStockErrors.Text = "Notice. The following items are out of stock. Please see below for product-specific availability dates. If you have any questions, please contact Customer Service at (800) 843-2020. All direct to patient orders will ship complete. The entire order will ship when out-of-stock product becomes available."
Dim OutOfStockDoc As New HtmlAgilityPack.HtmlDocument
OutOfStockDoc.LoadHtml(ChromeDriver.PageSource)
Dim OutOfStockProductNodes As HtmlAgilityPack.HtmlNodeCollection = OutOfStockDoc.DocumentNode.SelectNodes("//div[contains(#id,'id_detail_item_')]")
FLPOutOfStock.SuspendLayout()
If OutOfStockProductNodes IsNot Nothing Then
For Each OutOfStockProductNode As HtmlAgilityPack.HtmlNode In OutOfStockProductNodes
Dim FLPOutOfStockProduct As New FlowLayoutPanel
FLPOutOfStockProduct.SuspendLayout()
FLPOutOfStockProduct.FlowDirection = FlowDirection.LeftToRight
FLPOutOfStockProduct.AutoSize = False
FLPOutOfStockProduct.Size = New Size(420, 160)
FLPOutOfStock.Controls.Add(FLPOutOfStockProduct)
Dim WBProductText As New WebBrowser
WBProductText.Size = New Size(400, 120)
WBProductText.AllowNavigation = False
WBProductText.AllowWebBrowserDrop = False
WBProductText.IsWebBrowserContextMenuEnabled = False
WBProductText.ScriptErrorsSuppressed = True
WBProductText.ScrollBarsEnabled = True
WBProductText.Margin = New Padding(0, 0, 0, 0)
WBProductText.Padding = New Padding(0, 0, 0, 0)
WBProductText.Navigate("about:blank")
Dim ProductTextHtmlDoc As New HtmlAgilityPack.HtmlDocument
ProductTextHtmlDoc.LoadHtml(OutOfStockProductNode.OuterHtml)
ProductTextHtmlDoc.DocumentNode.SelectSingleNode("/descendant::a[#id='patient-outOfStock-delete-item']").Remove()
WBProductText.DocumentText = ProductTextHtmlDoc.DocumentNode.OuterHtml
FLPOutOfStockProduct.Controls.Add(WBProductText)
FLPOutOfStockProduct.SetFlowBreak(WBProductText, True)
Dim SpaceReducer0 As New Panel
SpaceReducer0.Size = New Size(0, 0)
FLPOutOfStockProduct.Controls.Add(SpaceReducer0)
Dim cmdProductDeleteButton As New Button
cmdProductDeleteButton.Text = "Delete"
cmdProductDeleteButton.BackColor = Color.White
cmdProductDeleteButton.ForeColor = Color.Black
cmdProductDeleteButton.Margin = New Padding(0, 0, 0, 0)
cmdProductDeleteButton.Padding = New Padding(0, 0, 0, 0)
cmdProductDeleteButton.Size = New Size(400, 20)
Dim OnClickAttributeValueWithQuoteEscaping As String = OutOfStockProductNode.SelectSingleNode("/descendant::a[#id='patient-outOfStock-delete-item']").GetAttributeValue("onclick", "")
cmdProductDeleteButton.Tag = "//a[#id='patient-outOfStock-delete-item' and (#onclick=""" & OnClickAttributeValueWithQuoteEscaping & """)]"
AddHandler cmdProductDeleteButton.Click, AddressOf cmdProductDeleteButton_Click
FLPOutOfStockProduct.Controls.Add(cmdProductDeleteButton)
FLPOutOfStockProduct.SetFlowBreak(cmdProductDeleteButton, True)
Dim SpaceReducer1 As New Panel
SpaceReducer1.Size = New Size(0, 0)
FLPOutOfStockProduct.Controls.Add(SpaceReducer1)
FLPOutOfStockProduct.ResumeLayout()
Next
FLPOutOfStock.ResumeLayout()
FLPOutOfStock.Refresh()
End If
cmdOutOfStockDeleteAll.Enabled = True
cmdOutOfStockContinue.Enabled = True
Catch Ex As Exception
cmdOutOfStockDeleteAll.Enabled = True
cmdOutOfStockContinue.Enabled = True
End Try
End Sub
So I’ve been asked to show what is in the improved clear method. Here it is:
<Extension()>
Public Function ImprovedClear(ByRef Control as Control)
For Each controlchild as control in control.controls
Control.Dispose()
Next
Control.Controls.Clear()
Return Nothing
End Function

Related

Capture Active Window with Keyboard SendKeys

After experiencing problems with Visual Basic's printform quality, I have decided to take a different approach. I am going to simulate an ALT-PRINTSCREEN keyboard key-press to capture the form in its current state.
However, before this happens, I need to remove certain elements from the form (such as the border), and after getting the image, I need to replace these elements. I am having some timing/synchronization problems with this, as I am noticing that certain elements are still on the image that has been copied to the clipboard.
I figured this was due to the fact that it takes time to process the changes, and time for Windows to process the simulated "send keys", so I set up sleep timers. I am having mixed results. I notice that if I don't replace the elements afterwords, the clipboard image appears correctly. However, if I do replace them, even if I set up a 20 second sleep timer, they do not appear.
Therefore, I know there is some sort of synchronization problem, but I am not sure how to fix it. Here is the code I am using:
'Make these forms invisble so they are not printed
Logo.Visible = False
MenuStrip1.Visible = False
ReportsButton.Visible = False
pnlmain.BorderStyle = 0 'remove the border
'Sleep
System.Threading.Thread.Sleep(1000)
'simulate an ALT and PRINTSCREEN key click to get ONLY the report
SendKeys.Send("%{PRTSC}")
'Sleep
System.Threading.Thread.Sleep(1000)
'Now, get the image from the clipboard
Dim formImage As System.Drawing.Image
formImage = My.Computer.Clipboard.GetImage()
Logo.Visible = True
MenuStrip1.Visible = True
ReportsButton.Visible = True
pnlmain.BorderStyle = 1
Changing the sleep durations does not seem to change much (unless they are very low).
Any suggestions?
EDIT: here is the code for draw to bitmap (after modifying the code above):
formImage = New Bitmap(Me.Width, Me.Height, Me.CreateGraphics())
Me.DrawToBitmap(formImage, New Rectangle(0, 0, Me.Width, Me.Height))
When it is sent to the print action:
Dim g As Graphics = e.Graphics
'transform the form image so it fits the height and width of the paper, with a 5 px margin
Dim res = printSettings.PrinterResolutions
Dim newSizeDestinationPoints As Point() = {
New Point(5, 5),
New Point(841, 5),
New Point(5, 956)
} 'These values are based on A4 sized paper
'g.DrawImage(formImage, 5, 5)
g.DrawImage(formImage, newSizeDestinationPoints)
The low quality is not coming from the stretch.

VB.NET ClientSize

I have one form and I want it to be fullscreen, but taskbar should be still visible. And I want it to have one Panel on it, whose borders are 10px away from form borders
I tried hundreds of combinations, and I simply can't achieve this.
here's my code
Public Class Form1
Sub New()
InitializeComponent()
WindowState = FormWindowState.Maximized
Size = New Size(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height)
Dim p1 As New Panel()
p1.Location = New Point(10, 10)
p1.Size = New Size(ClientSize.Width - 20, ClientSize.Height - 20)
p1.BackColor = Color.Blue
Controls.Add(p1)
End Sub
End Class
what I want: http://i.imgur.com/4BxoBeh.png
what I get: http://i.imgur.com/QynIdaU.png
I would take an entirely different approach where there is no need to calculate anything:
WindowState = FormWindowState.Maximized
Size = New Size(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height)
Padding = New Padding(10)
Dim p1 As New Panel()
p1.Location = New Point(0, 0)
p1.Dock = DockStyle.Fill
p1.BackColor = Color.Blue
Controls.Add(p1)
Your calculation is correct for a form that takes entire screen but is not maximized, which you can see by unmaximizing it as soon as it appears. The reason is that you are observing the form sizes from the constructor which is a bit too early (namely, even though you are setting WindowState = FormWindowState.Maximized before everything else, ClientSize still has values corresponding to non-maximized window because the window has not yet been created and shown). If you move your original code to e.g. a Form.Load handler it will give the opposite result - looking correct when the form is maximized and incorrect if not.
The padding approach works as expected in all cases.

DataGridView bound to DataTable is not showing

I am trying to show a DataGridView in a form that is bound to a DataTable, but it's not showing up. I was doing this using a C1TrueDBGrid and that was working...I decided to switch to using a DataGridView due to some complications with the TrueDBGrid. Can anyone help me figure out why nothing is showing?
In the form I declare these:
Public binData As DataSet
Friend WithEvents dgvData As System.Windows.Forms.DataGridView
The binData is filled with tables created via a separate calculation routine. Then this is the form load event:
'create a tab page and add data grid view for every table in the set
For i = 0 To binData.Tables.Count - 1
Dim tabPage As C1.Win.C1Command.C1DockingTabPage = New C1.Win.C1Command.C1DockingTabPage
tabPage.Text = binData.Tables(i).TableName
tabContent.TabPages.Add(tabPage)
Dim dgvData = New System.Windows.Forms.DataGridView
Dim binding As New BindingSource
binding.DataSource = binData.Tables(i)
With dgvData
.Dock = DockStyle.Fill
.AllowUserToOrderColumns = False
.AllowUserToAddRows = False
.AllowUserToDeleteRows = False
.DefaultCellStyle.Alignment = DataGridViewContentAlignment.BottomLeft
.DataSource = binding
.AutoGenerateColumns = True
End With
tabPage.Controls.Add(dgvData)
Next 'DataTable In binData.Tables
When the form loads, the tab pages are there and labeled as expected, but they look empty (no table).
I did try instead setting the DataSource to the DataSet called binData (as opposed to a specific table), and then setting dgvData's DataMember property to the name of the specific table I want to display in it...that made no difference.
Note: I need to be able to do this programmatically at runtime as opposed to using the visual designer because I do not know the exact number of grids I need until the form loads with a particular dataset - the dataset it gets can have a different number of tables depending on what the user wants.
Here's some rough code to add dgvs to a flow panel:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Static dgvCount As Integer
dgvCount += 1
Dim dgvNew As New DataGridView
dgvNew.Width = DataGridView1.Width
dgvNew.Height = DataGridView1.Height
dgvNew.Name = "dgv" & dgvCount
' clone other properties as need
FlowLayoutPanel1.Controls.Add(dgvNew)
Debug.Print(FlowLayoutPanel1.Controls(FlowLayoutPanel1.Controls.Count - 1).Name)
End Sub
Starts with one dgv - DataGridView1 as the model for properties. Anchoring is not working in the flow panel so some custom code may be need to change width.
Flow panel doesn't scroll so may not be the best choice - look into TableLayout as a possibility. TabControl is another option.
OK, well it turns out there was nothing wrong with what I was doing. The issue turned out to be in one line of code that has nothing to do with binding the DGV's datasource or anything.
ComponentOne has a control called a ThemeController in which you can set themes for your forms and the controls within. I had a line of code to set the theme for dgvData to my default application theme (which sets visual style and details regarding colors, fonts, etc.). For whatever reason, THAT was rendering my grid non-visible. I will be logging a ticket with them.

Unable To Close A Form Embedded In A Panel

I have an issue that is really starting to do my head in...
My application has a ListView control that is populated with items. When you double click on an item it creates a new instance of a form. It then creates a new panel and adds the form to the panel. However, for the life of me I cannot work out how to close the form inside the panel.
Inside my DoubleClick event:
Dim frm As New frmStorePage(_store.Code, _store.Name)
'Create a new panel with the store page
Dim pnl As New Panel
pnl.Name = _store.Code
pnl.BackColor = SystemColors.Control
pnl.Size = New Size(1522, 892)
pnl.Location = New Point(3, 3)
frm.TopLevel = False
frm.Name = _store.Code
pnl.Controls.Add(frm)
frm.Show()
pnlStores.Controls.Add(pnl)
pnl.BringToFront()
...
Inside my Close event:
Dim panel As Panel = CType(pnlStores.Controls.Find(lsvOpenStoreList.SelectedItems(0).Name, False)(0), Windows.Forms.Panel)
For Each control As Control In panel.Controls
If TypeOf control Is Windows.Forms.Form And control.Name = panel.Name Then
control.Dispose()
End If
Next
pnlStores.Controls.Remove(panel)
panel.Dispose()
pnlStoreList.BringToFront()
...
I have also tried declaring my form as a global variable but still cannot seem to close it.
The form has a number of timer events that get stopped when the form closes, however, even though the panel gets closed, the timer events are still running. It seems that the form is still active in the background.
Any help would be appreciated.
Due to a silly oversight, I failed to see that the code I was using to dispose of all the controls and close the form was not in the FormClosing event, but actually under a Command Button called Close. Therefore, my closing procedures were never getting called and thus the timers remained active.
Thanks

Add tooltip control dynamically

I have a child form that is completely created in code.
I would like to add tool tips to the textbox controls on the form. I know how to set up the tool tips on the fly but can't find a way to add the tooltip control to the form on the fly. All the hits I find on google refer to dragging the control from the designers toolbox.
I would need to do something like:
' Add tool tip control
Dim toolTip1 As New ToolTip()
toolTip1.ShowAlways = True
frm.Controls.Add(toolTip1)
This does not work
I tried adding a sub to handle the from.load event(with a handler that poitn to the sub) at design time but can not get passed the error "Tooltip1 is not declared" when adding the tooltip to the iundividual dynamic controls.
If I call this dynamic form from a parent form and add the tooltip control to the parent form I can use it for the child form. But how would I do this if I was creating the form from a routine that does not live on a parent form?
thanks
Dim frm As New Form
' Add tool tip control
''Dim toolTip1 As New ToolTip()
''toolTip1.ShowAlways = True
'Draw the Form object
'close the dynamic frm if existing already
If frm IsNot Nothing Then
frm.Close()
End If
frm = New Form()
frm.AutoScaleDimensions = New System.Drawing.SizeF(6.0F, 13.0F)
frm.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
frm.Name = "frm_test"
'dimension is irrelevant at the moment
frm.ClientSize = New System.Drawing.Size(10, 10)
'the parent will be the current form
'frm.MdiParent = this;
'splash screen mode form, why not...
frm.ControlBox = True
frm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
frm.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink
frm.BackColor = System.Drawing.Color.LightGray
For Each item As MYFILE.Class in the Collection
Dim aTextBox As New TextBox()
aTextBox.Font = New System.Drawing.Font(sFont, Single.Parse(sSizeFont), System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CByte(0))
aTextBox.BackColor = System.Drawing.Color.Yellow
aTextBox.Location = New System.Drawing.Point(iNewColumnPosition + 5 + intMaxWidthLabel, intVertPos)
aTextBox.Size = New System.Drawing.Size(intWidthTextBox + 10, intGapHeight)
'store the biggest width, so that the textboxes can be vertically aligned
If intWidthTextBox > intMaxWidthText Then
intMaxWidthText = intWidthTextBox
End If
'giving a name to all your object will be the only way
'to retrieve them and use them
'for the purpose of this sample, the name can be the
'same for all textboxes.
aTextBox.Name = item.ParameterName
'giving the maximun size in caracters for the textbox.
aTextBox.MaxLength = Integer.Parse(item.ParameterLength)
toolTip1.SetToolTip(aTextBox, "TIP" & intIndex.ToString)
'tab have to be ordered
aTextBox.TabIndex = intIndex
intIndex += 1
'Vertical position is to be manage according the
'tallest object in the form, in this case the
'textbox it self
intVertPos += intGapHeight
'adding the textbox to the form
frm.SuspendLayout()
aTextBox.SuspendLayout()
frm.Controls.Add(aTextBox)
Next
I left a lot of code out but this should give you an idea as to what I am doing
In vb it is
Dim tooltip As New ToolTip(components)
tooltip.SetToolTip(textBox1, "This is a textbox tooltip")
Sorry this is not in VB.NET but I am pretty sure you can easily convert this. The first line is to set ToolTip control to your form components. The second is how you set a tooltip to a control and give it the related text.
ToolTip tooltip = new ToolTip(components);
tooltip.SetToolTip(textBox1, "This is a textbox tooltip");