I’ve declared a type similar to the following.
type
TLikes = record
Name : string[20];
favColours : array of string[20];
faves = array of TLikes;
Once the records are populated I save them to a binary file so the structure is like that shown below.
[John], [Green] [White] [Blue]
[Paul], [Blue] [Red] [White] [Green]
[David], [Red] [Blue] [Green]
[Bob], [White] [Blue]
[Peter], [Blue] [Green] [Red]
It’s easy to find out what colours David, for example, likes. A small problem occurs when I want the to know who likes blue. So what I’ve done is build a second file, like so …
[Blue], [John] [Paul] [David] [Peter] [Bob]
[Red], [David] [Paul] [Peter]
[White], [Bob] [David] [John] [Paul]
[Green], [John] [David] [Paul] [Peter]
But something is telling me, I shouldn’t really need to create a second file / data structure, it just seems inefficient.
Here’s a bigger issue ….
What if I need to find who likes any combination of what David likes? My results would be …
Blue and red and green = Paul, David, Peter
Blue and red = Paul, David, Peter
Blue and green = John, Paul, David, Peter
Red and Green = Paul, David, Peter
My question is.
Is there a better way to structure the data / records so I can figure out what Bob and Paul have in common (Blue and White) or what red and white have in common (David and Paul) ?
I guess I need to point out that I have tried to simplify the example above. In reality the data for Tlikes.Name will be strings like …
‘decabbadc’
‘bacddbcad’
‘eebadeaac’
There are something in the order of 200k+ of these strings. And the Tlikes.FavColours data is a filename (there are around 2k of these files). The file name indicates a file that contains the Tlikes.Name string.
I want to be able to retrieve a list of file names given a Tlikes.Name string or a list of strings given a file name.
NB – Something is drawing me to ‘sets’ but from the little I understand, I’m limited in the number of elements in sets, am I on the right track ?
Thank you for taking the time to read the post.
Here is a generic set, TSet<T> which could be used as a tool to get relations between your data.
TSet<T> can hold data of simple types, not restricted to byte size as the normal Set type.
Supports:
Include (addition)
Exclude (subtraction)
Intersect (mutual inclusion, multiplication)
Symmetrical difference (mutual exclusion, xor)
Test for contains (in operator)
Test for equality (equal operator)
Test for superset of (>= operator)
Test for subset of ( <= operator)
Sorting
BinarySearch
Use TSet<T> to benchmark your application.
unit GenericSet;
interface
Uses
System.Generics.Defaults,
System.Generics.Collections;
Type
TSet<T> = record
// Include (union)
class operator Add(const aSet: TSet<T>; aValue: T) : TSet<T>; overload;
class operator Add(const aSet: TSet<T>; const aTArr: TArray<T>) : TSet<T>; overload;
class operator Add(const aSet1: TSet<T>; const aSet2: TSet<T>) : TSet<T>; overload;
// Exclude
class operator Subtract(const aSet: TSet<T>; aValue: T): TSet<T>; overload;
class operator Subtract(const aSet: TSet<T>; const aTArr: TArray<T>) : TSet<T>; overload;
class operator Subtract(const aSet1: TSet<T>; const aSet2: TSet<T>) : TSet<T>; overload;
// left in right, i.e right.Contains(left)
class operator In(aValue: T; const aSet: TSet<T>): Boolean; overload;
class operator In(const aTArr: TArray<T>; const aSet: TSet<T>): Boolean; overload;
class operator In(const aSet1: TSet<T>; const aSet2: TSet<T>): Boolean; overload;
// Intersect, mutually common, A and B
class operator Multiply(const aSet: TSet<T>; aValue: T): TSet<T>; overload;
class operator Multiply(const aSet: TSet<T>; const aTArr: TArray<T>): TSet<T>; overload;
class operator Multiply(const aSet1,aSet2 : TSet<T>): TSet<T>; overload;
// Symmetric difference, A xor B = (A+B) - A.Intersect(B)
class operator LogicalXor(const aSet: TSet<T>; aValue: T): TSet<T>; overload;
class operator LogicalXor(const aSet: TSet<T>; aTArr: TArray<T>): TSet<T>; overload;
class operator LogicalXor(const aSet1,aSet2 : TSet<T>): TSet<T>; overload;
//
class operator Equal(const aSet: TSet<T>; aValue: T): Boolean; overload;
class operator Equal(const aSet: TSet<T>; aTArr: TArray<T>): Boolean; overload;
class operator Equal(const aSetLeft,aSetRight: TSet<T>): Boolean; overload;
// SubsetOf (Left <= Right)
class operator LessThanOrEqual(const aSet: TSet<T>; aValue: T): Boolean; overload;
class operator LessThanOrEqual(const aSet: TSet<T>; aTArr: TArray<T>): Boolean; overload;
class operator LessThanOrEqual(const aSetLeft,aSetRight: TSet<T>): Boolean; overload;
// SupersetOf (Left >= Right)
class operator GreaterThanOrEqual(const aSet: TSet<T>; aValue: T): Boolean; overload;
class operator GreaterThanOrEqual(const aSet: TSet<T>; aTArr: TArray<T>): Boolean; overload;
class operator GreaterThanOrEqual(const aSetLeft,aSetRight: TSet<T>): Boolean; overload;
// Set creator
class function Create(const aTArr: array of T; checkDuplicates: Boolean = False): TSet<T>; static;
private
FSetArray : array of T;
FSorted : String; // !! Will be predefined as '' (=False) by compiler.
function GetEmpty: Boolean; inline;
function GetItem(index: Integer): T; inline;
function GetItemCount: Integer; inline;
function GetSorted: Boolean; inline;
procedure SetSorted( sorted: Boolean); inline;
public
// Add
procedure Include(aValue: T); overload;
procedure Include(const aTArr: TArray<T>); overload;
procedure Include(const aTArr: array of T); overload;
procedure Include(const aSet: TSet<T>); overload;
// Subtract; A=[1,2,3]; B=[2,3,4]; B.Exclude(A) = B-A = [4]
procedure Exclude(aValue: T); overload;
procedure Exclude(const aTArr: TArray<T>); overload;
procedure Exclude(const aTArr: array of T); overload;
procedure Exclude(const aSet: TSet<T>); overload;
// Multiply (A and B) A=[1,2,3]; B=[2,3,4]; B.Intersect(A) = B*A = [2,3]
function Intersect(aValue: T): TSet<T>; overload;
function Intersect(const aTArr: TArray<T>): TSet<T>; overload;
function Intersect(const aTArr: array of T): TSet<T>; overload;
function Intersect(const aSet: TSet<T>): TSet<T>; overload;
// A xor B; A=[1,2,3]; B=[2,3,4]; (A+B)-A.Intersect(B) = [1,4]
function SymmetricDiff(aValue: T): TSet<T>; overload;
function SymmetricDiff(const aTArr: TArray<T>): TSet<T>; overload;
function SymmetricDiff(const aTArr: array of T): TSet<T>; overload;
function SymmetricDiff(const aSet: TSet<T>): TSet<T>; overload;
// Identical set
function Equal(aValue: T): Boolean; overload;
function Equal(const aTArr: array of T; checkDuplicates: Boolean = False): Boolean; overload;
function Equal(const aTArr: TArray<T>; checkDuplicates: Boolean = False): Boolean; overload;
function Equal(const aSet: TSet<T>): Boolean; overload;
// Self <= aSet
function SubsetOf(aValue: T): Boolean; overload;
function SubsetOf(const aTArr: array of T; checkDuplicates: Boolean = False): Boolean; overload;
function SubsetOf(const aTArr: TArray<T>; checkDuplicates: Boolean = False): Boolean; overload;
function SubsetOf(const aSet: TSet<T>): Boolean; overload;
// Self >= aSet
function SupersetOf(aValue: T): Boolean; overload;
function SupersetOf(const aTArr: array of T; checkDuplicates: Boolean = False): Boolean; overload;
function SupersetOf(const aTArr: TArray<T>; checkDuplicates: Boolean = False): Boolean; overload;
function SupersetOf(const aSet: TSet<T>): Boolean; overload;
// Is included
function Contains(aValue: T): Boolean; overload;
function Contains(const aTArr: array of T): Boolean; overload;
function Contains(const aTArr: TArray<T>): Boolean; overload;
function Contains(const aSet: TSet<T>): Boolean; overload;
procedure Sort; // QuickSort
function Search( aValue: T): Boolean; // BinarySearch (Set must be sorted)
procedure Clear;
property IsSorted: Boolean read GetSorted;
property IsEmpty: Boolean read GetEmpty;
property Items[index: Integer]: T read GetItem; default;
property ItemCount: Integer read GetItemCount;
end;
implementation
class function TSet<T>.Create(const aTArr: array of T; checkDuplicates: Boolean = False): TSet<T>;
var
i,j,elements : Integer;
duplicate : Boolean;
c : IEqualityComparer<T>;
begin
if checkDuplicates then
begin
c := TEqualityComparer<T>.Default;
// This will remove duplicates
SetLength(Result.FSetArray,Length(aTArr));
elements := 0;
for i := 0 to High(aTArr) do
begin
duplicate := False;
for j := 0 to Pred(elements) do
begin
duplicate := c.Equals(Result.FSetArray[j],aTArr[i]);
if duplicate then
Break;
end;
if not duplicate then
begin
Result.FSetArray[elements] := aTArr[i];
Inc(elements);
end;
end;
SetLength(Result.FSetArray,elements);
end
else
begin
SetLength(Result.FSetArray, Length(aTArr));
for i := 0 to High(aTArr) do
Result.FSetArray[i] := aTArr[i];
end;
end;
class operator TSet<T>.Add(const aSet: TSet<T>; aValue: T): TSet<T>;
begin
Result := aSet;
Result.Include(aValue);
end;
class operator TSet<T>.Add(const aSet: TSet<T>; const aTArr: TArray<T>): TSet<T>;
begin
Result := aSet;
Result.Include(aTArr);
end;
class operator TSet<T>.Add(const aSet1, aSet2: TSet<T>): TSet<T>;
begin
Result := aSet1;
Result.Include(aSet2);
end;
procedure TSet<T>.Include(aValue: T);
begin
if not Contains(aValue) then begin
SetLength(FSetArray,Length(FSetArray)+1);
FSetArray[High(FSetArray)] := aValue;
SetSorted(False);
end;
end;
procedure TSet<T>.Include(const aSet: TSet<T>);
begin
if Self.IsEmpty then
Self := aSet
else
Include(aSet.FSetArray);
end;
procedure TSet<T>.Include(const aTArr: TArray<T>);
var
i : Integer;
begin
for i := 0 to High(aTArr) do
Self.Include(aTArr[i]);
end;
procedure TSet<T>.Include(const aTArr: array of T);
var
i : Integer;
begin
for i := 0 to High(aTArr) do
Self.Include(aTArr[i]);
end;
procedure TSet<T>.Exclude(const aTArr: TArray<T>);
var
i : Integer;
begin
for i := 0 to High(aTArr) do
Exclude(aTArr[i]);
end;
procedure TSet<T>.Exclude(const aTArr: array of T);
var
i : Integer;
begin
for i := 0 to High(aTArr) do
Exclude(aTArr[i]);
end;
procedure TSet<T>.Exclude(const aSet: TSet<T>);
begin
Exclude(aSet.FSetArray);
end;
procedure TSet<T>.Exclude(aValue: T);
var
i : Integer;
c : IEqualityComparer<T>;
begin
c := TEqualityComparer<T>.Default;
for i := 0 to High(FSetArray) do
begin
if c.Equals(FSetArray[i],aValue) then
begin
SetLength(FSetArray,Length(FSetArray)); // Ensure unique dyn array
if (i < High(FSetArray)) then
begin
FSetArray[i] := FSetArray[High(FSetArray)]; // Move last element
Self.SetSorted(False);
end;
SetLength(FSetArray,Length(FSetArray)-1);
Break;
end;
end;
end;
class operator TSet<T>.Subtract(const aSet1, aSet2: TSet<T>): TSet<T>;
begin
Result := aSet1;
Result.Exclude(aSet2.FSetArray);
end;
class operator TSet<T>.Subtract(const aSet: TSet<T>;
const aTArr: TArray<T>): TSet<T>;
begin
Result := aSet;
Result.Exclude(aTArr);
end;
class operator TSet<T>.Subtract(const aSet: TSet<T>; aValue: T): TSet<T>;
begin
Result := aSet;
Result.Exclude(aValue);
end;
class operator TSet<T>.In(aValue: T; const aSet: TSet<T>): Boolean;
begin
Result := aSet.Contains(aValue);
end;
class operator TSet<T>.In(const aTArr: TArray<T>; const aSet: TSet<T>): Boolean;
begin
Result := aSet.Contains(aTArr);
end;
class operator TSet<T>.In(const aSet1: TSet<T>; const aSet2: TSet<T>): Boolean;
begin
Result := aSet2.Contains(aSet1.FSetArray);
end;
function TSet<T>.Contains(aValue: T): Boolean;
var
i : Integer;
c : IEqualityComparer<T>;
begin
if IsSorted then
begin
Result := Search(aValue);
end
else
begin
Result := false;
c := TEqualityComparer<T>.Default;
for i := 0 to High(FSetArray) do
if c.Equals(FSetArray[i],aValue) then
Exit(True);
end;
end;
function TSet<T>.Contains(const aTArr: array of T): Boolean;
var
i: Integer;
begin
Result := High(aTArr) >= 0;
for i := 0 to High(aTArr) do
begin
if IsSorted then
Result := Search(aTArr[i])
else
Result := Contains(aTArr[i]);
if not Result then
Exit(false);
end;
end;
function TSet<T>.Contains(const aTArr: TArray<T>): Boolean;
var
i : Integer;
begin
Result := High(aTArr) >= 0;
for i := 0 to High(aTArr) do
begin
if IsSorted then
Result := Search(aTArr[i])
else
Result := Contains(aTArr[i]);
if not Result then
Exit(false);
end;
end;
function TSet<T>.Contains(const aSet: TSet<T>): Boolean;
begin
Result := Contains(aSet.FSetArray);
end;
function TSet<T>.GetEmpty: Boolean;
begin
Result := (Self.ItemCount = 0);
end;
function TSet<T>.GetItem(index: Integer): T;
begin
Result := Self.FSetArray[index];
end;
function TSet<T>.GetItemCount: Integer;
begin
Result := Length(Self.FSetArray);
end;
procedure TSet<T>.Clear;
begin
SetLength(FSetArray,0);
Self.SetSorted(False);
end;
// Get the mutually common elements, aka the intersect.
class operator TSet<T>.Multiply(const aSet: TSet<T>; aValue: T): TSet<T>;
begin
Result:= aSet.Intersect(aValue);
end;
class operator TSet<T>.Multiply(const aSet: TSet<T>; const aTArr: TArray<T>): TSet<T>;
begin
Result:= aSet.Intersect(aTArr);
end;
class operator TSet<T>.Multiply(const aSet1,aSet2: TSet<T>): TSet<T>;
begin
Result := aSet1.Intersect(aSet2);
end;
function TSet<T>.Intersect(aValue : T): TSet<T>;
var
i : Integer;
begin
if Self.Contains(aValue) then
Result.Include(aValue)
else
Result.Clear;
Result.SetSorted(Result.ItemCount = 1);
end;
function TSet<T>.Intersect(const aSet: TSet<T>): TSet<T>;
var
i,items : Integer;
begin
SetLength(Result.FSetArray,aSet.ItemCount);
items := 0;
for i := 0 to High(aSet.FSetArray) do
begin
if Self.Contains(aSet.FSetArray[i]) then
begin
Result.FSetArray[items] := aSet.FSetArray[i];
Inc(items);
end;
end;
SetLength(Result.FSetArray,items);
Result.SetSorted(Self.IsSorted and aSet.IsSorted);
end;
function TSet<T>.Intersect(const aTArr: array of T): TSet<T>;
var
i : Integer;
begin
for i := 0 to High(aTArr) do
begin
if Self.Contains(aTArr[i]) then
Result.Include(aTArr[i]);
end;
Result.SetSorted(False);
end;
function TSet<T>.Intersect(const aTArr: TArray<T>): TSet<T>;
var
i : Integer;
begin
for i := 0 to High(aTArr) do
begin
if Self.Contains(aTArr[i]) then
Result.Include(aTArr[i]);
end;
Result.SetSorted(False);
end;
//
function TSet<T>.Equal(aValue: T): Boolean;
begin
Result := (Self.ItemCount = 1) and Self.Contains(aValue);
end;
function TSet<T>.Equal(const aTArr: array of T; checkDuplicates: Boolean = False): Boolean;
begin
if checkDuplicates then
Result :=
(Self.ItemCount <= Length(aTArr)) and
Self.Equal(TSet<T>.Create(aTArr,True)) // Remove possible duplicates
else
Result :=
(Self.ItemCount = Length(aTArr)) and
Self.Contains(aTArr);
end;
function TSet<T>.Equal(const aTArr: TArray<T>; checkDuplicates: Boolean = False): Boolean;
begin
if checkDuplicates then
Result :=
(Self.ItemCount <= Length(aTArr)) and
Self.Equal(TSet<T>.Create(aTArr,True)) // Remove possible duplicates
else
Result :=
(Self.ItemCount = Length(aTArr)) and
Self.Contains(aTArr);
end;
function TSet<T>.Equal(const aSet: TSet<T>): Boolean;
begin
Result :=
(Self.ItemCount = aSet.ItemCount) and
Contains(aSet);
end;
class operator TSet<T>.Equal(const aSet: TSet<T>; aValue: T): Boolean;
begin
Result := aSet.Equal(aValue);
end;
class operator TSet<T>.Equal(const aSet: TSet<T>; aTArr: TArray<T>): Boolean;
begin
Result := aSet.Equal(aTArr,True);
end;
class operator TSet<T>.Equal(const aSetLeft,aSetRight: TSet<T>): Boolean;
begin
Result := aSetLeft.Equal(aSetRight);
end;
// Self <= aSet
function TSet<T>.SubsetOf(aValue: T): Boolean;
begin
Result := (Self.ItemCount = 1) and Self.Equal(aValue);
end;
function TSet<T>.SubsetOf(const aTArr: array of T; checkDuplicates: Boolean = False): Boolean;
begin
Result := Self.SubsetOf(TSet<T>.Create(aTArr,checkDuplicates));
end;
function TSet<T>.SubsetOf(const aTArr: TArray<T>; checkDuplicates: Boolean = False): Boolean;
begin
Result := SubsetOf(TSet<T>.Create(aTArr,checkDuplicates));
end;
function TSet<T>.SubsetOf(const aSet: TSet<T>): Boolean;
begin
Result :=
(Self.ItemCount <= aSet.ItemCount) and
aSet.Contains(Self);
end;
// SubsetOf (Left <= Right)
class operator TSet<T>.LessThanOrEqual(const aSet: TSet<T>; aValue: T): Boolean;
begin
Result := aSet.SubsetOf(aValue);
end;
class operator TSet<T>.LessThanOrEqual(const aSet: TSet<T>; aTArr: TArray<T>): Boolean;
begin
Result := aSet.SubsetOf(aTArr,True);
end;
class operator TSet<T>.LessThanOrEqual(const aSetLeft,aSetRight: TSet<T>): Boolean;
begin
Result := aSetLeft.SubsetOf(aSetRight);
end;
// Self >= aSet
function TSet<T>.SupersetOf(const aSet: TSet<T>): Boolean;
begin
Result :=
(Self.ItemCount >= aSet.ItemCount) and
Self.Contains(aSet);
end;
function TSet<T>.SupersetOf(aValue: T): Boolean;
begin
Result := (Self.ItemCount >= 1) and Self.Contains(aValue);
end;
function TSet<T>.SupersetOf(const aTArr: array of T; checkDuplicates: Boolean = False): Boolean;
begin
Result := SupersetOf(TSet<T>.Create(aTArr,checkDuplicates));
end;
function TSet<T>.SupersetOf(const aTArr: TArray<T>; checkDuplicates: Boolean = False): Boolean;
begin
Result := SupersetOf(TSet<T>.Create(aTArr,checkDuplicates));
end;
// SupersetOf (Left >= Right)
class operator TSet<T>.GreaterThanOrEqual(const aSet: TSet<T>; aValue: T): Boolean;
begin
Result := aSet.SupersetOf(aValue);
end;
class operator TSet<T>.GreaterThanOrEqual(const aSet: TSet<T>; aTArr: TArray<T>): Boolean;
begin
Result := aSet.SupersetOf(aTArr,True);
end;
class operator TSet<T>.GreaterThanOrEqual(const aSetLeft,aSetRight: TSet<T>): Boolean;
begin
Result := aSetLeft.SupersetOf(aSetRight);
end;
// A xor B; A=[1,2,3]; B=[2,3,4]; (A+B)-A.Intersect(B) = [1,4] alt:
function TSet<T>.SymmetricDiff(aValue: T): TSet<T>;
begin
Result := Self;
Result.Include(aValue);
Result.Exclude(Self.Intersect(aValue));
Result.SetSorted(False);
end;
function TSet<T>.SymmetricDiff(const aTArr: TArray<T>): TSet<T>;
begin
Result := Self;
Result.Include(aTArr);
Result.Exclude(Self.Intersect(aTArr));
Result.SetSorted(False);
end;
function TSet<T>.SymmetricDiff(const aTArr: array of T): TSet<T>;
begin
Result := Self;
Result.Include(aTArr);
Result.Exclude(Self.Intersect(aTArr));
Result.SetSorted(False);
end;
function TSet<T>.SymmetricDiff(const aSet: TSet<T>): TSet<T>;
begin
Result:= Self;
Result.Include(aSet);
Result.Exclude(Self.Intersect(aSet));
Result.SetSorted(False);
end;
class operator TSet<T>.LogicalXor(const aSet: TSet<T>; aValue: T): TSet<T>;
begin
Result := aSet.SymmetricDiff(aValue);
end;
class operator TSet<T>.LogicalXor(const aSet: TSet<T>; aTArr: TArray<T>): TSet<T>;
begin
Result := aSet.SymmetricDiff(aTArr);
end;
class operator TSet<T>.LogicalXor(const aSet1,aSet2 : TSet<T>): TSet<T>;
begin
Result := aSet1.SymmetricDiff(aSet2);
end;
procedure TSet<T>.Sort;
begin
SetLength(Self.FSetArray,Length(Self.FSetArray)); // Ensure COW
TArray.Sort<T>(Self.FSetArray);
SetSorted(True);
end;
function TSet<T>.Search(aValue: T): Boolean;
var
Index: Integer;
begin
Result := TArray.BinarySearch<T>(Self.FSetArray,aValue,Index);
end;
function TSet<T>.GetSorted: Boolean;
begin
Result := (FSorted = '1');
end;
procedure TSet<T>.SetSorted(sorted: Boolean);
begin
if sorted then
FSorted := '1'
else
FSorted := '0';
end;
end.
A benchmark:
program ProjectGenericSet;
{$APPTYPE CONSOLE}
uses
System.Diagnostics,
System.Generics.Defaults,
System.Generics.Collections,
GenericSet in 'GenericSet.pas';
var
set1,set2,set3 : TSet<Word>;
sw : TStopWatch;
ok : Boolean;
i,j,max: Integer;
begin
Randomize;
max := $10000;
// Populate a sample set with 32K items.
repeat
set1.Include(Random(max));
until (set1.ItemCount = (max DIV 2));
// Populate a test set with items in sample set
repeat
set2.Include(set1[Random(max DIV 2)]);
until (set2.ItemCount = 100);
WriteLn('Test in Sample (unsorted), 1.000 iterations...');
sw := TStopWatch.StartNew;
for i := 1 TO 1000 DO
ok := set1.Contains(set2);
sw.Stop;
WriteLn('Result:',ok,' ',sw.ElapsedMilliseconds,' [ms]');
set1.Sort; // Sort
WriteLn('Test in Sample (sorted), 200.000 iterations...');
sw := TStopWatch.StartNew;
for i := 1 TO 200000 DO
begin
ok := set1.Contains(set2);
end;
sw.Stop;
WriteLn('Result:',ok,' ',sw.ElapsedMilliseconds,' [ms]');
WriteLn('Test*Test (unsorted), 200.000 iterations...');
sw := TStopWatch.StartNew;
for i := 1 TO 200000 DO
begin
set3 := set2.Intersect(set2);
end;
sw.Stop;
WriteLn('Result:',set3=set2,' ',sw.ElapsedMilliseconds,' [ms]');
set2.Sort;
WriteLn('Test*Test (sorted), 200.000 iterations...');
sw := TStopWatch.StartNew;
for i := 1 TO 200000 DO
begin
set3 := set2.Intersect(set2);
end;
sw.Stop;
WriteLn('Result:',set3=set2,' ',sw.ElapsedMilliseconds,' [ms]');
ReadLn;
end.
You're dealing with a many-to-many relationship here.
If it were a database that means you'd put in 3 tables:
1. People
2. Colors
3. Link table between 1 and 2
I suggest you either fix the problem by utilizing a database or model the thing in Delphi just like it where a database.
Using Delphi structures
Furthermore stop using shortstring They are outdated and have zero benefits over longstrings.
Using 3 tables means you can quickly get a list of people per color and colors per person.
Here's how it would work:
TPerson = record
name: string;
other_data....
end;
TPeople = array of TPerson;
TFavColor = record
name: string;
other_data....
end;
TFavColors = array of TFavColor;
TPersonColor = record
PersonIndex: Cardinal; <<-- index into the TPeople array
ColorIndex: Cardinal; <<-- index into the TFavColors array
end;
TPersonColors = array of TPersonColor;
Now you can just loop over the TPersonColors array to extract your data.
Using a database
In SQL it would be even faster because your data is indexed (foreign key are (should be) always indexed).
The SQL statement the see all people that like blue and red would look like (using MySQL syntax here):
SELECT p.name
FROM person p
INNER JOIN personcolor pc ON (pc.person_id = p.id)
INNER JOIN color c1 ON (pc.color_id = c1.id)
INNER JOIN color c2 ON (pc.color_id = c2.id)
WHERE c1.name = 'red' AND c2.name = 'blue'
GROUP BY p.id <<-- eliminate duplicates (not sure it's needed)
Using Delphi its trivial to link a database to your app.
So that's the route I'd recommend.
Related
I'm adding a checkbox to the BrowseForFolder dialog using the following calls...
ControlCreateStyles := WS_CHILD or {WS_CLIPSIBLINGS or} WS_VISIBLE or WS_TABSTOP or BS_CHECKBOX;
ChkBoxHdl := CreateWindow('BUTTON', PChar(ChkBoxCap), ControlCreateStyles,
Left, Top, Width, Height, Wnd, FB_CHECKBOX_ID, HInstance, nil);
The checkbox displays and operates correctly. However, when I resize the dialog down to its smallest size, the checkbox and caption disappear. Resizing the dialog causes the checkbox to reappear but not consistently. I tried enabling WS_CLIPSIBLINGS but doing so causes the component to not display at all.
Here is my test unit...
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
function BrowseForFolder(Title, Caption: string; const InitFolder: string = ''; DoNewBtn: Boolean = True; DoCheckBox: Boolean = False): string;
var
Form1: TForm1;
ShowCheckBox: Boolean = False;
DialogCaption: string;
implementation
{$R *.dfm}
uses
ShlObj, FileCtrl;
const
BIF_NEWDIALOGSTYLE = $40;
BIF_NONEWFOLDERBUTTON = $200;
FB_CHECKBOX_ID = 4005;
var
lg_StartFolder: String;
OldWndProc: Pointer;
function WndProcLocal(HWindow: HWND; MsgId: UINT; wP: WPARAM; lP: LPARAM): LRESULT; stdcall;
var
NewFolder: string;
Cnt: Integer;
maxwidth: Integer;
MyFB: HWND;
begin
if (MsgId = WM_COMMAND) and (wP = FB_CHECKBOX_ID) then begin
Result := 0;
NewFolder := '';
Cnt := 0;
if (IsDlgButtonChecked(HWindow, FB_CHECKBOX_ID) = 0) then begin
CheckDlgButton(HWindow, FB_CHECKBOX_ID, BST_CHECKED);
// Do Something
end
else begin
CheckDlgButton(HWindow, FB_CHECKBOX_ID, BST_UNCHECKED);
// Do Something
end;
end
else begin
if (MsgId = WM_SHOWWINDOW) then begin
// Do Something
end
else if (MsgId = WM_SIZE) then begin
// Do Something
end
else if (MsgId = WM_MOVE) then begin
// Do Something
end;
Result := CallWindowProc(OldWndProc, HWindow, MsgId, wP, lP);
end;
end;
function BrowseForFolderCallBack(Wnd: HWND; uMsg: UINT; lParam, lpData: LPARAM): Integer stdcall;
var
ControlCreateStyles: Integer;
ChkBoxCap: String;
ChkBoxHdl: HWND;
Left, Top, Width, Height: Integer;
PPI: Integer;
Cnv: TCanvas;
TempFont: TFont;
begin
Result := 0;
if uMsg = BFFM_INITIALIZED then begin
if ShowCheckBox then begin
Left := 16;
Top := 32;
//Width := ?; { Calculated next based on caption }
Height := 16;
ChkBoxCap := 'Checkbox Caption';
Cnv := TCanvas.Create;
try
Cnv.Handle := GetDC(Wnd);
Width := Height * 2 + Cnv.TextWidth(ChkBoxCap);
finally
Cnv.Free;
end;
ControlCreateStyles := WS_CHILD or {WS_CLIPSIBLINGS or} WS_VISIBLE or WS_TABSTOP or BS_CHECKBOX;
ChkBoxHdl := CreateWindow('BUTTON', PChar(ChkBoxCap), ControlCreateStyles,
Left, Top, Width, Height, Wnd, FB_CHECKBOX_ID, HInstance, nil);
TempFont := nil;
TempFont := TFont.Create;
TempFont.Assign(Screen.IconFont);
try
PostMessage(ChkBoxHdl, WM_SETFONT, Longint(TempFont.Handle), MAKELPARAM(1, 0));
finally
TempFont.Free;
end;
CheckDlgButton(Wnd, FB_CHECKBOX_ID, BST_UNCHECKED); { Should always default to False }
//EnableWindow(ChkBoxHdl, True); { Necessary? }
end; { ShowCheckBox }
SetWindowText(Wnd, PChar(DialogCaption));
SendMessage(Wnd, BFFM_SETSELECTION, 1, Integer(#lg_StartFolder[1]));
OldWndProc := Pointer(GetWindowLong(Wnd, GWL_WNDPROC));
SetWindowLong(Wnd, GWL_WNDPROC, Longint(#WndProcLocal));
end;
end;
function BrowseForFolder(Title, Caption: string; const InitFolder: string = ''; DoNewBtn: Boolean = True; DoCheckBox: Boolean = False): string;
var
lpItemID: PItemIDList;
BrowseInfo: TBrowseInfo;
DisplayName: array[0 .. MAX_PATH] of Char;
find_context: PItemIDList;
ptrWindows: Pointer;
begin
DialogCaption := Caption;
ShowCheckBox := DoCheckBox;
FillChar(BrowseInfo, SizeOf(BrowseInfo), #0);
FillChar(DisplayName, SizeOf(DisplayName), #0);
lg_StartFolder := InitFolder;
with BrowseInfo do begin
hwndOwner := Application.Handle;
pszDisplayName := #DisplayName[0];
lpszTitle := PChar(Title);
ulFlags := BIF_RETURNONLYFSDIRS or BIF_NEWDIALOGSTYLE;
if not DoNewBtn then
ulFlags := ulFlags or BIF_NONEWFOLDERBUTTON; { Hide New Folder Button }
if (InitFolder <> '') then
lpfn := #BrowseForFolderCallBack;
LPARAM := 0;
end;
ptrWindows := DisableTaskWindows(0);
try
lpItemID := SHBrowseForFolder(BrowseInfo);
finally
EnableTaskWindows(ptrWindows);
end;
if Assigned(lpItemID) then
begin
if SHGetPathFromIDList(lpItemID, DisplayName) then
Result := DisplayName
else
Result := '';
GlobalFreePtr(lpItemID);
end
else
Result := '';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Dir: String;
begin
BrowseForFolder('Title', 'Caption', 'C:\', True, True);
end;
end.
As recommended by Embarcadero, it looks like I would need to go this route.
JAM Software ShellBrowser Delphi Components
Creating Custom File Dialogs: ShellBrowser Delphi Components
Yes, I am aware these libraries are only supported on Delphi XE3 and later.
Using Remy's suggestion, I produced the following: A File Dialog set to Pick Folders with a custom checkbox item.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
btnSelectFolder: TButton;
BrowseForFolder: TFileOpenDialog;
procedure BrowseForFolderOkClick(Sender: TObject; var CanClose: Boolean);
procedure BrowseForFolderExecute(Sender: TObject);
procedure btnSelectFolderClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
const
FB_CHECKBOX_ID = 4005;
implementation
uses
Winapi.ShlObj;
{$R *.dfm}
type
TFBDialogEvents = class(TInterfacedObject, IFileDialogEvents, IFileDialogControlEvents)
public
{ IFileDialogEvents }
function OnFileOk(const pfd: IFileDialog): HResult; stdcall;
function OnFolderChanging(const pfd: IFileDialog; const psiFolder: IShellItem): HResult; stdcall;
function OnFolderChange(const pfd: IFileDialog): HResult; stdcall;
function OnSelectionChange(const pfd: IFileDialog): HResult; stdcall;
function OnShareViolation(const pfd: IFileDialog; const psi: IShellItem; out pResponse: DWORD): HResult; stdcall;
function OnTypeChange(const pfd: IFileDialog): HResult; stdcall;
function OnOverwrite(const pfd: IFileDialog; const psi: IShellItem; out pResponse: DWORD): HResult; stdcall;
{ IFileDialogControlEvents }
function OnItemSelected(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD; dwIDItem: DWORD): HResult; stdcall;
function OnButtonClicked(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD): HResult; stdcall;
function OnCheckButtonToggled(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD; bChecked: BOOL): HResult; stdcall;
function OnControlActivating(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD): HResult; stdcall;
end;
const
dwVisualGroup1ID: DWORD = 1900;
var
FB: IFileDialog = nil;
FBEvents: IFileDialogEvents = nil;
FBEventsCookie: DWORD = 0;
procedure TForm1.btnSelectFolderClick(Sender: TObject);
var
aFolder: string;
begin
BrowseForFolder.Options := [fdoPickFolders];
if BrowseForFolder.Execute(Self.Handle) then begin
// Do Something
aFolder := BrowseForFolder.FileName;
end;
end;
procedure TForm1.BrowseForFolderExecute(Sender: TObject);
var
iCustomize: IFileDialogCustomize;
iEvents: IFileDialogEvents;
cookie: DWORD;
begin
if Supports(BrowseForFolder.Dialog, IFileDialogCustomize, iCustomize) then begin
if BrowseForFolder.Dialog.QueryInterface(IFileDialogCustomize, iCustomize) = S_OK then begin
iCustomize.StartVisualGroup(0, 'Custom Caption');
try
iCustomize.AddCheckButton(FB_CHECKBOX_ID, 'Checkbox Caption', False);
iCustomize.MakeProminent(FB_CHECKBOX_ID);
finally
iCustomize.EndVisualGroup;
end;
iEvents := TFBDialogEvents.Create;
if Succeeded(BrowseForFolder.Dialog.Advise(iEvents, cookie)) then begin
FB := BrowseForFolder.Dialog;
FBEvents := iEvents;
FBEventsCookie := cookie;
end;
end;
end;
end;
// Grab the custom control's selection
procedure TForm1.BrowseForFolderOkClick(Sender: TObject; var CanClose: Boolean);
var
iCustomize: IFileDialogCustomize;
IsChecked: LongBool;
begin
if BrowseForFolder.Dialog.QueryInterface(IFileDialogCustomize, iCustomize) = S_OK then begin
iCustomize.GetCheckButtonState(FB_CHECKBOX_ID, IsChecked);
end;
end;
function TFBDialogEvents.OnFileOk(const pfd: IFileDialog): HResult;
begin
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnFolderChange(const pfd: IFileDialog): HResult;
begin
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnFolderChanging(const pfd: IFileDialog; const psiFolder: IShellItem): HResult;
begin
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnOverwrite(const pfd: IFileDialog; const psi: IShellItem; out pResponse: DWORD): HResult;
begin
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnSelectionChange(const pfd: IFileDialog): HResult;
begin
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnShareViolation(const pfd: IFileDialog; const psi: IShellItem; out pResponse: DWORD): HResult;
begin
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnTypeChange(const pfd: IFileDialog): HResult;
begin
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnItemSelected(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD; dwIDItem: DWORD): HResult;
begin
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnButtonClicked(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD): HResult;
begin
if dwIDCtl = dwVisualGroup1ID then begin
// ...
Result := S_OK;
end
else begin
Result := E_NOTIMPL;
end;
end;
function TFBDialogEvents.OnCheckButtonToggled(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD; bChecked: BOOL)
: HResult;
var
IsChecked: LongBool;
begin
pfdc.GetCheckButtonState(FB_CHECKBOX_ID, IsChecked);
if IsChecked then
// Do Somethihng
else
// Don't Do Anything
Result := E_NOTIMPL;
end;
function TFBDialogEvents.OnControlActivating(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD): HResult;
begin
Result := E_NOTIMPL;
end;
end.
Which produces this:
File Dialog (Select Folder)
However, all I want is this:
Browse For Folder
I receive data into my echo rest api by post method. I have two arrays. I import pq library.
My structure is
type Lien struct {
LinkID int `json: "linkID"`
Linklabel string `json: "label"`
Linkaddress string `json: "address"`
Langs []string `json: "langs"`
Cats []int `json: "cats"`
}
My post function is
func createLink(c echo.Context) error {
l := new(Lien)
if err := c.Bind(l); err != nil {
return err
}
sqlStatement := "INSERT INTO link_test (label, address,langs, cats)VALUES ($1, $2, $3, $4)"
res, err := db.Query(sqlStatement, l.Linklabel, l.Linkaddress, pq.Array(l.Langs), pq.Array(l.Cats))
if err != nil {
fmt.Println(err)
} else {
fmt.Println(res)
return c.JSON(http.StatusCreated, l)
}
return c.String(http.StatusOK, "ok")
}
It works for the first two fields but not for the arrays, I always get a null value.
I started learning Go and I want to implement some algorithm.
I want to iterate over a string and then extract some substrings for example : p -(40) *(GOT) +(FF)
should return an array like this: [p, 40, GOT, FF]
I wrote something like this but it's vague:
import (
"strings"
)
func find(input string){
var result []string
a := strings.SplitN(input, "-(", 2);
result[0] = a[0]
b := strings.SplitN(a[1], ") *(", 2)
result[1] = b[0]
c := strings.SplitN(a[1], ") +(", 2)
result[2] = c[0]
d := strings.SplitN(a[1], ")", 2)
result[3] = d[0]
}
Can someone please correct me or suggest something easier? Thanks!
You may use strings.FieldsFunc, like this working sample code:
package main
import "fmt"
import "strings"
func find(input string) []string {
return strings.FieldsFunc(input, split)
}
func main() {
strs := find("p -(40) *(GOT) +(FF)")
str := strings.Replace(fmt.Sprint(strs), " ", ", ", -1)
fmt.Println(str)
}
func split(r rune) bool {
return r == ' ' || r == '-' || r == '(' || r == ')' || r == '*' || r == '+'
}
output:
[p, 40, GOT, FF]
You may use strings.Fields then strings.Trim, like this working sample code:
package main
import "fmt"
import "strings"
func main() {
fmt.Println(find("p -(40) *(GOT) +(FF)"))
}
func find(input string) []string {
strs := strings.Fields(input)
result := make([]string, 0, len(strs))
for _, v := range strs {
s := strings.Trim(v, "()+-*")
if len(s) > 0 {
result = append(result, s)
}
}
return result
}
output:
[p 40 GOT FF]
Also this works for your special case:
package main
import "fmt"
import "strings"
func main() {
fmt.Println(find("p -(40) *(GOT) +(FF)"))
}
func find(input string) []string {
strs := strings.Fields(input)
for i := 0; i < len(strs); i++ {
strs[i] = strings.Trim(strs[i], "()+-*")
}
return strs
}
output:
[p 40 GOT FF]
And if you need output like this string: [p, 40, GOT, FF], try this working sample code:
package main
import "fmt"
import "strings"
func main() {
strs := find("p -(40) *(GOT) +(FF)")
str := strings.Replace(fmt.Sprint(strs), " ", ", ", -1)
fmt.Println(str)
}
func find(input string) []string {
strs := strings.Fields(input)
for i := 0; i < len(strs); i++ {
strs[i] = strings.Trim(strs[i], "()+-*")
}
return strs
}
output:
[p, 40, GOT, FF]
Based on the code below where I can retrieve the result of a query using dynamic object "ADODB.Command"?
This unit will always work with stored procedure, and the types of parameters that will be used are the input and output type.
use Delphi 7 + SQL Server Express 2012.
unit uadolibrary;
interface
uses
ComObj, Variants;
function ADOStart(strConexao: AnsiString): Boolean;
function ADOStop: Boolean;
function ADOConfigParams(ParamName: string; ParamType, ParamIO,
ParamSize: integer; Val: variant; CanClear: Boolean = False): Boolean;
function ADOSetParamVal(ParamName: string; val: variant): Boolean;
function ADOGetParamVal(ParamName: string): Variant;
function ADOGetValue(_Indece: integer): Variant;
function ADOExecute(procname: string): Boolean;
const
{Param Data Types}
adInteger = 3;
adSingle = 4;
adDate = 7;
adBoolean = 11;
adVarBites = 16;
adUnsignedTinyInt = 17;
adDateTime = 135;
advarChar = 200;
{Param Directions}
adParamInput = 1;
adParamOutput = 2;
adParamReturnValue = 4;
{Command Types}
adCmdText = 1;
adCmdTable = 2;
adCmdStoredProc = 4;
adCmdTableDirect = 512;
adCmdFile = 256;
implementation
var
ADODBConnection,
ADORecordSet,
ADOCommand: Variant;
function ADOStart(strConexao: AnsiString): Boolean;
begin
try
ADODBConnection:= CreateOleObject('ADODB.Connection');
ADODBConnection.ConnectionString:= strConexao;
ADODBConnection.Open;
ADORecordSet:= CreateOLEObject('ADODB.Recordset');
ADOCommand:= CreateOLEObject('ADODB.Command');
ADOCommand.ActiveConnection:= ADODBConnection;
ADOCommand.CommandText:= 'Procedure';
ADOCommand.CommandType:= adCmdStoredProc;
Result:= True;
except
begin
if not (VarIsEmpty(ADODBConnection)) then
begin
ADODBConnection.Close;
ADODBConnection:= Unassigned;
end;
if not (VarIsEmpty(ADOCommand)) then
ADOCommand:= Unassigned;
if not (VarIsEmpty(ADORecordSet)) then
ADORecordSet:= Unassigned;
Result:= False;
end;
end;
end;
function ADOStop: Boolean;
begin
try
if not (VarIsEmpty(ADODBConnection)) then
ADODBConnection.Close;
ADODBConnection:= Unassigned;
ADORecordSet:= Unassigned;
ADOCommand:= Unassigned;
Result:= True;
except
Result:= False;
end;
end;
function ADOConfigParams(ParamName: string; ParamType, ParamIO,
ParamSize: integer; Val: variant; CanClear: Boolean): Boolean;
var
_W: integer;
DerivedParamSize: integer;
begin
try
case ParamType of
adInteger: DerivedParamSize:= 4;
adSingle : DerivedParamSize:= 4;
adDate : DerivedParamSize:= 8;
adBoolean: DerivedParamSize:= 1;
adVarBites: DerivedParamSize:= 1;
adUnsignedTinyInt: DerivedParamSize:= 1;
adDateTime: DerivedParamSize:= 8;
advarChar: DerivedParamSize:= ParamSize;
end;
if CanClear then
for _W:= 0 to (ADOCommand.parameters.count) - 1 do
ADOCommand.parameters.delete(0);
ADOCommand.parameters.append(
ADOCommand.createparameter(ParamName, ParamType, ParamIO,
DerivedParamSize, Val));
Result:= True;
except
Result:= False;
end;
end;
function ADOSetParamVal(ParamName: string; val: variant): Boolean;
begin
try
ADOCommand.Parameters[ParamName].Value := val;
Result:= True;
except
Result:= False;
end;
end;
function ADOGetParamVal(ParamName: string): Variant;
begin
try
Result:= ADOCommand.Parameters[ParamName].Value;
except
Result:= varEmpty;
end;
end;
function ADOExecute(procname: string): Boolean;
begin
try
ADOCommand.CommandText:= procname;
ADOCommand.CommandType:= adCmdStoredProc;
ADORecordSet:= ADOCommand.Execute;
Result:= True;
except
Result:= False;
end;
end;
function ADOGetValue(_Indece: integer): Variant;
begin
Result:= ADORecordSet.Fields.Item[_Indece].Value; {<- I thought it was here}
end;
end.
Managed to solve the problem this in my stored procedure. This function can retrieve the data returned.
function ADOGetParamVal (ParamName: string): Variant;
begin
try
Result:= ADOCommand.Parameters [ParamName] .Value;
except
Result:= varEmpty;
end;
end;
I am trying to retrieve accessible information from a standard VCL TEdit control. The get_accName() and Get_accDescription() methods return empty strings, but get_accValue() returns the text value entered into the TEdit.
I am just starting to try to understand the MSAA and I'm a bit lost at this point.
Does my TEdit need to have additional published properties that would be exposed to the MSA? If so would that necessitate creating a new component that descends from TEdit and adds the additional published properties such as "AccessibleName", "AccessibleDescription", etc... ?
Also, note, I have looked at the VTVirtualTrees component which is supposed to be accessible, but the MS Active Accessibility Object Inspector still does not see the AccessibleName published property even on that control.
At this point I am at a loss and would be grateful for any advice or help in this matter.
...
interface
uses
Winapi.Windows,
Winapi.Messages,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs,
Vcl.StdCtrls,
Vcl.ComCtrls,
Vcl.ExtCtrls,
oleacc;
const
WM_GETOBJECT = $003D; // Windows MSAA message identifier
OBJID_NATIVEOM = $FFFFFFF0;
type
TForm1 = class(TForm)
lblFirstName: TLabel;
edFirstName: TEdit;
panel1: TPanel;
btnGetAccInfo: TButton;
accInfoOutput: TEdit;
procedure btnGetAccInfoClick(Sender: TObject);
procedure edFirstNameChange(Sender: TObject);
private
{ Private declarations }
FFocusedAccessibleObj: IAccessible;
FvtChild: Variant;
FAccProperties: TStringList;
FAccName: string;
FAccDesc: string;
FAccValue: string;
procedure DoGetAccessibleObjectFromPoint(aPoint: TPoint);
public
{ Public declarations }
procedure BeforeDestruction; override;
property AccName: string read FAccName;
property AccDescription: string read FAccName;
property AccValue: string read FAccName;
end;
var
Form1: TForm1;
const
cCRLF = #13#10;
implementation
{$R *.dfm}
function AccessibleObjectFromPoint(ptScreen: TPoint;
out ppacc: IAccessible;
out pvarChildt: Variant): HRESULT; stdcall; external 'oleacc.dll' ;
{------------------------------------------------------------------------------}
procedure TForm1.BeforeDestruction;
begin
VarClear(FvtChild);
FFocusedAccessibleObj := nil;
end;
{------------------------------------------------------------------------------}
procedure TForm1.DoGetAccessibleObjectFromPoint(aPoint: TPoint);
var
pt: TPoint;
bsName: WideString;
bsDesc: WideString;
bsValue: WideString;
begin
if (SUCCEEDED(AccessibleObjectFromPoint(aPoint, FFocusedAccessibleObj, FvtChild))) then
try
// get_accName returns an empty string
bsName := '';
FFocusedAccessibleObj.get_accName(FvtChild, bsName);
FAccName := bsName;
FAccProperties.Add('Acc Name: ' + FAccName + ' | ' + cCRLF);
// Get_accDescription returns an empty string
bsDesc := '';
FFocusedAccessibleObj.Get_accDescription(FvtChild, bsDesc);
FAccDesc := bsDesc;
FAccProperties.Add('Acc Description: ' + FAccDesc + ' | ' + cCRLF);
// this works
bsValue := '';
FFocusedAccessibleObj.get_accValue(FvtChild, bsValue);
FAccValue := bsValue;
FAccProperties.Add('Acc Value: ' + FAccValue + cCRLF);
finally
VarClear(FvtChild);
FFocusedAccessibleObj := nil ;
end;
end;
{------------------------------------------------------------------------------}
procedure TForm1.btnGetAccInfoClick(Sender: TObject);
begin
FAccProperties := TStringList.Create;
DoGetAccessibleObjectFromPoint(edFirstName.ClientOrigin);
accInfoOutput.Text := FAccProperties.Text;
end;
end.
The VCL itself does not natively implement any support for MSAA. Windows provides default implementations for standard UI controls, which many standard VCL components wrap. If you need more MSAA support than Windows provides, you will have to implement the IAccessible interface yourself, and then have your control respond to the WM_GETOBJECT message so it can return a pointer to an instance of your implementation.
Update: For example, one way to add MSAA to an existing TEdit (if you do not want to derive your own component) might look something like this:
uses
..., oleacc;
type
TMyAccessibleEdit = class(TInterfacedObject, IAccessible)
private
fEdit: TEdit;
fDefAcc: IAccessible;
public
constructor Create(aEdit: TEdit; aDefAcc: IAccessible);
function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
function Get_accParent(out ppdispParent: IDispatch): HResult; stdcall;
function Get_accChildCount(out pcountChildren: Integer): HResult; stdcall;
function Get_accChild(varChild: OleVariant; out ppdispChild: IDispatch): HResult; stdcall;
function Get_accName(varChild: OleVariant; out pszName: WideString): HResult; stdcall;
function Get_accValue(varChild: OleVariant; out pszValue: WideString): HResult; stdcall;
function Get_accDescription(varChild: OleVariant; out pszDescription: WideString): HResult; stdcall;
function Get_accRole(varChild: OleVariant; out pvarRole: OleVariant): HResult; stdcall;
function Get_accState(varChild: OleVariant; out pvarState: OleVariant): HResult; stdcall;
function Get_accHelp(varChild: OleVariant; out pszHelp: WideString): HResult; stdcall;
function Get_accHelpTopic(out pszHelpFile: WideString; varChild: OleVariant; out pidTopic: Integer): HResult; stdcall;
function Get_accKeyboardShortcut(varChild: OleVariant; out pszKeyboardShortcut: WideString): HResult; stdcall;
function Get_accFocus(out pvarChild: OleVariant): HResult; stdcall;
function Get_accSelection(out pvarChildren: OleVariant): HResult; stdcall;
function Get_accDefaultAction(varChild: OleVariant; out pszDefaultAction: WideString): HResult; stdcall;
function accSelect(flagsSelect: Integer; varChild: OleVariant): HResult; stdcall;
function accLocation(out pxLeft: Integer; out pyTop: Integer; out pcxWidth: Integer; out pcyHeight: Integer; varChild: OleVariant): HResult; stdcall;
function accNavigate(navDir: Integer; varStart: OleVariant; out pvarEndUpAt: OleVariant): HResult; stdcall;
function accHitTest(xLeft: Integer; yTop: Integer; out pvarChild: OleVariant): HResult; stdcall;
function accDoDefaultAction(varChild: OleVariant): HResult; stdcall;
function Set_accName(varChild: OleVariant; const pszName: WideString): HResult; stdcall;
function Set_accValue(varChild: OleVariant; const pszValue: WideString): HResult; stdcall;
end;
constructor TMyAccessibleEdit.Create(aEdit: TEdit; aDefAcc: IAccessible);
begin
inherited Create;
fEdit := aEdit;
fDefAcc := aDefAcc;
end;
function TMyAccessibleEdit.QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
begin
if IID = IID_IAccessible then
Result := inherited QueryInterface(IID, Obj)
else
Result := fDefAcc.QueryInterface(IID, Obj);
end;
function TMyAccessibleEdit.GetTypeInfoCount(out Count: Integer): HResult; stdcall;
begin
Result := fDefAcc.GetTypeInfoCount(Count);
end;
function TMyAccessibleEdit.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
begin
Result := fDefAcc.GetTypeInfo(Index, LocaleID, TypeInfo);
end;
function TMyAccessibleEdit.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
begin
Result := fDefAcc.GetIDsOfNames(IID, Names, NameCount, LocaleID, DispIDs);
end;
function TMyAccessibleEdit.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
begin
Result := fDefAcc.Invoke(DispID, IID, LocaleID, Flags, Params, VarResult, ExcepInfo, ArgErr);
end;
function TMyAccessibleEdit.Get_accParent(out ppdispParent: IDispatch): HResult; stdcall;
begin
Result := fDefAcc.Get_accParent(ppdispParent);
end;
function TMyAccessibleEdit.Get_accChildCount(out pcountChildren: Integer): HResult; stdcall;
begin
Result := fDefAcc.Get_accChildCount(pcountChildren);
end;
function TMyAccessibleEdit.Get_accChild(varChild: OleVariant; out ppdispChild: IDispatch): HResult; stdcall;
begin
Result := fDefAcc.Get_accChild(varChild, ppdispChild);
end;
function TMyAccessibleEdit.Get_accName(varChild: OleVariant; out pszName: WideString): HResult; stdcall;
begin
Result := fDefAcc.Get_accName(varChild, pszName);
if (Result = S_OK) and (pszName <> '') then Exit;
if Integer(varChild) = CHILDID_SELF then begin
pszName := fEdit.Name;
Result := S_OK;
end else
Result := S_FALSE;
end;
function TMyAccessibleEdit.Get_accValue(varChild: OleVariant; out pszValue: WideString): HResult; stdcall;
begin
Result := fDefAcc.Get_accValue(varChild, pszValue);
end;
function TMyAccessibleEdit.Get_accDescription(varChild: OleVariant; out pszDescription: WideString): HResult; stdcall;
begin
Result := fDefAcc.Get_accDescription(varChild, pszDescription);
if (Result = S_OK) and (pszDescription <> '') then Exit;
if Integer(varChild) = CHILDID_SELF then begin
pszDescription := fEdit.Hint;
Result := S_OK;
end else
Result := S_FALSE;
end;
function TMyAccessibleEdit.Get_accRole(varChild: OleVariant; out pvarRole: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.Get_accRole(varChild, pvarRole);
end;
function TMyAccessibleEdit.Get_accState(varChild: OleVariant; out pvarState: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.Get_accState(varChild, pvarState);
end;
function TMyAccessibleEdit.Get_accHelp(varChild: OleVariant; out pszHelp: WideString): HResult; stdcall;
begin
Result := fDefAcc.Get_accHelp(varChild, pszHelp);
end;
function TMyAccessibleEdit.Get_accHelpTopic(out pszHelpFile: WideString; varChild: OleVariant; out pidTopic: Integer): HResult; stdcall;
begin
Result := fDefAcc.Get_accHelpTopic(pszHelpFile, varChild, pidTopic);
end;
function TMyAccessibleEdit.Get_accKeyboardShortcut(varChild: OleVariant; out pszKeyboardShortcut: WideString): HResult; stdcall;
begin
Result := fDefAcc.Get_accKeyboardShortcut(varChild, pszKeyboardShortcut);
end;
function TMyAccessibleEdit.Get_accFocus(out pvarChild: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.Get_accFocus(pvarChild);
end;
function TMyAccessibleEdit.Get_accSelection(out pvarChildren: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.Get_accSelection(pvarChildren);
end;
function TMyAccessibleEdit.Get_accDefaultAction(varChild: OleVariant; out pszDefaultAction: WideString): HResult; stdcall;
begin
Result := fDefAcc.Get_accDefaultAction(varChild, pszDefaultAction);
end;
function TMyAccessibleEdit.accSelect(flagsSelect: Integer; varChild: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.accSelect(flagsSelect, varChild);
end;
function TMyAccessibleEdit.accLocation(out pxLeft: Integer; out pyTop: Integer; out pcxWidth: Integer; out pcyHeight: Integer; varChild: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild);
end;
function TMyAccessibleEdit.accNavigate(navDir: Integer; varStart: OleVariant; out pvarEndUpAt: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.accNavigate(navDir, varStart, pvarEndUpAt);
end;
function TMyAccessibleEdit.accHitTest(xLeft: Integer; yTop: Integer; out pvarChild: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.accHitTest(xLeft, yTop, pvarChild);
end;
function TMyAccessibleEdit.accDoDefaultAction(varChild: OleVariant): HResult; stdcall;
begin
Result := fDefAcc.accDoDefaultAction(varChild);
end;
function TMyAccessibleEdit.Set_accName(varChild: OleVariant; const pszName: WideString): HResult; stdcall;
begin
Result := fDefAcc.Set_accName(varChild, pszName);
end;
function TMyAccessibleEdit.Set_accValue(varChild: OleVariant; const pszValue: WideString): HResult; stdcall;
begin
Result := fDefAcc.Set_accValue(varChild, pszValue);
end;
type
TMyForm = class(TForm)
procedure FormCreate(Sender: TObject);
...
private
DefEditWndProc: TWndMethod;
procedure EditWndProc(var Message: TMessage);
...
end;
procedure TMyForm.FormCreate(Sender: TObject);
begin
DefEditWndProc := Edit1.WindowProc;
Edit1.WindowProc := EditWndProc;
end;
procedure TMyForm.EditWndProc(var Message: TMessage);
var
DefAcc, MyAcc: IAccessible;
Ret: LRESULT;
begin
DefEditWndProc(Message);
if (Message.Msg = WM_GETOBJECT) and (DWORD(Message.LParam) = OBJID_CLIENT) and (Message.Result > 0) then
begin
if ObjectFromLresult(Message.Result, IAccessible, Message.WParam, DefAcc) = S_OK then
begin
MyAcc := TMyAccessibleEdit.Create(Edit1, DefAcc) as IAccessible;
Message.Result := LresultFromObject(IAccessible, Message.WParam, MyAcc);
end;
end;
end;
I was able to get this working via
unit mainAcc;
interface
uses
Winapi.Windows,
Winapi.Messages,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs,
Vcl.StdCtrls,
Vcl.ComCtrls,
Vcl.ExtCtrls,
oleacc;
type
TForm1 = class(TForm)
lblFirstName: TLabel;
btnGetAccInfo: TButton;
accInfoOutput: TEdit;
procedure btnGetAccInfoClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
aEdit: TTWEdit;
FAccProperties: TStringList;
public
{ Public declarations }
end;
TAccessibleEdit = class(TEdit, IAccessible)
private
FOwner: TComponent;
FAccessibleItem: IAccessible;
FAccessibleName: string;
FAccessibleDescription: string;
procedure WMGetMSAAObject(var Message : TMessage); message WM_GETOBJECT;
// IAccessible
function Get_accParent(out ppdispParent: IDispatch): HResult; stdcall;
function Get_accChildCount(out pcountChildren: Integer): HResult; stdcall;
function Get_accChild(varChild: OleVariant; out ppdispChild: IDispatch): HResult; stdcall;
function Get_accName(varChild: OleVariant; out pszName: WideString): HResult; stdcall;
function Get_accValue(varChild: OleVariant; out pszValue: WideString): HResult; stdcall;
function Get_accDescription(varChild: OleVariant; out pszDescription: WideString): HResult; stdcall;
function Get_accRole(varChild: OleVariant; out pvarRole: OleVariant): HResult; stdcall;
function Get_accState(varChild: OleVariant; out pvarState: OleVariant): HResult; stdcall;
function Get_accHelp(varChild: OleVariant; out pszHelp: WideString): HResult; stdcall;
function Get_accHelpTopic(out pszHelpFile: WideString; varChild: OleVariant;
out pidTopic: Integer): HResult; stdcall;
function Get_accKeyboardShortcut(varChild: OleVariant; out pszKeyboardShortcut: WideString): HResult; stdcall;
function Get_accFocus(out pvarChild: OleVariant): HResult; stdcall;
function Get_accSelection(out pvarChildren: OleVariant): HResult; stdcall;
function Get_accDefaultAction(varChild: OleVariant; out pszDefaultAction: WideString): HResult; stdcall;
function accSelect(flagsSelect: Integer; varChild: OleVariant): HResult; stdcall;
function accLocation(out pxLeft: Integer; out pyTop: Integer; out pcxWidth: Integer;
out pcyHeight: Integer; varChild: OleVariant): HResult; stdcall;
function accNavigate(navDir: Integer; varStart: OleVariant; out pvarEndUpAt: OleVariant): HResult; stdcall;
function accHitTest(xLeft: Integer; yTop: Integer; out pvarChild: OleVariant): HResult; stdcall;
function accDoDefaultAction(varChild: OleVariant): HResult; stdcall;
function Set_accName(varChild: OleVariant; const pszName: WideString): HResult; stdcall;
function Set_accValue(varChild: OleVariant; const pszValue: WideString): HResult; stdcall;
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; override;
public
constructor Create(AOwner: TComponent); override;
published
property AccessibleItem: IAccessible read FAccessibleItem write FAccessibleItem;
property AccessibleName: string read FAccessibleName write FAccessibleName;
property AccessibleDescription: string read FAccessibleDescription write FAccessibleDescription;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{------------------------------------------------------------------------------}
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
inherited;
FreeAndNil(aEdit);
end;
{------------------------------------------------------------------------------}
procedure TForm1.FormCreate(Sender: TObject);
begin
aEdit := TAccessibleEdit.Create(self);
aEdit.Visible := true;
aEdit.Parent := Form1;
aEdit.Left := 91;
aEdit.Top := 17;
aEdit.Height := 21;
aEdit.Width := 204;
aEdit.Hint := 'This is a custom accessible edit control hint';
end;
{------------------------------------------------------------------------------}
procedure TForm1.btnGetAccInfoClick(Sender: TObject);
var
vWSTemp: WideString;
vAccObj: IAccessible;
begin
FAccProperties := TStringList.Create;
if (AccessibleObjectFromWindow(aEdit.Handle, OBJID_CLIENT, IID_IAccessible, vAccObj) = S_OK) then
begin
vAccObj.Get_accName(CHILDID_SELF, vWSTemp);
FAccProperties.Add('Name: ' + vWSTemp);
vWSTemp := '';
vAccObj.Get_accDescription(CHILDID_SELF, vWSTemp);
FAccProperties.Add('Description: ' + vWSTemp);
vWSTemp := '';
vAccObj.Get_accValue(CHILDID_SELF, vWSTemp);
FAccProperties.Add('Value: ' + vWSTemp);
end;
accInfoOutput.Text := FAccProperties.Text;
end;
{ TAccessibleEdit }
{------------------------------------------------------------------------------}
constructor TAccessibleEdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FOwner := AOwner;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accDoDefaultAction(varChild: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accHitTest(xLeft, yTop: Integer;
out pvarChild: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accLocation(out pxLeft, pyTop, pcxWidth, pcyHeight: Integer;
varChild: OleVariant): HResult;
var
P: TPoint;
begin
Result := S_FALSE;
pxLeft := 0;
pyTop := 0;
pcxWidth := 0;
pcyHeight := 0;
if varChild = CHILDID_SELF then
begin
P := self.ClientToScreen(self.ClientRect.TopLeft);
pxLeft := P.X;
pyTop := P.Y;
pcxWidth := self.Width;
pcyHeight := self.Height;
Result := S_OK;
end
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accNavigate(navDir: Integer; varStart: OleVariant;
out pvarEndUpAt: OleVariant): HResult;
begin
result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accSelect(flagsSelect: Integer; varChild: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accChild(varChild: OleVariant;
out ppdispChild: IDispatch): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accChildCount(out pcountChildren: Integer): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accDefaultAction(varChild: OleVariant;
out pszDefaultAction: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accDescription(varChild: OleVariant;
out pszDescription: WideString): HResult;
begin
pszDescription := '';
result := S_FALSE;
if varChild = CHILDID_SELF then
begin
pszDescription := 'TAccessibleEdit_AccessibleDescription';
Result := S_OK;
end;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accFocus(out pvarChild: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accHelp(varChild: OleVariant;
out pszHelp: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accHelpTopic(out pszHelpFile: WideString;
varChild: OleVariant; out pidTopic: Integer): HResult;
begin
pszHelpFile := '';
pidTopic := 0;
Result := S_FALSE;
if varChild = CHILDID_SELF then
begin
pszHelpFile := '';
pidTopic := self.HelpContext;
Result := S_OK;
end;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accKeyboardShortcut(varChild: OleVariant;
out pszKeyboardShortcut: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accName(varChild: OleVariant; out pszName: WideString): HResult;
begin
pszName := '';
Result := S_FALSE;
if varChild = CHILDID_SELF then
begin
pszName := 'TAccessibleEdit_AccessibleName';
result := S_OK;
end;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accParent(out ppdispParent: IDispatch): HResult;
begin
ppdispParent := nil;
result := AccessibleObjectFromWindow(self.ParentWindow, CHILDID_SELF, IID_IAccessible, Pointer(ppDispParent));
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accRole(varChild: OleVariant;
out pvarRole: OleVariant): HResult;
begin
Result := S_OK;
if varChild = CHILDID_SELF then
pvarRole := ROLE_SYSTEM_OUTLINE;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accSelection(out pvarChildren: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accState(varChild: OleVariant;
out pvarState: OleVariant): HResult;
begin
Result := S_OK;
if varChild = CHILDID_SELF then
pvarState := STATE_SYSTEM_FOCUSED;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accValue(varChild: OleVariant;
out pszValue: WideString): HResult;
begin
pszValue := '';
Result := S_FALSE;
if varChild = CHILDID_SELF then
begin
pszValue := WideString(self.Text);
result := S_OK;
end;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Set_accName(varChild: OleVariant;
const pszName: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Set_accValue(varChild: OleVariant;
const pszValue: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
procedure TAccessibleEdit.WMGetMSAAObject(var Message : TMessage);
begin
if (Message.Msg = WM_GETOBJECT) then
begin
QueryInterface(IID_IAccessible, FAccessibleItem);
Message.Result := LresultFromObject(IID_IAccessible, Message.WParam, FAccessibleItem);
end
else
Message.Result := DefWindowProc(Handle, Message.Msg, Message.WParam, Message.LParam);
end;
end.
end.