Delphi TComboBox Dropdown fields filtering [duplicate] - delphi

Everyone probably knows what I mean, but to clarify the control would need to:
Fire an event when user edits the text. The event would provide a SuggestionList: TStrings which you could fill with matches/suggestions.
if the SuggestionList is not empty a drop down should appear.
Unlike combo, the control should not attempt to automatically select/auto complete or otherwise affect the editing.
So, is there a Delphi edit/combo control that works like that ?

Use the autocompletion feature built in to all Windows edit controls.
First, fill your TStrings object however you want. Then use GetOleStrings to create a TStringsAdapter to wrap it. (The adapter does not claim ownership of the TStrings object, so you must make sure you don't destroy it while the adapter is still live.) The adapter gives you an IStrings interface, which you'll need because the autocompletion feature requires an IEnumString interface to provide the completion matches. Call _NewEnum for that.
Next, call CoCreateInstance to create an IAutoComplete object. Call its Init method to associate it with the window handle of your edit control. If you're using a combo box, then send it a cbem_GetEditControl message to find the underlying edit window.
You can stop at that point and autocompletion should work automatically. You can disable autocompletion if you want, or you can set any number of autocompletion options.
You say you don't want autocompletion, but in the OS terminology, I think what you really don't want is called auto append, where the remainder of the string is entered into the edit box automatically as the user types, but selected so that further typing will overwrite it, and the user needs to delete the excess text if the desired value is shorter than one of the matches.
There is also auto suggest, which displays a drop-down list of suggestions.
You can enable either or both options. You don't need to filter the list of suggestions yourself; the autocomplete object filters the IEnumString list by itself.

You can use standard TComboBox and faststrings library (for stringMatches() function).
procedure TForm1.cbChange(Sender: TObject);
var
s:Integer;
tmpstr:string;
begin
//suggestions: tstringlist
cb.AutoComplete:=false;
tmpstr:=cb.Text;
cb.Items.Clear;
for s:=0 to suggestions.Count - 1 do
if StringMatches(suggestions[s],cb.Text+'*') then
cb.Items.Add(suggestions[s]);
cb.DroppedDown:=(cb.Items.Count<>0) and (Length(cb.Text)<>0);
cb.Text:=tmpstr;
cb.SelStart:=Length(cb.Text)
end;

If you just want to show a file or url list:
SHAutoComplete(GetWindow(eb_MyComboBox->Handle, GW_CHILD), SHACF_AUTOSUGGEST_FORCE_ON | SHACF_FILESYS_DIRS);

I first implemented this feature like Rob described it in his answer. Later I saw that TComboBoxEx has the property AutoCompleteOptions where I set acoAutoSuggest to True and acoAutoAppend to False. The ComboBox now filters its item list when doing some entry and shows the matching items.
I'm using RAD Studio 10 Seattle and XE2 but don't know if this feature is available in older versions.

To the last bit of your question: "So, is there a Delphi edit/combo control that works like that ?":
A bit late to the party but yes, I have written a free and open source component that implements the Google Place Autocomplete and Google Place Details API's:
It does inherit from the standard TComboBox but you can modify the code to work with any TEdit
https://carbonsoft.co.za/components/
or
https://github.com/RynoCoetzee/TRCGPlaceAutoCompleteCombo

Related

How to refence a DBGrid from another form

I have a mainForm with a DBGrid and I have a second form with a CheckListBox that shows all of the DBGrid columns for the user to choose. I need to reference in Form2 the DBGrid that I have in MainForm.
I would like this second form to handle all of the procedures connected to the dbdgrid columns , so that I can reuse it easily.
That was the idea, but I dod'nt find the way to pass the DBGrid reference.
Is it possible ?
Answering the question you asked, on your Form2, define a property
TForm2
[...]
private
FGrid : TDBGrid
public
property Grid : TDBGrid read FGrid write FGrid;
Then, after you've created an instance of TForm2, just do
Form2.Grid := MainForm.DBGrid1;
Then, on Form2, you can do anything valid you like to change Grid and the changes will be made to MainForm.DBGrid1.
Is it possible?
The question should rather be Is there a better way to achieve what I want?
Would it be maintainable if Form2 worked basically with a control from a different form? What if other forms would also need to hold references to components on other forms?
How hard would it be in a year to find a bug if controls are used over different forms?
Would such a solution match to the SOLID principles?
Answering these questions should help you to look for a different approach.
You should consider to separate UI and business logic. A TDBGrid seems to be a convenient way to get data from a database into your application but it violates the Single Responsibility Principle since it loads and displays data at the same time. Don't use it as a basic data provider inside your application. Perform the SQL queries from a deeper UI independant layer of your software. Store the results in containers and display them in all the ways you want in your different forms.

Rearange TBindNavigator Buttons

I am using FireDac controls to connect with my db.
To navigate through the result sets i then use TBindNavigator.
This is a very handy component. However it lacks support for rearanging the buttons.
Here is an image of the control:
What i try to achieve is:
I downloaded the trial Version of TMS components and even their control (https://www.tmssoftware.com/site/dban.asp) isn't able to rearanche the buttons.
I am wondering if this is possible?
In order to achieve this, you need to create class inherited from dbnavigator, like e.g. :
type TNewDBNavigator = class(TDBNavigator);
It's needed because Buttons in TDBNavigator are protected.
Buttons are declared as:
Buttons: array[TNavigateBtn] of TNavButton;
And their order:
TNavigateBtn =
(nbFirst, nbPrior, nbNext, nbLast, nbInsert,
nbDelete, nbEdit, nbPost, nbCancel, nbRefresh);
If You had trouble with "hacking" VCL components, there is
nice article at delphi.about.com:
At this link
The other approach would be creating a set of custom buttons and modify their behaviur the way You want them to.
Best regards

dbctrlgrid delete and populate entries at runtime

I want to load the content of of dbctrlgrid at runtime (from database). So I encountered several challenges:
How to detect if the dbctrlgrid is empty and/or how to clear it.
How to put Tlabel and Tdbtext on the panel. The main problem seems to be to find the right parent. dbctrlgrid doesn't work. There is an object called Tdbctrlpanel which should work, but I don't know how to access it. I could not find it in properties or methods of Tdbctrlgrid.
Any code snipplet is welcome
To answer 1)
You don't query the TDBCtrlGrid, you query the underlying dataset; if it .IsEmpty the grid is empty.
When people start using data aware (grid) components they have the tendency to see that as the 'data container' that you can query and modify, but that is not the case. See it as a view on your underlying data with some built-in editors that modify that data. Then the 'same rules' apply to you as to these editors: update the underlying dataset.
To add controls to a TDBCtrlGrid you have to set the controls parent to the Panel property of the TDBCtrlGrid. The problem is that this property is protected. There are several ways to overcome this limitation. One is shown at Delphi About: Accessing protected members of a component
This is a common technique known to Delphi programmers as the 'protected hack'.

Is there a ComboBox that has Items like a TcxRadioGroup?

The TcxRadioGroup component of DevExpress has a very nice way to specify items. You can specify a Caption and a Value (and a Tag) for each TcxRadioGroupItem.
The TcxComboBox and the normal TComboBox of Delphi on the other hand use TStrings to store its items.
While TStrings can have a Name and an Object, there is no easy way to hook up a name and a value using the form designer of the Delphi IDE.
Is there a ComboBox control (preferably from DevExpress) that allows to visually design its items with a Caption and a Value?
PS: I'm not looking for a DB aware control.
Try a TcxImageComboBox. See here - you don't have to assign images despite the name. You can also edit the items visually.
(I use it as cell editor in cxGrids because of the separation Description/Value.)
Raize Components have TRzComboBox which introduces a Values property as an addition to the existing Items.
ESBPCS for VCL has an enhanced Lookup ComboBox. It stores 2 Lists, the ones normally in TCombobox's Items as well as the new Values list. These two StringLists are in a 1-1 relationship. Use AsItem to retrieve the string currently displayed and AsValue to retrieve the "related" string from Values.
Use a standard Delphi TComboBox, it can store a string (for visualization, and an object of any TObject descendant that you implement yourself, i.e you can store anything associated to a string in the dropdown).

What is the purpose of the 'Tag' property of Delphi VCL components?

Is there any specific purpose for the 'Tag' property of Delphi VCL components?
I have Googled a few examples using it as, for example, a 'color' property or using the value as a pointer address, but is it 'good practice' to use it, or is it considered 'bad practice' as it ties the program logic to the UI?
The "tag" property is there as a "cargo container" for whatever you might want to do with it.
Something it's often used for is in event handlers when you have a lot of similar components sharing one event handler. The event handler can find its caller and then query its tag value to get some more information about what it's supposed to be acting on.
EDIT:
Example: A calculator app might tag the number buttons with their respective numbers... silly and incomplete example, but you get the idea. The event handler could then pull the number to add into the display and accumulator right out of the tag instead of having to go figure out which button is meant to do what.
It is a place to add a piece of information to any component, even if you don't have the source for that component. It should be used carefully, because you can use it for only one purpose per component. For that reason Libraries should never use it.
I have some fundamental problems with the Tag property.
Well not exactly this property itself because it works as intended.
In general I consider using any universal/general/multi-purpose variables as a 'bad practice'.
They can be useful during debugging but are very harmful in production/mission critical environment.
They reduce code readability and understandability because nobody knows what a 'Tag' named attribute or property does. Of course you know why you are using this variable. But sooner or later you will forget (I know you will) and relying on this value makes anything more complicated.
This is why we should properly name every variable and property to help us understand what the code does.
Using Tag property is just a workaround/shortcut to avoid implementing understandable and well written code.
This is the PRACTICE and it is addictive.
Next time you need to store a new integer value bound to a component you will use the Tag property without considering any other way to store the desired values.
And storing a pointer in Tag property is a horrible idea: you have to cast this value every time you debug pointers.
Tell me: how many times did you find yourself in a situation where you wanted to store a new value in the Tag property but you realized this property is already used for a different purpose (if only there would be a 'Tag2' property in every component...).
As others have said, it's a place to put anything. Typically this comes in handy when associating two objects via an object reference or pointer. The tag happens to be perfectly sized to hold a pointer, so if you need to, say, keep an object tied to an item in a listbox, it becomes pretty straightforward.
Also it can be used for grouping purposes, say you'd want to access all components with a specific tag value, regardless of the component's type.
It's great! A freebie. I use it all the time to store one additional piece of information associated with the object.
Often I store a pointer to an associated data structure, or sometimes an integer which may be an index into some other array.
You can use it as a counter for times the object is accessed, or whatever.
The only downside is if your program uses lots of memory and you have millions of objects, those 4 bytes for each tag add up, especially if you're not using it. In that case, for your most prolific object type, you may want to create your own version without the tag.
You have 2 buttons on your form, on one you set the Tag = 1, and the other one Tag = 2. Now you assign the same OnClick event to both buttons and writhe the code like this:
procedure TForm28.Button1Click(Sender: TObject);
begin
case (Sender as TButton).Tag of
1: Caption := 'you pressed button 1';
2: Caption := 'you pressed button 2';
end;
end;
or more compact:
procedure TForm28.Button1Click(Sender: TObject);
begin
Caption := 'you pressed button ' + IntToStr((Sender as TButton).Tag);
end;
Basically,Tag will let you identify what control fired the event. Think if you have a form with dynamically created buttons... a list with users from the database, and on each record you put a button "Delete User". In this situation you can't create an event for each button, you will create one event that will assigned to all the buttons... and you can put in the Tag the userid for example. That way when you implement the event to handle all the buttons, you'll know what user to delete.
I Use tags all the time.
here are some examples;
a simple sample: you have a notebook (Like a pagecontroll without tabs)
so you can define buttons as tabs and write
NoteBook.ActivePage := TButton(Sender).Tag;
A more complicated sample;
an integer can hold 16 bitwise bolleans;
I can then check the senders up to 16 conditions to decide how to continue the pricedure
If (BitCheck (Bit2,TButton(sender).tag=True) And BitCheck(bit12,TButton(Sender).Tag=False) Then
Begin
end;
If (BitCheck (Bit9,TButton(sender).tag=True) Or BitCheck(bit14,TButton(Sender).Tag=True) Then
Begin
end;
You Get the idea

Resources