You may have read or heard about No Code and Low Code application development using systems, languages and run times. Atanas Popov, GM of Embarcadero Technology, recently blogged about low code development. “These days low-code development is en vogue. Various research groups, such as Gartner, put the low-code application development platform market at ~$10M billion in 2019 and project CAGR to be greater than 20% from 2020 to 2027.” He went on to discuss the importance of low code to Delphi developers (and I would add C++Builder developers).
In this post I am going to show you how to build a Windows C++ Customer/Sales, Master/Detail/Charting application that only needs 1 line of code. Using the IDE and leveraging some of my favorite RTL features including visual live bindings, FireDAC and TeeChart I can create a full customer and sales business application for Windows.
I’ll use the IDE’s form designer, VCL, FireDAC database access components with fields editor, visual live bindings and 1 line of C++ code (if anyone can show me how to remove the need for the one line of code, I’ll gladly update my C++Builder project.
Table of Contents
The UI
To start my project, I used the File | New | C++Builder VCL application menu item. My form contains a TPanel at the top of the form containing a checkbox and TDBNavigator, two TDBGrid components (one for the customer list and one for the sales for a selected customer), Steema Software’s TDBChart (to show a pie chart of a customer’s sales by item type), two TSplitter components (one to expand and contract the customer list and a second one to expand and contract the space for the customer sales and pie chart, and several FireDAC components (TFDConnection, 3 TFDQuery), 2 TDataSource, and a second TPanel to house the customer sales and pie chart.
To see the list of components and their TPanel containers you can view the form in the Structure Window.
The Database Components and SQL Queries
For this application I am using the InterBase Employee.gdb sample database that includes Customer and Sales tables.
You can view the Employee.gdb file structure in the Data Explorer Window.
For each TFDQuery component I used the FireDAC Query Editor window (right mouse click on each TFDQuery component) to create and test SQL statements for the customer query, sales by customer query and sales by item type for a customer (in the Parameters tab use 1001 for the customer number).
Right mouse click on each TFDQuery to bring up the fields editor to include just a few of the columns from each query.
To connect each query’s result data to the TDBGrids and TDBChart set up properties for the TDataSource components.
To create the connection between the customer (master) and sales (detail) queries, set up the MasterSource and MasterFields properties for the SalesQuery in the Object Inspector. Do the same for the SalesByItemTypeForCustomerQuery.
Visual Live Bindings
To control the connection to the database and connect the results of the queries to the UI I’ll use C++Builder’s Visual Live Bindings technology. LiveBindings is a data binding, expression-based framework that visually (or in code) allows you to bind objects to other objects or to dataset fields. Using LiveBindings, any object can be bound to any other object using a binding expression involving one or more properties of the objects you want to bind together. LiveBindings binding expressions can be either unidirectional or bidirectional.
To bring up the LiveBindings designer, right mouse click on the form and choose the “Bind Visually…” item in the pop up menu.
This will bring up a frame below the form containing the visual and non-visual components included on the form. You can use the “…” at the lower right of each object to bring up a selection list of properties you need for your binding expression.
In my case I just want to use the Checked property and CheckedState. Dragging the mouse between the CheckedState property and the Active property for each Query will form links to make of the binding expression. At execution time the binding expression will be evaluated and use the state of the checkbox to execute each of the SQL queries.
You can zoom in/out the LiveBindings Designer, create layers to hide some of the bindings complexity and save the design to a bitmap file.
Using Steema’s TeeChart to create a Pie Chart of Customer Sales by Item Type
Included in C++Builder is a standard set of TeeChart components. You can also upgrade to advanced functionality by contacting Steema Software.
Right mouse click on the TDBChart component in the lower right of the form to bring up the pop up menu containing component editors.
Use the “Edit Chart…” menu item. In the component editor window select “Series” and click the Add… button to choose a pie chart from the gallery of charts included with the TeeChart standard edition.
Select the series (“Series 1”) and click on the “Data Source” tab to choose the dataset (“SalesByItemTypeForCustomerQuery”) and set the Labels to be the ITEM_TYPE field and the Pie to be the SUM field.
Set the Chart Titles to Text to “Sales by Item Type for Customer”. Then click the close button to finish setting up the Pie Chart.
The Completed Form
Here is a text view of the completely designed form (right mouse click on the form designer and choose “view as text”) with UI components, FireDAC, TeeChart, and LiveBindings components and settings.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
object MasterDetailForm: TMasterDetailForm Left = 0 Top = 0 Caption = 'Customer and Orders Master Detail Using Live Bindings (C++ VCL)' ClientHeight = 509 ClientWidth = 736 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False OnShow = FormShow PixelsPerInch = 96 TextHeight = 13 object Splitter1: TSplitter Left = 0 Top = 161 Width = 736 Height = 3 Cursor = crVSplit Align = alTop ExplicitLeft = 24 ExplicitTop = 210 ExplicitWidth = 159 end object Panel1: TPanel Left = 0 Top = 0 Width = 736 Height = 33 Align = alTop TabOrder = 0 object DBNavigator1: TDBNavigator Left = 159 Top = 4 Width = 225 Height = 23 DataSource = CustomerDataSource VisibleButtons = [nbFirst, nbPrior, nbNext, nbLast, nbRefresh] TabOrder = 0 end object DatabaseActiveCheckBox: TCheckBox Left = 24 Top = 9 Width = 97 Height = 17 Caption = 'Database Active' TabOrder = 1 end end object CustomerDBGrid: TDBGrid Left = 0 Top = 33 Width = 736 Height = 128 Align = alTop DataSource = CustomerDataSource TabOrder = 1 TitleFont.Charset = DEFAULT_CHARSET TitleFont.Color = clWindowText TitleFont.Height = -11 TitleFont.Name = 'Tahoma' TitleFont.Style = [] end object Panel2: TPanel Left = 0 Top = 164 Width = 736 Height = 345 Align = alClient TabOrder = 2 object Splitter2: TSplitter Left = 305 Top = 1 Height = 343 ExplicitLeft = 208 ExplicitTop = 120 ExplicitHeight = 100 end object SalesDBGrid: TDBGrid Left = 1 Top = 1 Width = 304 Height = 343 Align = alLeft DataSource = SalesDataSource TabOrder = 0 TitleFont.Charset = DEFAULT_CHARSET TitleFont.Color = clWindowText TitleFont.Height = -11 TitleFont.Name = 'Tahoma' TitleFont.Style = [] end object DBChart1: TDBChart Left = 308 Top = 1 Width = 427 Height = 343 Title.Text.Strings = ( 'Sales by Item Type for Customer') View3DOptions.Elevation = 315 View3DOptions.Orthogonal = False View3DOptions.Perspective = 0 View3DOptions.Rotation = 360 Align = alClient TabOrder = 1 DefaultCanvas = 'TGDIPlusCanvas' ColorPaletteIndex = 13 object Series1: TPieSeries DataSource = SalesByItemTypeForCustomerQuery XLabelsSource = 'ITEM_TYPE' XValues.Order = loAscending YValues.Name = 'Pie' YValues.Order = loNone YValues.ValueSource = 'SUM' Frame.InnerBrush.BackColor = clRed Frame.InnerBrush.Gradient.EndColor = clGray Frame.InnerBrush.Gradient.MidColor = clWhite Frame.InnerBrush.Gradient.StartColor = 4210752 Frame.InnerBrush.Gradient.Visible = True Frame.MiddleBrush.BackColor = clYellow Frame.MiddleBrush.Gradient.EndColor = 8553090 Frame.MiddleBrush.Gradient.MidColor = clWhite Frame.MiddleBrush.Gradient.StartColor = clGray Frame.MiddleBrush.Gradient.Visible = True Frame.OuterBrush.BackColor = clGreen Frame.OuterBrush.Gradient.EndColor = 4210752 Frame.OuterBrush.Gradient.MidColor = clWhite Frame.OuterBrush.Gradient.StartColor = clSilver Frame.OuterBrush.Gradient.Visible = True Frame.Width = 4 OtherSlice.Legend.Visible = False end end end object DatabaseConnection: TFDConnection Params.Strings = ( 'Database=C:\Users\Public\Documents\Embarcadero\Studio\21.0\Sampl' + 'es\Data\EMPLOYEE.GDB' 'ConnectionDef=EMPLOYEE') Connected = True LoginPrompt = False Left = 104 Top = 64 end object CustomerQuery: TFDQuery AfterScroll = CustomerQueryAfterScroll Connection = DatabaseConnection SQL.Strings = ( 'select * from customer') Left = 240 Top = 72 object CustomerQueryCUST_NO: TFDAutoIncField FieldName = 'CUST_NO' Origin = 'CUST_NO' ProviderFlags = [pfInUpdate, pfInWhere, pfInKey] IdentityInsert = True end object CustomerQueryCUSTOMER: TStringField FieldName = 'CUSTOMER' Origin = 'CUSTOMER' Required = True Size = 25 end object CustomerQueryCITY: TStringField FieldName = 'CITY' Origin = 'CITY' Size = 25 end object CustomerQuerySTATE_PROVINCE: TStringField FieldName = 'STATE_PROVINCE' Origin = 'STATE_PROVINCE' Size = 15 end object CustomerQueryCOUNTRY: TStringField FieldName = 'COUNTRY' Origin = 'COUNTRY' Size = 15 end object CustomerQueryPOSTAL_CODE: TStringField FieldName = 'POSTAL_CODE' Origin = 'POSTAL_CODE' Size = 12 end object CustomerQueryON_HOLD: TStringField AutoGenerateValue = arDefault FieldName = 'ON_HOLD' Origin = 'ON_HOLD' FixedChar = True Size = 1 end end object CustomerDataSource: TDataSource DataSet = CustomerQuery Left = 341 Top = 72 end object SalesQuery: TFDQuery MasterSource = CustomerDataSource MasterFields = 'CUST_NO' DetailFields = 'CUST_NO' Connection = DatabaseConnection FetchOptions.AssignedValues = [evCache] FetchOptions.Cache = [fiBlobs, fiMeta] UpdateOptions.AssignedValues = [uvRefreshMode] SQL.Strings = ( 'select * from sales' 'where :Cust_NO = Cust_No') Left = 79 Top = 240 ParamData = < item Name = 'CUST_NO' DataType = ftInteger ParamType = ptInput Value = 1001 end> object SalesQueryCUST_NO: TIntegerField DisplayLabel = 'CUST#' FieldName = 'CUST_NO' Origin = 'CUST_NO' Required = True end object SalesQueryORDER_DATE: TSQLTimeStampField AutoGenerateValue = arDefault DisplayLabel = 'ORD_DATE' DisplayWidth = 10 FieldName = 'ORDER_DATE' Origin = 'ORDER_DATE' end object SalesQueryTOTAL_VALUE: TCurrencyField FieldName = 'TOTAL_VALUE' Origin = 'TOTAL_VALUE' Required = True end object SalesQueryITEM_TYPE: TStringField FieldName = 'ITEM_TYPE' Origin = 'ITEM_TYPE' Required = True Size = 12 end end object SalesDataSource: TDataSource DataSet = SalesQuery Left = 184 Top = 240 end object BindingsList1: TBindingsList Methods = <> OutputConverters = <> Left = 628 Top = 69 object LinkControlToPropertyActive: TLinkControlToProperty Category = 'Quick Bindings' Control = DatabaseActiveCheckBox Track = True Component = CustomerQuery ComponentProperty = 'Active' end object LinkControlToPropertyActive2: TLinkControlToProperty Category = 'Quick Bindings' Control = DatabaseActiveCheckBox Track = True Component = SalesQuery ComponentProperty = 'Active' InitializeControlValue = False end object LinkControlToPropertyActive3: TLinkControlToProperty Category = 'Quick Bindings' Control = DatabaseActiveCheckBox Track = True Component = SalesByItemTypeForCustomerQuery ComponentProperty = 'Active' InitializeControlValue = False end end object SalesByItemTypeForCustomerQuery: TFDQuery MasterSource = CustomerDataSource MasterFields = 'CUST_NO' Connection = DatabaseConnection UpdateOptions.AssignedValues = [uvRefreshMode] SQL.Strings = ( 'select Item_Type,sum(Total_Value) from Sales' 'where Cust_NO = :Cust_No' 'Group by Item_type') Left = 128 Top = 320 ParamData = < item Name = 'CUST_NO' DataType = ftInteger ParamType = ptInput Value = Null end> object SalesByItemTypeForCustomerQueryITEM_TYPE: TStringField AutoGenerateValue = arDefault FieldName = 'ITEM_TYPE' Origin = 'ITEM_TYPE' ProviderFlags = [] ReadOnly = True Size = 12 end object SalesByItemTypeForCustomerQuerySUM: TFMTBCDField AutoGenerateValue = arDefault FieldName = 'SUM' Origin = '"SUM"' ProviderFlags = [] ReadOnly = True Precision = 18 Size = 2 end end end |
The Project and the 1 Line of Code
Save the project to a folder.
In order to have the TeeChart pie chart refresh each time a customer is selected (by using the TDBNavigator or moving to a next row in the customer DBGrid) I’ve written one line of code for the CustomerQuery AfterScroll event handler.
1 2 3 4 5 |
void __fastcall TMasterDetailForm::CustomerQueryAfterScroll(TDataSet *DataSet) { // refresh the chart data when customer row scroll happens DBChart1->RefreshData(); } |
This one line of code tells the DBChart to refresh the Pie Chart data for the updated SalesByItemTypeForCustomerQuery.
MasterDetailUnit.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
//--------------------------------------------------------------------------- #ifndef MasterDetailUnitH #define MasterDetailUnitH //--------------------------------------------------------------------------- #include <System.Classes.hpp> #include <Vcl.Controls.hpp> #include <Vcl.StdCtrls.hpp> #include <Vcl.Forms.hpp> #include <Data.DB.hpp> #include <FireDAC.Comp.Client.hpp> #include <FireDAC.Comp.DataSet.hpp> #include <FireDAC.DApt.hpp> #include <FireDAC.DApt.Intf.hpp> #include <FireDAC.DatS.hpp> #include <FireDAC.Phys.hpp> #include <FireDAC.Phys.IB.hpp> #include <FireDAC.Phys.IBDef.hpp> #include <FireDAC.Phys.Intf.hpp> #include <FireDAC.Stan.Async.hpp> #include <FireDAC.Stan.Def.hpp> #include <FireDAC.Stan.Error.hpp> #include <FireDAC.Stan.Intf.hpp> #include <FireDAC.Stan.Option.hpp> #include <FireDAC.Stan.Param.hpp> #include <FireDAC.Stan.Pool.hpp> #include <FireDAC.UI.Intf.hpp> #include <FireDAC.VCLUI.Wait.hpp> #include <Vcl.DBCtrls.hpp> #include <Vcl.DBGrids.hpp> #include <Vcl.ExtCtrls.hpp> #include <Vcl.Grids.hpp> #include <Data.Bind.Components.hpp> #include <Data.Bind.DBScope.hpp> #include <Data.Bind.EngExt.hpp> #include <Data.Bind.Grid.hpp> #include <System.Bindings.Outputs.hpp> #include <System.Rtti.hpp> #include <Vcl.Bind.DBEngExt.hpp> #include <Vcl.Bind.Editors.hpp> #include <Vcl.Bind.Grid.hpp> #include <Vcl.WinXCtrls.hpp> #include <VCLTee.Chart.hpp> #include <VCLTee.Series.hpp> #include <VclTee.TeeGDIPlus.hpp> #include <VCLTee.TeEngine.hpp> #include <VCLTee.TeeProcs.hpp> #include <VCLTee.DBChart.hpp> #include <Datasnap.DBClient.hpp> #include <Datasnap.Provider.hpp> //--------------------------------------------------------------------------- class TMasterDetailForm : public TForm { __published: // IDE-managed Components TFDConnection *DatabaseConnection; TFDQuery *CustomerQuery; TDataSource *CustomerDataSource; TFDQuery *SalesQuery; TDBNavigator *DBNavigator1; TPanel *Panel1; TSplitter *Splitter1; TCheckBox *DatabaseActiveCheckBox; TDBGrid *CustomerDBGrid; TDBGrid *SalesDBGrid; TDataSource *SalesDataSource; TBindingsList *BindingsList1; TLinkControlToProperty *LinkControlToPropertyActive; TLinkControlToProperty *LinkControlToPropertyActive2; TPanel *Panel2; TIntegerField *SalesQueryCUST_NO; TSQLTimeStampField *SalesQueryORDER_DATE; TCurrencyField *SalesQueryTOTAL_VALUE; TStringField *SalesQueryITEM_TYPE; TSplitter *Splitter2; TFDAutoIncField *CustomerQueryCUST_NO; TStringField *CustomerQueryCUSTOMER; TStringField *CustomerQueryCITY; TStringField *CustomerQuerySTATE_PROVINCE; TStringField *CustomerQueryCOUNTRY; TDBChart *DBChart1; TPieSeries *Series1; TFDQuery *SalesByItemTypeForCustomerQuery; TLinkControlToProperty *LinkControlToPropertyActive3; TStringField *CustomerQueryPOSTAL_CODE; TStringField *CustomerQueryON_HOLD; TStringField *SalesByItemTypeForCustomerQueryITEM_TYPE; TFMTBCDField *SalesByItemTypeForCustomerQuerySUM; void __fastcall CustomerQueryAfterScroll(TDataSet *DataSet); private: // User declarations public: // User declarations __fastcall TMasterDetailForm(TComponent* Owner); }; //--------------------------------------------------------------------------- extern PACKAGE TMasterDetailForm *MasterDetailForm; //--------------------------------------------------------------------------- #endif |
MasterDetailUnit.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "MasterDetailUnit.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TMasterDetailForm *MasterDetailForm; //--------------------------------------------------------------------------- __fastcall TMasterDetailForm::TMasterDetailForm(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TMasterDetailForm::CustomerQueryAfterScroll(TDataSet *DataSet) { // refresh the chart data when customer row scroll happens DBChart1->RefreshData(); } //--------------------------------------------------------------------------- |
The Running Program
Here are two screen grabs of the running program for the first two customers. Notice that the customer sales grid and pie chart are updated for each selected customer.
References
- FireDAC
- LiveBindings in RAD Studio
- LiveBindings Designer
- TeeChart
- Steema Software TeeChart
- Source Code for Project (Zip file)
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition