How to test the value of an option field in AL (Dynamics) Business Central - dynamics-al

I'm new to AL (Business Central). I want to be able to test the value of an option field.
if myOptionField::Booking then
begin
Message('Booking');
end;
But it didn't work.

I recomend you to try this way.
if rec.myOptionField = myOptionField:: Booking then
begin
Message('Booking');
end;

Related

It does not work in ADODataSet IndexDef?

I am creating a persistent index ADODataSet but not order information, my code is as follows:
ADODataSetInforArtic.IndexDefs.Clear;
case ComboBoxOrden.AsValue of
0: begin
with ADODataSetInforArtic.IndexDefs.AddIndexDef do
begin
Name := 'DenomArtic';
Fields := 'DenomArtic';
if ComboBoxOrden.Buttons.Image1.Id = 59 then
Options := [ixDescending];
ADODataSetInforArtic.IndexName := Name;
end;
end;
What am I doing wrong?
I do it this way because it is a report I'm doing. No grid, what you have is a combo in which you choose the field that will be ordered information and another button that can select ascending or descending. I do not understand that is not working ....
I use delphi xe2
Best regards
Sorry for the delay, the components I use in my projects so developed by independent programmers Spanish (JfControls), greatly facilitate the development and are more aesthetically cute.
I solved my problem by using the Sort property ...
ADODataSet.Sort := 'CodigDenom DESC';
I do not understand why my previous code did not work in ADODataset. The indexDefs work wonderfully in the ClientDataSet.
Also, I use for my reports ReportBuilder
Best regards.

Delphi how to save 3 datasources with one save button?

