Delphi. How to get a pointer to a dynamically created object - pointers

I would like to access components via pointers instead of searching using FindComponent. The following assignment works for components placed on the Form, but not for dynamically created components. For example:
var
Pbtn: ^TButton;
Button2: TButton;
begin
Pbtn := #Button1;
Showmessage(pbtn.caption); // works well
Button2 := TButton.Create(Form2);
Pbtn := #Button2;
Showmessage(pbtn.caption); // not work
...

A class type is a reference type, so object instances of a class type are already represented as pointers. Don't use ^/# to refer to these objects (that will only refer to the pointers themselves, which is rarely ever needed in most situations), eg:
var
Pbtn: TButton;
Button2: TButton;
begin
Pbtn := Button1;
ShowMessage(pbtn.Caption); // works well
Button2 := TButton.Create(Form2);
Pbtn := Button2;
ShowMessage(pbtn.Caption); // also works well
...

Your code works just fine.
But why the second message in your example doesn't return any text then? That is because when you dynamically create a TButton no value is assigned to its caption and therefore your second message returns an empty string.
If you change your code to assign a specific caption to your second Button you will see that it works just fine
Pbtn := #Button1;
Showmessage(pbtn.caption); // works well
Button2 := TButton.Create(Form3);
//Assign some value to the dynamically created button's caption
Button2.Caption := 'It works!';
Pbtn := #Button2;
Showmessage(pbtn.caption); // not work
But why there is a caption on design-time placed button. That is because Delphi IDE sets the caption to be same as the component name that was placed on the form. This is done just out of convenience to the Delphi user.

Related

Business Central - 'OnCustomDocumentMergerEx' even does not trigger

I Recently tried to refactor a deprecated part of our code, which is an event subscription to 'OnBeforeMergeDocument', Because i had some problems regarding the printer name which I posted about in this Stack Overflow post.
I then tried to bind to the new event using the following code
[EventSubscriber(ObjectType::Codeunit, Codeunit::ReportManagement, 'OnCustomDocumentMergerEx', '', true, true)]
local procedure OnCustomDocumentMergerEx(ObjectID: Integer; ReportAction: Option SaveAsPdf,SaveAsWord,SaveAsExcel,Preview,Print,SaveAsHtml; ObjectPayload: JsonObject; XmlData: InStream; LayoutData: InStream; var DocumentStream: OutStream; var IsHandled: Boolean)
var
Test: Text;
begin
Test := 'test';
IsHandled := true;
end;
Just like the 'OnBeforeMergeDocument' event, i expected it to fire when i preview or print or send a report (for example if you go to business central > posted sales invoices > print/send > print). However it doesnt, and im getting a 'The custom report layout for '' is empty.' why is this? and why doesnt the event fire when I think it would fire?
This behaviour is pretty much different for on-prem/SaaS deployment and versions from 19 to 21. I am assuming that you are running a SaaS instance which is updated to the latest release (21.3).
In this version, preparation of the report layout is handled by the platform by default, and application events are not triggered. To change the flow, subscribe to the event ApplicationReportMergeStrategy in codeunit Reporting Triggers and change the merge strategy to Application.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Reporting Triggers", 'ApplicationReportMergeStrategy', '', false, false)]
local procedure SetMergeStrategy(ObjectId: Integer; LayoutCode: Text; var InApplication: Boolean)
begin
// if ObjectId = ... - if the strategy is set for a specific report object
InApplication := true;
end;
Once the merge strategy is changed to Application, the event OnCustomDocumentMergerEx starts firing.

Cannot injectScript in Custom Variable Template

I'm trying to injectScript via Custom Variable Template (not tag).
Here is simplified code:
const log = require('logToConsole');
const setTimeout = require('callLater');
const setInWindow = require('setInWindow');
const copyFromWindow = require('copyFromWindow');
const copyFromDataLayer = require('copyFromDataLayer');
const injectScript = require('injectScript');
const pixelSend = function(eventType, eventParams, tries) {
// logic
log('success')
};
log('event - ', copyFromDataLayer('event'));
if (copyFromDataLayer('event') === 'gtm.js') {
injectScript('https://vk.com/js/api/openapi.js', // this one should create **"VK"** object in global scope, used to actually send the events
pixelSend(),
data.gtmOnFailure);
}
return true;
Unfortunately openapi.js never gets injected (checking in network tab) and thus VK object never gets created and I cannot use it.
If I just run in console:
var head = document.getElementsByTagName('head')[0]
var js = document.createElement('script');
js.src = 'https://vk.com/js/api/openapi.js';
head.appendChild(js);
It gets injected and VK object becomes available.
What am I doing wrong?
Just in case:
queryPermission('inject_script', 'https://vk.com/js/api/openapi.js') = true
I tried this, and there were just a few minor bugs - line 11 is missing a semicolon, and you did not mention if you allowed access to read the "event" key from the datalayer.
After that was fixed, the script worked as expected.
Obviously it will only work on the page view trigger (since this is the only case when event equals gtm.js. I probably would move the condition from the tag to the trigger).
Instead of "return true" you should end this will a call to data.gtmOnSuccess(), else you might have trouble using this tag in a tag sequence.
If in the template UI you hit the "run code" switch you will actually get information on all error in your code (alas one at a time, since execution stops at the first error). You can also write tests with mock input, for templates that require settings via input fields.

