I'm working on a Visual Basic Project. My working environment is :
Windows 10 32bit
Visual Studio 2015
.Net Framework 4.8
Winform
At this stage,I have :
Class (Class1.vb)
Form1 (Form1.vb) with TreeView Control
I'm supposed to Scrape a webpage (i.e: https://www.example.com), I want to display the result of Scraping in a Treeview Control placed on Form1. I have tried some approaches and they worked fine, except that they require using Webbrowser Control which I do not wish to use. I found a method that I'm using now, but it seems to not letting me display the Results on the Form.
Here is my Code of Class1.vb and it's working fine
Imports System.Threading.Tasks
Public Class Class1
' Create a WebBrowser instance.
Private Event DocumentCompleted As WebBrowserDocumentCompletedEventHandler
Private ManufacturersURi As New Uri("https://www.example.com/Webpage.php3")
Public ManList As New List(Of TreeNode)
Public Sub GettHelpPage()
' Create a WebBrowser instance.
Dim webBrowserForPrinting As New WebBrowser() With {.ScriptErrorsSuppressed = True}
' Add an event handler that Scrape Data after it loads.
AddHandler webBrowserForPrinting.DocumentCompleted, New _
WebBrowserDocumentCompletedEventHandler(AddressOf GetManu_Name)
' Set the Url property to load the document.
webBrowserForPrinting.Url = ManufacturersURi
End Sub
Private Sub GetManu_Name(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
Dim webBrowserForPrinting As WebBrowser = CType(sender, WebBrowser)
Dim Divs = webBrowserForPrinting.Document.Body.GetElementsByTagName("Div")
' Scrape the document now that it is fully loaded.
Dim T As Task(Of List(Of TreeNode)) =
Task.Run(Function()
Dim LinksCount As Integer = 0
For Each Div As HtmlElement In Divs
If InStr(Div.GetAttribute("ClassName").ToString, "Div-Name", CompareMethod.Text) Then
LinksCount = Div.GetElementsByTagName("a").Count - 1
For I As Integer = 0 To LinksCount
Dim Txt() As String = Div.GetElementsByTagName("a").Item(I).InnerHtml.Split("<BR>")
Dim Manu_TreeNode As New TreeNode() With
{.Name = I.ToString, .Text = Txt(0)}
ManList.Add(Manu_TreeNode)
Next
End If
Next
Return ManList
End Function)
' Dispose the WebBrowser now that the task is complete.
Debug.WriteLine(T.Result.Count) 'Result is 116
webBrowserForPrinting.Dispose()
End Sub
The above Code results 116 TreeNodes, which are the count of Tags that I scraped. Now when I attempt to display this result on Form1_Load, nothing happens, because the Form loads before the Code finishes executing.
Here is the Form1_Load Code :
Public Class Form1
Dim ThisClass As New Class1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ThisClass.GetHelpPage()
TreeView1.Nodes.Clear()
For I As Integer = 0 To ThisClass.ManList.Count - 1
TreeView1.Nodes.Add(ThisClass.ManList(I))
Next
End Sub
End Class
I noticed that if I placed an empty msgbox("") in the Form1_Load somewhere before For..Next, it forces the Form1_Load Event to wait and successfully populates the TreeView Control.
What am I doing wrong ? or What am I missing there ?
I noticed that if I placed an empty msgbox("") in the Form1_Load somewhere before For..Next, it forces the Form1_Load Event to wait and successfully populates the TreeView Control.
Yes, it plays the await role if you keep it open long enough until the task in the GetManu_Name method is completed. Since the MsgBox is a modal window which blocks the next lines from being executed until it been closed.
Now, either you make it a complete synchronous call by removing the Task.Run(...) from the GetManu_Name method, or utilize an asynchronous pattern in such a way as:
Public Class WebStuff
Public Shared Async Function ToTreeNodes(url As String) As Task(Of IEnumerable(Of TreeNode))
Dim tcsNavigated As New TaskCompletionSource(Of Boolean)
Dim tcsCompleted As New TaskCompletionSource(Of Boolean)
Dim nodes As New List(Of TreeNode)
Using wb As New WebBrowser With {.ScriptErrorsSuppressed = True}
AddHandler wb.Navigated,
Sub(s, e)
If tcsNavigated.Task.IsCompleted Then Return
tcsNavigated.SetResult(True)
End Sub
AddHandler wb.DocumentCompleted,
Sub(s, e)
If wb.ReadyState <> WebBrowserReadyState.Complete OrElse
tcsCompleted.Task.IsCompleted Then Return
tcsCompleted.SetResult(True)
End Sub
wb.Navigate(url)
Await tcsNavigated.Task
'Navigated.. if you need to do something here...
Await tcsCompleted.Task
'DocumentCompeleted.. Now we can process the Body...
Dim Divs = wb.Document.Body.GetElementsByTagName("Div")
Dim LinksCount As Integer = 0
For Each Div As HtmlElement In Divs
If Div.GetAttribute("ClassName").
IndexOf("Div-Name", StringComparison.InvariantCultureIgnoreCase) > -1 Then
LinksCount = Div.GetElementsByTagName("a").Count - 1
For I As Integer = 0 To LinksCount
Dim Txt = Div.GetElementsByTagName("a").Item(I).InnerHtml.
Split({"<BR>"}, StringSplitOptions.RemoveEmptyEntries)
Dim n As New TreeNode With {
.Name = I.ToString, .Text = Txt.FirstOrDefault
}
nodes.Add(n)
Next
End If
Next
End Using
Return nodes
End Function
End Class
Notes on the method:
A single Async function to do the lengthy task and returns IEnumerable(Of TreeNode) to the caller.
Lambda Expressions are used to add the WebBrowser.Navigated and WebBrowser.DocumentCompleted events.
The TaskCompletionSource is necessary here to wait for the completion of the WebBrowser.DocumentCompleted event in order to be able to process the HTML contents.
You need to add the Async modifier to the caller's signature to call the function and wait for the result. For example, the Form.Load event:
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim nodes = Await WebStuff.ToTreeNodes("www....")
TreeView1.Nodes.AddRange(nodes.ToArray)
End Sub
Or Async method:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
PopulateTree()
End Sub
Private Async Sub PopulateTree()
Dim nodes = Await WebStuff.ToTreeNodes("www....")
TreeView1.Nodes.AddRange(nodes.ToArray)
End Sub
Try this
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ThisClass As New Class1
Dim i As Integer = 0
Do Until ThisClass.IsCompleted
Threading.Thread.Sleep(100)
'if the document takes too much time
i += 1
If i > 30 Then Exit Do 'more than 3 sec
Loop
TreeView1.Nodes.Clear()
For I As Integer = 0 To ThisClass.ManList.Count - 1
TreeView1.Nodes.Add(ThisClass.ManList(I))
Next
End Sub
End Class
Class Class1
Dim Completed As Boolean = False
ReadOnly Property IsCompleted As Boolean
Get
Return Completed
End Get
End Property
Private Sub GetManu_Name(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
'your code
Completed = True
Return ManList
End Sub
Related
Issue
I am using multi-threading inside my application and the way it works is that i have an array that contains 22 string which stand for some file names:
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "FFCHLDP", "FFDBKDP", "FFDREQP", "FFINVHP", "FFJMNEP", "FFPIVHP", "FFUNTTP", "FJBJM1P", "FJBJM2P", "FJBNT2P", "FPPBE9P", "FTPCP1P", "FTTEO1P", "FTTRQ1P", "FJBJU1P", "FTTEG1P", "FFJACPP", "XATXTDP"}
I then loop through the array and create a new thread for each file:
For Each mThreadName As String In ThreadList
Dim mFileImportThread = New FileImportThreadHandling(mThreadName, mImportGuid, mImportDate, Directory_Location, mCurrentProcessingDate, mRegion)
Next
So inside the new thread 'FileImportThreadHandling' it will call a method by starting a new thread:
mThread = New Thread(AddressOf DoWork)
mThread.Name = "FileImportThreadHandling"
mThread.Start()
Then in 'DoWork' it will determine what file is current in question and will run the code related to the file.
After the code has ran for the file I want to report this back to the main thread. Can somebody give me a solution please.
You will need to use Delegates
EDIT:
Take a look at this snippet:
Sub Main()
mThread = New Thread(AddressOf doWork)
mThread.Name = "FileImportThreadHandling"
mThread.Start()
End Sub
Sub doWork()
'do a lot of hard work
workDone(result)
End Sub
Delegate Sub workDoneDelegate(result As Integer)
Sub workDone(abilita As Boolean, Optional src As Control = Nothing)
If Me.InvokeRequired Then
Me.Invoke(New workDoneDelegate(AddressOf workDone), {result})
Else
'here you're on the main thread
End If
End Sub
Here is an example that might give you some ideas. Note the use of Async and Task. You will need a form with two buttons, and a label.
Public Class Form1
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "*******", "FFJACPP", "XATXTDP"}
'note Async keyword on handler
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Dim running As New List(Of Task)
For Each tskName As String In ThreadList
'start new task
Dim tsk As Task
tsk = Task.Run(Sub()
For x As Integer = 1 To 10
Dim ct As Integer = x
'simulate code related to the file
Threading.Thread.Sleep(500)
'report to the UI
Me.Invoke(Sub()
Label1.Text = String.Format("{0} {1}", tskName, ct)
End Sub)
Next
End Sub)
Threading.Thread.Sleep(100) 'for testing delay between each start
running.Add(tsk)
Next
'async wait for all to complete
For Each wtsk As Task In running
Await wtsk
Next
Button1.Enabled = True
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'test UI responsive during test
Label1.Text = DateTime.Now.ToString
End Sub
End Class
Using Actions and a callback to track remaining operations. (Simplified your class for my example)
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "FFCHLDP"}
Private actionCounter As Integer = 0
Private lockActionCounter As New Object()
Sub Main()
Console.WriteLine("Starting...")
For Each mThreadName As String In ThreadList
Dim mFileImportThread = New FileImportThreadHandling(mThreadName)
actionCounter += 1
Call New Action(AddressOf mFileImportThread.DoWork).
BeginInvoke(AddressOf callback, mThreadName)
Next
Console.Read()
End Sub
Private Sub callback(name As IAsyncResult)
Dim remainingCount As Integer
SyncLock lockActionCounter
actionCounter -= 1
remainingCount = actionCounter
End SyncLock
Console.WriteLine("Finished {0}, {1} remaining", name.AsyncState, actionCounter)
If remainingCount = 0 Then Console.WriteLine("All done")
End Sub
Private Class FileImportThreadHandling
Shared r = New Random()
Private _name As String
Public Sub New(name As String)
_name = name
End Sub
Public Sub DoWork()
Dim delayTime = (r).Next(500, 5000)
Console.WriteLine("Doing {0} for {1:0}ms.", _name, delayTime)
Thread.Sleep(delayTime)
End Sub
End Class
Public Class ThreadWithState
' State information used in the task.
Private locationx As Integer
Private locationy As Integer
' The constructor obtains the state information.
Public Sub New(x As Integer, y As Integer)
locationx = x
locationy = y
End Sub
' The thread procedure performs the task, such as formatting
' and printing a document.
Dim win As New VBSoftKeyboard.KeyBoardForm
Public Sub ThreadProc()
'MessageBox.Show(locationy)
Console.WriteLine(locationx, locationy)
win.FormBorderStyle = Windows.Forms.FormBorderStyle.None
win.Location = New Point(locationx, locationy)
win.StartPosition = FormStartPosition.Manual
Application.Run(win)
End Sub
Public Sub HideWin()
win.Hide()
End Sub
Public Sub ShowWin()
win.Show()
End Sub
End Class
Dim tws As New ThreadWithState(1000, 0) 'show the keyboard form location
Private Sub SimpleButton1_Click_1(sender As Object, e As EventArgs) Handles SimpleButton1.Click
Dim t As New Thread(New ThreadStart(AddressOf tws.ThreadProc))
t.Start()
tws.HideWin() //PROBLEM HERE ! how to hide the form ? and when i want to use i can show the form
End Sub
I use a thread to create a form, and then i m trying to hide the form first, when want to use the form i only call the ShowWin() function. t as thread to start the thread and create the form, how to call the t thread HideWin() function?
How to use created thread to call the others function ?
I haven't really done much with threads before and I'm having a problem updating a label and closing a form.
When debugging, the CloseDialog() sub is definitely running in the main thread so I don't understand why it's not closing. There are no loops or anything else running on the form to keep it open. I'm also having a problem updating the text on a label with information passed from another thread in real time.
The AddOUToTreeView sub gets invoked and works fine, but the subs from frmStatus never do anything.
frmMain:
Private WithEvents bkg As New ADSearcher
Private Sub startSearch_Click(ByVal sender As Object, ByVal e As EventArgs) Handles startSearch.Click
With bkg
.RootPath = "LDAP://domain.com"
.FilterString = "(objectCategory=organizationalUnit)"
If Not integratedAuth Then
.UserID = "user"
.Password = "pass"
End If
.PageSize = 5
.PropertiesToLoad = New String() {"cn", "name", "distinguishedName", "objectCategory"}
Dim search As New Threading.Thread(AddressOf .StartSearch)
search.Start()
Dim statusDialog As frmStatus = New frmStatus
statusDialog.Show() 'I want to use ShowDialog() but removed it when trouble shooting
End With
End Sub
Private Delegate Sub displayStatus(ByVal entriesFound As Integer)
Private Sub bkg_ResultFound(ByVal ousFound As Integer) Handles bkg.ResultFound
Dim display As New displayStatus(AddressOf frmStatus.UpdateOUSearcherStatus)
Me.Invoke(display, New Object() {ousFound})
End Sub
Private Delegate Sub displayResult(ByVal node As TreeNode)
Private Delegate Sub closeStatusDialog()
Private Sub bkg_SearchCompleted(ByVal ouNodes As TreeNode) Handles bkg.SearchCompleted
Dim display As New displayResult(AddressOf AddOUToTreeView)
Me.Invoke(display, New Object() {ouNodes})
Dim closeStatus As New closeStatusDialog(AddressOf frmStatus.CloseDialog)
Me.Invoke(closeStatus)
End Sub
Private Sub AddOUToTreeView(ByVal node As TreeNode)
tvOU.Nodes.Add(node)
tvOU.TopNode.Expand()
End Sub
frmStatus (Both of these functions do nothing):
Public Sub CloseDialog()
'Me.DialogResult = Windows.Forms.DialogResult.OK
Me.Close()
'Me.Dispose()
End Sub
Public Sub UpdateOUSearcherStatus(ByVal entriesFound As Integer)
'lblOUsFound.Text = Format("{0} Organizational Units Found", ousFound)
lblOUsFound.Text = entriesFound.ToString
End Sub
In my ADSearcher class I have:
Public Event ResultFound(ByVal ousFound As Integer)
Public Event SearchCompleted(ByVal ouNodes As TreeNode)
and raise the events with:
RaiseEvent ResultFound(resultCount)
'and
RaiseEvent SearchCompleted(rootNode)
I'm attempting to make a multi-threaded download manager that has a limit of 4 concurrent downloads. In my research, I came across the following: C# Downloader: should I use Threads, BackgroundWorker or ThreadPool?
[edit] updated code:
Imports System.Net
Imports System.Collections.Concurrent
Imports System.ComponentModel
Imports System.Threading
Public Class Form1
Const MaxClients As Integer = 4
' create a queue that allows the max items
Dim ClientQueue As New BlockingCollection(Of WebClient)(MaxClients)
' queue of urls to be downloaded (unbounded)
Dim UrlQueue As New Queue(Of String)()
Dim downloadThread As Thread
'Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' create four WebClient instances and put them into the queue
For i As Integer = 0 To MaxClients - 1
Dim cli = New WebClient()
AddHandler cli.DownloadFileCompleted, AddressOf DownloadFileCompleted
AddHandler cli.DownloadProgressChanged, AddressOf DownloadProgressChanged
ClientQueue.Add(cli)
Next
' Fill the UrlQueue here
UrlQueue.Enqueue("http://www.gnu.org/licenses/gpl-1.0.txt")
UrlQueue.Enqueue("http://www.gnu.org/licenses/gpl-2.0.txt")
UrlQueue.Enqueue("http://www.gnu.org/licenses/gpl-3.0.txt")
UrlQueue.Enqueue("http://www.gnu.org/licenses/lgpl-2.1.txt")
UrlQueue.Enqueue("http://www.gnu.org/licenses/lgpl-3.0.txt")
UrlQueue.Enqueue("http://www.gnu.org/licenses/fdl-1.1.txt")
UrlQueue.Enqueue("http://www.gnu.org/licenses/fdl-1.2.txt")
UrlQueue.Enqueue("http://www.gnu.org/licenses/fdl-1.3.txt")
downloadThread = New Thread(AddressOf downloadQueue)
downloadThread.IsBackground = True
downloadThread.Start()
End Sub
Private Sub downloadQueue()
' Now go until the UrlQueue is empty
While UrlQueue.Count > 0
Dim cli As WebClient = ClientQueue.Take() ' blocks if there is no client available
Dim url As String = UrlQueue.Dequeue()
Dim fname As String = CreateOutputFilename(url)
cli.DownloadFileAsync(New Uri(url), fname, New DownloadArgs(url, fname, cli))
AppendText(url & " started" & vbCrLf)
End While
End Sub
Private Sub DownloadProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs)
Dim args As DownloadArgs = DirectCast(e.UserState, DownloadArgs)
' Do status updates for this download
End Sub
Private Sub DownloadFileCompleted(sender As Object, e As AsyncCompletedEventArgs)
Dim args As DownloadArgs = DirectCast(e.UserState, DownloadArgs)
' do whatever UI updates
Dim url As String = "Filename" '<============I'd like to be able to pass the filename or URL but can't figure this out
AppendText(url & " completed" & vbCrLf)
' now put this client back into the queue
ClientQueue.Add(args.Client)
End Sub
Public Function CreateOutputFilename(ByVal url As String) As String
Try
Return url.Substring(url.LastIndexOf("/") + 1)
Catch ex As Exception
Return url
End Try
End Function
Private Delegate Sub SetTextCallback(text As String)
Private Sub AppendText(text As String)
If Me.TextBox1.InvokeRequired Then
TextBox1.Invoke(New Action(Of String)(AddressOf AppendText), text)
Return
End If
Me.TextBox1.AppendText(text)
Me.TextBox1.SelectionStart = TextBox1.TextLength
Me.TextBox1.ScrollToCaret()
End Sub
End Class
Class DownloadArgs
Public ReadOnly Url As String
Public ReadOnly Filename As String
Public ReadOnly Client As WebClient
Public Sub New(u As String, f As String, c As WebClient)
Url = u
Filename = f
Client = c
End Sub
End Class
This will successfully download the first 4 files in the UrlQueue, but it then seems to freeze and no further files download. I'd imagine the problem lies in something minor I missed in the process of converting from C# to vb.net, but I can't seem to figure this out.
ClientQueue.Take() blocks the UI thread. Also, WebClient will want to raise the DownloadFileCompleted event on the UI thread - but it is already blocked by ClientQueue.Take(). You have a deadlock.
To resolve this, you got to move your blocking loop to another background thread.
You are blocking the ability for your async queue to process. Not sure this is the "Correct" way to do this but the changes here make it work:
While UrlQueue.Count > 0
Do While ClientQueue.Count = 0
Application.DoEvents()
Loop
Dim cli As WebClient = ClientQueue.Take() ' blocks if there is no client available
Dim url As String = UrlQueue.Dequeue()
Dim fname As String = CreateOutputFilename(url) ' or however you get the output file name
cli.DownloadFileAsync(New Uri(url), fname, New DownloadArgs(url, fname, cli))
End While
I have been very interested as of late in interfaces and the ability to further customize them beyond using them in their default state.
I have been researching IList(of T) specifically. The advantages of using generic lists as opposed to ArrayLists has astounded me. Here is a picture of a test. This is the site that goes into further explanation about the Test.
So, naturally I wanted to experiment. When I first iterate through the list with the ForNext method the code works fine. The second time I can't access the name of the Form in the list because it is disposed. Anyone have any insight how I can access the forms properties in the list.
Public Class frmMain
Dim Cabinet As List(Of Form) = New List(Of Form)
Dim FormA As New Form1
Dim FormB As New Form2
Dim FormC As New Form3
Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles _Me.Load
Cabinet.Add(FormA)
Cabinet.Add(FormB)
Cabinet.Add(FormC)
End Sub
Sub displayForm(ByVal aForm As Form)
Dim myFormName As String = ""
Stopwatch.Start()
If aForm.IsDisposed = False Then
aForm.Show()
Else
myFormName = aForm.(How do I access this objects Name?)
aForm = New Form '<----- I would rather simply use aForm = New(aForm)
aForm.Name = myFormName
aForm.Show()
End If
Stopwatch.Stop()
Dim RealResult As Decimal = (Stopwatch.ElapsedMilliseconds / 1000)
Debug.WriteLine(RealResult)
Stopwatch.Reset()
End Sub
Private Sub btnForEach_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnForEach.Click
'Dim instance as List
'Dim action as Action(of T)
'instance.ForEach(action)
'action = delegate to a method that performs an action on the object passeed to it
Cabinet.ForEach(AddressOf displayForm)
End Sub
I really don't understand why if VB knows that this is a Generic list, which means it is knowledgable of the list's type, and the objects are all constrained to be forms; why I can't call a constructor on an item in the list. Ex. aForm = New aForm or aForm = New Cabinet.aForm
Tear this one open for me somebody. Thanks.
You can't construct a new instance of "aForm" because its isn't a type, it is an instance of type Form.
If you wanted to prevent the ObjectDisposedException, you could hide the form instead of closing it. Place the following code in each forms code behind:
Public Class Form1
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
Dim form = CType(sender, Form)
form.Visible = False
e.Cancel = True
End Sub
End Class
This is a bit hacky, however, but then you wouldn't need the code in the Else block.
Edit
You could try this instead:
Private Sub displayForm(ByVal aForm As Form)
Dim indexOfCab As Integer = Cabinet.IndexOf(aForm)
If indexOfCab <> -1 Then
If aForm.IsDisposed Then
aForm = CreateForm(aForm.GetType())
Cabinet(indexOfCab) = aForm
End If
aForm.Show()
End If
End Sub
Private Shared Function CreateForm(formType As Type) As Form
Return CType(Activator.CreateInstance(formType), Form)
End Function
You wouldn't need that big Select statement.
This is the only way I have been able to get it to work. I feel it is extremely inefficient however, and hope someone can set me on a path to a better way to do this. The below is what I'm trying to achieve.
Sub displayForm(ByVal aForm As Form)
Dim myFormName As String = ""
If Cabinet.Contains(aForm) Then
Dim indexOfCab As Integer = Cabinet.IndexOf(aForm)
Dim ObjForm As Form = Cabinet.Item(indexOfCab)
If aForm.IsDisposed Then
Select Case indexOfCab
Case 0
aForm = Nothing
aForm = New Form1
Cabinet.Item(indexOfCab) = aForm
Cabinet.Item(indexOfCab).Show()
Case 1
aForm = Nothing
aForm = New Form2
Cabinet.Item(indexOfCab) = aForm
aForm.Show()
Case 2
aForm = Nothing
aForm = New Form3
Cabinet.Item(indexOfCab) = aForm
Cabinet.Item(indexOfCab).Show()
End Select
Else
Cabinet.Item(indexOfCab).Show()
End If
End If
End Sub