Screen capture of a particular instance of multiple application windows - vb.net

I need to screen capture a particular window of a multi-window application.
A good example: I run OUTLOOK with two windows - Mail and Calendar.
When I use Process.GetProcessesByName(ApplicationToWatch).FirstOrDefault() I of course get the "first" window. How do I get access to the "second" or subsequent windows? (Interesting that there is a FirstOrDefault but not another method to get something else - I'm clearly missing something).

You already have some indications on how to get a child window Handle using EnumChildWindows, to be associated with GetClassName.
So I'll propose you another method, using UI Automation.
It's probably not so well known, but in this case it's quite straightforward and it can simplify this task.
You just need to know the Handle of the Main Window whose child you want to enumerate and filter the collection (AutomationElementCollection) of child windows using LINQ's .Where() or .FirstOrDefault() methods.
As a note, the UI Automation enumeration is not as thorough as EnumChildWindows. Also, the returned Class Names can be different, in some cases, from the actual class name of specific UI Elements.
But those are, possibly, UI Elements your not interested in.
To get the Handle of the Outlook main window, we use Process.GetProcessesByName() as usual.
The Automation Element is then aquired using the AutomationElement.FromHandle() method.
To find specific child Elements, we can use this Main Automation Element (source reference) .FindAll() method, filtering the returned collection, if needed, with .Where() - to define a sub-collection - or .FirstOrDefault(), to get the reference of a specific Element Class or Handle (or other known details).
These methods show how to take a screenshot of Outlook's side Calendar panel and the Main Calendar window.
Use the code already discussed in your previous question to take the actual screenshot of the selected Screen bounds.
This code requires to add a reference to: UIAutomationClient
UIAutomationTypes WindowsBase
Imports System.Diagnostics
Imports System.Drawing
Imports System.Windows.Automation
Dim OutLookProc As Process = Process.GetProcessesByName("OUTLOOK").FirstOrDefault()
Dim MainElement As AutomationElement = AutomationElement.FromHandle(OutLookProc.MainWindowHandle)
Dim SmallCalendar As AutomationElement =
MainElement.FindAll(TreeScope.Subtree, Automation.RawViewCondition).
OfType(Of AutomationElement)().
FirstOrDefault(Function(elm) elm.Current.Name.Contains("NUIDocument") AndAlso
(Not String.IsNullOrEmpty(elm.Current.AutomationId)))
Dim CalendarNavigator As AutomationElement =
MainElement.FindAll(TreeScope.Subtree, Automation.RawViewCondition).
OfType(Of AutomationElement)().
FirstOrDefault(Function(elm) elm.Current.ClassName.Contains("TreeDisplayNode"))
If SmallCalendar IsNot Nothing Then
Dim SmallCalendarHeight As Integer = CInt(CalendarNavigator.Current.BoundingRectangle.Y -
SmallCalendar.Current.BoundingRectangle.Y)
Dim CalLocation As Point = New Point(CInt(SmallCalendar.Current.BoundingRectangle.Location.X),
CInt(SmallCalendar.Current.BoundingRectangle.Location.Y))
Dim CalSize As Size = New Size(CInt(SmallCalendar.Current.BoundingRectangle.Width), SmallCalendarHeight)
Dim SmallCalendarBounds As Rectangle = New Rectangle(CalLocation, CalSize)
SmallCalendarBounds.Inflate(-20, 0)
'CopyFormScreen() the SmallCalendarBounds rectangle. Inflate as needed
End If
This part of the code calculates the Screen bounds of Outlook's side Calendar object.
It may not be that useful, but it's a way to show how you can use these classes to parse/inspect the UI Elements of a process Main Window.
This is the result:
The Main Calendar window is simpler to identify and capture.
All Calendars classes names end with ViewWnd, thus, no matter which one is selected/in use, you will always be able to identify it quite easily.
Dim LargeCalendar As AutomationElement =
MainElement.FindAll(TreeScope.Descendants, Automation.RawViewCondition).
OfType(Of AutomationElement)().
FirstOrDefault(Function(elm) elm.Current.ClassName.Contains("ViewWnd"))
If LargeCalendar IsNot Nothing Then
Dim LCalLocation As Point = New Point(CInt(LargeCalendar.Current.BoundingRectangle.Location.X),
CInt(LargeCalendar.Current.BoundingRectangle.Location.Y))
Dim LCalSize As Size = New Size(CInt(LargeCalendar.Current.BoundingRectangle.Width),
CInt(LargeCalendar.Current.BoundingRectangle.Height))
Dim LargeCalendarBounds As Rectangle = New Rectangle(LCalLocation, LCalSize)
End If
This is the result:

This should help:
getting the window position:
How to get and set the window position of another application in C#
getting the window handle:
Get Application's Window Handles
bringing another application into the front:
https://social.msdn.microsoft.com/Forums/en-US/b5a91ac4-4894-45a1-aa66-b4d548ca8163/bring-another-application-to-front?forum=winforms
you could try those tricks and just copy from screen.

Related

VB.NET - Dynamically created controls and how to manipulate them

If I create a control like so:
Dim fb As New Label
With fb
.Name = "newLabel"
.text = "some text"
<etc.>
Me.Controls.Add(fb)
End With
Now, if I wanted to change the text on that label during run time, I would normally do:
newLabel.text = "some other text"
Unfortunately, Visual Studio won't let me do that as 'newLabel' isn't defined until run time.
So, my question is: How do I reference a control created in such a way? (The only way I can think of is to loop through all controls until I find the one I'm looking for, but that seems a tad inefficient to me.)
'newLabel' isn't defined until run time"
That isn't really accurate. You are confusing the object with a variable used to reference the object. When you add a control to a form in the designer, VS generates code to create and configure that control. It's much the same as the code you wrote and posted. You can see it in the designer code file, which you can access if you click the 'Shoe All Files' button in the Solution Explorer. That code includes a member variable to which the created object is assigned. You then use that member variable to refer to that object in code.
If you're creating controls at run time then you generally can't declare a member variable for each one to be assigned to because you don't know how many there will be. If you do know how many there will be then you probably ought to be adding them at design time. That means that you have two options:
Declare a single member variable that will refer to a collection of controls created at run time and then access then via name or index from that.
Access them by name from the Controls collection of the form or other container control that you must add them to in order for them to be displayed.
Option 2 requires that you provide a unique name for each control when you create it. Option 1 doesn't require a name at all, although it doesn't preclude one.
Option 1 might look like this:
At the class level:
Private labels As New List(Of Label)
In a method somewhere:
For i = 0 To 9
Dim lbl As New Label
labels.Add(lbl)
Controls.Add(lbl)
Next
Later:
Dim lbl = labels(recordIndex)
Option 2 might look like this:
In a method somewhere:
For i = 0 To 9
Dim lbl As New Label With {.Name = "titleLabel" & i}
Controls.Add(lbl)
Next
Later:
Dim lbl = DirectCast(Controls("titleLabel" & recordIndex), Label)

How to set a listview FocusedItem programatically

How can I set the FocusedItem property programatically?
I've tried this so far with no success:
If lvw.FocusedItem Is Nothing Then
If lvw.Items.Count > 0 Then
lvw.Focus()
lvw.HideSelection = False
lvw.Items(0).Selected = True
lvw.Items(0).Focused = True
lvw.FocusedItem = lvw.Items(0)
lvw.Select()
End If
End If
btw the form where the listview is has not called the ShowDialog method yet.
Can this be the reason for this not to work?
You have to understand that each control on the Form and the Form itself is a window and for the window to have focus it must first be created and assigned a handle.
For a basic description of this, I refer you to: All About Handles in Windows Forms The following excerpts are from the referenced article.
What is a Handle?
A handle (HWND) is the return value from CreateWindowEx which the Windows Operating System uses to identify a window. A "window" in win32 is a much broader concept than you may think - each individual button, combobox, listbox etc comprises a window. (For more information see About Window Classes ) NOTE: there are other things known as "Handles" in the Framework - e.g. GDI Handles from a Bitmap or Handles to Device Contexts (HDCs) - this article discusses HWNDs only.
...
When does a Control create its handle? (When does a control call CreateWindowEx?)
A control tries as much as possible to defer creating its handle. This is because setting properties forces chatty interop between the CLR and user32.
Typically the handles for all the controls are created before the
Form.Load event is called. Handles can also be created if the "Handle"
property is called and the handle has not yet been created, or
CreateControl() is called.
So a window's handle is not immediately created when you instantiate a control. However, you can force a control to create its handle by referencing its Handle property.
So if you first get the ListView to create its handle, then when you set those properties that you wanted.
Dim f2 As New Form2
' you do not need this condition, it is here only for demonstration purposes
' so that you can step through the code in the debugger and observe the
' code execution.
If Not f2.ListView1.IsHandleCreated Then
' retrieval of the Handle will cause a handle to be created
' if it has not yet been created
' if you delete the If-Then block, you will need to retain the
' following statement
Dim h As IntPtr = f2.ListView1.Handle
End If
f2.ListView1.FocusedItem = f2.ListView1.Items(2)
f2.ListView1.Items(2).Selected = True
f2.ListView1.Items(2).Focused = True
f2.ActiveControl = f2.ListView1
f2.ShowDialog()
As others have commented, your code should work as written. If all you need is to programmatically access the focused item in your code, you shouldn't be experiencing any difficulties. (If you are, please describe them.)
If you are looking for a visual effect (the row being highlighted), my guess is that your code is in another control's event and the focus is being set back to that control automatically the instant after your code runs. More than likely your code needs to be where it is and trying to move it elsewhere to prevent this issue would be a waste of time.
However, there are other ways to set a row apart visually. When a list view isn't likely to stay focused, my preferred method is to distinguish the selected item with a different fore/back color. (You can use the focused item if you prefer, but I find the selected item more useful. Your call.)
Here is an example which visually highlights the selected row, regardless of focus:
Private Sub lvw_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lvw.SelectedIndexChanged
If lvw.Items Is Nothing Then Exit Sub
For Each lvi As ListViewItem In lvw.Items
If lvi.Selected = True Then
lvi.ForeColor = Color.DarkGray
lvi.BackColor = Color.LightCyan
Else
lvi.ForeColor = Color.Black
lvi.BackColor = Color.White
End If
Next
End Sub
EDIT:
In response to the added information that this form is being displayed using ShowDialog, yes, that is likely the source of your problem.
ShowDialog creates a new instance of the form. Therefore, if you have set any properties of a form or its controls, and later call ShowDialog to display that form, the form displayed is a new copy of the original form and will not reflect the changes you made programatically.
Imagine you sit down at a computer where a blank Word document is already open. You type something in it and then open a new document. The text you typed in the first document is not copied to the second. I think this is the root of your troubles here.

BringToFront isn't bringing form to the front

I'm trying to set up a button that does the following:
Checks to see if a form is open (and has lost focus). If so, it brings that form to the front.
If not, it opens a new instance of the form.
However, I've tried a few different methods and it will always either create a new form (if I use frm_About.visible as the check) or simply not do anything (with the following code).
Private Sub counter_aboutClick(sender As Object, e As EventArgs) Handles counter_About.Click
If Application.OpenForms().OfType(Of frm_About).Any Then
frm_About.BringToFront()
Else
Dim oAbout As frm_About
oAbout = New frm_About()
oAbout.Show()
oAbout = Nothing
End If
End Sub
I've heard that there's a bug with BringToFront in certain scenarios, am I hitting that bug?
VB.Net does a terrible thing and creates a default instance of a form (which can be referred to by its class name). This creates endless confusion and headaches - I suggest you read up on default instances (google can find a lot to read about, surely)
In this case, you have a class called frm_About as well as a default instance of that form which is also called frm_About. If you've created a new form of type frm_About then the following code
If Application.OpenForms().OfType(Of frm_About).Any Then
frm_About.BringToFront()
will search your open forms to look for a form of type frm_About and, if it finds one, will attempt to bring the default instance of frm_About to the front - note that the open form can be (an in your case is most likely) not the default instance but any instance created with New frm_About().
To find the actual instance of the form you would have to do something like :
For Each openForm In Application.OpenForms()
If TypeOf (openForm) Is frm_About Then _
CType(openForm, frm_About).BringToFront()
Next

i written a winforms application in VB .NET in visual studio 2010

I have to run a thread create in the code.
In the form1 i have a button that run the new separate thread for elaborate some data, so i need it for not freeze the form.
I have inizialized thread:
dim th as thread = new thread (addressof elaborate)
And at the button.click event:
th.isbackground= true
th.start()
Now, at the form load i have iconized my program, but when i start new thread the tray icon is duplicated from it.
I want to resolve that when start new thread it's not show new notifyicon.
Any ideas?
(i don't have found anything online, only Multiple notification icons appear when using multithreading)
Create a class called Elab
inside that class, put a sub called work
Add a timer to your form that is disabled
with a tickcount of say 1000
Declare this in your form class:
Dim El as Elab
inside Form_Load() put:
El = New Elab()
Under your button, put this:
Dim gThread as new System.Threading.Thread(Address of El.Work)
Timer1.Enabled = True
Inside Elab declare a variable called Result:
Public Result as boolean
When elab has finished whatever it is doing, set result as true, and store the results in public variables you can access later.
Inside the timer:
If El.Result = True then
'Get results, deal with data
end if
This isn't written particularly well, and isn't a working example, but is to mearly point you in the right direction, by giving a thread an address of a sub inside a class, you can then access the same class from other threads, which means your form doesn't freeze, and you're not creating a new instance of your form, you are just accessing an existing classes sub routine; just make sure to give yourself a way to get the results (in this example i suggested a timer, but a "get result" button would do the same job) once the thread has completed.
Remeber:
If you need Elab to finish before a particular part of code can continue (for example, elab might add two numbers, and you need the result to continue) you can start the thread and do this:
Do until El.Result = True
Application.DoEvents()
System.Threading.Thread.Sleep(1)
Loop
gThread.Join()
Hope this helps a little.
So the answer to your question is; don't put your sub inside a form, instead put it in a class that you can create an instance of.

InvokeMember using GetField. Field not found in VB.NET

This is probably a simple one but I can't seem to figure it out.
I have a bunch of form items created by the form designer declared as (in frmAquRun.Designer.vb)
Public WithEvents btnAquRunEvent1 As VisibiltyButtonLib.VisibilityButton
Public WithEvents btnAquRunEvent2 As VisibiltyButtonLib.VisibilityButton
... etc
And I basically want to be able to supply a number to a function access each of these fields. So I wrote this function. (in frmAquRun.vb)
Const EVENT_BUTTON_PREFIX As String = "btnAquRunEvent"
Public Function getEventButton(ByVal id As Integer) As Windows.Forms.Button
Dim returnButton As Windows.Forms.Button = Nothing
Try
returnButton = DirectCast(Me.GetType().InvokeMember(eventButtonName, Reflection.BindingFlags.GetField Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance, Nothing, Me, Nothing), Windows.Forms.Button)
Catch ex As Exception
End Try
Return returnButton
End Function
But it always seems to be generating field not found exceptions.
The message in the exception is "Field 'ATSIS_ControlProgram.frmAquRun.btnAquRunEvent1' not found.".
The namespace and form name in the message are correct. Any idea what i'm doing wrong?
The problem is that for WithEvents fields, VB actually creates a property that does the necessary event handler attaching and detaching. The generated property has the name of the field. The actual backing field gets renamed to _ + original name.1)
So in order for your code to work just prefix the button name by _ or use the BindingFlag that corresponds to the property getter (instead of GetField).
Alternatively, you can do this a lot easier by using the Controls collection of the form:
returnButton = DirectCast(Me.Controls(eventButtonName), Windows.Forms.Button)
But beware that this only works if the button is top-level, i.e. not nested within a container control on the form.
1) This is an implementation detail of the VB compiler but it’s portable (especially to Mono’s vbnc compiler) since the handling for WithEvents fields is described in great detail in the VB language specifications.
The problem is that the event handlers aren't really fields. As compiled they're really properties that implement add_btnAquRunEventX, remove_btnAquRunEventX and fire_btnAquRunEventX methods. There are ways of using reflection to get around this, but that's probably not the best way to approach the problem. Instead you can simply create a List<> and populate it with the event handlers, then index into that list.
I'm a little rusty in VB syntax but it should look something like this:
Dim events = New List<EventHandler>()
events.Add( btnAquRunEvent1 )
events.Add( btnAquRunEvent2 )
....
events( 0 )( null, EventArgs.Empty )
Take a step back though and evaluate why you're invoking by index. There may be a simpler way of abstracting the whole thing that doesn't involve all this indirection.