how to set autosum property in x++ for a morphx report

I have the following code in the init() of a report:
QueryBuildDataSource qbdsTable;
QueryOrderByField QueryOrderByFieldTransDate;
QueryOrderByField QueryOrderByFieldDimZone
QueryOrderByField QueryOrderByFieldDimCC;
;
super();
qbdsTable = query.dataSourceTable(tableNum(Table));
QueryOrderByFieldTransDate = qbdsTable.addOrderByField(fieldNum(Table, TransDate));
QueryOrderByFieldTransDate.autoSum(true);
QueryOrderByFieldDimZone = qbdsTable.addOrderByField(fieldNum(Table, DimZone),SortOrder::Descending);
QueryOrderByFieldDimZone.autoSum(true);
QueryOrderByFieldDimCC = qbdsTable.addOrderByField(fieldNum(Table, DimCostCenter));
QueryOrderByFieldDimCC.autoSum(true);
and the autosum property is functioning properly (I have set the SumAll property for the field I use to calculate these subtotals).
The problem is that, whenever I try to add an groupBy field or a selection field, the autosum property isn't honored anymore (the subtotals are not displayed anymore):
qbdsTable.addSelectionField(fieldNum(Table, AmountMST), selectionField::Sum);
or
qbdsTable.addGroupByField(fieldNum(Table, TransDate));
I have tried to use:
qbdsTable.addSortField(fieldNum(Table, TransDate));
qbdsTable.autoHeader(1, true);
but I have the same problem
Does anyone has an Idea how I can use both autosum and addGroupByField on the same datasorce of a report?
For historical reasons old style AX reports behaves differently when called directly (run on the report node) or through on a report menu item.
The execution order of the first is:
init
fetch
dialog
The second runs via class RunbaseReportStd in the following order:
init
dialog
fetch
This matters because you have change the query after the user has made any changes.
So move your code changes from init to fetch, like this:
public boolean fetch()
{
QueryBuildDataSource qbdsCustTrans = query.dataSourceTable(tableNum(CustTrans));
;
qbdsCustTrans.addSelectionField(fieldNum(CustTrans, AmountMST), selectionField::Sum);
qbdsCustTrans.addGroupByField(fieldNum(CustTrans, AccountNum));
qbdsCustTrans.addGroupByField(fieldNum(CustTrans, TransDate));
qbdsCustTrans.addGroupByField(fieldNum(CustTrans, CurrencyCode));
//info(qbdsCustTrans.toString());
return super();
}
This will only work, if called through the menu item.
Also, I could not get the auto-sum functionality to work, when added by code.
Instead you will have to add the order by and autosum using Sorting node of the report query.
I don't know why, but maybe this is because you use auto design, which is generated at run time.

Select multiple time slots on click

When I click on a empty time slot on fullCalendar, it draws a rectangle on that empty cell. So, If my slotDuration is 30min, the block represents 30 min. I also can drag the cursor over multiple cells and select a custom range. But what I need to do is, when the user click (not drag) on a cell, select and draw the rectangle on 2 cells (representing 1 hour). Is this possible? I cannot change the slotDuration.
If I change the snapDuration to 1 hour, it works, but sadly, I cannot change it also.
What I was looking for is a way to override the event.end but that did not work.
Update 1:
I was able to do this exposing the cellDuration property:
on fullCalendar.js:
t.setCellDuration = function (minutes) {
var duration = moment.duration(minutes, 'minutes');
var view = t.getView();
view.timeGrid.cellDuration = duration;
}
now on the renderEvent handler, I can call
element.fullCalendar("setCellDuration", 60);
It works but if there is an alternative that does not involve change fullCalendar code, it would be nice.
I think you cannot do it just modifying the properties of the calendar, but you could do it modifying the fullCalendar.js file. Yes, I know you specify it on your question, but I think there is not alternative.
Exactly the listenStop function, which resides at line 4527 at version 2.3.3
listenStop check an array call dates
dates[
{FCMoment}, //start
{FCMoment} //end
]
So, before that check, you can modify your end time as you prefer. In addition, you have to render it.
In your code, now listenStop() function should be something like:
listenStop: function(ev) {
if (dates) { // started and ended on a cell?
if (dates[0].isSame(dates[1])) {
dates[1] = dates[0].clone().add(1, 'hours'); //Now we modify the end
_this.renderSelection(dates[0], dates[1]); //And render the modified selection
view.trigger('dayClick', dayEl[0], start, ev);
}
if (isSelectable) {
// the selection will already have been rendered. just report it
view.reportSelection(start, end, ev);
}
}
}

