OLE automation - WORD tabels (Delphi) - delphi

I'm trying to make tables inside tables in WORD. of course in finall program it will be dinamical, which is not in this sample.
Here is my sample code.
var
aTable, bTable, cTable : OLEVariant;
begin
m_WordApplication := CreateOleObject('Word.Application') ;
m_WordDocument := m_WordApplication.Documents.Add;
aTable := m_WordDocument.Tables.Add(m_WordApplication.Selection.Range, 2, 1);
aTable.Borders.Item(wdBorderLeft).LineStyle:=wdLineStyleSingle;
aTable.Borders.Item(wdBorderRight).LineStyle:=wdLineStyleSingle;
aTable.Borders.Item(wdBorderTop).LineStyle:=wdLineStyleSingle;
aTable.Borders.Item(wdBorderBottom).LineStyle:=wdLineStyleSingle;
bTable := m_WordDocument.Tables.Add(aTable.Cell(1, 1).Range, 2, 1);
bTable.Borders.Item(wdBorderLeft).LineStyle:=wdLineStyleSingle;
bTable.Borders.Item(wdBorderRight).LineStyle:=wdLineStyleSingle;
bTable.Borders.Item(wdBorderTop).LineStyle:=wdLineStyleSingle;
bTable.Borders.Item(wdBorderBottom).LineStyle:=wdLineStyleSingle;
cTable := m_WordDocument.Tables.Add(aTable.Cell(2, 1).Range, 3, 1);
cTable.Borders.Item(wdBorderLeft).LineStyle:=wdLineStyleSingle;
cTable.Borders.Item(wdBorderRight).LineStyle:=wdLineStyleSingle;
cTable.Borders.Item(wdBorderTop).LineStyle:=wdLineStyleSingle;
cTable.Borders.Item(wdBorderBottom).LineStyle:=wdLineStyleSingle;
m_WordDocument.SaveAs('C:/test.doc', False) ;
m_WordApplication.Quit(False);
Firstly i put new table(2 rows, 1 column) on position of the cursor, and then i try to put second table in cell(1,1) and third in cell(2,1) of the first table. second table has also 2 rows and 1 column, but third table has 3 rows and 1 column. but instead of what i want i get second and third table whit only one row, regardless if i putt something in thier cell or not.i always see only the last string i put in that table.
even more, if i put 1 row and 2 column table inside first table, than everything is normal.
can you help me.
thanks, Rok

When you have problems creating those tables in code, do the following:
Open Word
record a new macro
While recording, build the table you want, then stop the recording.
View your macro code in the Visual Basic Editor and try to translate that to OLE-automation code (which isn't that hard, it's almost the same)

aTable.Borders.Item(wdBorderVertical).LineStyle:=wdLineStyleSingle;
aTable.Borders.Item(wdBorderHorizontal).LineStyle:=wdLineStyleSingle;
You will have to do the same for bTable & cTable.
When you add more than 1 row/column, it will need border to separate it (i.e to separate 1 row from another OR separate 1 column from another).
Hope this helps.

Related

Deleting a entry in a DBGrid with a button in Delphi

