9

I have just spent quite a lot of time trying to make the Tools/Environment Options dialog of the Delphi 6/7 IDE sizable from within GExperts. Everything seemed to work fine until I found that changing the form's BorderStyle to bsSizable closes and recreates the handle of the form and in the process loses the content of the list box for the palette configuration. (The Items property is empty afterwards.)

Changing the form's size (by setting the height and width) as such works fine, but allowing the user to adjust the size runs into the aforementioned problem.

Is there any way to make a Delphi form sizable without changing the BorderStyle?

dummzeuch
  • 10,975
  • 4
  • 51
  • 158
  • Can't you set that property early on – David Heffernan Dec 13 '15 at 18:27
  • "in the process loses the content of the list box for the palette configuration" I'm not sure what this means, but I'm willing to bet it's along the lines of not doing your drawing when you're supposed to - drawing to a control canvas should only be done upon the `WM_PAINT` message which Windows sends. – Jerry Dodge Dec 13 '15 at 19:02
  • @david unfortunately I get a reference to the form only after it has been constructed and been set as the active form. By that time it is too late as it is already visible. – dummzeuch Dec 13 '15 at 19:24
  • @jerry no it's not that. The Items property is empty afterwards. That's actually a known problem with the VCL. – dummzeuch Dec 13 '15 at 19:27
  • Processing `WM_NCHITTEST` is the way to go. – Dalija Prasnikar Dec 13 '15 at 19:38
  • Recreating the window is a VCL requisite, you can use api to remove and set appropriate flags. I don't know if that would be applicable to gexperts. – Sertac Akyuz Dec 13 '15 at 19:39
  • Could you hook the creation of the options dialog or is it created before your Expert loads? – Warren P Dec 13 '15 at 19:49
  • @warren unfortunately it is already fully created before I can get a reference to it. – dummzeuch Dec 13 '15 at 22:11
  • Could you read and recreate the items list, e.g. by assigning to a TStringList and before changing the style and reassigning back after? – Dsm Dec 14 '15 at 08:25
  • @Dsm I tried that, but it didn't work. It's not just the strings in Listbox.Items[] but also pointers to some TLists in Listbox.Items.Objects[] which in turn contain references to objects stored in the Items.Objects[] of the second ListBox on the page. These objects get lost as well but apparently get recreated automatically (so that list box is not empty). It's a real mess but of course it was never meant to be hackable in the way I am trying to do it. – dummzeuch Dec 14 '15 at 11:06
  • I wonder if you could reparent the options form to your own resizeable window, or would that reparently also cause this window-re-creation? I have hacked a "manual wm_sizemove workalike" into a dialog before, I will see if I can dig the code up. – Warren P Dec 14 '15 at 14:35

2 Answers2

8

"Wnd" being the dialog handle, you can transform the dialog to an overlapped window with a sizing frame:

SetWindowLong(Wnd, GWL_STYLE,
    GetWindowLong(Wnd, GWL_STYLE) and not WS_POPUP or WS_THICKFRAME);

remove the dialog frame:

SetWindowLong(Wnd, GWL_EXSTYLE,
    GetWindowLong(Wnd, GWL_EXSTYLE) and not WS_EX_DLGMODALFRAME);

then attach the appropriate system menu item for sizing messages to be processed:

AppendMenu(GetSystemMenu(Wnd, False), MF_STRING, SC_SIZE, 'Size');

and have the new frame drawn:

