Zoë Peterson
Members-
Content Count
15 -
Joined
-
Last visited
-
Days Won
2
Zoë Peterson last won the day on August 28 2021
Zoë Peterson had the most liked content!
Community Reputation
15 GoodRecent Profile Visitors
The recent visitors block is disabled and is not being shown to other users.
-
"Incompatible parameter lists" when using Types.T*DynArray?
Zoë Peterson replied to Zoë Peterson's topic in VCL
Sure, here's a trivial example. Compile/install the SsExample.bpl, which adds a new minimal TSsControl. SsProj.dproj already has a form with the relevant control. If you make any change at all to it, you'll get the error. If you delete the existing CtrlInit and add a new one, it'll switch to the generic declaration. This isn't restricted to something as simple as TArray<string> though. If the argument is a much more complicated generics declaration (e,g. TDictionary<string, TDictionary<Integer, Integer>>), that gets exploded out too. Why would that be preferable to using a named alias? DynArrayFailure.zip -
I have some visual components that have event handlers that are declared like so: TSsInitEvent = procedure(Sender: TObject; var AStrs: TStringDynArray) of object; In the units that are using them, they're declared like: procedure CommandControlInit(Sender: TObject; var AStrs: TStringDynArray); Ctrl+Clicking shows that TStringDynArray is a direct reference to System.Types.TStringDynArray in both cases. In the older releases, TStringDynArray was "array of string", but it's apparently now been changed to "TStringDynArray = TArray<string>". That worked just fine in older Delphi releases, but in 11.3 whenever I save the form I get the error "The CommandControlInit method referenced by CommandControl.OnInit has an incompatible parameter list. Remove the reference?" If I try to create a new event for the relevant property, it's created like so: procedure CommandControlInit(Sender: TObject; var AStrs: TArray<System.string>); Aside from just being a more obnoxious declaration, that doesn't work for projects that also need to compile in older Delphi releases. Has anyone else run into this? Any good workarounds? Any open bug reports about it I can vote for?
-
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.
-
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.
-
Compiler accepts virtual methods in class helpers?
Zoë Peterson posted a topic in RTL and Delphi Object Pascal
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. -
JSON serialization using published properties w/o attributes
Zoë Peterson replied to Zoë Peterson's topic in Algorithms, Data Structures and Class Design
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 -
JSON serialization using published properties w/o attributes
Zoë Peterson replied to Zoë Peterson's topic in Algorithms, Data Structures and Class Design
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. -
JSON serialization using published properties w/o attributes
Zoë Peterson replied to Zoë Peterson's topic in Algorithms, Data Structures and Class Design
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. -
JSON serialization using published properties w/o attributes
Zoë Peterson replied to Zoë Peterson's topic in Algorithms, Data Structures and Class Design
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. -
Delphi VCL/FireMonkey Style (.vsf) to JSON converter released
Zoë Peterson posted a topic in Delphi Third-Party
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 -
JSON serialization using published properties w/o attributes
Zoë Peterson posted a topic in Algorithms, Data Structures and Class Design
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. -
Full time Delphi developer position in USA
Zoë Peterson posted a topic in Job Opportunities / Coder for Hire
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 -
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
-
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?