I got a problem with saving all values from 3 datasources into a SMDBGrid with another datasouce.
I got AdressID, ContactpersonID and RelationID.
Those all dont match each others.
The problem is that my SMDBGrid has another datasource then those 3.
I wanna save them with one button.
Tried many ways but can't find a good result.
this is the code i use right now for my Insert button:
procedure TFRelatiebeheer.ToolButton1Click(Sender: TObject);
begin
DRelatiebeheer.ContactpersonID.Insert;
DRelatiebeheer.RelationID.Insert;
DRelatiebeheer.AdressID.Insert;
end;
This is the code i use for my save button right now
if (DRelatiebeheer.ContactpersonID.State in dsEditModes) then
if not (DRelatiebeheer.ContactpersonID.State in [dsInsert]) then
begin
KJSMDBGrid1.RefreshData;
KJPanel4.Visible := True;
end
else
begin
if (DRelatiebeheer.ContactpersonID.State IN dsEditModes) then
DRelatiebeheer.ContactpersonID.Post;
if (DRelatiebeheer.AdressID.State IN dsEditModes) then
DRelatiebeheer.AdressID.Post;
end;
Hope you have a good sight for what I am doing right now, if not please notify.
I got the problem with the datasources that need to be saved on 1 click and then be refreshed in the database and in the Grid.
That means that when I insert a Contactperson there needs to be a AdressID and a RelationID coupled with it.
After that the grid needs to reload all of the data.
Focusing on the given problem
Depending on the intended behavior (should it be possible posting only one or two table(s) or is it necessary to post all tables) the first thing to do would be to ensure that the tables can be posted. You coulds create a function for each table e.g. CanAdressIDBePosted:Boolean to check if required fields are already entered. The condition of the table ContactpersonID would contain additional conditions: needed fields are entered AND CanAdressIDBePosted AND CanRelationIDBePosted. You could create an Action which would be bound on your button with an OnUpdate event which could look like this:
procedure TForm1.PostActionUpdate(Sender: TObject);
begin
TAction(Sender).Enabled := CanAdressIDBePosted and CanContactpersonIDBePosted and CanRelationIDBePosted;
// depending on your requirements (e.g. no need to post RelationID if not entered) it also could be
TAction(Sender).Enabled := CanAdressIDBePosted or CanContactpersonIDBePosted ;
end;
procedure TForm1.PostActionExecute(Sender: TObject);
begin
if CanAdressIDBePosted then AdressID.Post; // ensure ID fields will be generated
if CanRelationIDBePosted then RelationID.Post; // ensure ID fields will be generated
if CanContactpersonIDBePosted then
begin
ContactpersonID.FieldByName('AdressID').value := AdressID.FieldByName('ID').Value;
ContactpersonID.FieldByName('RelationID').value := RelationID.FieldByName('ID').Value;
end;
DateSetBoundToTheGrid.Requery;
// furthor actions you need
end;
Function TForm1.CanAdressIDBePosted:Boolean;
begin
// example implementation
Result := (AdressID.State in [dsEdit,dsInsert]) and (not AdressID.FieldByName('NeededField').IsNull);
end;
Function TForm1.CanContactpersonIDBePosted:Boolean;
begin
// example implementation
Result := (ContactpersonID.State in [dsEdit,dsInsert]) and (not ContactpersonID.FieldByName('NeededField').IsNull)
and CanAdressIDBePosted and CanRelationIDBePosted;
end;
An addidtional Action should be created to cancel if needed:
procedure TForm1.CancelActionExecute(Sender: TObject);
begin
AdressID.Cancel;
RelationID.Cancel;
ContactpersonID.Cancel;
end;
procedure TForm1.CancelActionUpdate(Sender: TObject);
begin
TAction(Sender).Enabled := (AdressID.State in [dsEdit,dsInsert])
or (RelationID.State in [dsEdit,dsInsert])
or (ContactpersonID.State in [dsEdit,dsInsert]);
end;
In general I am not sure if the approach you took ist the best which can be taken, since from the structure given IMHO it should be possible to assign already existing relations and adresses to new generated contactpersons, but that would be another question.
This code just looks somewhat random to me. What SHOULD happen there ?
if (DRelatiebeheer.ContactpersonID.State in dsEditModes) then
// remember this check (1)
if not (DRelatiebeheer.ContactpersonID.State in [dsInsert]) then
// this check better written as "...State = dsInsert"
begin
// why don't you call DRelatiebeheer.ContactpersonID.Post to save chanegs ?
KJSMDBGrid1.RefreshData;
KJPanel4.Visible := True;
end
else
begin
if (DRelatiebeheer.ContactpersonID.State IN dsEditModes) then
// you already checked this above (1), why check again ?
DRelatiebeheer.ContactpersonID.Post;
if (DRelatiebeheer.AdressID.State IN dsEditModes) then
DRelatiebeheer.AdressID.Post;
end;
// so what about DRelatiebeheer.RelationID ?
For what i may deduce, you don't have to make any complex if-ladders, you just have to literally translate your words to Delphi. You want to save three tables and then refresh the grid. Then just do it.
procedure TFRelatiebeheer.SaveButtonClick(Sender: TObject);
begin
DRelatiebeheer.ContactpersonID.Post;
DRelatiebeheer.RelationID.Post;
DRelatiebeheer.AdressID.Post;
DatabaseConnection.CommitTrans;
KJSMDBGrid1.RefreshData;
KJPanel4.Visible := True;
end;
Just like you was told in your other questions.
Delphi save all values from different datasources with 1 save button
Delphi set Panel visible after post
PS. ToolButton1Click - plase, DO rename the buttons. Believe me when you have 10 buttons named Button1, Button2, ...Button10 you would never be sure what each button should do and would mix everything and make all possible program logic errors.

How to design class dependency trying to avoid Law of Demeter

