News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Another application's window as MDI child window of my application

Started by Serum, September 10, 2016, 11:26:30 AM

Previous topic - Next topic

Serum

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.

jj2007

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?

If you really want help, you will have to post the complete code, of course.

Serum

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.


Zen

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
...And, here is the MSDN documentation for Child Processes: Child Processes, MSDN

jj2007


Zen

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:

jj2007

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 ::)

Serum

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.

jj2007
QuoteIt seems the rc file is missing. Can you post that one, too?

I have added my RC file too.

Serum

jj2007
QuoteWhich 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.

jj2007

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


Serum

Zen
QuoteBut, 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.

Serum

jj2007
QuoteThis 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" :(




jj2007

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?

Zen

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.

...Frankly, I don't how that is going to help you,...

Serum

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.