I have the following code to open my Excel Worksheet, using TADOConnection and TADOQuery:
conExcel.ConnectionString := 'Provider=Microsoft.JET.OLEDB.4.0;Data Source="D:\temp\file.xls";Extended Properties="Excel 8.0;HDR=No"';
conExcel.Connected := true;
When I run the following code:
queryExcel1.SQL.Text := 'SELECT * FROM [Hoja1$]';
queryExcel1.Open;
while not queryExcel1.eof do
begin
showmessage(queryExcel1.Fields.Fields[0].AsString);
queryExcel1.Next;
end;
I get each line of the file, so it is working ok, but I want to update a cell.
I am using this code:
queryExcel2.SQL.Text := 'UPDATE [Hoja1$] SET F1 = 555555';
queryExcel2.ExecSQL;
Which somehow updates every "A" cell on the worksheet to 555555, but what I really want is to just set the A1 cell to 555555. Any hints on how to include the WHERE section of the query. Thanks.
If you want to use an Adoquery you will have to set ParamCheck to false, since you will have to use : for the range you want to update.
An other option would be to use the connection directly.
In the example below the range B2:B2 is used with F1 which is the default name of Jet for the first column. (B2 is the cell we want to update)
Q.SQL.Text := 'UPDATE [Hoja1$B2:B2] SET [F1] = 555555';
Q.ExecSQL;
// or:
//AdoConnection1.Execute('UPDATE [Hoja1$B2:B2] SET [F1] = 555555');
Related
Seems pretty straightforward, but am not having any success.
Pretty simple, using AHK, I want to get the number of the last row in a worksheet with a value in it, going from the bottom up. I can't go top down because some rows are blank, so has to be bottom up.
My code loops through all files in a selected folder (which only contain excel files), opens them, then tries to set the print area based on how many rows are used in the file. If it's <= 43 rows, set the print area to 43rd row, if it's more, set the print area to that row.
My code for test.ahk looks like this:
FileSelectFolder, WhichFolder ; Ask the user to pick a folder.
Loop, %WhichFolder%\*.*
{
Xl := ComObjActive("Excel.Application")
Xl.Workbooks.open(A_LoopFileFullPath) ; open workbook
Xl.Visible := 1 ; make Excel Application visible
Xl.DisplayAlerts := False
; First check if it's the dumb file that opens first, not part of the folder
if xl.ActiveWorkbook.Name = "test.ahk" {
xl.Quit()
continue
}
; Wait for the workbook to open
Sleep, 2000
; Always start on the first worksheet
Xl.Worksheets(1).Activate
; Now find the last used row for the print area
; If the last used row is on page 1, set it to the 43rd row
lastRow := xl.ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row
;MsgBox, %lastRow%
; Set the print area
if lastRow <= 43 {
xl.ActiveSheet.PageSetup.PrintArea := "A1:K43"
else {
xl.ActiveSheet.PageSetup.PrintArea := "A1:K"lastRow
}
; Wait to close the script if you want for testing
Sleep, 20000
; Close and save the workbook
xl.ActiveWorkbook.Close(1)
Xl.DisplayAlerts := True
}
ExitApp
...and I'm getting this error:
I've tried lots of other combinations of trying to get the last row with a value in it too, but have had no success. Any help would be greatly appreciated. Thanks.
UDPATE!
Found a working formula thanks to the help above:
xlUp := -4162
BottomRow := xl.Sheets(1).Rows.Count
LastRow1 := xl.Sheets(1).Range("B" BottomRow).End(xlUp).Row
Delphi Tokyo - I have a Delphi app which is working against an Excel spreadsheet. I have a source sheet... called RawData. I need to copy specific rows (based on a filter) to another worksheet in the same workbook (called ActivitySheet). While I can get this to work... I am getting an Excel error message at the end of the process with the message 'The picture is too large and will be truncated.' This makes me believe that the command I am using is using the Clipboard, so I don't want to do that.
Here is what I am doing now...
var
ActivitySheet : _WorkSheet;
destRange : OleVariant;
begin
oExcel.Worksheets.Add(EmptyParam, EmptyParam, 1, xlWorksheet, LOCALE_USER_DEFAULT);
ActivitySheet:= oExcel.ActiveSheet as _Worksheet;
ActivitySheet.Name := 'Activity';
destRange := ActivitySheet.Range['A1', 'A1'];
// Set RawData Sheet as active
RawDataSheet.Activate(LOCALE_USER_DEFAULT);
Application.ProcessMessages;
// Now add the both filters that we want... (Not real filters, but easier to understand)
RawDataSheet.UsedRange[LOCALE_USER_DEFAULT].AutoFilter(1, '=*Blue*', xlOr, '=*Red*', True );
RawDataSheet.UsedRange[LOCALE_USER_DEFAULT].AutoFilter(2, '=Car', xlOr, '=Truck', True );
ShowMessage(IntToStr(RawDataSheet.UsedRange[LOCALE_USER_DEFAULT].Rows.Count));
RawDataSheet.UsedRange[LOCALE_USER_DEFAULT].Copy(destRange);
...
While this code works, and only copies the rows that are valid based on the current filter, it gives me the error at the 'Save Spreadsheet' stage later in my code. I thought about using a Vararray, but the ShowMessage line shows me the TOTAL rows in RawSheet, not the Filtered rows...so I would have to iterate through all the rows in the sheet (as opposed to just filtered rows) and then determine if the row is valid...
What is the best way to copy a set of filtered rows to a new sheet?
When I first saw this q, I had no idea how to do what you are asking. After a number
of false starts, I came across this answer
Excel Filtering and Copying in VBA
which shows that it can be done very simply, if you know the exact "magic spell" to do it.
Here is the code I wrote. I have used late binding to Excel throughout because it
avoids having to spray references to LOCALE_USER_DEFAULT everywhere.
type
TDefaultForm = class(TForm)
[...]
public
vExcel,
vWB,
vRange,
vSheet,
vActivitySheet,
vRawDataSheet : OleVariant;
end;
[...]
procedure TDefaultForm.CopyFilteredRange;
var
i : Integer;
vRange : OleVariant;
vDestRange : OleVariant;
begin
vRange := vRawDataSheet.Range['A1', 'A100'];
vDestRange := vActivitySheet.Range['A1', 'A1'];
vRange.SpecialCells(xlCellTypeVisible).Copy(vDestRange);
end;
procedure TDefaultForm.TestFilter;
var
vRange : OleVariant;
begin
vExcel := CreateOleObject('Excel.Application');
vExcel.Visible := True;
vWB := vExcel.WorkBooks.Add;
vRawDataSheet := vWB.ActiveSheet;
vActivitySheet := vWB.Sheets[2];
vRange := vRawDataSheet.Range['A1', 'A100'];
vRange.Item[1, 1] := 'Vehicle';
vRange.Item[2, 1] := 'Car';
vRange.Item[3, 1] := 'Truck';
vRange.Item[4, 1] := 'Truck';
vRange.Item[5, 1] := 'Car';
vRange.Item[6, 1] := 'Truck';
vRange.Item[7, 1] := 'Truck';
vRawDataSheet.UsedRange.Select;
vRawDataSheet.UsedRange.AutoFilter(Field := 1, Criteria1 := 'Car');
CopyFilteredRange;
end;
Btw, in case you are not familiar with it, the way of passing the arguments in the call
vRawDataSheet.UsedRange.AutoFilter(Field := 1, Criteria1 := 'Car');
is a special Delphi syntax for late-binding which avoids the rigmarole of passing
all the arguments required for early binding.
Also btw, I got the "Picture too large ..." message, but it seems easy to avoid: just include ClipBrd in the Uses list, and call ClipBoard.Clear after copying the filtered rows. Ordinarily, I don't like messing with the Clipboard because it's a system-wide resource, but as a fast fix here, clearing it may be acceptable.
Delphi 10 / Seattle, with Excel 2013. I am writing a plugin (using AddIn Express) for Excel. One of the things that I need to do is create a series of Excel Pivot Tables/Pivot Charts. I have recorded macros within Excel, so I have VBA code to do what I want. My challenge is porting it to Delphi.
My code compiles, and when I step through it, the very last line gives me the error .. Method 'SetSourceData' not supported by Automation Object. FYI - XLApp is a variable point to the Excel Application.
procedure TMyTemplateForm.Pivot_TouchesByQuarter;
var
myPivotCache: OleVariant;
myActive_WB : OleVariant;
MyChart : OleVariant;
ChartSourceRange : OleVariant;
TabDestination : string;
begin
// Add the new Sheet
XLApp.Connect;
myActive_WB := XLApp.ActiveWorkbook;
XLApp.Worksheets.Add(EmptyParam, EmptyParam,1, xlWorksheet, LOCALE_USER_DEFAULT );
// Get a handle to the new sheet and set the Sheet Name
sheet_graph1 := XLApp.ActiveSheet;
sheet_graph1.Name := 'Graph1'; // CANNOT CONTAIN SPACES.. ?????
// Parameters: SourceType, SourceData, Version
// Doc at: https://msdn.microsoft.com/en-us/library/office/ff839430.aspx
myPivotCache := myActive_WB.PivotCaches.Create(xlDatabase,'Raw Data!R1C1:R1048576C36',xlPivotTableVersion15);
// Parameters: TableDestination, TableName, DefaultVersion
TabDestination := 'Graph1!R3C1';
myPivotCache.CreatePivotTable(TabDestination, 'PivotTable1',xlPivotTableVersion15);
// Select where we want this placed...
sheet_Graph1.Cells.Item[3, 1].Select;
// https://msdn.microsoft.com/en-us/library/office/jj228277.aspx
// Create the chart object
myChart := sheet_Graph1.Shapes.AddChart2(201, xlColumnClustered);
// Define the Range that is the source for the chart. This is the Pivot Table I created just above
ChartSourceRange := sheet_Graph1.Range['Graph1!$A$3:$C$20'];
// Tell the Pivot Chart to use the Chart Range
myChart.SetSourceData(ChartSourceRange);
end;
Why am I getting this error? As a related question, can I point my Chart Source to the PivotTable1 object? Right now, it is hard coded to specific cell locations, but depending on the data, my Pivot table can be bigger than from Row3 to Row20.
If it helps any, the VBA macro code (last 2 lines) is..
ActiveSheet.Shapes.AddChart2(201, xlColumnClustered).Select
ActiveChart.SetSourceData Source:=Range("Sheet1!$A$3:$C$20")
I found the answer. Instead of
// Tell the Pivot Chart to use the Chart Range
myChart.SetSourceData(ChartSourceRange)
the code should be
myChart.chart.SetSourceData(ChartSourceRange);
Delphi 10 / Seattle, Excel 2013. I am using Delphi to build an Excel plugin. This plugin will create a Pivot table. My challenge is setting the specific FUNCTION I want (Count vs Average vs Sum, etc). If I do NOT set a function, the default (COUNT) is used, and my code works fine, the pivot table is like it should be. If I try to change the function on a PivotField, then I get an error "Unable to set the Function property of the PivotField class." In searching the error, I have found 2 common causes. (1) The Pivot table has to have a default version of xlPivotTableVersion15, and (2) the PivotField orientation has to be set to xlDataField. I have done both, and when moused-over, the IDE shows the Integer values for both, so I know they are defined.
I have tried getting (as opposed to setting) the value of the PivotField.function. I get a similar error "Unable to get the Function property of the PivotField class". The column I am wanting to Sum is called '#Clicks'. The offending line of code is
myPivotTable.PivotFields('#Clicks').function := xlSum;
If I comment this line out, my routine runs fine, although I get the default COUNT function, instead of the SUM function that I want.
Any ideas appreciated. Note that when the line is commented out, and I run the code, I can then go into Excel, and go into the PivotField listings, and change the function from Count to Sum. Here is my full code.
procedure T_ZTemplateForm.TestPivotTable;
var
myPivotCache: OleVariant;
myPivotTable : OleVariant;
myActive_WorkBook : OleVariant;
TabDestination : string;
f1: OleVariant;
begin
// Add the new Sheet
XLApp.Connect; // XLApp is a global variable, pointing to the Excel instance
myActive_WorkBook := XLApp.ActiveWorkbook;
XLApp.Worksheets.Add(EmptyParam, EmptyParam,1, xlWorksheet, LOCALE_USER_DEFAULT );
// Get a handle to the new sheet and set the Sheet Name
sheet_graph1 := XLApp.ActiveSheet;
sheet_graph1.Name := 'Graph1';
// Create a Pivot Cache
myPivotCache := myActive_WorkBook.PivotCaches.Create(xlDatabase,'Raw Data!R1C1:R1048576C36',xlPivotTableVersion15);
// Create a Pivot table within the PivotCache
TabDestination := 'Graph1!R3C1';
myPivotTable := myPivotCache.CreatePivotTable(TabDestination, 'PivotTable1',xlPivotTableVersion15);
// Now start adding the fields...
f1 := myPivotTable.PivotFields('Fiscal Quarter');
f1.Orientation := xlRowField;
f1.Position := 1;
myPivotTable.PivotFields('#Clicks').orientation := xlDataField;
// myPivotTable.PivotFields('#Clicks').function := xlSum;
end;
Update I can reproduce your problems with Set_Function and GetFunction using Seattle and Excel 2007, so please disregard the original version of my answer.
However, I have found a way to use CreateDataField to create a PivotField with a Function of xlCount, and it is very simple.
Given local variables
var
DataField : OleVariant;
Value : OleVariant;
the following code executes without complaint and correctly
Value := xlCount;
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
vPivotField,
'Count',
Value
);
whereas
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
vPivotField,
'Count',
xlCount
);
fails with the error message you quoted. So I can only guess that when AddDataField is called with the "raw" value xlCount, the Function_ argument the compiler generates is somehow incorrectly "packaged" whereas when the argument is an OleVariant containing the xlCount value, it is correctly packaged.
I'll leave you to try out the other XlConsolidationFunction values - I've had enough of this problem for now!
Original Answer: Judging by my experiments, you can change the Excel 'function' you want to use without specifying the final parameter (in your case xlSum) of AddDataField. In fact with Seattle and Excel2007, I can't get AddDataField to execute without getting a 'The parameter is incorrect' exception for any value of the final, 'function' parameter.
I have a WorkBook with a table of Company names, dividend payment dates and amounts. The table headers are Company, PaymentDate and Amount.
The code below works for me and allows me to choose the function to be applied to the Amount column, simply by specifying the name of the function as the Caption parameter of AddDataField. I've used late binding mainly for ease of set-up, and so that I can easily omit arguments for parameters I don't want to specify.
Code:
procedure TForm1.TestPivotTable;
var
vXLApp : OleVariant;
PivotCache: OleVariant;
PivotTable : OleVariant;
ActiveWorkBook : OleVariant;
DestinationSheet : OleVariant;
FN : String;
Destination : OleVariant;
PivotField : OleVariant;
DataField : OleVariant;
begin
vXLApp := CreateOleObject('Excel.Application');
vXLApp.Visible := True;
FN := 'D:\aaad7\officeauto\MAPivot.xlsm';
Assert(FileExists(FN));
vXLApp.Workbooks.Open(FN);
ActiveWorkBook := vXLApp.ActiveWorkbook;
PivotCache := ActiveWorkbook.PivotCaches.Create(SourceType := xlDatabase,
SourceData := 'Table1', //'R2C1R30C3',
Version:=xlPivotTableVersion2000);
DestinationSheet := ActiveWorkBook.Sheets['Sheet3'];
Destination := DestinationSheet.Name + '!R3C1';
PivotTable := PivotCache.CreatePivotTable(TableDestination := Destination,
TableName := 'APivotTable'
);
DestinationSheet.Select;
DestinationSheet.Cells[3, 1].Select;
DestinationSheet.PivotTables('APivotTable').PivotFields('Company').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('Company').Position := 1;
DestinationSheet.PivotTables('APivotTable').PivotFields('PayDate').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('PayDate').Position := 2;
DestinationSheet.PivotTables('APivotTable').PivotFields('Amount').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('Amount').Position := 3;
PivotField := DestinationSheet.PivotTables('APivotTable').PivotFields('Amount');
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
Field := PivotField,
Caption := 'Sum');
end;
The simplest way to create a handle to your currently active Excel sheets in your .ahk script is:
Xl := ComObjActive("Excel.Application")
Nonetheless, if one switches to another workbook, this becomes the new "currently active sheet" and AutoHotkey tries to use methods for sheets and cells on the new workbook through COM: of course scripts designed to work with specific sheets and cells don't work anymore on a different workbook.
Do you know how to create COM handles to specific workbooks instead of currently active sheet?
The goal should be to allow the user to loop between workbooks without Xl object losing its previous handle and going to a new one.
Example
Open an Excel workbook from scratch and type 1234 in cell A1 of sheet named "Sheet1"; then create a new .ahk script with the following content:
#Persistent
Xl := ComObjActive("Excel.Application")
SetTimer, xlRead, 5000
Return
xlRead:
{
value := Xl.Sheets("Sheets1").Range("A1").Value
MsgBox, %value%
}
Return
Script above should display "1234" in a message box every 5 seconds.
While that is running, open a new workbook and type 5678 in cell A1 of sheet named "Sheet1" and wait for 5 seconds: according to my trials, AutoHotkey should just switch the handle to the new active sheet and show a message box whose content is "5678".
Any way to keep it linked to the first sheet? Of course assume one can save Excel files to hard disk with proper names which COM can refer to.
one way is to store the active workbook object in a variable like this
#Persistent
oExcel := ComObjActive("Excel.Application")
this_book := oExcel.ActiveWorkbook
SetTimer, xlRead, 10000
Return
xlRead:
{
value := this_book.Sheets(1).Range("A1").Value
MsgBox, %value%
}
Return
2nd way is to use ComObjGet with the full name of the active workbook if you know it before hand no need for the ControlGetText command just use the workbooks full name
#Persistent
SetTitleMatchMode, 2
ControlGetText, WorkBookName, Excel71, Microsoft Excel
oWorkbook := ComObjGet(WorkBookName)
SetTimer, xlRead, 10000
Return
xlRead:
{
value := oWorkbook.Sheets(1).Range("A1").Value
MsgBox, %value%
}
Return
You can also use ComObjGet with the full path of an excel file it will return the workbook object
#Persistent
fileselectfile, path
oWorkbook := ComObjGet(path)
SetTimer, xlRead, 10000
Return
xlRead:
{
value := oWorkbook.Sheets(1).Range("A1").Value
MsgBox, %value%
}
Return
Hope this helps you do what you need