|
|
Let me start out by saying this is a fantastic code.
I was able to integrate into my own WPF application. I had to play some funny tricks so it wouldn't close the main application window, but close the sub windows instead.
The issue that I am running into is that if I have an instance of Microsoft excel open on the desktop and I try to open a completely different excel workbook in your hosting control, the new file opens in the current app instead of creating a new window (Process).
Any Ideas?
|
|
|
|
|
First, thank you for your feedback. Let me guess, you have host excel into the control and open another workbook then the workbook displayed into wrong excel process? I think you should get the reference of the aim excel application first and add the specify workbook into the excel application. Then the workbook will be displayed in the right window.
Wish this helpful to you.
|
|
|
|
|
I was trying to do that with excel, but also with other applications like word and such. I was able to find a work around by modifying your code. The second issue that I ran into was when I closed the child window or hosted application, it would prompt the window closing event for the main window. I got around this by forcing a boolean that is only switched when the user closes the main window.
The main window hosts a document previewer which will "host" the application hosting that you created. The hosted application will maintain the size of content presenter that was used in the document previewer.
Declared Global Variables
Public DVHostedApplicationControl As New HostedApplicationViewer
Public CloseApplication As Boolean = False
Closing event on main Window.
Private Sub Window_Closing(sender As System.Object, e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
If CloseApplication = False Then
e.Cancel = True
DVHostedApplicationControl.Dispose()
Else
...
End If
End Sub
Code for loading different files and applications
This code is used in a user control that is a document previewer of the file.
The user can select to edit the file which prompts the application to load in the sub window controller you created.
Private Sub Load_Application()
Me.DocumentViewer1 = Nothing
DVHostedApplicationControl = New HostedApplicationViewer(True)
DVHostedApplicationControl.FileName = _FilePath
If _FilePath.ToLower Like "*.doc*" Then
DVHostedApplicationControl.ExeName = "Word.exe"
ElseIf _FilePath.ToLower Like "*.xls*" Then
DVHostedApplicationControl.ExeName = "Excel.exe"
End If
HostController.Content = DVHostedApplicationControl
End Sub
Your Modified Code.
I added a file path instead of just the application so that it will load the a file into hosted application. When the hosted Window (application) closes (Disposes), it will also prompt the previewer to show a revised preview.
Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Runtime.InteropServices
Imports System.Diagnostics
Imports System.Windows.Interop
Imports System.IO
Imports System.Threading
Public Class HostedApplicationViewer
Inherits UserControl
Implements IDisposable
<System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)> _
Public Structure HWND__
Public unused As Integer
End Structure
Public Sub New(Optional ByVal FromViewer As Boolean = False)
InitializeComponent()
AddHandler Me.SizeChanged, AddressOf OnSizeChanged
AddHandler Me.Loaded, AddressOf OnVisibleChanged
AddHandler Me.SizeChanged, AddressOf OnResize
_FromViewer = FromViewer
End Sub
Protected Overrides Sub Finalize()
Try
Me.Dispose()
Finally
MyBase.Finalize()
End Try
End Sub
Private Sub OnChangedInMainThread()
Dim D As DocViewer = Find_UserControl(Me._Parent)
If D IsNot Nothing Then
D.Load_File(Me.FileName, True)
Else
End If
End Sub
Private _iscreated As Boolean = False
Private _FromViewer As Boolean = False
Private _isdisposed As Boolean = False
Private _appWin As IntPtr
Private _childp As Process
Private _Parent As ContentPresenter
Private m_exeName As String = ""
Public Property ExeName() As String
Get
Return m_exeName
End Get
Set(value As String)
m_exeName = value.ToUpper
End Set
End Property
Private m_FileName As String = ""
Public Property FileName() As String
Get
Return m_FileName
End Get
Set(value As String)
m_FileName = value
End Set
End Property
<DllImport("user32.dll", EntryPoint:="GetWindowThreadProcessId", SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
Private Shared Function GetWindowThreadProcessId(hWnd As Long, lpdwProcessId As Long) As Long
End Function
<DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function FindWindow(lpClassName As String, lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function SetParent(hWndChild As IntPtr, hWndNewParent As IntPtr) As Long
End Function
<DllImport("user32.dll", EntryPoint:="GetWindowLongA", SetLastError:=True)> _
Private Shared Function GetWindowLong(hwnd As IntPtr, nIndex As Integer) As Long
End Function
<DllImport("user32.dll", EntryPoint:="SetWindowLongA", SetLastError:=True)> _
Public Shared Function SetWindowLongA(<System.Runtime.InteropServices.InAttribute()> hWnd As System.IntPtr, nIndex As Integer, dwNewLong As Integer) As Integer
End Function
<DllImport("user32.dll", EntryPoint:="GetWindowLongA", SetLastError:=True)> _
Public Shared Function GetWindowLongA(<System.Runtime.InteropServices.InAttribute()> hWnd As System.IntPtr, nIndex As Integer) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function SetWindowPos(hwnd As IntPtr, hWndInsertAfter As Long, x As Long, y As Long, cx As Long, cy As Long, wFlags As Long) As Long
End Function
<DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function MoveWindow(hwnd As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, repaint As Boolean) As Boolean
End Function
Private Const SWP_ASYNCWINDOWPOS As Integer = &H4000
Private Const SWP_FRAMECHANGED As Integer = &H20
Private Const SWP_NOACTIVATE As Integer = &H10
Private Const SWP_NOMOVE As Integer = &H2
Private Const SWP_NOOWNERZORDER As Integer = &H200
Private Const SWP_NOREDRAW As Integer = &H8
Private Const SWP_NOSIZE As Integer = &H1
Private Const SWP_NOZORDER As Integer = &H4
Private Const SWP_SHOWWINDOW As Integer = &H40
Private Const GWL_STYLE As Integer = (-16)
Private Const WS_EX_MDICHILD As Integer = &H40L
Private Const WS_CHILD As Integer = &H40000000
Private Const WS_VISIBLE As Integer = &H10000000L
Protected Sub OnSizeChanged(s As Object, e As SizeChangedEventArgs)
Me.InvalidateVisual()
End Sub
Protected Sub OnVisibleChanged(s As Object, e As RoutedEventArgs)
If _iscreated = False Then
_iscreated = True
_appWin = IntPtr.Zero
Try
Dim procInfo As New System.Diagnostics.ProcessStartInfo()
procInfo.FileName = m_exeName
procInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(Me.m_exeName)
procInfo.WindowStyle = ProcessWindowStyle.Hidden
procInfo.UseShellExecute = False
procInfo.CreateNoWindow = False
If Not m_FileName = "" Then
procInfo.Arguments = (Convert.ToString("""") & Me.m_FileName.ToString) + """"
Dim Str As String = Right(m_FileName, Len(m_FileName) - InStrRev(m_FileName, "."))
Dim fp As String = GetAssociatedProgram(Str)
procInfo.FileName = fp
procInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(fp)
End If
Try
_childp = New Process
_childp = Process.Start(procInfo)
_childp.WaitForInputIdle()
_appWin = _childp.MainWindowHandle
Me._Parent = TryCast(Me.VisualParent, ContentPresenter)
Dim helper = New WindowInteropHelper(Window.GetWindow(Me))
SetParent(_appWin, helper.Handle)
SetWindowLongA(_appWin, GWL_STYLE, WS_VISIBLE)
Catch
End Try
Catch ex As Exception
Debug.Print(ex.Message + "Error")
End Try
MoveWindow(_appWin, GetPosition(_Parent).X, GetPosition(_Parent).Y, CInt(Me.ActualWidth), CInt(Me.ActualHeight), True)
End If
End Sub
Protected Sub OnResize(s As Object, e As SizeChangedEventArgs)
If Me._appWin <> IntPtr.Zero Then
MoveWindow(_appWin, GetPosition(_Parent).X, GetPosition(_Parent).Y, CInt(Me.ActualWidth), CInt(Me.ActualHeight), True)
End If
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If Not _isdisposed Then
If disposing Then
If _iscreated AndAlso _appWin <> IntPtr.Zero AndAlso Not _childp.HasExited Then
If _FromViewer Then
Try
Dim d As Windows.Threading.Dispatcher = Nothing
d = Application.Current.Dispatcher
If d.CheckAccess() Then
OnChangedInMainThread()
Else
d.BeginInvoke(DirectCast(AddressOf OnChangedInMainThread, Action))
End If
Catch
End Try
End If
_childp.Kill()
_childp.CloseMainWindow()
_childp.Close()
_appWin = IntPtr.Zero
If _childp IsNot Nothing Then
Dim F As New FileInfo(m_FileName)
For Each Proc As Process In Process.GetProcessesByName(Left(m_exeName, Len(m_exeName) - 4))
If SimilarText(50, Proc.MainWindowTitle, F.Name) And Not Proc.MainWindowTitle = "" And Not m_FileName = "" Then
Proc.Kill()
End If
Next
End If
End If
End If
_isdisposed = True
End If
End Sub
Public Sub Dispose() Implements System.IDisposable.Dispose
Me.Dispose(True)
GC.SuppressFinalize(Me)
Return
End Sub
End Class
There is still some strangeness with hosting the application. It seems to become an issue when I resize the main window or user control "hosting" the application. At the moment, it is not enough of a concern for me to work on.
Thanks again for your feedback and great code.
|
|
|
|
|
If i have another WPF Application i replace the notepad.exe to the WPF Application.exe filename even with Thread.Sleep(1000); it couldn't work it will pop up as another window. Do you know any way to solve this?
|
|
|
|
|
Sorry I haven't try to host another WPF app with this control. It is design for hosting legacy application. Why don't you try to merge two WPF applications from source code level? By the way, I have successfully used this control to host a python builted executable into a WPF application.
Thank you for your feedback.
|
|
|
|
|
Yeah i notice that part of the codes, anyway due to the nature of application controls, i could not dictate how the other WPF application could integrate with me also it was due to some requirement change that the other WPF Application needs to be embedded into the WPF Application I'm working on.
Hoping you could point a way i could wrap this WPF application up and embed it to mine WPF application.
|
|
|
|
|
You could use content presenter in the container wpf application to display the other wpf application in the GUI. If you use MVVM pattern, the embedded application will be wrapper as a single model for the content presenter control.
|
|
|
|
|
This works beautifully! Good job!
|
|
|
|
|
Thank you! Glad to see you like it.
|
|
|
|
|
ArgumentNullException: Value cannot be null.
Parameter name: window
at System.Windows.Interop.WindowInteropHelper..ctor(Window window)
at WpfAppControl.AppControl.OnVisibleChanged(Object s, RoutedEventArgs e)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
at System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent)
at System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root)
at MS.Internal.LoadedOrUnloadedOperation.DoWork()
at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()
at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
I down.oad the example, i can run, but have this error,on designer screen so i cant open mainwindow.xaml
I runing visual studio 2013 and Windows 8.1
|
|
|
|
|
|
Hy Erxin,
great work and good help.
Maybe i dont undertsand something but don't go with inside other exe vb6?
I insert other old exe (for example hp help, diagnostic driver of pc, ecc) and all go but when i make for example a simple exe ("hello world") with vb6 don't go, because open a pop up (the same of last discussion with paint - i make sleep(10000))
So, i hope you help because for me is important
Thanks and best regards,
Andrea
|
|
|
|
|
|
Hi, using your method i can add an external application to my WPF project.
but for some reason, when the hosted exe app gets loaded, all the buttons in the loaded app are disabled.
i can see them change when i hover above them with the mouse, but i can't click them...
i tried to set the FocusManager.IsFocusScope of the WPF hosting element to "True", but it did nothing.
this does'nt happen in all applications... i'm trying to load a QT based application, maybe it has something to do with that ? any ideas as to why this can happen ?
Thanks
|
|
|
|
|
Hi. Sorry for the late response. I have a really busy working period during the previous month.
I haven't use it with a QT based application before, but it do work with WX based application. You could try to host your QT based application in a test application, just a simple WPF window, then check if it is works correctly. If it's not, you could use SPY++ to detect if there is a window handler for the QT application in side the WPF application.
You could also adjust the z-index of the WPF control, here is a link about how to change the z-index in WPF.
http://stackoverflow.com/questions/630006/bring-element-forward-z-index-in-silverlight-wpf[^]
Wish this helpful
Erxin.
|
|
|
|
|
Our need is basically to host the webbrowser control in a separate process for each toolwindow instance. This was done to ensure no memory leak in the webbrowser. I've created a separate windowsform application that only contains the webbrowser control and it is this .exe that I'm loading in a WPF User Control to use in the ToolWindow. It works alright but when I'm loading multiple instances of the toolwindow, the embedded process gets killed and doesn't load in the next toolwindow. Is there any way to allow multiple instances of the windowsform to run?
|
|
|
|
|
Do you mean your window window form application can't be run in multiple instance model? If you manually run it twice, does it will be created two instances in window?
|
|
|
|
|
I've added your wpfappcontrol in a wpfusercontrol that I am using as the content of my toolwindow. The toolwindow can be run in multi-instance mode but whenever I navigate out of the toolwindow or create a new one, the Dispose() function is being called.
What modification would I need to make to ensure that the Dispose is only being called when I close the toolwindow and not when I navigate to another toolwindow?
UPDATE:
By commenting out: this.Unloaded += new RoutedEventHandler((s, e) => { appControl.Dispose(); });
It doesn't dispose the appcontrol when I change between toolwindows but the process remains the be seen on top of each other. Is there anyway to toggle the visibility?
modified 3-Jun-14 0:55am.
|
|
|
|
|
You could hook the close event of the tool window, currently the appControl is support manually call the Dispose method.
When you change between two or more toolwindows of your application, you could try to set the visibility property of the control to Collapsed then the control will be hided.
By the way, when you navigate between different window in WPF, the visual tree is recreated. So if you want the Exe control is deleted when the your application close, just need to hook the unload event of your application.
Thank you for your feedback!
|
|
|
|
|
Ok thanks a lot! I'm able to kill the process by disposing the appcontrol when I close the toolwindow. Works perfectly
Only problem now is the visibility is not changing when I switch between toolwindows. The Visibility.Collapse and Visibility.Hidden doesn't work.
An issue also occurs when moving the toolwindow and resizing it. The process resizing and moving doesn't work well with it. I've set up the toolwindow style to vsdockstyle.mdi which maximizes it when I create the toolwindow. The correct size comes there but upon moving or resizing it the process gets resized to the correct size but in an incorrect location.
|
|
|
|
|
If the Collapse and Hidden dosn't work, may be you need to add additional win api call to send hide signal to the subprocess window and send show signal to let it show again. Check this article Window Hiding with C#[^]
You could manually relocate the appContorl by add two additional dependency property and set it from the xaml. The codes is posted in the answer of AppContainer which is asked by the Uni Chen.
Thank you.
|
|
|
|
|
Hi first off I want to say thank you for all your timely responses. You've been a great help and completely invaluable.
I was able to hide the appcontrol by changing the maxHeight and maxWidth to 0 upon unloading and changing it to a max value when loaded back.
The only last problem I am having is that that the coordinates given in the MoveWindow function are relative to the entire Visual Studio Application. When I have the topleft coordinates as 0,0 it covers all the toolbars and everything else. Do you know if there's any way to get the coordinates of the ToolWindow that I containing the AppControl in?
Once again, thanks for all your help.
|
|
|
|
|
|
Hi so I started from scratch again and I'm seeing some weird behavior.
If the toolwindow is originally docked (during initialization) then the positions that I specify in MoveWindow need to be absolute positions on the screen. (0,0) refers to the extreme top left of the visual studio window and thus overlapping with everything. Also, on moving the toolwindow, the external application does not move along with it.
However, if the toolwindow is not docked originally (on initialization) then the positions specified in the toolwindow are relative to the toolwindow. (0,0) will refer to the topleft of the toolwindow and not the complete visual studio window. This works well for me but upon docking the application gets killed. I've put breakpoints for the dispose function in the appcontrol but it never seems to be called when docking the toolwindow and yet the application still exits. Any idea why?
|
|
|
|
|