Link to home
Create AccountLog in
Avatar of shawn857
shawn857

asked on

A component to allow user to specify multiple "constraints" ?

Hi, I'm making a program where the user can request and generate his own "custom" reports from a file of data. I'd like to be able to present the user a screen where he could specify three things (from dropdown list boxes or something)  - a specific datafield, then choose an arithmetic operator (ie. >=, <=, <>, etc), then a numeric value. So for example, he could specify something like:


Monthly_Sales     >=      15000

This would be one "constraint" for the report output. I'd like to allow my user to specify as many of these "constraints" as he wishes. So basically, he would specify one, then there would be a little button "Add another" which he could click and all the dropdown boxes and controls would appear again just below the previous "constraint" to allow him to specify a new one. Would there be some simple, efficient and elegant way of handling this? Some magic component?  :-)

Thanks!
   Shawn
Avatar of Sinisa Vuk
Sinisa Vuk
Flag of Croatia image

Simplest solution is to set filter on already opened query with whole data range.

So...open query ...
...
qry.Sql.Text := 'select field1, field2, .. from table1 ';
qry.Open;
...

Open in new window



..and filter out with your custom string where Monthly_Sales is a name of field in qry select statement.
sOperation := Combobox1.Text; //>=
sFilter := '(Monthly_Sales '+sOperation+edtValue.Text+')';
//add another one
sOperation := Combobox2.Text; //=
sFilter := sFilter + 'AND' + '(Customer '+sOperation+QuotedStr(edtCustomer.Text)+')';
qry.Filtered := False;
qry.Filter := sFilter;
qry.Filtered := True;

Open in new window


Another way is to make dynamic sql command using your filter request:
...
var
  sSQL: String;
begin
  sOperation := Combobox1.Text; //>=
  sFilter := '(Monthly_Sales '+sOperation+edtValue.Text+')';
  //add another one
  sOperation := Combobox2.Text; //=
  sFilter := sFilter + 'AND' + '(Customer '+sOperation+QuotedStr(edtCustomer.Text)+')';
  sSQL := 'select field1, field2, .. from table1 where '+sFilter;
  qry.Close;
  qry.Sql.Text := sSQL;
  qry.Open;
end;

Open in new window


If your user interface displays user friendly field names - then translate it to real field names before set filter.
Avatar of shawn857
shawn857

ASKER

Thanks Sinisav, but looks more complicated than need be. I've never worked with SQL before, and also, I'm not using a database as my data source. I'm just reading in a flat ascii file of records. Sometimes this data file can be very very huge, so I don't think creating a SQL database from it would be practical.
   Really, I already have a plan on how to read the datafile and create the report... what I need is an elegant interface with the user so he can efficiently specify his "constraints" (ie. rules for creating the report).

Thanks
   Shawn
ASKER CERTIFIED SOLUTION
Avatar of uaexpert
uaexpert

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
Thanks uaexpert, but as I said, I'm not using a database, just records in a flat ascii file. I simply need an elegant way to allow the user to input multiple sets of "constraints" as I described in my original message.

Thanks
   Shawn
If I remember it right you don't need to query a database with EasyQuery. You can use their visual component to allow the users to add constraints and then read the list of conditions and use this information to query your plain text file.
In the worst case you can take some ideas of possible UI from their screenshots and demos.
Thanks uaexpert, I had a more closer look at some of the screenshots there and it did help reinforce in me what I am looking for. Please see my very poorly drawn attachment. I guess what I'm looking for is a magical control or component that is kind of like a TListView with a grid, but within each "cell" is a Combobox, or an edit box, or a simple button. For example, referring to my drawing, in the "Field" column will be a combobox allowing the user to select his "field" (ie. First_name, Last_name, etc). Then in the next column "Compare", the user can choose the operation he wants to perform on that field (>=, <=, =, <>, etc). Then in the next column "Value" is an edt box where the user can simply type in his desired numeric value. Then in the next column "Delete" is simply a button that the user can click if he wishes to delete this whole row. To the right is a big button "Add Another" which when clicked will add a whole new row of these controls just below the previous row. In this manner, the user can specify an unlimited number of his "constraints".
   Does such an animal exist... or can be constructed?

Thanks!
   Shawn
illustration.JPG
According to your illustration I made simple example project. It uses TFrame component
and OnFilterRecord event of Tdataset. Before usage you must install great kbmMemTable
component: TkbmMem Table v.5.52
Example uses csv formatted ascii data file. Sample csv file is included too.
First load csv file, table is populated then and you can add new filter. I'm try to detect if
data entered in edit is numeric or string type and filter goes differently then....
csvfilter.zip
Thanks Sinisav, but here's the thing - the ascii data files my app will be reading could be absolutely HUGE... up to several gigabytes in size. There's no way I could load all that in the table at once... no?

Thanks
   Shawn
You have few gigabytes in ascii file!?!? There is no way to do this as that. You should import all data in one of database - few of them are free (sql express, firebird, mysql, ...). There are lots of tools which can import some ascii file into database. All other solutions will fail on speed too.
Yes the files could be that big Sinisav, so a database-type solution is of course not practical. But that's okay, I already know how I am going to pull the data and create my reports... that's not the problem here. All I'm needing is a user-interface "front-end" to efficiently obtain the "contraints" from my user - just as I described in my crude drawing.
    Basically, how to just dynamically (at runtime) add a "row" of controls (comboboxes, edit boxes, buttons,etc). So when the user clicks my "Add another" button, it will add a new row of controls for him. See what I mean?

Thanks!
    Shawn
fwiw,
i'd change the output to a database table instead of using that ascii file