Ok,I´ve searched and couldn´t find a suitable solution for my problem, I am redesigning a part of our point of sale system. Let´s suppose we have the following classes:
TWorkShift = class
Date: TDateTime;
fTotalSold: Currency;
fSales: TList<TSale>;
public
property TotalSold: Currency read fTotalSold write fTotalSold;
property Sales: Currency read fSales write fSales;
end;
TSale = class
fAmount: Currency;
fWorkShift: TWorkShift;
public
property Amount: Currency read fAmount write fAmount;
procedure Save;
end;
Now, the problem I am facing is trying to come to the best idea without violating the Law of Demeter. What I am trying to accomplish is the following:
Every time a new TSale is saved I want to add it to the Sales list of the TWorkShift of the current user, and also I want to sum the amount of the sale to the "TotalSold" of the TWorkShift.
I´ve tried two different approaches:
Approach A:
// Let´s suppose we have a working shift with the ID 1 and gets loaded from database with:
CurrentShift := TWorkShift.Create(1);
NewSale := TSale.Create;
NewSale.Amount:=100;
NewSale.Save;
CurrentShift.Sales.Add(NewSale);
CurrentShift.TotalSold := CurrentShift.TotalSold + NewSale.Amount;
The problem with this approach is that It´s difficult to test, because I want to encapsulate the logic of the sum in some of the classes or somewhere else (a new class maybe?).
Approach B:
My other approach is including that code inside the TSale class itself:
procedure TSale.Save;
begin
SaveToDataBase;
fWorkShift.Sales.Add(Self);
fWorkShift.TotalSold := fWorkShift.TotalSold + Self.Amount;
end;
This approach I think violates the Law of Demeter and doesn´t feel right to me.
I Want to find a "right way" to do it maximizing code simplicity and easy of maintenance in the future. So any suggestions would be appreciated.
Thanks
If you want to add a sale to TWorkShift, then you should have
TWorkShift.AddSale(aSale: TSale);
begin
Sales.Add(aSale);
end;
In other words, TWorkShift should "ask" for the thing it needs.
Also, I don't see any reason that TSale would have a TWorkShift field. A Workshift has many sales, but why would a Sale have a WorkShift?
You are doing something when you add items to a TList so you can use the OnNotify.
I don't know if Aurelius is also using that event so I added some code for that. You only have to see if assigning the OnNotify can happen inside the framework after the list is assigned to your TWorkShift object because then it might overwrite the NotifySales event handler.
type
TWorkShift = class
private
Date: TDateTime;
fTotalSold: Currency;
fSales: TList<TSale>;
fNotifySales: TCollectionNotifyEvent<TSale>;
procedure NotifySales(Sender: TObject; const Item: TSale;
Action: TCollectionNotification);
procedure SetSales(const Value: TList<TSale>);
public
property TotalSold: Currency read fTotalSold write fTotalSold;
property Sales: TList<TSale> read fSales write SetSales;
end;
procedure TWorkShift.NotifySales(Sender: TObject; const Item: TSale;
Action: TCollectionNotification);
begin
if Assigned(fNotifySales) then
fNotifySales(Sender, Item, Action);
case Action of
cnAdded: fTotalSold := fTotalSold + Item.Amount;
cnRemoved: fTotalSold := fTotalSold - Item.Amount;
end;
end;
procedure TWorkShift.SetSales(const Value: TList<TSale>);
begin
if Assigned(fSales) then
begin
fSales.OnNotify := fNotifySales;
fNotifySales := nil;
end;
fSales := Value;
if Assigned(fSales) then
begin
fNotifySales := fSales.OnNotify;
fSales.OnNotify := NotifySales;
end;
end;

Maintain UpDown-Associate connection while recreating the associate

