Is there a way to make a Delphi VCL form sizable without changing the BorderStyle?
Asked Answered
S

2

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?

Sandor answered 13/12, 2015 at 18:15 Comment(11)
Can't you set that property early onDentil
"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.Gwendolyn
@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.Sandor
@jerry no it's not that. The Items property is empty afterwards. That's actually a known problem with the VCL.Sandor
Processing WM_NCHITTEST is the way to go.Saville
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.Stanza
Could you hook the creation of the options dialog or is it created before your Expert loads?Bluebell
@warren unfortunately it is already fully created before I can get a reference to it.Sandor
Could you read and recreate the items list, e.g. by assigning to a TStringList and before changing the style and reassigning back after?Foreword
@Foreword 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.Sandor
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.Bluebell
S
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);
Stanza answered 13/12, 2015 at 19:50 Comment(12)
Sounds promising, I'll give it a try.Sandor
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).Balance
@Balance - 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..Stanza
@Sertac: msdn.microsoft.com/en-us/library/windows/desktop/… 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).Balance
@Balance - 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.Stanza
.. in fact SetWindowLong specifically mentions about changing frame styles.Stanza
@Sertac - What makes you think you turn scrollbars on/off by changing the style rather than calling ShowScrollBar() ? #286087 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).Balance
I don't understand this discussion. This answer appears to be the one. +1Dentil
@Balance - 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.Stanza
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. /endBalance
@Balance Your comments here reflect your desire to defend yourself rather than face up to factsDentil
@Balance - 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.Stanza
B
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.

Balance answered 13/12, 2015 at 19:45 Comment(3)
Thanks, if Sertac Akyuz' suggestion doesn't work, I'll try yours.Sandor
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. :)Balance
Yep. Here's an msdn blog example that switches window frame. Note, WS_OVERLAPPEDWINDOW includes WS_THICKFRAME.Stanza

© 2022 - 2024 — McMap. All rights reserved.