ALV Grid toolbar is missing in fullscreen

I've created a simple ALV grid and populated the grid with data, now the grid is displayed after the selection screen. I'm not using custom container and display the grid in full screen.
Is there a property of ALV grid object that enables toolbar with buttons filter, sort, etc, that is normally on top of the grid?
So far this is what I have:
TRY.
cl_salv_table=>factory(
IMPORTING
r_salv_table = gr_alv
CHANGING
t_table = tbl_data
).
CATCH cx_salv_msg.
ENDTRY.
* initialize the alv settings - nothing done here for the moment.
PERFORM define_settings USING gr_alv.
* Display the ALV
gr_alv->display( ).
Each ALV function is implemented as a separate CLASS in Simple ALV, so you have to handle them separately. You do not need a custom control.
In order to add the toolbar:
data: lr_func TYPE REF TO CL_SALV_FUNCTIONS_LIST.
"Functions
lr_func = gr_alv->get_functions( ).
lr_func->set_all( ).
Complete ALV display:
form display_results.
data: ls_key type salv_s_layout_key,
lo_table type ref to cl_salv_table,
lo_cols type ref to cl_salv_columns_table,
lo_events type ref to cl_salv_events_table,
lo_funcs type ref to cl_salv_functions_list,
lo_layout type ref to cl_salv_layout,
lo_display type ref to cl_salv_display_settings,
lo_selections type ref to cl_salv_selections.
try.
call method cl_salv_table=>factory
exporting
list_display = abap_false
importing
r_salv_table = lo_table
changing
t_table = gt_list.
catch cx_salv_msg . "#EC NO_HANDLER
endtry.
"Events
create object go_events.
lo_events = lo_table->get_event( ).
set handler go_events->double_click for lo_events.
"Layouts
ls_key-report = sy-repid.
lo_layout = lo_table->get_layout( ).
lo_layout->set_key( ls_key ).
lo_layout->set_default( abap_true ).
lo_layout->set_save_restriction( ).
lo_layout->set_initial_layout( p_var ).
lo_cols = lo_table->get_columns( ).
perform change_columns changing lo_cols.
"Functions
lo_funcs = lo_table->get_functions( ).
lo_funcs->set_all( ).
"Display Settings
lo_display = lo_table->get_display_settings( ).
lo_display->set_striped_pattern( abap_true ).
"Selections
lo_selections = lo_table->get_selections( ).
lo_selections->set_selection_mode( if_salv_c_selection_mode=>row_column ).
lo_table->display( ).
endform. " DISPLAY_RESULTS
This is confusing at first when you use the ALV object model. If you use the ALV in fullscreen mode you have to reference a GUI status in your program, and use the method SET_SCREEN_STATUS on your grid instance. It's explained in the SAP Help here.
It helps to copy the GUI status SALV_TABLE_STANDARD from function group SALV_METADATA_STATUS into your report as a starting point, and then you can remove any functions you don't need. For example, if you copied the status into your program as ALV_STATUS, you would write:
gr_alv->set_screen_status( report = sy-repid
pfstatus = 'ALV_STATUS' ).
If you want to use the class-based model of setting up ALV functions, you have to embed the grid object in a custom container in a screen.
Seems what you need to do is get an instance of CL_SALV_FUNCTIONS_LIST from your grid object like so:
data: lr_func TYPE REF TO CL_SALV_FUNCTIONS_LIST.
lr_func = gr_alv->get_functions( ).
lr_func->set_all( ).
But, from there, it seems you need to do a bit or work. My advice: Look at the documentation on classes CL_SALV_TABLE and CL_SALV_FUNCTIONS_LIST (that is, click the documentation button when you display the class in transaction SE24). The latter tells you exactly what you need to do.
(Also, a little hint: Put your processing logic inside the try-catch block, because if the initialization fails, you might catch that exception but go on to try call a method on an uninstantiated or uninitialized class).
add a customer container to your gui
create an object of the class cl_gui_custom_container and supply the name of your container
create an instance of the class cl_gui_alv_grid and supply the custom container object
use the method set_table_for_first_display
this will display a toolbar with all buttons. you can control which buttons you want in the toolbar with the IT_TOOLBAR_EXCLUDING parameter to the set_table_for_first_display method.

Resources