I have an TUpDown control whose Associate is set to an instance of a TEdit subclass. The edit class calls RecreateWnd in its overriden DoEnter method. Unfortunately this kills the buddy connection at the API level which leads to strange behavior e.g. when clicking on the updown arrows.
My problem is that the edit instance doesn't know that it is the buddy of some updown to which it should reconnect and the updown isn't notified of the loss of its buddy. Any ideas how I could reconnect the two?
I noticed how TCustomUpDown.SetAssociate checks that updown and buddy have the same parent and uses this to avoid duplicate associations. So I tried calling my own RecreateWnd method:
procedure TAlignedEdit.RecreateWnd;
var
i: Integer;
c: TControl;
ud: TCustomUpDown;
begin
ud := nil;
for i := 0 to Pred(Parent.ControlCount) do
begin
c := Parent.Controls[i];
if c is TCustomUpDown then
if THACK_CustomUpDown(c).Associate = Self then
begin
ud := TCustomUpDown(c);
Break;
end;
end;
inherited RecreateWnd;
if Assigned(ud) then
begin
THACK_CustomUpDown(ud).Associate := nil;
THACK_CustomUpDown(ud).Associate := Self;
end;
end;
et voila - it works!
You've discovered something rather unfortunate. You set up an association between two controls at the application level, so you should be able to continue to manage that association in application-level code, but the VCL doesn't provide the framework necessary for maintaining that. Ideally, there would be a generic association framework, so associated controls could notify each other that they should update themselves.
The VCL has the beginnings of that, with the Notification method, but that only notifies of components being destroyed.
I think your proposed solution is a little too specific to the task. An edit control shouldn't necessarily know that it's attached to an up-down control, and even if it does, they shouldn't be required to share a parent. On the other hand, writing an entire generic observer framework for this problem would be overkill. I propose a compromise.
Start with a new event property on the edit control:
property OnRecreateWnd: TNotifyEvent read FOnRecreateWnd write FOnRecreateWnd;
Then override RecreateWnd as you did above, but instead of all the up-down-control-specific code, simply trigger the event:
procedure TAlignedEdit.RecreateWnd;
begin
inherited;
if Assigned(OnRecreateWnd) then
OnRecreateWnd(Self);
end;
Now, handle that event in your application code, where you know exactly which controls are associated with each other, so you don't have to search for anything, and you don't need to require any parent-child relationships:
procedure TUlrichForm.AlignedEdit1RecreateWnd(Sender: TObject);
begin
Assert(Sender = AlignedEdit1);
UpDown1.Associate := nil;
UpDown1.Associate := AlignedEdit1;
end;
Try storing the value of the Associate property in a local variable before you call RecreateWnd, then setting it back afterwards.

Delphi 5 & Crystal XI Rel. 2 (RDC) how to?

I'm trying to work with the class from JosephStyons but I do get an "Invalid Index" Error on the line where the "User ID" should get set.
FRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] := edUserName.Text;
Here's my environment:
WinXP Sp3, Crystal Reports Developer XI Rel.2 SP4, Delphi 5 Update Pack 1
Any help or ideas greatly appreciated!
Thx,
Reinhard
Your value for [i] could be the culprit...I can't remember for sure but I believe the first table will be Table[1] instead of Table[0] as one would expect.
I altered my loop to use:
CrTables := CrDatabase.Tables;
for crTableObj in crTables do
You might try stepping through the table using a for loop as shown above or by starting with 1 instead of 0.
I hope this helps.
Put a break point on that line and use Evaluate/Modify.
It will return an error if you try something invalid.
Examine FRpt.Database.Tables[i] and see if it's valid for what you think are the min and max values for i.
If Tables is an array, one way to avoid that is to use ...Low(Tables) to High(Tables)
If you get your Table Ok, examine FRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] and see if it's valid.
It might be that the Item getter does not like the space embedded in "User ID". Some products need either to surround by special characters like "[User ID]", other to replace by an underscore like "User_ID"
Are you also setting the password, server name and database name?
procedure TReports.LogonToDBTables(cReport:
CrystalDecisions.CrystalReports.Engine.ReportDocument;
ConnInfo: ConnectionInfo);
var
CrDataBase: Database;
CrTables: Tables;
CrTableObj: TObject;
CrTable: Table;
CrTableLogonInfo: TableLogonInfo;
iSubReportIndex: smallint;
begin
CrDataBase := CReport.Database;
CrTables := CrDatabase.Tables;
cReport.DataSourceConnections[0].IntegratedSecurity := False;
for crTableObj in crTables do
begin
crTable := CrystalDecisions.CrystalReports.Engine.Table(crTableObj);
crTableLogonInfo := crTable.LogOnInfo;
crTableLogonInfo.ConnectionInfo := ConnInfo;
crTable.ApplyLogOnInfo(crTableLogonInfo);
end;
end;
function TReports.GetConnectionInfo(): ConnectionInfo;
var
cTemp: ConnectionInfo;
begin
cTemp := ConnectionInfo.Create();
cTemp.AllowCustomConnection := True;
cTemp.ServerName := GetServerName();
cTemp.DatabaseName := GetDBName();
cTemp.UserID := GetDBUserID();
cTemp.Password := GetDBPassword();
Result := cTemp;
end;

Resources