Delphi字符串变体比较是否用loInvariantLocale?如何改用loUserLocale?
问题背景
我的问题源于DevExpress TcxGrid的默认比较逻辑(用于数据排序),其核心代码实现于cxVariants.pas:
if VarIsEmpty(V1) then if VarIsEmpty(V2) then Result := 0 else Result := -1 else if VarIsEmpty(V2) then Result := 1 else if V1 = V2 then Result := 0 else if V1 < V2 then Result := -1 else Result := 1;
测试表明V1 < V2的行为等同于CompareStr(V1, V2, loInvariantLocale),但我们期望它以CompareStr(V1, V2, loUserLocale)的方式工作。
这种差异在cxGrid排序结果中很明显:东欧语言通常要求带变音符号的字符紧跟其原字符(如aā..zž的顺序),但loInvariantLocale会将所有变音符号字符排在非变音符号字符之后(即a..z, ā..ž的顺序)。
目前我可以重写cxGridDBTableView.Controller.OnCompare()事件,但想知道是否能从全局层面影响变体比较逻辑?
解决方案
1. 修改cxVariants源码(全局生效最直接方案)
直接修改cxVariants.pas中的变体比较核心代码,替换为用户区域设置的比较逻辑:
if VarIsEmpty(V1) then if VarIsEmpty(V2) then Result := 0 else Result := -1 else if VarIsEmpty(V2) then Result := 1 else if VarSameValue(V1, V2) then // 用VarSameValue替代直接=,避免类型匹配问题 Result := 0 else // 替换为loUserLocale的字符串比较 Result := CompareStr(V1, V2, loUserLocale);
修改后重新编译DevExpress的组件包,所有依赖cxVariants的模块都会自动使用用户区域排序规则。
2. Hook变体比较函数(无需修改源码)
如果不想改动DevExpress源码,可以通过Hook技术拦截VarCompareValue函数(cxVariants中负责变体比较的核心函数),替换为自定义实现:
function CustomVarCompareValue(const V1, V2: Variant): Integer; begin if VarIsEmpty(V1) then if VarIsEmpty(V2) then Result := 0 else Result := -1 else if VarIsEmpty(V2) then Result := 1 else if VarSameValue(V1, V2) then Result := 0 else Result := CompareStr(V1, V2, loUserLocale); end; // 在程序启动时完成Hook,需确保在DevExpress组件初始化前执行 initialization HookFunction(@VarCompareValue, @CustomVarCompareValue);
注:Hook实现需要用到第三方Hook库或自定义Hook代码,需保证Hook时机正确。
3. 全局绑定cxGrid默认比较事件
通过扩展cxGrid类,为所有cxGridDBTableView设置默认的OnCompare事件处理逻辑:
procedure GlobalGridCompareHandler(Sender: TcxCustomGridTableView; ARecord1, ARecord2: TcxCustomGridRecord; const AValue1, AValue2: Variant; var Compare: Integer); begin if VarIsEmpty(AValue1) then if VarIsEmpty(AValue2) then Compare := 0 else Compare := -1 else if VarIsEmpty(AValue2) then Compare := 1 else if VarSameValue(AValue1, AValue2) then Compare := 0 else Compare := CompareStr(AValue1, AValue2, loUserLocale); end; // 程序启动时绑定全局默认事件 initialization cxGridDBTableViewClass.OnCompare := GlobalGridCompareHandler;
这种方式无需修改源码,所有新建的cxGridDBTableView都会自动应用该比较逻辑。
内容的提问来源于stack exchange,提问作者TomR