I'm relatively new to delphi and I would like to know how to delete an entry in a dbGrid without using a dbNavigator but a button. The number of the entry that should be deleted must be entered in a spinedit, not be clicked on in the dbGrid. Thanks for any help.
First it's nice to position in the first record of the DataSet, then it will delete from the first to the N record.
DBGrid1.DataSource.DataSet.First;
Now you create the Loop (Don't forget to create the variable {var I : integer})
For I:=0 to SpinEdit1.Value-1 Do
Before start deleting records, you will need to verify if there is any record on DataSet.
You can do something like this:
if DBGrid1.DataSource.DataSet.RecordCount > 0 then
And finally you can delete the record
DBGrid1.DataSource.DataSet.Delete;
The final code would be like this:
DBGrid1.DataSource.DataSet.First; //Set on the first Record of the DataSet
For I:=0 to SpinEdit1.Value-1 Do //Do loop
if DBGrid1.DataSource.DataSet.RecordCount > 0 then //Check if have records
DBGrid1.DataSource.DataSet.Delete; //Delete

TSQLQuery not returning right amount of records

I have a table called Artist which currently contains four records and a TSQLQuery that contains the following statement:
SELECT name
FROM Artist
The table Artist also contains the following records:
id name
1 Artist 1
2 Artist 2
3 Artist 3
4 Artist 4
Current method of record retrieval:
Query1.Open;
for i := 0 to qArtist.FieldCount -1 do
with cbArtist.ListBox.ListItems[i] do
Text := qArtist.Fields[i].AsString;
Previous method of record retrieval:
Data bind Query1 to ComboBox1.
With the "previous" method, ComboBox1 would display all the expected records from the Artist table. However, when I try to use "current" method Query1 is only selecting the very first record from the Artist table despite there being three other existing values. I have tried the "current" method across other queries and they also returned only the first value of the table.
The reason I am going for this new approach is because I feel that I am very limited in what I can do if I continue to the "previous" / data bind method, but that is besides the point.
So how can I fix this problem? i.e. the problem of the query only selecting the very first record from the table.
You must use the Eof and Next methods to iterate over the records.
Query1.Open;
while not Query1.eof do
begin
cbArtist.Items.Add(Query1.FieldByName('Artist').AsString);
Query1.Next;
end;
You code show an interaction over fields, if you need iterate all record then you must use a code like:
Query1.Open;
Query1.first;
while not Query1.eof do
begin
with cbArtist.ListBox.ListItems[i] do
Text := qArtist.Fields[1].AsString; //put here field you want to bind on ListBox.
Query1.next;
end;
I don't think you are navigating your query's dataset correctly. The FieldCount and Fields[i] access the field metadata (columns going across), not the rows. I beleive in Delphi you use While not Eof begin... end.
Navigating Datasets
I would consider altering the data binding fields to suit your needs. Delphi's Databinding is very powerful. Manually iterating the dataset just to populate a control will just be extra code where bugs can hide. Utilize the built-in capabilities of the tools and it will be easier to understand and maintain.

How to delete certain rows from Access via Delphi?

while not TBLOrder.Eof do
begin
TBLOrder.Locate('OrderID', Total, []);
TBLOrder.Delete;
end;
This just deletes every single row in my Access Database, which is really annoying.
I'm trying to get the program to delete the selected row (which is Total).
From what I understand, It should locate the selected row, which is equal to Total. e.g. If Total = 3 it should find the row where OrderID = 3 and then delete that row.
Any help is appreciated.
Try this instead (Max's routine requires you to loop through the entire dataset, which is fine unless it's got many rows in it):
while (TblOrder.Locate('OrderID', Total, [])) do
TblOrder.Delete;
TDataSet.Locate returns a Boolean; if it's True, the found record is made the active record and you can then delete it. If it returns False (meaning the record is not found), the call to Delete is never made.
BTW, the problem with your original code is that you test for Eof, but never check to see if the Locate finds the record; you just delete whatever record you're on, and then test for Eof again. If you're not at Eof, you call Locate, ignore whether or not it found the record, and delete whatever row you're on. This then repeats over and over again until there are no more records, at which point Eof returns true and you break the loop.
If there is just one row that contains an ORDERID equal to 3, you don't need the WHILE loop.
If you expect more than one row with an ORDERID equal to 3, do this:
TBLOrder.first; // you could also do the locate here if it's a big table
while not TBLOrder.Eof do
begin
if TBLOrder.FieldByName('OrderID').AsInteger = 3 then
TBLOrder.delete
else
TBLOrder.next;
end;
Otherwise, you could also use SQL.

TListView: Subitem order after adding a new column between existing during runtime

If you add a new column between existing columns during runtime, the subitem indices are not like I'ld assume.
e.g. after adding a new column between the second and third column the columns/subitems look like this:
colums[0] | colums[1] | (new) columns[2] | columns[3]
caption | subitems[0] | subitems[2] | subitems[1]
but i would assume:
colums[0] | colums[1] | (new) columns[2] | columns[3]
caption | subitems[0] | subitems[1] | subitem[2]
I need to be able to dynamically update the subitem's content under some conditions. That's why I would like to rely on the assumption, that the subitem for a column with Column.Index = X is at Item.SubItems[X-1].
Do you think this is a default and designated behavior? If so, what would you suggest for updating the subitems according to the columns. A possibility might be saving the subitem index that belongs to the recently added columns.
Note: The Columns.Tag-property is already in use.
I`m using Delphi XE and XE2, but I need to be compatible with Delphi 7 and higher.
You don't need to save index positions, you can always ask the list view control itself the columns' original position:
procedure TForm1.Button1Click(Sender: TObject);
var
ColumnOrder: array of Integer;
begin
SetLength(ColumnOrder, ListView1.Columns.Count);
ListView_GetColumnOrderArray(ListView1.Handle, ListView1.Columns.Count,
PInteger(ColumnOrder));
For the example in the question, the ColumnOrder array will hold (0, 1, 3, 2). If we want to update the subitem of the newly inserted column (3rd column from left), then its original position is '3'. Code example:
var
ColumnOrder: array of Integer;
SubIndex: Integer;
begin
SetLength(ColumnOrder, ListView1.Columns.Count);
ListView_GetColumnOrderArray(ListView1.Handle, ListView1.Columns.Count,
PInteger(ColumnOrder));
SubIndex := ColumnOrder[2]; // We want to update 3rd column from left
// (visually -> SubItems[1])
// Test if the index is not 0, otherwise it holds an *item*,
// not a subitem (the first column can change position too).
if SubIndex > 0 then begin
Dec(SubIndex); // VCL subitems are 0 based
ListView1.Items[1].SubItems[SubIndex] := 'updated!';
end;
Note that if you're adding columns and not just re-ordering existing ones, this will only work if you have a fix for the bug in the other question (then again if you don't, providing both column re-ordering and column adding functionality is not possible at all anyway).
Regarding if the default behavior is as it should be, suppose you have a list view which you're displaying file information having columns 'name', 'size', 'date'. As a developer you shouldn't be worrying about where a user might have put the 'size' column, just put the information to 'SubItems[0]'. Furhermore, what if the user drags the 'name' column, will it demote from being an item to a subitem.
I'd think it is only logical to expect that items/subitems would follow their respective columns..

Sorting a table physically in Delphi

Delphi does not seem to like multi-field indexes.
How do I physically sort a a table so that I wind up with a table that has the rows in the desired order?
Example:
mytable.dbf
Field Field-Name Field-Type Size
0 Payer Character 35
1 Payee Character 35
2 PayDate Date
3 Amount Currency
I need to produce a table sorted alphabetically by "Payee"+"Payer"
When I tried using an index of "Payee+Payer", I got an error:
"Field Index out of range"
The index field names need to be separated by semicolons, not plus symbols. Try that and it should work.
Ok, let's try to put some order.
First, isn't advisable to physically sort a table. In fact the most RDBMS even don't provide you this feature. Usually, one, in order to not force a full table scan (it is called sometimes natural scan) creates indexes on the table fields on which he thinks that the table will be sorted / searched.
As you see, the first step in order to sort a table is usually index creation. This is a separate step, it is done once, usually at, let's say, "design time". After this, the DB engine will take care to automatically update the indexes.
The index creation is done by you (the developer) using (usually) not Delphi (or any other development tool) but the admin tool of your RDBMS (the same tool which you used when you created your table).
If your 'DB engine' is, in fact, a Delphi memory dataset (TClientDataSet) then you will go to IndexDefs property, open it, add a new index and set the properties there accordingly. The interesting property in our discussion is Fields. Set it to Payee;Payer. Set also the Name to eg. "idxPayee". If you use other TDataSet descendant, consult the docs of your DB engine or ask another question here on SO.com providing the details.
Now, to use the index. (IOW, to sort the table, as you say). In your program (either at design time either at run time) set in your 'Table' the IndexName to "idxPayee" or any other valid name you gave or set IndexFieldNames to Payee;Payer.
Note once again that the above is an example based on TClientDataSet. What you must retain from the above (if you don't use it) is that you must have an already created index in order to use it.
Also, to answer at your question, yes, there are some 'table' types (TDataSet descendants in Delphi terminology) which support sorting, either via a Sort method (or the like) either via a SortFields property.
But nowadays usually when one works with a SQL backend, the preferred solution is to create the indexes using the corresponding admin tool and then issue (using Delphi) an SELECT * FROM myTable ORDER BY Field1.
HTH
If you're still using BDE you can use the BDE API to physically sort the DBF table:
uses
DbiProcs, DbiTypes, DBIErrs;
procedure SortTable(Table: TTable; const FieldNums: array of Word; CaseInsensitive: Boolean = False; Descending: Boolean = False);
var
DBHandle: hDBIDb;
RecordCount: Integer;
Order: SORTOrder;
begin
if Length(FieldNums) = 0 then
Exit;
Table.Open;
RecordCount := Table.RecordCount;
if RecordCount = 0 then
Exit;
DBHandle := Table.DBHandle;
Table.Close;
if Descending then
Order := sortDESCEND
else
Order := sortASCEND;
Check(DbiSortTable(DBHandle, PAnsiChar(Table.TableName), nil, nil, nil, nil, nil,
Length(FieldNums), #FieldNums[0], #CaseInsensitive, #Order, nil, False, nil, RecordCount));
end;
for example, in your case:
SortTable(Table1, [2, 1]); // sort by Payee, Payer
Cannot check, but try IndexFieldNames = "Payee, Payer".
Sure indexes by these 2 fields should exist.
You can create an index on your table using the TTable.AddIndex method in one call. That will sort your data when you read it, that is if you use the new index by setting the TTable.IndexName property to the new index. Here's an example:
xTable.AddIndex('NewIndex','Field1;Field2',[ixCaseInsensitive]);
xTable.IndexName := 'NewIndex';
// Read the table from top to bottom
xTable.First;
while not xTable.EOF do begin
..
xTable.Next;
end;

Resources