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
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
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
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
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
ASKER
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
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.
In the worst case you can take some ideas of possible UI from their screenshots and demos.
ASKER
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
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
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
ASKER
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
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.
ASKER
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
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
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:TEd it;
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.iteminde x = 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
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:TEd
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.iteminde
// 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.
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.
ASKER
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
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:TMyDataClas sUI;
begin
// create with aScrollbox as parent
NewLineOfDatas := TMyDataClassUI.create (aScrollbox);
//position at a lew Line
NewLineOfDatas.PanelForGro uping.left := 0;
NewLineOfDatas.PanelForGro uping.top := NewLineOfDatas.PanelForGro uping.heig ht*TListWi thAllDatas .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.c ount down to 0 do
begin
TMyDataClassUI(TListWithAl lDatas [index]).parent := nil;
TMyDataClassUI(TListWithAl lDatas [index]).owner := nil;
TMyDataClassUI(TListWithAl lDatas [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.
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:TMyDataClas
begin
// create with aScrollbox as parent
NewLineOfDatas := TMyDataClassUI.create (aScrollbox);
//position at a lew Line
NewLineOfDatas.PanelForGro
NewLineOfDatas.PanelForGro
// 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.c
begin
TMyDataClassUI(TListWithAl
TMyDataClassUI(TListWithAl
TMyDataClassUI(TListWithAl
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.
ASKER
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
Thanks!
Shawn
ASKER
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
Can I close up this question now?
Thanks!
Shawn
So...open query ...
Open in new window
..and filter out with your custom string where Monthly_Sales is a name of field in qry select statement.
Open in new window
Another way is to make dynamic sql command using your filter request:
Open in new window
If your user interface displays user friendly field names - then translate it to real field names before set filter.