for cases like this databases were invented in the first place
because of you answers i think you want just an idea how to do the UI for the query.

Wild guessing here:
You have already a way to enter one query:
That means you have three variables that can hold:
a specific datafield, an arithmetic operator and a numeric value

these three you could put together into a class:

type
  TMyDataClass = class(TObject)
    aDataField: TMyDataField;
    anOperator: TMyOperator;
    aNumericValue:integer;
  end;

Then you create your own UI Class:

  TMyDataClassUI = class(TObject)
  private
    aFieldDropDownBox: TCombobox;
    anOperatorDropDownBox: TCombobox;
    aNumericValueEditField:TEdit;
    aDeleteButton:TButton;
  public
    PanelForGrouping:TPanel;
    constructor create (aParent:TWinControl);
    procedure GetValues (var aData:TMyDataClass);
  end;

In the create of this class i would create the panel and add the other controls into it
(set their parent property to the panel). Then you can set the parent of the panel to the given parameter and show it whereever you like.

With the GetValues you translate the values of the comboboxes and editfields into the TMyDataClass - variable.
i.e.  
procedure GetValues (var aData:TMyDataClass);
begin
  // if you use strings for the DataField
  aData.DataField := aFieldDropDownBox.text;

  // otherwise you need an if and check for the itemindex
  if aFieldDropDownBox.itemindex = 0 then aData.DataField := First_name_constant;

  // or you can compare the text
  if aFieldDropDownBox.text = 'First Name' then aData.DataField := First_name_constant;

...
 
Then you create a TList.
Whenever you press AddAnother you create a new TMyDataClassUI object and add it into the TList. Later you walk through the TList and get all the Values you need for the query.


btw the deletebutton will have to do the delete with a timer.
its not safe to delete a control in its own onclick procedure. And dont forget to set parent and owner to nil before deleting.


hope  that helps
Why don't you try some ready-to-use solutions like Simple Query (http://devtools.korzh.com/query-builder-delphi/).

Here is a screenshot of their UI which is quite similar to what you would like to implement:
http://devtools.korzh.com/images/eq/sshot_eq4.gif

This particular example builds a filter for some DB table but I'm sure you can use that UI component (right top corner of screenshot) to generate your own query over your huge text file.
Geert_Gruwez - thanks for the response, but I know nothing about using databases in Delphi and I'm really trying to keep this as simple as possible.


Delphiexp - thanks for the suggestion, but uaexpert made this same suggestion earlier. I think it would be more hassle trying to dovetail a 3rd party solution into my project, than to make my own solution. Also, I wasn't counting on spending $130  :-)


Gandalf - yes, it's just the UI I'm looking for , you got it. I think you're on the right track with your proposal. So just to get it straight - when I click the "Add another", it will create a whole new row of controls placed on a Panel? And can I place this Panel into a TScrollbox? (I need to be able to allow the user to specify an unlimited number of constraints). Also, I'm not clear what you meant by doing the delete with a timer... I'm not sure how that would work.

Thanks to all who replied!
   Shawn
Oh sorry. I didn't notice that SimpleQuery I suggested and EasyQuery mentioned previously are actually the same solution, only for different platforms.
I dont know how much programming experience you have, so I just gave a raw explanation how I would do it.

The wincontrols in delphi have 2 properties that are important: owner and parent.
If the owner object is destroyed it will destroy all objects that are owned by it.
The parent object must be a wincontrol and in it the wincontrol will be shown.

I dont see an problem with setting the parent of the panel to the scrollbox.

something like:

procedure AddButtonClick (sender:TObject);
var NewLineOfDatas:TMyDataClassUI;
begin
  // create with aScrollbox as parent
  NewLineOfDatas := TMyDataClassUI.create (aScrollbox);

  //position at a lew Line  
  NewLineOfDatas.PanelForGrouping.left := 0;
  NewLineOfDatas.PanelForGrouping.top := NewLineOfDatas.PanelForGrouping.height*TListWithAllDatas.count + 10;
 
  // add to the TList with the other already created Datas
  TListWithAllDatas.add (NewLineOfDatas);
...


The timer thing is something that may happen to you depending how you actually implement it.
Imagine the following:
You have a button: myButton:TButton;

procedure myButtonClick (sender:TObject);
begin
  // destroy the button
  myButton.Free;
  // move the destroyed button
  myButton.left := 0;

Here you will get an error because the button doesnt exist anymore.

Thats what you eventually may have coded if you press the deleteButton. You will be in the OnClick procedure of the deleteButton and in there you delete the whole control, where this deleteButton resides in. Solution is just to start a timer in the OnClick procedure and the OnTimer method will do the destroy.

If I create UI-Elements myself I always take care of destroying them.
This means at the end of the Program I would go through the TList and destroy the Objects manually.

for index:=TListWithAllDatas.count down to 0 do
begin
  TMyDataClassUI(TListWithAllDatas [index]).parent := nil;
  TMyDataClassUI(TListWithAllDatas [index]).owner := nil;
  TMyDataClassUI(TListWithAllDatas [index]).free;
end;

If you dont set the parent to nil the parent control will try to free it when it is destroyed and you will get an error because the program tries to free an already destroyed object.


I hope that is not too much details / too much stuff you already know.
Thanks for the details on that Gandalf - let me make up a quick and dirty program to see if this will do what I need!

Thanks!
    Shawn
Sorry for the lonnnng delay on this. I found a perfect ready-to-use component to do exactly what I need: "NextGrid" from bergsoft.net.

Can I close up this question now?

Thanks!
   Shawn