|
5 | 5 | [](#does-this-example-address-your-development-requirementsobjectives) |
6 | 6 | <!-- default badges end --> |
7 | 7 |
|
8 | | -# DevExpress VCL Dashboards — Store Dashboard Layouts in a Database |
| 8 | +# DevExpress Dashboards for Delphi/C++Builder — Store Dashboard Layouts and User Interaction State in a Database |
9 | 9 |
|
10 | | -This sample app stores a [dashboard layout](https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.Layout) (XML-based template) in the BLOB field of a memory-based dataset ([TdxMemData](https://docs.devexpress.com/VCL/dxmdaset.TdxMemData) inherited from the [TDataSet](https://docwiki.embarcadero.com/Libraries/Athens/en/Data.DB.TDataSet) class shipped with the standard VCL library). |
| 10 | +This example application allows users to create new layouts/modify existing layouts |
| 11 | +(using the built-in Dashboard Designer), interact with dashboard UI elements, and save |
| 12 | +[state][TdxCustomDashboardControl.State] or [layout][TdxCustomDashboardControl.Layout] changes to the data source. |
| 13 | + |
| 14 | + |
11 | 15 |
|
12 | | - |
13 | 16 |
|
14 | 17 | ## Prerequisites |
15 | 18 |
|
16 | | -* Microsoft Windows 10 or newer |
17 | | -* Embarcadero RAD Studio IDE 12.3 (Athens) or newer (Community Edition is not supported) |
18 | | -* DevExpress VCL Components v25.2.3 or newer |
| 19 | +[DevExpress Dashboards Prerequisites][req] |
| 20 | + |
| 21 | +[req]: https://docs.devexpress.com/VCL/405773/ExpressCrossPlatformLibrary/vcl-backend/reports-dashboards-app-deployment#vcl-reportsdashboards-prerequisites |
| 22 | + |
19 | 23 |
|
20 | 24 | ## Test the Example |
21 | 25 |
|
22 | | -1. Run the sample app. |
23 | | -2. Click **New Dashboard** to create a new dashboard and a dataset record. Alternatively, you can click **Design Dashboard** to modify the existing dashboard. |
24 | | -3. Create or modify the dashboard layout using tools available within the UI. |
25 | | -4. Click the hamburger button, select the **Save** option, and close the dialog. |
26 | | -5. Close the app. The [TdxMemData](https://docs.devexpress.com/VCL/dxmdaset.TdxMemData) component will store layout data between sessions. |
27 | | -6. Run the sample again. Click on grid records to switch between loaded dashboards. |
| 26 | +1. Run the sample app. |
| 27 | +1. Click **New Dashboard** to create a new dashboard or **Design Dashboard** to modify the pre-defined dashboard. |
| 28 | +1. Create or modify the dashboard layout using tools available within the UI. |
| 29 | +1. Click the hamburger button, select the **Save** option, and close the dialog. |
| 30 | +1. Create additional layouts if necessary. |
| 31 | +1. Close and restart the app. |
| 32 | + Click on grid records to switch between dashboard layouts you set up previously. |
| 33 | + Press **Design Dashboard** or **Delete Dashboard** to modify or delete entries. |
| 34 | + |
| 35 | + |
| 36 | + |
| 37 | + |
| 38 | +## Implementation Details |
| 39 | + |
| 40 | +The example stores dashboard layouts using a DevExpress memory-based dataset ([TdxMemData]). |
| 41 | +You can modify the application to use any other [TDataSet] descendant instead. |
| 42 | +To review our data module implementation, see the following file: [uData.pas]/[uData.cpp]. |
| 43 | + |
| 44 | +The instructions assume that you start with a Delphi or C++Builder project that already includes |
| 45 | +a configured data source for DevExpress Dashboards. |
| 46 | +This example application uses a memory-based dataset as the dashboard's data source |
| 47 | +(see `mdRevenueByIndustry` in the data module). |
| 48 | +To configure a dashboard data source in your project, refer to the following tutorial: |
| 49 | +[Create a dashboard using the Designer Dialog][designer]. |
| 50 | + |
| 51 | +### Step 1: Create a Dataset to Store Dashboard Layout and State Data |
| 52 | + |
| 53 | +1. Add a [TdxMemData] component to the data module (`mdLayouts` in the example). |
| 54 | +1. Add a [TDataSource] component to the data module (`dsLayouts` in the example). |
| 55 | + Assign the previously created dataset component to `TDataSource.DataSet`: |
| 56 | + |
| 57 | + > <img src="./images/create-bind-data-source.png" style="width: 50%" |
| 58 | + alt="Object Inspector panel displaying TDataSource properties."/> |
| 59 | +
|
| 60 | +1. Open the context menu for the dataset component and select **Field Editor…**: |
| 61 | + |
| 62 | + > <img src="./images/open-context-menu.png" style="width: 50%" |
| 63 | + alt="Context menu for the TdxMemData component displaying a 'Field Editor' option."/> |
| 64 | +
|
| 65 | +1. Click **Add…** to create a BLOB field ([ftBlob]) for layout data: |
| 66 | + |
| 67 | + > <img src="./images/create-layout-field.png" style="width: 50%" |
| 68 | + alt="New Field dialog adding a 'Layout' field of type ftBlob"/> |
| 69 | +
|
| 70 | +1. Click **Add…** to create a string field ([ftWideString]) for layout names: |
| 71 | + |
| 72 | + > <img src="./images/create-name-field.png" style="width: 50%" |
| 73 | + alt="New Field dialog adding a 'Name' field of type ftWideString"/> |
| 74 | +
|
| 75 | +1. Click **Add…** to create another BLOB field for dashboard states: |
| 76 | + |
| 77 | + > <img src="./images/create-state-field.png" style="width: 50%" |
| 78 | + alt="New Field dialog adding a 'State' field of type ftBlob"/> |
| 79 | +
|
| 80 | +1. (*Optional*) Preload persistent data to the dataset to make layouts available in the application upon first launch. |
| 81 | + |
| 82 | + This example includes a sample dashboard layout that displays revenue data from an included dataset. |
| 83 | + You can preload dashboard layout and data from [layout.dat] and [revenue.dat], respectively. |
| 84 | + Open the context menu for the dataset component, select **Persistent Editor…**, click **Load…**, and select the file. |
| 85 | + |
| 86 | + > <img src="./images/create-persistent-data.png" style="width: 50%" |
| 87 | + alt="Context menu for the TdxMemData component displaying a 'Persistent Editor' option."/> |
| 88 | +
|
| 89 | + Alternatively, you can use the Dashboard Designer later to import dashboard data from an XML file. |
| 90 | + |
| 91 | + |
| 92 | +## Step 2: Load a Dashboard Layout Definition |
| 93 | + |
| 94 | +To load a layout definition to the Dashboard Control ([TdxCustomDashboardControl]), you must specify |
| 95 | +dashboard name ([TdxCustomDashboardControl.DashboardName]), layout ([TdxCustomDashboardControl.Layout]), |
| 96 | +and, optionally, dashboard user interaction state ([TdxCustomDashboardControl.State]): |
| 97 | + |
| 98 | +<!-- start-code-block --> |
| 99 | +#### Delphi |
| 100 | + |
| 101 | +```delphi |
| 102 | +procedure TMainForm.LoadLayoutDefinition; |
| 103 | +begin |
| 104 | + // Ensure that the dataset has at least one record or a new record is being created |
| 105 | + if (DataModule1.mdLayouts.RecordCount = 0) and (DataModule1.mdLayouts.State <> dsInsert) then |
| 106 | + begin |
| 107 | + dxDashboardControl1.Clear; |
| 108 | + Exit; |
| 109 | + end; |
| 110 | + // Load dashboard name and layout from the database |
| 111 | + dxDashboardControl1.DashboardName := DataModule1.mdLayoutsName.AsString; |
| 112 | + dxDashboardControl1.Layout.Assign(DataModule1.mdLayoutsLayout); |
| 113 | + // Load a dashboard state if it is stored in the database |
| 114 | + if not DataModule1.mdLayoutsState.IsNull then |
| 115 | + dxDashboardControl1.State.Assign(DataModule1.mdLayoutsState); |
| 116 | +end; |
| 117 | +``` |
| 118 | +<!-- end-code-block --> |
| 119 | + |
| 120 | +To load a different dashboard in the Dashboard Control, assign a new dashboard name and layout. |
| 121 | +The assigned dashboard replaces the current layout definition and resets the dashboard state. |
| 122 | + |
| 123 | +You can also clear the Dashboard Control using [TdxCustomDashboardControl.Clear]. |
| 124 | + |
| 125 | + |
| 126 | +### Step 3: Display the Dashboard Designer |
| 127 | + |
| 128 | +Once you assign a dashboard layout definition to the Dashboard Control, |
| 129 | +you can display the [Dashboard Designer][designer] dialog: |
| 130 | + |
| 131 | +<!-- start-code-block --> |
| 132 | +#### Delphi |
| 133 | + |
| 134 | +```delphi |
| 135 | +procedure TMainForm.btnDesignClick(Sender: TObject); |
| 136 | +begin |
| 137 | + dxDashboardControl1.ShowDesigner; // Displays the Dashboard Designer |
| 138 | +end; |
| 139 | +``` |
| 140 | +<!-- end-code-block --> |
| 141 | + |
| 142 | +### Step 4: Store Dashboard State in a Dataset |
| 143 | + |
| 144 | +When a user interacts with the dashboard in the Dashboard Control or Designer, |
| 145 | +the value of [TdxCustomDashboardControl.State] changes and an |
| 146 | +[OnStateChanged][TdxCustomDashboardControl.OnStateChanged] event is called. |
| 147 | +Handle this event to save dashboard state changes to the database. |
| 148 | + |
| 149 | +<!-- start-code-block --> |
| 150 | +#### Delphi |
| 151 | + |
| 152 | +```delphi |
| 153 | +procedure TMainForm.dxDashboardControl1StateChanged( |
| 154 | + ASender: TdxCustomDashboardControl); |
| 155 | +begin |
| 156 | + // Start editing the active dataset record |
| 157 | + DataModule1.mdLayouts.Edit; |
| 158 | +
|
| 159 | + // Save the current dashboard state to the active record |
| 160 | + DataModule1.mdLayoutsState.Assign(dxDashboardControl1.State); |
| 161 | +
|
| 162 | + // Finish editing and post the modified record to the database |
| 163 | + DataModule1.mdLayouts.Post; |
| 164 | +end; |
| 165 | +``` |
| 166 | +<!-- end-code-block --> |
| 167 | + |
| 168 | +### Step 5: Store Dashboard Layouts in a Dataset |
| 169 | + |
| 170 | +When a user edits and saves a dashboard in the Dashboard Designer, |
| 171 | +the value of [TdxCustomDashboardControl.Layout] changes and an |
| 172 | +[OnLayoutChanged][TdxCustomDashboardControl.OnLayoutChanged] event is called. |
| 173 | +Handle this event to save layout changes to the database. |
| 174 | + |
| 175 | +<!-- start-code-block --> |
| 176 | +#### Delphi |
| 177 | + |
| 178 | +```delphi |
| 179 | +procedure TMainForm.dxDashboardControl1LayoutChanged( |
| 180 | + ASender: TdxCustomDashboardControl); |
| 181 | +begin |
| 182 | + if DataModule1.mdLayoutsName.AsString <> dxDashboardControl1.DashboardName then |
| 183 | + begin |
| 184 | + // Create and start editing a new dataset record |
| 185 | + DataModule1.mdLayouts.Append; |
| 186 | + DataModule1.mdLayoutsName.AsString := dxDashboardControl1.DashboardName; |
| 187 | + end |
| 188 | + else |
| 189 | + // Start editing the active dataset record |
| 190 | + DataModule1.mdLayouts.Edit; |
| 191 | +
|
| 192 | + // Save the dashboard layout to the database |
| 193 | + DataModule1.mdLayoutsLayout.Assign(dxDashboardControl1.Layout); |
| 194 | + // Finish editing and post the modified record to the database: |
| 195 | + DataModule1.mdLayouts.Post; |
| 196 | +end; |
| 197 | +``` |
| 198 | +<!-- end-code-block --> |
| 199 | + |
| 200 | +### Step 6: Persist Data between Application Sessions |
| 201 | + |
| 202 | +This step is applicable only to the memory-based [TdxMemData] datasource. |
| 203 | + |
| 204 | +To save the dataset to a file and restore data on app restart, |
| 205 | +handle `OnCreate` and `OnDestroy` events of the data module: |
| 206 | + |
| 207 | +<!-- start-code-block --> |
| 208 | +#### Delphi |
| 209 | + |
| 210 | +```delphi |
| 211 | +const |
| 212 | + DataFileName = 'data.dat'; |
| 213 | +
|
| 214 | +procedure TDataModule1.DataModuleCreate(Sender: TObject); |
| 215 | +begin |
| 216 | + if FileExists(DataFileName) then |
| 217 | + mdLayouts.LoadFromBinaryFile(DataFileName) |
| 218 | +end; |
| 219 | +
|
| 220 | +procedure TDataModule1.DataModuleDestroy(Sender: TObject); |
| 221 | +begin |
| 222 | + if mdLayouts.RecordCount > 0 then |
| 223 | + mdLayouts.SaveToBinaryFile(DataFileName) |
| 224 | +end; |
| 225 | +``` |
| 226 | +<!-- end-code-block --> |
| 227 | + |
| 228 | +## Files to Review |
| 229 | + |
| 230 | +- [uData.pas]/[uData.cpp] — stores dashboard layouts and supplies data to the dashboard. |
| 231 | +- [uMainForm.pas]/[uMainForm.cpp] — loads dashboard layouts from the data module |
| 232 | + and displays Dashboard Control and Dashboard Designer. |
| 233 | +- [layout.dat] and [revenue.dat] — store memory-based dataset states you can load to reproduce this example. |
| 234 | +- [data.dat] — stores the memory-based dataset state between application sessions. |
28 | 235 |
|
29 | | - |
30 | 236 |
|
31 | 237 | ## Documentation |
32 | 238 |
|
33 | | -* [TdxCustomDashboardControl.Layout](https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.Layout) |
34 | | -* [TdxBackendDataSetJSONConnection](https://docs.devexpress.com/VCL/dxBackend.ConnectionString.JSON.DataSet.TdxBackendDataSetJSONConnection) |
| 239 | +- [Introduction to DevExpress Dashboards for Delphi/C++Builder][dashboards-intro] |
| 240 | +- [Tutorial: Create a dashboard using the Designer Dialog][designer] |
| 241 | +- [Use JSON as a data source for dashboards (as demonstrated in the current example)][json-data-source] |
| 242 | +- [Save the dashboard layout to file on every change (code example)][save-to-file] |
| 243 | +- API reference: |
| 244 | + - [TdxCustomDashboardControl] (used to display a dashboard on an application form) |
| 245 | + - [TdxCustomDashboardControl.State] (a JSON-based dashboard state you can store in a BLOB dataset field) |
| 246 | + - [TdxCustomDashboardControl.Layout] (an XML-based layout template you can store in a BLOB dataset field) |
| 247 | + - [TdxCustomDashboardControl.DashboardName] (internal dashboard name that is not included in the layout or state) |
| 248 | + - [TdxCustomDashboardControl.OnStateChanged] (event called when a user interacts with a dashboard and changes its state) |
| 249 | + - [TdxCustomDashboardControl.OnLayoutChanged] (event called when a user edits and saves a dashboard in the Dashboard Designer) |
| 250 | + - [TdxMemData] (DevExpress in-memory dataset implementation) |
| 251 | + - [TDataSet] (contains generic database connection methods) |
| 252 | + - [TdxBackendDataSetJSONConnection] (supplies data to dashboards) |
| 253 | + |
| 254 | + |
| 255 | +<!-- documentation links --> |
| 256 | + |
| 257 | +[dashboards-intro]: https://docs.devexpress.com/VCL/405642/ExpressDashboards/vcl-dashboards |
| 258 | +[designer]: https://docs.devexpress.com/VCL/405774/ExpressDashboards/getting-started/create-dashboard-using-designer-dialog |
| 259 | +[json-data-source]: https://docs.devexpress.com/VCL/405747/ExpressCrossPlatformLibrary/vcl-backend/database-engines/vcl-backend-memory-based-data-storage |
| 260 | +[save-to-file]: https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.Layout#save-dashboard-layout-to-file-on-every-change |
| 261 | +[supported-dbms]: https://docs.devexpress.com/VCL/405703/ExpressCrossPlatformLibrary/vcl-backend/vcl-backend-supported-database-systems |
| 262 | + |
| 263 | +<!-- reference links --> |
| 264 | +[TdxCustomDashboardControl]: https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl |
| 265 | +[TdxCustomDashboardControl.Clear]: https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.Clear |
| 266 | +[TdxCustomDashboardControl.DashboardName]: https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.Layout |
| 267 | +[TdxCustomDashboardControl.Layout]: https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.Layout |
| 268 | +[TdxCustomDashboardControl.State]: https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.State |
| 269 | +[TdxCustomDashboardControl.OnLayoutChanged]: https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.OnLayoutChanged |
| 270 | +[TdxCustomDashboardControl.OnStateChanged]: https://docs.devexpress.com/VCL/dxDashboard.Control.TdxCustomDashboardControl.OnStateChanged |
| 271 | +[TdxBackendDataSetJSONConnection]: https://docs.devexpress.com/VCL/dxBackend.ConnectionString.JSON.DataSet.TdxBackendDataSetJSONConnection |
| 272 | +[TdxMemData]: https://docs.devexpress.com/VCL/dxmdaset.TdxMemData |
| 273 | + |
| 274 | + |
| 275 | +<!-- external documentation links --> |
| 276 | +[TDataSet]: https://docwiki.embarcadero.com/Libraries/Athens/en/Data.DB.TDataSet |
| 277 | +[TDataSource]: https://docwiki.embarcadero.com/Libraries/Athens/en/Data.DB.TDataSource |
| 278 | +[ftString]: https://docwiki.embarcadero.com/Libraries/Athens/en/Data.DB.TFieldType |
| 279 | +[ftWideString]: https://docwiki.embarcadero.com/Libraries/Athens/en/Data.DB.TFieldType |
| 280 | +[ftBlob]: https://docwiki.embarcadero.com/Libraries/Athens/en/Data.DB.TFieldType |
| 281 | + |
| 282 | + |
| 283 | +<!-- in-repository links --> |
| 284 | +[uData.pas]: ./Delphi/uData.pas |
| 285 | +[uData.cpp]: ./CPB/uData.cpp |
| 286 | +[data.dat]: ./Delphi/data.dat |
| 287 | +[layout.dat]: ./layout.dat |
| 288 | +[revenue.dat]: ./revenue.dat |
| 289 | +[uMainForm.pas]: ./Delphi/uMainForm.pas |
| 290 | +[uMainForm.cpp]: ./CPB/uMainForm.cpp |
| 291 | + |
| 292 | + |
| 293 | +## More Examples |
| 294 | + |
| 295 | +- [Pass Hidden Parameters to a SQL Query][hidden-parameter-example] |
| 296 | +- [Generate Dashboards in a Backend / Service Application][non-interactive-example] |
| 297 | +- [Store Layouts in XML Files (DevExpress Reports for Delphi/C++Builder)][file-example] |
| 298 | + |
| 299 | +[hidden-parameter-example]: https://github.com/DevExpress-Examples/vcl-dashboards-pass-hidden-parameters-to-custom-sql-query |
| 300 | +[file-example]: https://github.com/DevExpress-Examples/vcl-reports-store-layout-template-file |
| 301 | +[non-interactive-example]: https://github.com/DevExpress-Examples/vcl-reports-store-layout-template-database |
35 | 302 |
|
36 | 303 | <!-- feedback --> |
37 | 304 | ## Does This Example Address Your Development Requirements/Objectives? |
|
0 commit comments