dmitrybv 3 Posted February 5 Good day. I can't get Object Inspector to work correctly with a property of the Variant type. I have a published property CheckedValue of the Variant type in my object with the following implementation. When I try to set a new value for this property in Design-Time, I can't do it. In the Type drop-down list, the system doesn't let me select a new type. When I enter a new value in the CheckedValue field, the error 'Could not convert variant' keeps popping up. Embarcadero® RAD Studio 12 Version 29.0.53982.0329 { TTestComponent } TTestComponent = class(TComponent) private FCheckedValue: Variant; function GetCheckedValue: Variant; function IsCheckedValueStored: Boolean; procedure SetCheckedValue(const Value: Variant); procedure CheckedValueChanged(); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property CheckedValue: Variant read GetCheckedValue write SetCheckedValue stored IsCheckedValueStored; end; { TTestComponent } constructor TTestComponent.Create(AOwner: TComponent); begin inherited Create(AOwner); FCheckedValue := True; end; destructor TTestComponent.Destroy; begin inherited Destroy; end; function TTestComponent.GetCheckedValue: Variant; begin Result := FCheckedValue; end; procedure TTestComponent.SetCheckedValue(const Value: Variant); begin if VarSameValue(FCheckedValue, Value) = False then begin FCheckedValue := Value; CheckedValueChanged(); end; end; function TTestComponent.IsCheckedValueStored: Boolean; begin if VarSameValue(FCheckedValue, True) then Result := False else Result := True; end; procedure TTestComponent.CheckedValueChanged; begin end; Share this post Link to post
Anders Melander 1894 Posted February 5 Apparently the standard design-time property editors doesn't fully support Variant. https://stackoverflow.com/questions/29979950/how-to-correctly-set-a-variant-published-property I've looked at the code of the property editor but I can't make sense of it so I can't tell if it should work or not. I would have to debug Delphi in Delphi to figure it out and... Nope. Don't compare against a boolean value. Use boolean logic: if not VarSameValue(FCheckedValue, Value)then Same with the test in IsCheckedValueStored. Just set result directly: Result := not VarSameValue(FCheckedValue, True); Share this post Link to post
Remy Lebeau 1501 Posted February 6 (edited) When using VarSameValue(), you can get a conversion failure if the 2 Variants are not holding compatible types. This is even stated in the documentation: Quote If A and B can't be compared, VarSameValue raises an exception. So, you should first make sure the 2 Variants have the same type before comparing their values. This is just logical to do anyway, since if you know they have different types then there is no point in wasting resources to compare the values. When I make these small changes to TTestComponent, the "Could not convert variant" error goes away: procedure TTestComponent.SetCheckedValue(const Value: Variant); begin if (VarType(FCheckedValue) <> VarType(Value)) or // <-- ADD THIS! (not VarSameValue(FCheckedValue, Value)) then begin FCheckedValue := Value; CheckedValueChanged(); end; end; function TTestComponent.IsCheckedValueStored: Boolean; begin Result := (VarType(FCheckedValue) <> varBoolean) or // <-- ADD THIS! (not VarSameValue(FCheckedValue, True)); end; Now, that just leaves the problem of the 'CheckedValue.Type' property displaying "Unknown" for string values. That is indeed a bug in the default Variant property editor, which I have now reported to Embarcadero: RSS-2844; TVariantTypeProperty is broken for string values You can easily work around the bug, either directly in your component's property setter: procedure TTestComponent.SetCheckedValue(const Value: Variant); begin if (VarType(FCheckedValue) <> VarType(Value)) or (not VarSameValue(FCheckedValue, Value)) then begin FCheckedValue := Value; if VarType(FCheckedValue) = varString then FCheckedValue := VarToStr(Value); // <-- change the VarType to varUString CheckedValueChanged(); end; end; Or by deriving a custom property editor in your component's design-time package (if you don't have one, make one) to fix the bug directly: uses Variants, DesignIntf, DesignEditors, DesignConst; //... { TMyVariantTypeProperty } // unfortunately, TVariantTypeProperty is hidden in the implementation // of the DesignEditors unit, so we have to copy the entire class just // to change a couple of lines! const VarTypeNames: array[varEmpty..varInt64] of string = ( 'Unassigned', // varEmpty 'Null', // varNull 'Smallint', // varSmallint 'Integer', // varInteger 'Single', // varSingle 'Double', // varDouble 'Currency', // varCurrency 'Date', // varDate 'OleStr', // varOleStr '', // varDispatch '', // varError 'Boolean', // varBoolean '', // varVariant '', // varUnknown '', // [varDecimal] '', // [undefined] 'Shortint', // varShortInt 'Byte', // varByte 'Word', // varWord 'LongWord', // varLongWord 'Int64'); // varInt64 type TMyVariantTypeProperty = class(TNestedProperty) public function AllEqual: Boolean; override; function GetAttributes: TPropertyAttributes; override; function GetName: string; override; function GetValue: string; override; procedure GetValues(Proc: TGetStrProc); override; procedure SetValue(const Value: string); override; end; function TMyVariantTypeProperty.AllEqual: Boolean; var i: Integer; V1, V2: Variant; begin Result := False; if PropCount > 1 then begin V1 := GetVarValue; for i := 1 to PropCount - 1 do begin V2 := GetVarValueAt(i); if VarType(V1) <> VarType(V2) then Exit; end; end; Result := True; end; function TMyVariantTypeProperty.GetAttributes: TPropertyAttributes; begin Result := [paMultiSelect, paValueList, paSortList]; end; function TMyVariantTypeProperty.GetName: string; begin Result := 'Type'; end; function TMyVariantTypeProperty.GetValue: string; begin case VarType(GetVarValue) and varTypeMask of Low(VarTypeNames)..High(VarTypeNames): Result := VarTypeNames[VarType(GetVarValue) and varTypeMask]; varString,varUString: // <-- FIX HERE! Result := SString; else Result := SUnknown; end; end; procedure TMyVariantTypeProperty.GetValues(Proc: TGetStrProc); var i: Integer; begin for i := 0 to High(VarTypeNames) do if VarTypeNames[i] <> '' then Proc(VarTypeNames[i]); Proc(SString); end; procedure TMyVariantTypeProperty.SetValue(const Value: string); function GetSelectedType: Integer; var i: Integer; begin Result := -1; for i := 0 to High(VarTypeNames) do if VarTypeNames[i] = Value then begin Result := i; break; end; if (Result = -1) and (Value = SString) then Result := varUString; // <-- FIX HERE! end; var NewType: Integer; V: Variant; begin NewType := GetSelectedType; case NewType of varEmpty: VarClear(V); varNull: V := NULL; -1: raise EDesignPropertyError.CreateRes(@SUnknownType); else V := GetVarValue; // <-- move here for good measure... try VarCast(V, V, NewType); except { If it cannot cast, clear it and then cast again. } VarClear(V); VarCast(V, V, NewType); end; end; SetVarValue(V); end; { TMyVariantProperty } // fortunately, TVariantProperty is public in the DesignEditors unit, // so we need to override only 1 method in it... type TMyVariantProperty = class(TVariantProperty) procedure GetProperties(Proc: TGetPropProc); override; end; procedure TMyVariantProperty.GetProperties(Proc: TGetPropProc); begin Proc(TMyVariantTypeProperty.Create(Self)); end; procedure Register; begin //... // change the 2nd and 3rd properties if you want to reuse this editor for all Variant properties, eg: // RegisterPropertyEditor(TypeInfo(Variant), nil, '', TMyVariantProperty); RegisterPropertyEditor(TypeInfo(Variant), TTestComponent, 'CheckedValue', TMyVariantProperty); end; Edited February 6 by Remy Lebeau 4 Share this post Link to post
dmitrybv 3 Posted February 6 Thanks for the detailed answer. I used the option: if VarType(FCheckedValue) = varString then FCheckedValue := VarToStr(Value); // <-- change the VarType to varUString Almost everything works except if you specify the Date type and enter the value 1111111111 then an error is displayed that does not allow you to close the window with the error and you have to kill the bds.exe process. Share this post Link to post
Kas Ob. 126 Posted February 6 33 minutes ago, dmitrybv said: Almost everything works except if you specify the Date type and enter the value 1111111111 then an error is displayed that does not allow you to close the window with the error and you have to kill the bds.exe process. You landed on the holy grail of bugs ! If you can make smallest demo to reproduce this bug then it is great for reporting, these Variant handling exception/bugs/AV are fatal in the IDE and debugger and there quite few of them, not all do show error messages, many leads to silent IDE crash or just freeze, there is bugs is many places but it could shed light on this Variant mishandle in IDE/Debugger in general. Share this post Link to post
Die Holländer 67 Posted February 6 51 minutes ago, dmitrybv said: Thanks for the detailed answer. Is there any way to train an AI model with Remy's brain? 1 2 Share this post Link to post
Anders Melander 1894 Posted February 6 51 minutes ago, Die Holländer said: Is there any way to train an AI model with Remy's brain? Yes; It's called a tarpit 🙂 Share this post Link to post