VB.NET access winforms non-public member - vb.net

In my application that uses a multi-document interface model I have one of the windows that is constantly refreshing itself in an asynchronously. And when the UI launches another window (wizard for instance), newly created window can in some cases loose focus(wasn't able to reproduce this behavior, but is was reported by many).
I think this can be related to the fact that when async function finishes in main window it actually grabs the focus back(but apparently it only happens when wizard window doing some computation).
I was planning to cancel the timer updates in a main window when it is not active. But hit a problem of inability to access "My.Active" property.
I can see it in a debug but cannot access it from the code:
This is a partial screenshot of "My" component:
I must be missing something simple, but wasn't able to figure this out for a couple hours now.

I ended up using Reflection to get the property as it was suggested in comments to my question. It's not pretty but it works for my current scenario.
Here is the solution:
Dim prop As System.Reflection.PropertyInfo = Me.GetType().GetProperty("Active",
System.Reflection.BindingFlags.NonPublic Or
System.Reflection.BindingFlags.Instance)
Dim value As Boolean = prop.GetValue(Me)

Related

Operating a runtime webbrowser control from a background thread vb.net

I'm trying to use a bunch of webbrowsers in some background threads. This works no problem when I use webbrowser controls that i have placed on the form in design view but now when they are created at runtime.
I declare the webbrowsers array globally:
Dim webbroswers(-1) As WebBrowser
The following code is on the main thread:
ReDim Preserve webbroswers(somenum)
For i = 0 To sumnum
webbroswers(currentbrowsermax + i) = New WebBrowser
Next
Then this code is run on the background thread:
If webbroswers(num).InvokeRequired Then
webbroswers(num).Invoke(Sub() webbroswers(num).Navigate(someurl))
Else
webbroswers(num).Invoke(Sub() webbroswers(num).Navigate(someurl))
The program crashes at this point with the following error:
Unable to get the window handle for the 'WebBrowser' control. Windowless ActiveX controls are not supported.
Any help on this would be great. Also if anyone knows how to suppress script errors then I think this might help. I've tried: WebBrowser(num).ScriptErrorsSuppressed = True but this doesn't work (it doesn't work elsewhere in my code when running on the main thread either) Thanks!
The Control.InvokeRequired and Invoke members use the Handle property to figure out what thread owns the control. Problem is, the Handle is null for the web browsers that you created. A control only has a valid handle when you made it visible on a form. Which you didn't do. It will then try to create the handle but that's a fail whale, an ActiveX control like WebBrowser needs a valid Parent. Without Me.Control.Add(), as was done in your original version, it won't have one.
The workaround is simple, you just need another control with a valid Handle property. Any will do, it only cares about the thread that owns the handle, not the value of the handle.
You have one: your form. Use Me.InvokeRequired and Me.Invoke() instead. Or Application.OpenForms(0) if you can't easily get a reference to the form object, best avoided.

Issue with OpenFileDialog and threading

I just upgraded from VS 2005 to VS 2012. This is a new issue that I do not understand. I am using the default "Form1" class the VS automatically creates. I added a button to open a file open dialog and when I click the button I get this error:
Current thread must be set to single thread apartment (STA) mode before OLE calls can be >made. Ensure that your Main function has STAThreadAttribute marked on it. This exception >is only raised if a debugger is attached to the process.
I have added " to Public Class Form1:
<STAThread()> Public Class Form1
But I get this...
Attribute 'STAThreadAttribute' cannot be applied to 'Form1' because the attribute is not >valid on this declaration type.
I have searched but get some info telling me that I need to set the entry point (Form1 I believe) to be Single Thread Attribute but the above code does not work.
How?
The <STAThread()> attribute cannot be added to classes like your form. It only works when it is applied to the Main function, which is the entry point of your application.
But VB.NET hides this function from you because it is rare that one needs to mess with Main in a WinForms application. It is just needed to get the plumbing set up for your app, which the compiler can manage for you. This is controlled by the "Application Framework" checkbox in the project options. If this is checked, the compiler automatically generates the Main function and the required plumbing. You can disable this option, but it makes life quite a bit harder for the average WinForms developer because you'll have to write and maintain your own Main function.
The real question here is why this is a problem at all. The compiler-generated Main function for a WinForms application is always going to have the STAThread attribute applied to it. That is just how the WinForms framework is designed to run. If that is not happening, then there is something badly wrong with your project. I would recommend scrapping it and starting over letting Visual Studio create a new WinForms project from one of the built-in templates. Everything should Just Work™.
The other option, of course, is that you're trying to display the OpenFileDialog on a separate thread (other than your main UI thread). But from your description in the question (adding a button to the form to display the dialog), it doesn't sound like this is the case. Regardless, the solution is not to do that. For example, if you're using a BackgroundWorker to do work on a non-UI thread in order to keep the UI responsive, that's great, but you'll want to do all of the UI stuff like showing an OpenFileDialog on the main UI thread before invoking the BackgroundWorker. There is a way to set a particular thread's apartment state using the SetApartmentState function, but I really don't recommend showing an OpenFileDialog on a background thread.

accessing dynamically created form elements from a thread in vb.net

I am new to VB and .Net and I have a task that I am unable to proceed.
I start a thread when screen1 Loads. User then goes to screen2.
On screen2 the thread is still running behind. when the user performs an action(say click) on screen2, it will trigger the thread to access elements on screen2
The elements in screen 2 are dynamically created and not designed in IDE.
So in essence a thread that is created on one form needs to access dynamically created form elements on another form.
If my question is too simple, please forgive me. If it is not clear please let me know and I will rephrase it.
Note: The element that I am talking about is basically a picture box inside a flow layout panel.
Thanks in advance
All help is eagerly(biting nails now) awaited and greatly appreciated.
EDIT
Lets say a thread called ctThread was started in dashboard screen
ctThread.start()
This thread is running endlessly, waiting for a trigger event.
Meanwhile User has gone to a screen called QuizScreen and on this screen(form) I have to update some dynamically created elements whose names I know.
So when the time is right for ctThread which is waiting (Listener Thread) it will call the sub below.
Sub
public sub changeComputerStatus(ByVal node)
Dim flowpanel As FlowLayoutPanel = CType(QuizScreen.FlowLayoutPanel1.Controls("flow_" + node), FlowLayoutPanel)
Dim pictControl As PictureBox = CType(flowpanel.Controls("pict_" + node), PictureBox)
pictControl.Image = System.Drawing.Image.FromFile(Application.StartupPath & "\images\application-on.png")
end sub
here node keeps changing. This is how I differentiate each control I create.
Note : This thread was started in a screen called dashboardscreen and the user is now on a different screen QuizScreen.
The first line of the sub I gave above runs and returns nothing to the flowpanel. Hence when It goes to the next line, it is not able to use the nothing as reference. And hence the above mentioned error.
Two things.
The most important thing you must know is that you cannot directly access a control's properties from a background thread. Controls may only be manipulated from the foreground UI thread. In order to access controls from a background thread, you need to use some form of asynchronous programming, e.g. creating delegates a la .NET 1.x - 3.5 or using the new Task<T> and async and await keywords.
Did you actually add the dynamically created controls to the form's control collection? Mind you, you'll still need to access the controls via delegates or some other asynchronous method as explained in 1 above.
UPDATE:
To answer the question in the OP's comment below: you can also Invoke a method on a UI object. Basically, you're telling .NET to run the invoked method and it runs on the UI object's creating thread (in this case, the UI thread), which is what you want. This will allow you (depending on the method or property invoked) to "update" the control "from the background"—again, this is all sleight of hand; when invoking a method on a UI object, the invkoked method runs on the UI thread possibly using data passed into said method from the background task.
Also, check out the MSDN documentation on the BacgkroundWorker (this was introduced in .NET 2.0 and is superseded by the async and await keywords along with Task<T> in .NET 4.5). There is lots of documentation available that explains exactly how to do what you're asking. It's not hard to find by performing a quick search on MSDN or Bing (or your preferred search engine).

What is the correct way to fully remove a control from a parent control/form?

I have a UI element in my application where a Panel is used to host one of several potential custom UserControls. The Panel itself is hosted in a standardised UserControl that I am using something like a non-modal dialog that I'm calling a 'pane'.
The method I use is to instantiate a new instance of the standard pane, then with logic instantiate one of the several optional hosted controls inside it using Panel.Controls.Add(control). I then add the new pane to the interface control in a set location, again with a Control.Controls.Add(control), followed by a control.BringToFront() to maximise its z position.
This all works well, however when the time comes to hide the pane and destroy it, I cannot seem to fully get rid of it. Originally I was simply using Control.Controls.Remove(control) and for good measure setting the pane's Parent property to Nothing. This would have the desired effect of making the pane disappear, and my assumption was that now the control was unreferenced, that GC would dispose of it.
What I am seeing however is that the control still blits instantaneously onto the screen when the next outer hosting TabControl changes tab page, implying it still exists somewhere. I can confirm that this is not a graphical issue and the pane object persists using the VS Watch window's 'Make Object ID'. (At least I think this is proof, that without a code-accessible reference I can still directly see the object and its properties continue to exist.)
I have tried replacing
Control.Controls.Remove(pane)
pane.Parent = Nothing
with
pane.Dispose()
GC.Collect()
where the Dispose call I can confirm both removes the control from its parent's Controls collection and sets its Parent property to Nothing, but appears to do no more. It persists after forced GC and still blits onscreen occasionally.
This all leads to my original question, what is the proper way to remove and fully destroy controls after they have served their purpose?
According to this article from MSDN it seems like you might be experiencing side affects from the object being on the finalization queue.
A Dispose method should call the GC.SuppressFinalize method for the object it is disposing. If the object is currently on the finalization queue, GC.SuppressFinalize prevents its Finalize method from being called.
Translation: The finalize method isn't being called, and so the resources associated with your control are not being released. After a bit more digging, I found that you should
Always call Dispose before you release your last reference to the Component. Otherwise, the resources it is using will not be freed until the garbage collector calls the Component object's Finalize method.
From this article.
So either you need to release your last reference OR you need to call the components finalize method directly so your GC.Collect() will work.

Force multi-threaded VB.NET class to display results on a single form

I have a windows form application that uses a Shared class to house all of the common objects for the application. The settings class has a collection of objects that do things periodically, and then there's something of interest, they need to alert the main form and have it update.
I'm currently doing this through Events on the objects, and when each object is created, I add an EventHandler to maps the event back to the form. However, I'm running into some trouble that suggests that these requests aren't always ending up on the main copy of my form. For example, my form has a notification tray icon, but when the form captures and event and attempts to display a bubble, no bubble appears. However, if I modify that code to make the icon visible (though it already is), and then display the bubble, a second icon appears and displays the bubble properly.
Has anybody run into this before? Is there a way that I can force all of my events to be captured by the single instance of the form, or is there a completely different way to handle this? I can post code samples if necessary, but I'm thinking it's a common threading problem.
MORE INFORMATION: I'm currently using Me.InvokeRequired in the event handler on my form, and it always returns FALSE in this case. Also, the second tray icon created when I make it visible from this form doesn't have a context menu on it, whereas the "real" icon does - does that clue anybody in?
I'm going to pull my hair out! This can't be that hard!
SOLUTION: Thanks to nobugz for the clue, and it lead me to the code I'm now using (which works beautifully, though I can't help thinking there's a better way to do this). I added a private boolean variable to the form called "IsPrimary", and added the following code to the form constructor:
Public Sub New()
If My.Application.OpenForms(0).Equals(Me) Then
Me.IsFirstForm = True
End If
End Sub
Once this variable is set and the constructor finishes, it heads right to the event handler, and I deal with it this way (CAVEAT: Since the form I'm looking for is the primary form for the application, My.Application.OpenForms(0) gets what I need. If I was looking for the first instance of a non-startup form, I'd have to iterate through until I found it):
Public Sub EventHandler()
If Not IsFirstForm Then
Dim f As Form1 = My.Application.OpenForms(0)
f.EventHandler()
Me.Close()
ElseIf InvokeRequired Then
Me.Invoke(New HandlerDelegate(AddressOf EventHandler))
Else
' Do your event handling code '
End If
End Sub
First, it checks to see if it's running on the correct form - if it's not, then call the right form. Then it checks to see if the thread is correct, and calls the UI thread if it's not. Then it runs the event code. I don't like that it's potentially three calls, but I can't think of another way to do it. It seems to work well, though it's a little cumbersome. If anybody has a better way to do it, I'd love to hear it!
Again, thanks for all the help - this was going to drive me nuts!
I think it is a threading problem too. Are you using Control.Invoke() in your event handler? .NET usually catches violations when you debug the app but there are cases it can't. NotifyIcon is one of them, there is no window handle to check thread affinity.
Edit after OP changed question:
A classic VB.NET trap is to reference a Form instance by its type name. Like Form1.NotifyIcon1.Something. That doesn't work as expected when you use threading. It will create a new instance of the Form1 class, not use the existing instance. That instance isn't visible (Show() was never called) and is otherwise dead as a doornail since it is running on thread that doesn't pump a message loop. Seeing a second icon appear is a dead give-away. So is getting InvokeRequired = False when you know you are using it from a thread.
You must use a reference to the existing form instance. If that is hard to come by (you usually pass "Me" as an argument to the class constructor), you can use Application.OpenForms:
Dim main As Form1 = CType(Application.OpenForms(0), Form1)
if (main.InvokeRequired)
' etc...
Use Control.InvokeRequired to determine if you're on the proper thread, then use Control.Invoke if you're not.
You should look at the documentation for the Invoke method on the Form. It will allow you to make the code that updates the form run on the thread that owns the form, (which it must do, Windows forms are not thread safe).
Something like
Private Delegate Sub UpdateStatusDelegate(ByVal newStatus as String)
Public sub UpdateStatus(ByVal newStatus as String)
If Me.InvokeRequired Then
Dim d As New UpdateStatusDelegate(AddressOf UpdateStatus)
Me.Invoke(d,new Object() {newStatus})
Else
'Update the form status
End If
If you provide some sample code I would be happy to provide a more tailored example.
Edit after OP said they are using InvokeRequired.
Before calling InvokeRequired, check that the form handle has been created, there is a HandleCreated property I belive. InvokeRequired always returns false if the control doesn't currently have a handle, this would then mean the code is not thread safe even though you have done the right thing to make it so. Update your question when you find out. Some sample code would be helpful too.
in c# it looks like this:
private EventHandler StatusHandler = new EventHandler(eventHandlerCode)
void eventHandlerCode(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(StatusHandler, sender, e);
}
else
{
//do work
}
}