Show window of another program - vb.net

I've created a GUI in Visual Basic 2010 that launches another program, except the other program is hidden behind the GUI window when it starts. I'm already retrieving the Process ID of that other program in order to later kill it, but I'm not sure how to turn the ID into something I can use to bring the window forward.
The other solution is to move send my GUI to the back, but that doesn't work either. I think that's because of the other program though. There's a splash screen that comes up before the main window launches that requires interaction. My program doesn't get sent to back until after the splash screen is closed, defeating the purpose.

Have you looked into AppActivate. It's supposed to set focus to the application you want as long as it's running.

There are a couple ways you could do this. You can use AppActivate as Timmy has suggested or you can use pinvoke with the SetForegroundWindow function:
Imports System.Runtime.InteropServices
Public Class Form1
Dim oProcess As New Process()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Add buttons to the form
Dim cmd As New Button
cmd.Name = "cmdAppActivate"
cmd.Text = "App Activate"
cmd.Location = New Point(0, 0)
cmd.Size = New Size(90, 25)
AddHandler cmd.Click, AddressOf cmdAppActivate_Click
Me.Controls.Add(cmd)
cmd = New Button
cmd.Name = "cmdSetForegroundWindow"
cmd.Text = "Set Foreground Window"
cmd.Location = New Point(0, 30)
cmd.Size = New Size(130, 25)
AddHandler cmd.Click, AddressOf cmdSetForegroundWindow_Click
Me.Controls.Add(cmd)
' Open notepad
oProcess.StartInfo = New ProcessStartInfo("notepad.exe")
oProcess.Start()
End Sub
Private Sub cmdAppActivate_Click(sender As Object, e As EventArgs)
AppActivate(oProcess.Id) ' use appactivate to bring the notepad window to the front
End Sub
Private Sub cmdSetForegroundWindow_Click(sender As Object, e As EventArgs)
SetForegroundWindow(oProcess.MainWindowHandle) ' Use pinvoke (SetForegroundWindow) to bring the notepad window to the front
End Sub
End Class
Module Module1
<DllImport("user32.dll")> _
Public Function SetForegroundWindow(ByVal hWnd As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
End Module

Related

CrystalReportViewer.RefreshReport hangs when running from BackgroundWorker

I'm trying to "enhance" my reporting code by adding a loading screen while the Crystal Report is being prepared/loaded. Before I started trying to add the loading screen, all of my reports would come up just fine, but the cursor change just wasn't "enough" of an indication that the application was still working on pulling the report - some of them can take a while - so I wanted to provide a more "obvious" visual cue.
In order to accomplish this, I've put the report creation method calls into a BackgroundWorker that exists in the loading screen itself (I haven't gotten around to learning how to use Async/Await well enough yet to feel comfortable using that instead). The loading screen comes up correctly and everything appears to work as expected until it actually attempts to display the report on screen. At that point, the "Please wait while the document is processing." box comes up (in the CrystalReportViewer control in the form used to display reports), but it just sits there, not even spinning. Eventually, my IDE throws an error about receiving a ContextSwitchDeadlock and I pretty much just have to cancel execution.
Here's my dlgReportLoading "splash screen" with a PictureBox control that contains an animated GIF:
Imports System.Windows.Forms
Public Class dlgReportLoading
Private DisplayReport As Common.CRReport
Private WithEvents LoadReportWorker As System.ComponentModel.BackgroundWorker
Public Sub New(ByRef Report As Common.CRReport)
InitializeComponent()
DisplayReport = Report
End Sub
Private Sub dlgReportLoading_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Cursor = Cursors.WaitCursor
Me.TopMost = True
Me.TopMost = False
LoadReportWorker = New System.ComponentModel.BackgroundWorker
LoadReportWorker.RunWorkerAsync()
End Sub
Private Sub dlgReportLoading_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
Me.Cursor = Cursors.Default
End Sub
Private Sub LoadReport_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles LoadReportWorker.DoWork
If Not DisplayReport.ReportOption = Common.CRReport.GenerateReportOption.None Then
Select Case DisplayReport.ReportOption
Case Common.CRReport.GenerateReportOption.DisplayOnScreen
'-- This is the method I'm currently testing
DisplayReport.ShowReport()
Case Common.CRReport.GenerateReportOption.SendToPrinter
DisplayReport.PrintReport()
Case Common.CRReport.GenerateReportOption.ExportToFile
DisplayReport.ExportReport()
End Select
End If
DisplayReport.ReportOption = Common.CRReport.GenerateReportOption.None
'--
'-- This code was in use before trying to generate the reports in the background
'If Not DisplayReport.CrystalReport Is Nothing Then
' DisplayReport.CrystalReport.Dispose()
'End If
'--
End Sub
Private Sub LoadReport_Complete(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LoadReportWorker.RunWorkerCompleted
Me.DialogResult = DialogResult.OK
Me.Close()
End Sub
End Class
As noted in the code above, I'm currently testing the ShowReport() method as defined here:
Protected Friend Sub ShowReport()
Dim ReportViewer As frmReportPreview
Me.PrepareReport()
ReportViewer = New frmReportPreview(Me)
With ReportViewer
.WindowState = FormWindowState.Maximized
.Show()
End With
End Sub
And the frmReportPreview is this:
Imports System.ComponentModel
Public Class frmReportPreview
Private DisplayReport As Common.CRReport
Private ReportToDisplay As CrystalDecisions.CrystalReports.Engine.ReportDocument
Public Sub New(ByRef Report As Common.CRReport)
InitializeComponent()
DisplayReport = Report
PrepareReportForDisplay()
Me.rptViewer.ReportSource = Nothing
Me.rptViewer.ReportSource = ReportToDisplay
' SET ZOOM LEVEL FOR DISPLAY:
' 1 = Page Width
' 2 = Whole Page
' 25-100 = zoom %
Me.rptViewer.Zoom(1)
Me.rptViewer.Show()
End Sub
Private Sub frmReportPreview_Shown(sender As Object, e As EventArgs) Handles Me.Shown
'-- HANGS HERE
Me.rptViewer.RefreshReport()
End Sub
Private Sub frmReportPreview_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
ReportToDisplay.Dispose()
Me.rptViewer.ReportSource = Nothing
End Sub
'...CODE FOR PREPARING THE REPORT TO BE DISPLAYED
End Class
The dlgReportLoading form pops up correctly and the animation plays until the frmReportPreview pops up in front of it (it doesn't close). The little box that has what is normally an animated spinning circle indicating the report data is being loaded appears, but almost immediately freezes in place.
I have a breakpoint in the LoadReport_DoWork() method of my dlgReportLoading form after the call to the ShowReport() method, but it never gets to that point. I also have one in the LoadReport_Complete() method of that form that it never hits either and that dialog never actually closes.
I put another breakpoint at the end of the frmReportPreview_Shown method, right after the Me.rptViewer.RefreshReport() call, but it never hits that either, so it seems clear that this is where things are getting stuck, but only when the report is being generated through the BackgroundWorker. If I just call the ShowReport() method without sending it through the "splash screen" and BackgroundWorker, everything generates and displays normally.
I've tried putting the RefreshReport() method into its own BackgroundWorker with no change in the behavior. I've tried making the frmReportPreview object display modally with ShowDialog() instead of just Show(). None of this seems to help the issue.
I have a feeling something is being disposed of too early somewhere, but I can't figure out what that would be. I can provide the rest of the report preparation code from frmReportPreview if required, but that all seems to be working without error, as far as I can tell. I'm not averse to trying alternate methods of accomplishing my goal of showing the user a loading screen while all the report preparation is taking place - e.g., Async/Await or other multi-threading methods - so any suggestions are welcome. Please let me know if any additional clarification is needed.
ENVIRONMENT
Microsoft Windows 10 Pro 21H1 (OS build 19043.1348)
Microsoft Visual Studio Community 2017 (v15.9.38)
Crystal Reports for .NET Framework v13.0.3500.0 (Runtime version 2.0.50727)
EDIT: I forgot to mention that this whole mess is being called from a GenerateReport() method in my CRReport class defined as:
Public Sub GenerateReport(ByVal ReportGeneration As GenerateReportOption)
Me.ReportOption = ReportGeneration
If Me.ReportOption = GenerateReportOption.None Then
'...CODE FOR REQUESTING A GENERATION OPTION FROM THE USER
End If
Dim ReportLoadingScreen As New dlgReportLoading(Me)
ReportLoadingScreen.ShowDialog()
End Sub
Which, in turn, is being called from my main form like this:
Private Sub PrintMyXMLReport(ByVal XMLFile As IO.FileInfo)
Dim MyXMLReport As New IO.FileInfo("\\SERVER\Applications\Reports\MyXMLReport.rpt")
Dim Report As New Common.CRReport(MyXMLReport, XMLFile)
Report.GenerateReport(Common.CRReport.GenerateReportOption.DisplayOnScreen)
End Sub
You should separate the heavy lifting and UI operations into distinct methods in order to put them into the appropriate BackgroundWorker events:
Protected Friend Sub PrepareReport()
' perform long-running background work
End Sub
Protected Friend Sub ShowReport()
Dim ReportViewer = New frmReportPreview(Me) With {.WindowState = FormWindowState.Maximized}
ReportViewer.Show()
End Sub
Private DisplayReport As Common.CRReport
Private Sub LoadReport_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles LoadReportWorker.DoWork
DisplayReport.PrepareReport()
End Sub
Private Sub LoadReport_Complete(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LoadReportWorker.RunWorkerCompleted
DisplayReport.ShowReport()
Me.DialogResult = DialogResult.OK
Me.Close()
End Sub
because LoadReport_DoWork actually runs on a new non-UI thread, and LoadReport_Complete runs on the caller thread, which is a UI thread. Only there can you interact with the UI and show Forms etc.

Threading and modal form windows

VB.Net code.
I have a program where I am running a process in a thread and in that thread I need to have a pop up message information box that is non-modal. The main process is in a thread because it has to run in parallel and the user can initiate this process many times at the same time.
I read that the modal message box needs to be a custom form that is also ran from a thread to not block the program from continuing on. such as .Show() stops the program and waits for the user input. And you have to use .ShowDialog() via a thread
My code:
Calling initial thread:
Public Event Report As EventHandler
'In a method
Task.Run(Function() BackgroundThread())
Private Function BackgroundThread() As Task()
RaiseEvent Report(Me, New System.EventArgs)
End Function
In the Report method I have a snippet of code that then calls the form window to pop up the modal window:
Private mDiaplayMessageBox As NonModalPopUp
Private Sub DisplayMessageBox()
mDiaplayMessageBox = New NonModalPopUp()
Task.Run(Sub() mDiaplayMessageBox.ShowDialog())
End Sub
The issue I am having is that when I am finished with the report method I want to close this popup message. But when there is more than one of these pop up windows open at a time, only the last window opened will close and the program loses the handle I think to the other pop up windows and they will not close.
To close the windows I have in the modal form this code
Public Sub CloseMe()
'This will grab the thread that this window is running on, solves Cross-Threading issue.
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf CloseMe))
Exit Sub
End If
Me.BackColor = Color.Red
Me.Close()
End Sub
This first time this code is called its will hit the Me.Invoke and then close the window. However, on any subsequent calls when it gets to Me.InvokeRequired this will then be set to false, not called the Me.Invoke and go to the Me.Close() but it will not close the window.
I tried to do something where I grab the Handle intptr value but when ever I vent just look at that value the program immediately throws a cross-threading exception.
All I want to do is close the other windows which does not seem like a hard task but I do not know what I am missing.
One of approaches you can follow to achieve your goal might be as code below shows:
You can create a custom event which you can use as a “call” to listen to for the closure of your form.
Public Class Form1
Dim frm2 As Form2
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
frm2 = New Form2
Task.Run(Sub()
AddHandler CloseFrm2, Sub()
Dim CloseMe As Action = Sub()
frm2.Close()
frm2.Dispose()
End Sub
If frm2.InvokeRequired Then
frm2.Invoke(Sub() CloseMe())
Else
CloseMe()
End If
End Sub
frm2.ShowDialog()
End Sub)
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
RaiseEventCloseFrm2()
End Sub
End Class
Module EventHelper
Public Event CloseFrm2()
Sub RaiseEventCloseFrm2()
RaiseEvent CloseFrm2()
End Sub
End Module

Unable to run my own created exe inside parrent form (vb.net)

I have been able to run an external program using the following code.
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("user32.dll")> Public Shared Function SetParent(ByVal hwndChild As IntPtr, ByVal hwndNewParent As IntPtr) As Integer
End Function
Private Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("notepad.exe")
PRO.Start()
Do Until PRO.WaitForInputIdle = True
'Nothing
Loop
SetParent(PRO.MainWindowHandle, Me.Handle)
PRO.Dispose()
End Sub
This works fine..... (for notepad that is)
However If I swich notepad for my own vb.net application it fails to launch that aplication inside the form but rather runs it outside of the form. I thought that the application I am trying to launch might of had somthing in it so I created a new application with nothing in it (as bare as I could get it) and run that instead of notepad but it also fails to launch within its "parent" form but rather it also triggers outside of the "parent" form insted?
Could someone please help me fix this?
You just need to wait a tiny bit longer for the MainWindowHandle property to be populated.
Here's a kludge that'll do it:
Private Async Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("C:\Users\mikes\Desktop\temp.exe")
PRO.Start()
Await Task.Run(Sub()
PRO.WaitForInputIdle()
While PRO.MainWindowHandle.Equals(IntPtr.Zero)
Threading.Thread.Sleep(10)
End While
End Sub)
SetParent(PRO.MainWindowHandle, Me.Handle)
End Sub
If you want a ten second fail-safe, and exceptions caught, then you could change it up to:
Private Async Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Try
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("C:\Users\mikes\Desktop\temp.exe")
PRO.Start()
Await Task.Run(Sub()
Dim timeout As DateTime = DateTime.Now.AddSeconds(10)
While timeout > DateTime.Now AndAlso PRO.MainWindowHandle.Equals(IntPtr.Zero)
Threading.Thread.Sleep(10)
End While
End Sub)
If (Not PRO.MainWindowHandle.Equals(IntPtr.Zero)) Then
SetParent(PRO.MainWindowHandle, Me.Handle)
Else
MessageBox.Show("Timed out waiting for main window handle.", "Failed to Launch External Application")
End If
Catch ex As Exception
MessageBox.Show(ex.ToString, "Failed to Launch External Application")
End Try
End Sub

How to run a console application without showing the console window

I have written an application with the following sub main:
Public Sub Main()
Dim Value As String() = Environment.GetCommandLineArgs
Dim F As Form
Select Case Value.Last.ToLower
Case "-character"
F = New frmCharacterSheet
Case "-viewer"
F = New frmClient
Case Else
F = New frmCombat
End Select
Application.Run(F)
End Sub
This is because I want to be able to install my app with three different startup modes based on the command line. I did have a form that did this, but this has made error trapping very hard because the main form just reports the error.
This console seems to work well but I don't want the user to see the black console screen at startup.
I have searched for the answer but most solutions are 'switch back to a windows forms application'. I don't want to do this though for the above reason. (I cannot use application.run(f) in a winforms start situation because I get a threading error.
I need to know either how to hide the console window, or alternatively how to code a main menu that will launch one of the other three forms (but making them the startup form).
Any help would be appreciated....
Try:
Private Declare Auto Function ShowWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As Boolean
Private Declare Auto Function GetConsoleWindow Lib "kernel32.dll" () As IntPtr
Private Const SW_HIDE As Integer = 0
Sub Main()
Dim hWndConsole As IntPtr
hWndConsole = GetConsoleWindow()
ShowWindow(hWndConsole, SW_HIDE)
'continue your code
End Sub
It has a side effect that the window will be shown and then immediately hidden
valter
"or alternatively how to code a main menu that will launch one of the other three forms (but making them the startup form)."
Start with a standard WinForms Project and use the Application.Startup() event. From there you can check your startup parameters and then dynamically change the Startup form by assigning your desired instance to "My.Application.MainForm". This will cause that form to load as if it was the one originally assigned to the "Startup Form" entry.
Click on Project --> Properties --> Application Tab --> "View Application Events" Button (bottom right; scroll down).
Change the Left dropdown from "(General)" to "(MyApplication Events)".
Change the Right dropdown from "Declarations" to "Startup".
Simplified code:
Namespace My
' The following events are available for MyApplication:
'
' Startup: Raised when the application starts, before the startup form is created.
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
' UnhandledException: Raised if the application encounters an unhandled exception.
' StartupNextInstance: Raised when launching a single-instance application and the application is already active.
' NetworkAvailabilityChanged: Raised when the network connection is connected or disconnected.
Partial Friend Class MyApplication
Private Sub MyApplication_Startup(sender As Object, e As ApplicationServices.StartupEventArgs) Handles Me.Startup
If True Then
My.Application.MainForm = New Form1 ' <-- pass your desired instance to MainForm
End If
End Sub
End Class
End Namespace
Just go to Project Properties> Application> Application Type> and select Windows Forms Application
At this point your ConsoleApplication turns totally invisible, with no User-Interface.
I just want to add another solution although Idle_Mind has already provided an excellent one. This demonstrates that you can use Application.Run(Form) inside a WinForms app.
Public Class Form1
Private Shared applicationThread As New Threading.Thread(AddressOf Main)
Private Shared Sub Main()
Dim myForm As Form
Dim config = 2 ' if 3, will run Form3
Select Case config
Case 2
myForm = New Form2
Case 3
myForm = New Form3
Case Else
MessageBox.Show("Bad config!")
Exit Sub
End Select
Application.Run(myForm)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
applicationThread.Start()
' immediately dispose Form1 so it's not even shown
Dispose()
End Sub
End Class

add external process to panel vb.net

I have a list of files displayed in a listbox.
when I select a file from the listbox i want the file to load into a panel on my form.
i.e. if its a word document word will open in the panel, if its a pdf reader wil open into the panel.
I can get the files to load externally using
Dim ProcStart As New ProcessStartInfo
ProcStart.FileName = ListBox1.SelectedItem
Process.Start(ProcStart)
however i am unsure of how to get it to then dock in my panel. I tried
Me.Panel1.Controls.Add(ProcStart)
but this is obviously wrong as I can't add a process as a control.
I did a bit of googleing and have tried to do it this way
<DllImport("user32.dll")>
Shared Function SetParent(ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As UInteger
End Function
Private Sub ListBox1_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
Dim proc As Process
Dim AppPath As String
AppPath = lstDocs & ListBox1.SelectedItem
proc = Process.Start(AppPath)
proc.WaitForInputIdle()
SetParent(proc.MainWindowHandle, Me.Panel1.Handle)
End Sub
but the word application still opens outside my program and not in the panel!!
Any ideas? and thanks for looking!
Have you tried adding a button with code behind it to start the process?
'This is how i would start the process
this would be in your code that starts the control ( inserting )
Dim dep1 As (INSERT YOUR EVENT HERE)= New (INSERT YOUR EVENT HERE)
AddHandler dep.OnChange, AddressOf dep_onchange
the actuall button
Private Sub dep_onchange1(ByVal sender As System.Object, ByVal e As System.EventArgs)
' this event is run asynchronously so you will need to invoke to run on the UI thread(if required)
If Me.InvokeRequired Then
lbnoes.BeginInvoke(New MethodInvoker(AddressOf GetNoes))
Else
GetNoes()
End If
End Sub