Hello!
I'm trying to write program which runs (on menu item click) some standard Windows application (e.g. Wordpad) and transforms it into own MDI child window. After I do this by SetParent API function call, Wordpad's window behave as child of my client window: it is minimizing, restoring, resizing even tiling and cascading correctly, but it is not maximizing properly: it's not merging with client window, but simply changing position and size slightly (even not always stretching to whole client window's area) when I'm trying to maximize it :( So, it behaves as child window but not as real MDI-child. Setting WS_EX_MDICHILD style by SetWindowLong API does not help.
I suspect, this problem could be solved only by subclassing my client window or Wordpad's window. To do this I even tried to change behavior of Wordpad's window by DLL injection and subclassing of WM_GETMINMAXINFO messages, but I have achieved only merging of Wordpad's window with my client window, however Wordpad's window still not changing it's size during resizing client window and losing min/max/restore buttons in maximized state. Subclassing (redirecting to DefMDIChildProc API call) other messages concerned with resizing leads to Wordpad crash usually.
So, I have several questions to specialists who understand the features of the MDI:
1) Is it possible to transform another application's window into the full-value MDI child window of my application?
2) Is it possible to do this without subclassing?
3) Which window have I to subclass to achieve correct maximization: Wordpad's window or my MDI client one?
4) Which messages must I subclass for correct MDI-maximization and how?
Thank you in advance.
Dear Serum,
Welcome to the Forum :icon14:
You have a remarkable little project there. Interesting that WordPad is willing to play with your app ;-)
You write "Wordpad's window behave as child of my client window" - do you mean "as MDI child of my main window"? That is the only point where I could guess a possible cause of your problem. Did you monitor all error messages, e.g. with a debugger or a deb macro (http://www.webalice.it/jj2006/MasmBasicQuickReference.htm#Mb1019)?
If you really want help, you will have to post the complete code, of course.
Dear jj207,
Thank you for your reply! :icon14:
QuoteYou write "Wordpad's window behave as child of my client window" - do you mean "as MDI child of my main window"?
What do you mean under "main window"? Is it frame window? In my case Wordpad behaves as a "child" of client window (not frame window). As I understand, all MDI-child windows must be "childs" of client window, and client window is a "child" of a frame window.
QuoteDid you monitor all error messages, e.g. with a debugger or a deb macro?
No, I didn't, but I checked error status of API calls in "IDM_NEW" case by GetLastError. They works OK, otherwise WorldPad window would not become a child of my client window.
My code is based on Iczelion's MDI lesson, it does not process errors yet, but it's only on a stage of experiment (please change WPadPath variable to the correct path to WordPad on your PC):
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\masm32.inc
include \masm32\macros\macros.asm
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\masm32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
ProcEnum proto :DWORD,:DWORD
.const
IDR_MAINMENU equ 101
IDM_EXIT equ 40001
IDM_TILEHORZ equ 40002
IDM_TILEVERT equ 40003
IDM_CASCADE equ 40004
IDM_NEW equ 40005
IDM_CLOSE equ 40006
.data
ClassName db "MDIASMClass",0
MDIClientName db "MDICLIENT",0
AppName db "Win32asm MDI Demo",0
WPadPath db "C:\Program Files\Windows NT\Accessories\wordpad.exe",0
.data?
hInstance dd ?
hMainMenu dd ?
hwndClient dd ?
hwndFrame dd ?
hPad dd ?
ExCode dd ?
Procinfo PROCESS_INFORMATION <>
WinStartup STARTUPINFO <>
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
;=============================================
; Register the frame window class
;=============================================
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,\
hInst,NULL
mov hwndFrame,eax
invoke ShowWindow, hwndFrame,SW_SHOWNORMAL
invoke UpdateWindow, hwndFrame
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.break .if (!eax)
invoke TranslateMDISysAccel,hwndClient,addr msg
.if eax==0
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endif
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL ClientStruct:CLIENTCREATESTRUCT
.if uMsg==WM_CREATE
invoke GetMenu,hWnd
mov hMainMenu,eax
invoke GetSubMenu,hMainMenu,1
mov ClientStruct.hWindowMenu,eax
mov ClientStruct.idFirstChild,100
INVOKE CreateWindowEx,NULL,ADDR MDIClientName,NULL,\
WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\
hInstance,addr ClientStruct
mov hwndClient,eax
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_EXIT
invoke SendMessage,hWnd,WM_CLOSE,0,0
.elseif ax==IDM_TILEHORZ
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
.elseif ax==IDM_TILEVERT
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
.elseif ax==IDM_CASCADE
invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0
.elseif ax==IDM_NEW
invoke GetStartupInfo,ADDR WinStartup
invoke CreateProcess,ADDR WPadPath,0,0,0,0,CREATE_DEFAULT_ERROR_MODE,0,0,ADDR WinStartup,ADDR Procinfo
invoke WaitForInputIdle,Procinfo.hProcess,INFINITE
invoke EnumWindows,ADDR ProcEnum,Procinfo.dwProcessId
invoke SetParent,hPad,hwndClient
invoke GetWindowLong,hPad,GWL_EXSTYLE
or eax,WS_EX_MDICHILD
invoke SetWindowLong,hPad,GWL_EXSTYLE,eax
invoke ShowWindow,hPad,SW_SHOWNORMAL
.elseif ax==IDM_CLOSE
invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
invoke SendMessage,eax,WM_CLOSE,0,0
.else
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
ProcEnum proc hWnd:DWORD,lParam:DWORD
LOCAL ProcID:DWORD
invoke GetWindowThreadProcessId,hWnd,ADDR ProcID
mov eax,ProcID
cmp eax,lParam
jne @f
invoke GetParent,hWnd
cmp eax,NULL
jne @f
m2m hPad,hWnd
xor eax,eax
ret
@@:
mov eax,TRUE
ret
ProcEnum endp
end start
#include "resource.h"
#define IDR_MAINMENU 101
#define IDR_CHILDMENU 102
#define IDM_EXIT 40001
#define IDM_TILEHORZ 40002
#define IDM_TILEVERT 40003
#define IDM_CASCADE 40004
#define IDM_NEW 40005
#define IDM_CLOSE 40006
IDR_MAINMENU MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New", IDM_NEW
MENUITEM "E&xit", IDM_EXIT
END
POPUP "&Window"
BEGIN
MENUITEM "Tile Horizontal",IDM_TILEHORZ
MENUITEM "Tile Vertical",IDM_TILEVERT
MENUITEM "Cascade",IDM_CASCADE
END
END
IDR_CHILDMENU MENU DISCARDABLE
BEGIN
POPUP "&File (child)"
BEGIN
MENUITEM "&New", IDM_NEW
MENUITEM "&Close",IDM_CLOSE
MENUITEM "E&xit", IDM_EXIT
END
POPUP "&Window (child)"
BEGIN
MENUITEM "Tile Horizontal",IDM_TILEHORZ
MENUITEM "Tile Vertical",IDM_TILEVERT
MENUITEM "Cascade",IDM_CASCADE
END
END
If you assemble and run this code, you can see that only problem here is
maximization, other MDI functions work correctly.
SERUM,
I ran your code almost exactly as you posted it. The IDM_NEW message, which is supposed to invoke CreateProcess for WordPad, did NOT create a child window.
CreateProcess is returning a nonzero value (succeeding). And, the Icon for the WordPad application is displayed on the taskbar.
...But, EnumWindows is returning zero (If the function fails, the return value is zero). Obviously, SERUM, you should be checking the return values of invoked Windows APIs.
Here is the MSDN Documentation for MDIs: Multiple Document Interface, MSDN (https://msdn.microsoft.com/en-us/library/windows/desktop/ms632591(v=vs.85).aspx)
...And, here is the MSDN documentation for Child Processes: Child Processes, MSDN (https://msdn.microsoft.com/en-us/library/windows/desktop/ms682015(v=vs.85).aspx)
It seems the rc file is missing. Can you post that one, too?
He posted it above (added it as an edit after the original post).
I think the problem is (from a conceptual point of view) that the two processes are running on two separate threads (in two separate virtual address spaces), and, it is nearly impossible (using conventional means :dazzled:) to access the message loop of the WordPad application, and, modify it to accept MDI Child Proc messages sent from the menu of your main MDI Frame window. :bgrin:
...However, I think you could fake the Child Window display,...convincingly, with a little imagination, and, a ridiculous amount of error-prone code,...
:bgrin:...But, if anyone can do it,...JOCHEN can,...:bgrin:
hi Serum,
Which OS are you running? I've done some tests, on XP it works and does maximise correctly, but on Win7-64 no luck: The Wordpad window exists but it does not show in the main application ::)
Zen,
I just checked my exe under Win 8.1 and yes, it runs WordPad successfully, but Wordpad's window is
invisible, even despite I call
invoke ShowWindow,hPad,SW_SHOWNORMAL.
By some reason, I can't make WordPad's window visible if it was started with SW_HIDE attribute under Win 8.1. (IsWindowVisible indicates that it's visible, but really it's not).
It is very strange, because it works correctly under WinXP. :icon_eek:
Now I have corrected and updated my code (see above):
1) added:
invoke GetStartupInfo,ADDR WinStartup
before CreateProcessCall;
2) deleted:
;Child windows startup parameters
mov WinStartup.cb, SIZEOF STARTUPINFO
mov WinStartup.dwFlags,STARTF_USESHOWWINDOW
mov WinStartup.wShowWindow,SW_HIDE
It works OK under Win8.1 now. The exe is attached.
Quoteit is nearly impossible (using conventional means ) to access the message loop of the WordPad application, and, modify it to accept MDI Child Proc messages sent from the menu of your main MDI Frame window.
So, only by subclassing?
I tried to subclass WordPad's window procedure by injecting my DLL into Wordpad's virtual address space. And even successfully subclassed WM_GETMINMAXINFO, but subclassing other messages concerned with resizing leads to Wordpad crash usually. So, access to the message loop of the WordPad application is not a problem for me. Main question:
which messages and how I must subclass there?Ofcourse, I can embed Wordpad's window into real MDI-child window from Iczelion's example created with
invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate or
invoke CreateMDIWindow, but it is rather coarse way, I guess. And I will do that as a last resort.
jj2007QuoteIt seems the rc file is missing. Can you post that one, too?
I have added my RC file too.
jj2007QuoteWhich OS are you running? I've done some tests, on XP it works and does maximise correctly, but on Win7-64 no luck: The Wordpad window exists but it does not show in the main application
Hi, jj2007!
I'm on WinXP SP3 now. Yes, I just corrected the code and it works under Win 8.1 now.
Quoteon XP it works and does maximise correctly
No, it does not maximize correctly: maximized MDI-child window must merge with client window and change own size during frame window resizing, but Wordpad's window doesn't. Please compare it's behavior with behavior of real MDI-child window from Iczelion's example.
I see you are on a good path.
mov WinStartup.wShowWindow, SW_RESTORE works, too. A bit ugly that it shows, but at least it's visible.
Re max behaviour, now I see what you mean. It seems that the WM_SIZE message doesn't get passed to WordPad :(
This works: .elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.elseif uMsg==WM_SIZE
.if wParam==SIZE_RESTORED
.if rv(IsZoomed, hPad)
invoke GetClientRect, hWnd, addr rc
invoke MoveWindow, hPad, 0, 0, rc.right, rc.bottom, 1
.endif
jmp DefRet
.endif
.else
DefRet:
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
ZenQuoteBut, EnumWindows is returning zero
If EnumWindowsProc returns zero, the return value is also zero. (MSDN web site)
So, EnumWindows API function works normally, it returns zero because my ProcEnum callback procedure interrupts the enumeration process when finds appropriate window and returns zero.
Following GetLastError also returns zero.
jj2007QuoteThis works:
Thank you!
But:
1) WordPad's window still not merging with client window: in real MDI-child windows (in maximized state)
min/max/restore buttons are situated on the same line as main frame menu, and I afraid it's impossible or very hard to imitate such merging by means of simple SDI windows (without subclassing).
2) Moreover, your resizing works only during dragging, it does not work when I maximize my main frame window (I guess, it could be easily fixed, but 1) is much more important drawback for me).
WordPad is already "child", but still not "MDI-child" :(
Re subclassing, it should use DefMDIChildProc. However, the WordPad "child" is still in its own address space, so that would crash the application - or have you seen evidence somewhere that it is possible?
I wonder what is your goal here once the design is OK for you. Can you explain in a few words?
SERUM,
Progress ! Yes, the updated executable that you posted (MDIPad.zip) works on Windows Seven Professional. I wouldn't have guessed that it would operate differently on different Operating System versions.
On one of my old MDI applications, I ran into a problem similar to what you are experiencing. The application would crash when you try to resize the child window. There's a trick to it (which I found by GOOGLING). Give me a few minutes, and I'll look it up,...:bgrin:
...OK,...I found it,...unfortunately, it's something you must incorporate into the Child Window Procedure,...:dazzled:
I incorporated it into a number of Child window procedures in several different programs,...and, it works by disabling the message functionality (SC_MINIMIZE, SC_MAXIMIZE, and SC_SIZE).
; Technique for disabling maximizing, minimizing, and sizing functionality, Note that wParam must be combined with
; 0xFFF0, using the C bitwise AND (&) operator, when processing the WM_SYSCOMMAND message. This works correctly.
RegChildProc PROC hRegChldWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
mov ebx, uMsg
; This is part of the .IF/ENDIF block that comprises the Message Loop of the Child Window Procedure
.ELSEIF ebx==WM_SYSCOMMAND ; Must call ShowWindow (SW_HIDE) for the List View Control to get this to work.
mov edx, wParam
AND edx, 0FFF0h ; This is the trick,...cool, huh ???
.IF edx==SC_MINIMIZE
RET
.ELSEIF edx==SC_SIZE
RET
.ELSEIF edx==SC_MAXIMIZE
RET
.ENDIF
; This little block of code prevents the application from crashing when you select the Minimize, Maximize, or Resize buttons from the system menu (or send the SC_SIZE, SC_MAXIMIZE, or SC_MINIMIZE message to you Child Window Procedure).
I think this is where I found the information: How To Preventing an MDI Child Window from Changing Size, Microsoft Support (https://support.microsoft.com/en-us/kb/71669).
...Frankly, I don't how that is going to help you,...
Quote from: jj2007 on September 11, 2016, 06:27:10 PM
I wonder what is your goal here once the design is OK for you. Can you explain in a few words?
Well, I want to write some application that uses WordPad as a text editor, something like an email client.
Quote from: jj2007 on September 11, 2016, 06:27:10 PM
or have you seen evidence somewhere that it is possible?
Yes, it is possible of course.
Quote from: Zen on September 13, 2016, 05:31:04 AM
...OK,...I found it,...unfortunately, it's something you must incorporate into the Child Window Procedure,...:dazzled:
Thank you, but it's not what I need. This code completely blocks any sizing and moving actions. I want to force WordPad's window to behave as real MDI-child during maximization instead.
Unfortunately, it turned out that this task is much more complicated: WordPad's interfaces in WinXP and in Win8 (for example) are very different: this on in Win8 has the damn Microsoft Fluent (Ribbon) Interface which is incompatible with MDI-child behavior, so ribbon menu cannot be transfered to frame window (can be ignored). Moreover: there is a lot of focusing troubles when WordPad's window acts as a child in Win8. Also the QAT (Quick Access Toolbar) loses it's functionality (cannot be ignored!).
Hence, this problem must be solved separately for the old and new versions of Windows OS.
Could use something like scintilla editing control http://www.scintilla.org/ hosted in each mdi child window, to achieve something similar to what wordpad offers for editing emails and the like
or perhaps using the richedit control itself in each mdi child, and then creating the appropriate toolbars with the functions you require?