Jump to content

Zoë Peterson

  • Content Count

  • Joined

  • Last visited

  • Days Won


Zoë Peterson last won the day on August 28 2021

Zoë Peterson had the most liked content!

Community Reputation

15 Good

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Zoë Peterson

    ScrollBox ScrollBar Mouse Tracking

    I can't edit my previous message anymore, but after some further testing/research, overriding TScrollBox.MouseWheelHandler is as good of a place as DefaultHandler, and a bit more obvious. In that case the code is identical to the first block I posted for TScrollBox.WMMouseWheel.
  2. Zoë Peterson

    ScrollBox ScrollBar Mouse Tracking

    I just spent an unreasonable amount of time looking into this myself 😄 1) The stock TScrollBox didn't support reacting to the mouse wheel. That changed in Delphi 11, but they did a poor job of it and broke it so it kind of scrolls, but TScrollBox no longer calls the DoMouseWheel/OnMouseWheel events. [RSP-35333] (My comment there is outdated compared to the rest of this post) 2) The WM_MOUSEWHEEL messages used to require focus. In a recent Windows 10 update (and Windows 11), Microsoft introduced a new mouse setting, "Scroll inactive windows when I hover over them" that instead sends the message to the control that the mouse is over. AFAICT it's turned on by default now. If it's turned off it reverts to the old behavior of sending it to the focused control. 3) Delphi used to forward the CM_MOUSEWHEEL messages to the focused control in TCustomForm.MouseWheelHandler, which meant that it could still require focus even if Windows was sending the messages elsewhere. That code was removed sometime between XE7 and 10.4, so the CM_MOUSEWHEEL wheels now go to the control the message is sent to. 4) TControl.IsControlMouseMsg incorrectly treats the X/Y coordinates for WM_MOUSEWHEEL events as if they're in client coordinates like the other WM_MOUSE* events are, rather than in screen space coordinates. This has the effect that if you have a TScrollBox with a big TImage on it, whether the scrollbox scrolls or not depends on where the form is on the screen. Likewise, TControl.IsControlMouseMessage doesn't pay attention to the WM_MOUSEWHEEL return values, so if you fix the first issue, it actually breaks TScrollBox scrolling if it's over the image because the image swallows the WM_MOUSEWHEEL message and TScrollBox's handler never gets called. 5) If you don't have TScrollBox.VertScrollBar.Smooth := true, as you're scrolling there will be a smear as the nested controls don't redraw before the scrollbox scrolls again. It will clear up once the apps been idle a bit, and for simple scrolls this isn't noticeable, but it can look terrible, especially if the scrollbox redraw slowly (e.g., lots of nested controls with VCL styles enabled) and you use a free-spinning mouse. 6) Once you fix all of the above, if you have scrollable controls nested in a TScrollBox, the lack of focus requirement means that if the mouse cursor goes over the top of the nested control as a result of the TScrollBox's scroll, it'll start scrolling the nested one instead. I can provide a workaround for that, but it's a little out of scope of just "Fix the bugs". On to the fixes! 1) Patch VCL.Forms.pas (fixes issue #1). The least invasive fix is to change TScrollBox's WMMouseWheel handler to this, which at least fixes the OnMouseWheel handlders getting called. procedure TScrollBox.WMMouseWheel(var Message: TWMMouseWheel); const LScrollBarValues: array[Boolean] of WPARAM = (SB_LINEDOWN, SB_LINEUP); begin inherited; if (Message.Result <> 0) or not VertScrollBar.IsScrollBarVisible then Exit; Message.Result := 1; var LScrollBarValue := LScrollBarValues[Message.WheelDelta > 0]; for var LLineToScroll := 1 to Mouse.WheelScrollLines do Perform(WM_VSCROLL, LScrollBarValue, 0); end; A better fix is to instead remove TScrollBox.WMMouseWheel entirely (if you're on Delphi 11) and put the code in a newly overridden DefaultHandler: TScrollBox = class(TScrollingWinControl) public ... procedure DefaultHandler(var Message); override; end; procedure TScrollBox.DefaultHandler(var Message); const LScrollBarValues: array[Boolean] of WPARAM = (SB_LINEDOWN, SB_LINEUP); var LScrollBarValue: WPARAM; LLineToScroll: Integer; begin if (WindowHandle <> 0) and (TMessage(Message).Msg = WM_MOUSEWHEEL) and VertScrollBar.IsScrollBarVisible then begin LScrollBarValue := LScrollBarValues[TWMMouseWheel(Message).WheelDelta > 0]; for LLineToScroll := 1 to Mouse.WheelScrollLines do Perform(WM_VSCROLL, LScrollBarValue, 0); TMessage(Message).Result := 1; Exit; end; inherited; end; The advantage of using DefaultHandler instead of a WM_MOUSEWHEEL message handler is that the scrollbox now works just like native Win32 controls do, where the parents all the way up to the form get the OnMouseWheel messages, and the innermost scrollbox scrolls if none of them handle it. 2) Patch VCL.Controls.pas (fixes issue #4): function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean; var Control: TControl; P: TPoint; IsWheelMsg: Boolean; begin IsWheelMsg := (Message.Msg = WM_MOUSEWHEEL) or (Message.Msg = WM_MOUSEHWHEEL); if GetCapture = Handle then begin if (CaptureControl <> nil) and (CaptureControl.Parent = Self) then Control := CaptureControl else Control := nil; end else if IsWheelMsg then Control := ControlAtPos(ScreenToClient(SmallPointToPoint(Message.Pos)), False) else Control := ControlAtPos(SmallPointToPoint(Message.Pos), False); Result := False; if Control <> nil then begin if IsWheelMsg then begin Message.Result := Control.Perform(Message.Msg, Message.Keys, TMessage(Message).LParam); Result := (Message.Result <> 0); end else begin P.X := Message.XPos - Control.Left; P.Y := Message.YPos - Control.Top; Message.Result := Control.Perform(Message.Msg, Message.Keys, PointToLParam(P)); Result := True; end; end; end; 3) To make the mouse wheel consistently affect the control the mouse is over, regardless of which version of Windows you're running or which way the new setting is configured (which matches what official Microsoft apps do), add a TApplicationEvents object with this as the OnMessage handler. (Fixes issues #2 and #3) procedure TMyForm.ApplicationEventsMessage(var Msg: tagMSG; var Handled: Boolean); var Wnd: HWND; begin if (Msg.message = WM_MOUSEWHEEL) or (Msg.message = WM_MOUSEHWHEEL) then begin Wnd := GetCapture; if Wnd = 0 then Wnd := WindowFromPoint(SmallPointToPoint(TSmallPoint(Msg.lParam))); if Wnd <> 0 then msg.hwnd := Wnd; end; end; 4) To fix the smeared appearance, you can A) Turn on VertScrollBar.Smooth, which triggers an Update call between each scroll B) Call Update yourself, either in the OnMouseWheel handler or in the TScrollBox WMMouseWheel/DefaultHandler method in between Perform calls. C) Change TScrollBox WMMouseWheel/DefaultHandler's Perform loop to the below, to update the scrollbar position once instead of with repeated WM_VSCROLL messages. if VertScrollBar.Smooth then begin LScrollBarValue := LScrollBarValues[TWMMouseWheel(Message).WheelDelta > 0]; for LLineToScroll := 1 to Mouse.WheelScrollLines do Perform(WM_VSCROLL, LScrollBarValue, 0); end else { Move the scrollbar directly to only update the position once. TControlScrollBar.Increment uses different defaults for smooth vs chunky scrolling (TControlScrollBar.Update), so this may produce smaller scrolls than if you set Smooth:=true } VertScrollBar.Position := VertScrollBar.Position + VertScrollBar.Increment * Mouse.WheelScrollLines * -TWMMouseWheel(Message).WheelDelta div WHEEL_DELTA; And yes, none of the above should be necessary because the VCL should handle all of this. The IsControlMouseMsg bugs have existed forever though and appear to have been reported as QC-135258 at least as far back as 2015.
  3. This is "Does anyone know WTF is supposed to happen?", not "I want to do this and think it should work": The code below has two class helpers, one descending from the other. The parent one has a virtual method and the descendent overrides it. The compiler accepts it without any errors/warnings/hints. Actually running prints So the parent helper doesn't care about it being virtual, and the subclass ignores the 'inherited' call. If you don't actually create the object before calling Obj.Foo, it raises an access violation in System.pas::_GetHelperIntf(). I found an old blog post from Joe White where he stumbled on it back in 2007, but back then _GetHelperIntf() was essentially a no-op, and it now has code. Obviously since class helpers don't have their own VMTs, the code doesn't make sense, but why does the compiler accept it? I don't see anything in the class and record helper help about declaring interfaces, and AFAIK they still don't support interface helpers. Anyone have any idea what's going on here? program ClassHelperTest; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type TObjHelper = class helper for TObject procedure Foo; virtual; procedure Bar; end; TSubObjHelper = class helper(TObjHelper) for TObject procedure Foo; override; end; procedure TObjHelper.Bar; begin WriteLn('Bar'); Foo; end; procedure TObjHelper.Foo; begin WriteLn('BaseClass.Foo'); end; procedure TSubObjHelper.Foo; begin inherited; WriteLn('SubClass.Foo'); end; var Obj: TObject; begin Obj := TObject.Create; Obj.Bar; WriteLn('---'); Obj.Foo; Obj.Free; end.
  4. Like Uwe said, I'm trying to serialize objects I don't control (stock VCL components), so I can't modify the original declarations. Part of is learning curve too. This started for personal use and I wasn't expecting to spend days researching JSON serializers. I started by just passing the base object into various framework's ObjectToJSON routines. Delphi's built-in ones fail with a marshalling error several layers deep, with no indication of what it was faulting on. Neon, I think, failed because it decided that the object should be stored as an array since it had a "Count" property, and there wasn't a way to force it to do otherwise. I looked at a few other frameworks too and don't remember exactly how they all came up short, but my starting point was "This is a TPersistent descendent that already has published properties; it should be easy to get at least a minimal export without doing anything fancy", and aside from mORMOT and TMS's FNC framework, that wasn't true. Other than that though, I largely agree with the arguments Dalija covered in her blog: Part 1, Part 2, Part 3, Part 4
  5. I did see that one in my search and should have looked at it more closely. Ultimately I avoided it because I was trying to stick to an open source library to make it easier to share the code, and then sort of forgot about it. 🙂 Nice! StyleObjects is definitely a better name for that part than "Objects" is. 🙂 The FNC version is missing a bunch of the data I had to manually construct in the mORMot version though: Colors, SysColors, and Fonts are public sub-objects of TSeStyleSource and none of them have properties that match the name/value pairs that they represent. That's what the "WriteColors", "WriteSysColors" and "WriteFonts" functions were doing, which were loosely based on the SaveToStream functions in the respective objects (TSeStyleColors, TSeStyleSysColors, TSeStyleFonts). The style objects also support nesting, and your version only includes the top-level of it.
  6. As Uwe said, that wouldn't have worked since the parent object was creating child objects of the appropriate type, and I don't have any control over that. For context, it was for a VCL style to JSON converter and I want to use the VCL's existing style loading code. Regardless, I still think it's weird that literally every JSON serialization library I could find relied on attributes and public/private fields. Delphi has had TPersistent and properties with 'stored' and 'default' and different names than the backing fields for its entire existence. They work well. It's great that the newer extended RTTI/attribute approach is available, but I absolutely do not understand why that's the only approach anyone's used.
  7. Understandable, and thank you for the initial posts. We have a settings framework internally that works that way and works great, but it uses XML and would have been hard to extract. By the time I'd gotten to your posts I'd already run through half a dozen different JSON serialization libraries that all used extended RTTI and attributes, and I was beginning to think I was crazy for expecting one to take advantage of Delphi's long established persistence support.
  8. I just released a converter to go from Delphi VCL/FireMonkey style files (.vsf) to a structured JSON dump. Embedded bitmaps are ignored, and it's one-way only, but it's handy for comparing changes. There's a Beyond Compare 4 file format available as well, though if you have questions/issues, post here or on the Github page rather than going through Scooter Software's official support. https://github.com/boramis/vsf2json
  9. Are there any object-to-JSON serialization libraries available that work with published properties (not public, not fields) and which allow customizing the serialization externally to the class? Dalija talked about this in her "When in Rome" series (https://dalijap.blogspot.com/2021/04/when-in-rome-do-as-romans-do-part-i.html) but didn't point to an alternative that worked that way. Any license is fine, though open source of some sort would be preferable. Specifically, I'm working with third party TComponent classes which already have most of their state exposed as published properties, and I can't add attributes to customize things. A couple of the components do use DefineProperties for some additional state when stored as DFMs, and I need to be able to register some sort of callback to serialize those. I was able to get close with mORMot's ObjectToJSON with RegisterCustomSerializer, but it doesn't support mixing published and custom properties in a single class. All of the other ones I've found do public/private fields, require attributes in the original classes, or fail in the marshalling step with unhelpful error messages. Edit: I figured a workaround using mORMot for my specific usage, so I don't need this anymore, though it is a bit hacky, so if there's a better approach, I certainly won't mind hearing it.
  10. Scooter Software has a full time Delphi developer position available, working on Beyond Compare, our file and folder comparison utility. Details available at https://www.scootersoftware.com/index.php?zz=job_opening
  11. Zoë Peterson

    TTitlebarpanel and VCL styles

    Is there a way to set the form's border color when using TTitleBarPanel if you're not using seBorder in the StyleElements? For our usage this would be workable short term, if I can tone down the bright white window frame
  12. Currently all of our forms are designed using Tahoma 8 at 96dpi and then scaled up at runtime based on the system font and DPI. That's been working great, but there's a snag with the newer DPI-based scaling in XE7+: since it doesn't scale based on the font anymore, loading on Vista/Win7/Win10 where they use Segoe UI 9 makes things too cramped/clipped. I could add a manual ChangeScale call at runtime to handle that, but since we're not targetting XP anymore, it would be nice to just scale all of our design-time forms once and move everything over to using Segoe UI all the time. Has anyone else run into this, and if so, any solutions besides hand adjusting hundreds of forms? A "Scale form by X%" IDE expert, for example?