SetWindowPos(Wnd, 0, 0, 0, 0, 0,
    SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_FRAMECHANGED);
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • Sounds promising, I'll give it a try. – dummzeuch Dec 13 '15 at 22:12
  • Interesting. This shouldn't work since the WS_THICKFRAME style is documented as not being able to be changed after a window has been created. But it does seem to, albeit with an ugly initial glitches in the dialog caption bar (no system menu and gadgets in the wrong place) which aren't resolved until the form is actually resized, plus the addition of a rather odd-looking additional "Size" item on the dialog system menu (in cases where one is already present but disabled - e.g. BorderStyle = bsSingle). – Deltics Dec 13 '15 at 22:27
  • @Deltics - I didn't know about thickframe not being changeable, can you give a reference? About other points, I wonder if I am recalling wrong that a system menu modification would cause the frame to be redrawn. If that's the case, maybe a RedrawWindow with RDW_FRAME + RDW_INVALIDATE could help. An existing "size" menu should be modifiable through ModifyMenu, GetMenuItem[Count/Info] can be used to test if there is one. Would be a little more work.. – Sertac Akyuz Dec 13 '15 at 23:18
  • @Sertac: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx *WS_THICKFRAME* has nothing in its notes to indicate that it is modifiable post-creation (by contrast compare with, e.g. WS_GROUP which **is** noted as being changeable after creation). – Deltics Dec 14 '15 at 01:47
  • @Deltics - That doesn't imply you can't modify the style. Surely you can add/remove a scroll bar, but WS_[W/H]SCROLL does not mention anything about it. – Sertac Akyuz Dec 14 '15 at 02:26
  • .. in fact SetWindowLong specifically mentions about changing frame styles. – Sertac Akyuz Dec 14 '15 at 03:13
  • @Sertac - What makes you think you turn scrollbars on/off by changing the style rather than calling **ShowScrollBar()** ? http://stackoverflow.com/questions/285587/ws-vscroll-createwindow-style-works-setwindowlong-doesnt SetWindowLong() does appear to contradict the documentation for **WS_STYLE**, but that doesn't change what is implied from the **WS_STYLE** documentation *itself* (unless you extend the scope of "*except where noted*" to cover the entirety of MSDN documentation and not just that page itself; which is entirely possible but not at all obviously intended). – Deltics Dec 14 '15 at 03:55
  • 1
    I don't understand this discussion. This answer appears to be the one. +1 – David Heffernan Dec 14 '15 at 07:30
  • @Deltics - You have to ignore the remark at the top since it is not practically possible to extend the scope of the statement to the entire documentation. I'll give another example, read SetParent to see how you **have to** modify WS_CHILD and WS_POPUP flags, but nothing has been mentioned in the flags' documentation. – Sertac Akyuz Dec 14 '15 at 08:12
  • This ridiculous - The reality is, the documentation is at best contradictory but fine, if the game we're playing is "*ignore the bits of the documentation that we need to ignore to make our statements about what can be implied from documentation accurate*" then fine, you're 100% right. Congratulations. @David - the question was *how to make a dialog resizable*, not how to change a dialog into an overlapped window and thus get resizing *as a side effect*. /end – Deltics Dec 14 '15 at 17:57
  • @Deltics Your comments here reflect your desire to defend yourself rather than face up to facts – David Heffernan Dec 14 '15 at 18:10
  • @Deltics - I've proved you that not all modifiable flags are mentioned to be modifiable at the place where the flags are documented, I can't do anything about it, you put the name of the game. ... Changing the dialog to an overlapped window is the most natural course of action to have the desired effect. That's because what the question wants at the end is in fact an overlapped window. – Sertac Akyuz Dec 14 '15 at 18:40
5

Normally you could endow a window with resizing behaviours simply by implementing a response to WM_NCHITTEST and setting a result that indicates one of the resizing "zones" in the window frame.

For example:

procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;

...

procedure TForm2.WMNCHitTest(var Message: TWMNCHitTest);
const
  EDGEDETECT = 7;  //adjust as required
var
  deltaRect: TRect;  //not used as a rect, just a convenient structure
begin
  inherited;

  with Message, deltaRect do 
  begin
    Left   := XPos - BoundsRect.Left;
    Right  := BoundsRect.Right - XPos;
    Top    := YPos - BoundsRect.Top;
    Bottom := BoundsRect.Bottom - YPos;

    if (Top<EDGEDETECT)and(Left<EDGEDETECT) then
      Result := HTTOPLEFT
    else if (Top<EDGEDETECT)and(Right<EDGEDETECT) then
      Result := HTTOPRIGHT
    else if (Bottom<EDGEDETECT)and(Left<EDGEDETECT) then
      Result := HTBOTTOMLEFT
    else if (Bottom<EDGEDETECT)and(Right<EDGEDETECT) then
      Result := HTBOTTOMRIGHT
    else if (Top<EDGEDETECT) then
      Result := HTTOP
    else if (Left<EDGEDETECT) then
      Result := HTLEFT
    else if (Bottom<EDGEDETECT) then
      Result := HTBOTTOM
    else if (Right<EDGEDETECT) then
      Result := HTRIGHT
  end;
end;

The above code is pretty boiler-plate stuff for these circumstances, but for the record to save time I took this particular example from here. You would need to adjust this to fit a WndProc hook use case if applying to an existing window/form.

There is a complication...

If the hooked form has a BorderStyle of bsDialog or bsSingle (and possibly others) then this will not work if the form also has a system menu (biSysMenu is set in BorderIcons). The problem is this: Changing the BorderIcons property also forces recreation of the window which would put you back at square one w.r.t the form HWND being recreated.

However, having checked the Tools > Environment options dialog in Delphi 7, this does not appear to have a system menu so adding WM_NCHITTEST handling in a WndProc hook for that dialog should have the desired effect.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • Thanks, if Sertac Akyuz' suggestion doesn't work, I'll try yours. – dummzeuch Dec 13 '15 at 22:14
  • As noted in my comment to Sertac's answer, his approach *shouldn't* work, but it does appear to. However, it does suffer from some annoying visual glitches (in my testing of it on Windows 7). It's a bit more straightforward than hooking the window proc though so if you can live with those glitches and the uneasy feeling of relying on something working that shouldn't then it's probably the way to go. :) – Deltics Dec 13 '15 at 22:30
  • Yep. [Here](https://blogs.msdn.microsoft.com/oldnewthing/20100412-00/?p=14353)'s an msdn blog example that switches window frame. Note, WS_OVERLAPPEDWINDOW includes WS_THICKFRAME. – Sertac Akyuz Dec 14 '15 at 11:18