Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.117.196.217] |
|
Сообщ.
#1
,
|
|
|
Начинаю серию постов написания демо-приложения БД с использованием новейшей технологии FireMonkey!
Подобное я уже писал на MFC, ща буду щупать FMX и FireDAC! Ну держитесь! В демо я буду юзать готовую БД, знаете какую? Chinook! А в качестве движка SQLite! Потом это демо запустим на гавнофоне! мля думаю буде интресно посмотреть как оно работает! ПС. Разработку веду на Delphi 10.3 Rio, ссылки на сайт Chinook https://archive.codeplex.com/?p=chinookdatabase и тьюториал, где также есть сама БД и диаграмма таблиц http://www.sqlitetutorial.net/sqlite-sample-database/ |
Сообщ.
#2
,
|
|
|
Итак начнем, что мы имеем? мы имеем готовую БД, которая хорошо подходит для демо приложения.
Что это за приложение будет? Это будет класическое приложение БД, которое мы постепенно нарастим функционалом и также постепено доведем до совершенства! Я уже смотрел схему этой БД, в ней 8 таблиц, которые связаны между собой. Для начала надо разобраться в данными хранящимися в них. Давайте напишем первый прототип приложения, с учетом того что оно будет запускаться на смартфоне. Мы будем отображать каждую таблицу в TListView. Говорят он хорошо заходит для этого. А каждый листвью будет располагаться на отдельной вкладке компонент TTabControl. Говорят он хорошо подходит для дизайна под смартфон, вот и проверим |
Сообщ.
#3
,
|
|
|
Амиго, тебе бы в такой форме на хабр писать или в блог. Форум все же другой формат подразумевает
|
Сообщ.
#4
,
|
|
|
Цитата Fr0sT @ Амиго, тебе бы в такой форме на хабр писать или в блог. Форум все же другой формат подразумевает Да я ж учусь, блоги пока ран мне, да и могут возникнуть вопросы по теме Продолжаю вещать Когда делал прототип меня поразило то что в Делфи почти все можно сделать без написания кода! Чтобы отобразить данные таблицы Artists из Chinook.db вот что мне понадобилось: - Компоненты TListView, TBindSourceDB, TBindingsList на главной форме - TFDConnection, TFDQuery на дата форме - настроил TFDConnection на подключение к Chinook.db через SQLite, настоил select запрос в TFDQuery. - настроил TListView, через ItemAppearance -> Item добавил два итема ArtistID и Name. - заюзал LiveBindings Designer, чтобы связать TBindSourceDB, TBindingsList и TFDQuery. Там все визуально! ВСЕ! MVVM в действии никаких тебе заморочек с кодом! В архиве код ручного кодирования, но без MVVM Прикреплённый файлVersion1.zip (55,34 Кбайт, скачиваний: 107) |
Сообщ.
#5
,
|
|
|
Давайте продолжим кодирование нашего примера, оставим пока LiveBinding, визуальное кодирование удобно, но не всегда гибко.
Итак у есть метод обновления TListView UpdateArtistListView, произведем его рефакторинг. Почему так скоро? Потому что потом будет поздно Основной принцип успешного кодирования это пишем гавнокод потом его рефакторинг, только так можно получить из *авна конфетку Вы спросите а из любого гавнокода можно конфетку сделать? Да у меня всегда получалось это, но я опытный рефакторщик Вот исходный код UpdateArtistsListView: procedure TChinookMainForm.UpdateArtistsListView; begin var LQuery:= ChinookDataModule.ArtistSelectQuery; ArtistsListView.BeginUpdate; try LQuery.Open; try while not LQuery.Eof do begin var item:= ArtistsListView.Items.Add; item.Tag:= LQuery.FieldByName('ArtistID').AsInteger; item.Objects.FindObjectT<TListItemText>('ArtistID').Text:= LQuery.FieldByName('ArtistID').AsString; item.Objects.FindObjectT<TListItemText>('Name').Text:= LQuery.FieldByName('Name').AsString; LQuery.Next; end; finally LQuery.Close; end; finally ArtistsListView.EndUpdate; end; end; Что в нем не так? Все так он рабочий, но вот не гибкий, поскольку жестко привязан к данным, нам надо развязать Что мы делаем, выносим все ссылки на данные это TListView, TFDQuery: procedure TChinookMainForm.UpdateArtistsListViewV1(AListView: TListView; AQuery: TFDQuery); begin AListView.BeginUpdate; try AQuery.Open; try while not AQuery.Eof do begin var item:= AListView.Items.Add; ////<-- не выношу за цикл для оптимизации // VVV item.Tag:= AQuery.FieldByName('ArtistID').AsInteger; item.Objects.FindObjectT<TListItemText>('ArtistID').Text:= AQuery.FieldByName('ArtistID').AsString; item.Objects.FindObjectT<TListItemText>('Name').Text:= AQuery.FieldByName('Name').AsString; // ^^^ этот блок кода надо изменять AQuery.Next; end; finally AQuery.Close; end; finally AListView.EndUpdate; end; end; но еще не все там есть кусок кода, который надо тоже менять (я его отметил комментом), поскольку поля то у разных запросов тоже разные! Как быть? Идеи? По путно отмечу, что мы не оптимизируем код, а меняем структуру кода, например в случае с переменной item что находится внутри цикла, ее можно конечно вынести из цикла и возможно это увеличит производительность, но не на много и да преждевременная оптимизация вредна! Ее надо делать тока если код реально тормозной. Я ее не вынес наружу ибо так лучше смотрится и не отвлекается |
Сообщ.
#6
,
|
|
|
Решение простое юзаем фунциональщину!
Наша функция переходит в разряд высших функций! Зерте чо я делаю: procedure TChinookMainForm.UpdateListViewV2(AListView: TListView; AQuery: TFDQuery; InitItemProc: TProc<TListViewItem>); begin AListView.BeginUpdate; try AQuery.Open; try while not AQuery.Eof do begin var item:= AListView.Items.Add; InitItemProc(item); //<-- вызов иницилизации AQuery.Next; end; finally AQuery.Close; end; finally AListView.EndUpdate; end; end; и вызов UpdateListViewV2(ArtistsListView, ChinookDataModule.ArtistSelectQuery, procedure (item: TListViewItem) begin var query:= ChinookDataModule.ArtistSelectQuery; item.Tag:= query.FieldByName('ArtistID').AsInteger; item.Objects.FindObjectT<TListItemText>('ArtistID').Text:= query.FieldByName('ArtistID').AsString; item.Objects.FindObjectT<TListItemText>('Name').Text:= query.FieldByName('Name').AsString; end ); Тут есть одна особенность, не стал передавать в функцию InitItemProc ссылку на запрос TFDQuery, а внутри функции просто юзается внешняя ссылка ChinookDataModule.ArtistSelectQuery. Вот так уже гибче, теперь мы можем создать еще листвьюшек и добавить туда запросы на остальные таблицы. Например вот как может выглядеть вызов для Album: UpdateListViewV2(AlbumsListView, ChinookDataModule.AlbumSelectQuery, procedure (item: TListViewItem) begin var query:= ChinookDataModule.AlbumSelectQuery; item.Tag:= query.FieldByName('AlbumId').AsInteger; item.Text:= query.FieldByName('Title').AsString; end ); ну и так далее. Другой варик юзать один и тот же листвью для отображения разных запросов, но я не стал так делать. Почему? Потому что тогда придется еще в коде писать настройки каждой вьюшки |
Сообщ.
#7
,
|
|
|
Вынес длинющие вызовы FindObjectT с отдельный метод SetListItemText чтобы уменьшить объем кода анонимок:
procedure TChinookMainForm.SetListItemText(AItem: TListViewItem; AQuery: TFDQuery; itemName, fieldName: string); begin if fieldName = '' then fieldName:= itemName; AItem.Objects.FindObjectT<TListItemText>(itemName).Text:= AQuery.FieldByName(fieldName).AsString; end; procedure TChinookMainForm.UpdateArtistsListViewActionExecute(Sender: TObject); begin UpdateListView(ArtistsListView, ChinookDataModule.ArtistSelectQuery, procedure (item: TListViewItem; query: TFDQuery) begin item.Tag:= query.FieldByName('ArtistId').AsInteger; SetListItemText(item, query, 'ArtistId'); //<-- тут SetListItemText(item, query, 'Name'); //<-- и тут end ); end; Еще как оказалось названия итемов листвьюшек различают строчные и прописные буквы! Поэтому надо передавать их имена в соотвествии с регистром букв, что не совсем типично для Делфи. Случайно напоролся на эту фичу, но не сразу понял в чем дело Попутно обернул вызовы UpdateListView в действия, для удобства их вызова |
Сообщ.
#8
,
|
|
|
Так вроде все красиво и работает
Но есть одно но, мы отвязали данные только функции UpdateListView, но что если нам потом потребуется получать данные не от SQLite, а например из облака? Итак отделяем морду от тела Пишем модель данных, что представляют содержимое наших таблиц. Для начала берем таблицу Artists: unit uChinookModel; interface uses System.Generics.Collections; type TArtist = record ArtistId: integer; Name: string; constructor Create(AArtistId: integer; AName: string); end; TArtistList = TList<TArtist>; IArtistData = interface function GetArtistList: TArtistList; end; implementation { TArtist } constructor TArtist.Create(AArtistId: integer; AName: string); begin ArtistId:= AArtistId; Name:= AName; end; end. далее в модуле TChinookDataModule, определяем ArtistData и реализуем IArtistData: type TChinookDataModule = class(TDataModule) ... private FArtistData: IArtistData; public property ArtistData: IArtistData read FArtistData; end; .... implementation type TArtistData = class(TInterfacedObject, IArtistData) private FDataModule: TChinookDataModule; function GetArtistList: TArtistList; public constructor Create(ADataModule: TChinookDataModule); end; { TArtistData } constructor TArtistData.Create(ADataModule: TChinookDataModule); begin FDataModule:= ADataModule; end; function TArtistData.GetArtistList: TArtistList; var artist: TArtist; begin Result := TList<TArtist>.Create; var LQuery := FDataModule.ArtistSelectQuery; LQuery.Open; try while not LQuery.Eof do begin artist.ArtistId := LQuery.FieldByName('ArtistId').AsInteger; artist.Name := LQuery.FieldByName('Name').AsString; Result.Add(artist); LQuery.Next; end; finally LQuery.Close; end; end; procedure TChinookDataModule.DataModuleCreate(Sender: TObject); begin FArtistData:= TArtistData.Create(self); end; Теперь надо изменить код UpdateListView морды procedure TChinookMainForm.UpdateListView(AListView: TListView; AArtistData: IArtistData); begin AListView.BeginUpdate; try for var artist: TArtist in AArtistData.GetArtistList do begin var item: TListViewItem := AListView.Items.Add; item.Tag:= artist.ArtistId; item.Objects.FindObjectT<TListItemText>('ArtistId') .Text:=artist.ArtistId.ToString; item.Objects.FindObjectT<TListItemText>('Name').Text:=artist.Name; end; finally AListView.EndUpdate; end; end; Я ее не стал делать через колбек, этот рефакторинг мы уже умеем Как видим кода много, но зато теперь если нам надо подключиться как я уже говорил к облаку, то мы просто пишем модуль реализации IArtistData и ВСЕ! Морду не надо будет уже править, ибо она получает интерфейc IArtistData. Цикл while not LQuery.Eof do переместился в модуль TChinookDataModule где ему и место, а в UpdateListView идет цикл по списку TArtistList, т.е. по обобщеным данным. Сам же список формируется за пределами морды и получается через свойство ArtistData модуля TChinookDataModule. По аналогии делаем все тоже для остальных таблиц. |
Сообщ.
#9
,
|
|
|
Возникла проблема у нас UpdateListView снова зависит от данных, в даном случае ему можно передавать тока IArtistData. Как быть? Идеи?
Ответ Надо передавать не интерфейс, а сам список. Также надо типизировать UpdateListView. Пример позже запушу. |
Сообщ.
#10
,
|
|
|
Вот что получается
uChinookMainForm: type TChinookMainForm = class(TForm) ToolBar1: TToolBar; Label1: TLabel; TabControl1: TTabControl; ArtistsTabItem: TTabItem; ArtistsListView: TListView; AlbumsTabItem: TTabItem; AlbumsListView: TListView; ActionList1: TActionList; UpdateArtistsListViewAction: TAction; UpdateAlbumsListViewAction: TAction; procedure FormCreate(Sender: TObject); procedure UpdateArtistsListViewActionExecute(Sender: TObject); procedure UpdateAlbumsListViewActionExecute(Sender: TObject); procedure ArtistsTabItemClick(Sender: TObject); procedure AlbumsTabItemClick(Sender: TObject); private procedure SetListItemText(AItem: TListViewItem; AItemName, AValue: string); public procedure UpdateListView<T>(AListView: TListView; AList: TList<T>; InitItemProc: TProc<TListViewItem, T>); end; var ChinookMainForm: TChinookMainForm; implementation {$R *.fmx} {$R *.NmXhdpiPh.fmx ANDROID} uses uChinookDataModule {$IFDEF MSWINDOWS} , Winapi.Windows {$ENDIF} ; procedure TChinookMainForm.SetListItemText(AItem: TListViewItem; AItemName, AValue: string); begin AItem.Objects.FindObjectT<TListItemText>(AItemName).Text:= AValue; end; procedure TChinookMainForm.UpdateListView<T>(AListView: TListView; AList: TList<T>; InitItemProc: TProc<TListViewItem, T>); begin AListView.BeginUpdate; try AListView.Items.Clear; for var elem in AList do begin var item := AListView.Items.Add; InitItemProc(item, elem); end; finally AListView.EndUpdate; end; end; Модуль uChinookModel: type TArtist = record ArtistId: integer; Name: string; end; TAlbum = record AlbumId: integer; Title: string; ArtistId: integer; end; TArtistList = TList<TArtist>; TAlbumList = TList<TAlbum>; IArtistData = interface function GetArtistList: TArtistList; end; IAlbumData = interface function GetAlbumList: TAlbumList; end; uChinookDataModule: type TChinookDataModule = class(TDataModule) ChinookConnection: TFDConnection; ArtistSelectQuery: TFDQuery; AlbumSelectQuery: TFDQuery; procedure ChinookConnectionBeforeConnect(Sender: TObject); procedure DataModuleCreate(Sender: TObject); private FArtistData: IArtistData; FAlbumData: IAlbumData; public property ArtistData: IArtistData read FArtistData; property AlbumData: IAlbumData read FAlbumData; end; implementation {%CLASSGROUP 'FMX.Controls.TControl'} {$R *.dfm} uses System.IOUtils; type TBaseData = class abstract(TInterfacedObject) private FDataModule: TChinookDataModule; public constructor Create(ADataModule: TChinookDataModule); function ToList<T>(AQuery: TFDQuery; AFunc: TFunc<TFDQuery, T>): TList<T>; end; TArtistData = class(TBaseData, IArtistData) private function GetArtistList: TArtistList; end; TAlbumData = class(TBaseData, IAlbumData) private function GetAlbumList: TAlbumList; end; procedure TChinookDataModule.ChinookConnectionBeforeConnect(Sender: TObject); begin ChinookConnection.Params.Values['Database']:= TPath.Combine( TPath.GetDocumentsPath, 'chinook.db'); end; procedure TChinookDataModule.DataModuleCreate(Sender: TObject); begin FArtistData:= TArtistData.Create(self); FAlbumData:= TAlbumData.Create(self); end; { TArtistData } function TArtistData.GetArtistList: TArtistList; begin Result := ToList<TArtist>(FDataModule.ArtistSelectQuery, function(query: TFDQuery): TArtist begin Result.ArtistId := query.FieldByName('ArtistId').AsInteger; Result.Name := query.FieldByName('Name').AsString; end ); end; { TAlbumData } function TAlbumData.GetAlbumList: TAlbumList; begin Result := ToList<TAlbum>(FDataModule.AlbumSelectQuery, function(query: TFDQuery): TAlbum begin Result.AlbumId := query.FieldByName('AlbumId').AsInteger; Result.Title := query.FieldByName('Title').AsString; Result.ArtistId:= query.FieldByName('ArtistId').AsInteger; end ); end; { TBaseData } constructor TBaseData.Create(ADataModule: TChinookDataModule); begin FDataModule:= ADataModule; end; function TBaseData.ToList<T>(AQuery: TFDQuery; AFunc: TFunc<TFDQuery, T>): TList<T>; begin Result := TList<T>.Create; AQuery.Open; try while not AQuery.Eof do begin Result.Add(AFunc(AQuery)); AQuery.Next; end; finally AQuery.Close; end; end; Кроме метода UpdateListView изменил реализацию модели, создал дополнительный класс TBaseData, чтобы вынести некоторый общий код см обобщеный метод ToList. Прикреплённый файлVersion2.zip (60,94 Кбайт, скачиваний: 106) Добавлено Теперь все пучком, но! Ща у нас тейблы один в один отображаются во вьюшках, а что если нам надо данные миксовать! Ну например отобразить AlbumName и ArtistName или TrackName, AlbumName, ArtistName ну и тд. Ответ Меняем морду согласно требованию, и добавляем след код: procedure TChinookMainForm.UpdateAlbumsListViewActionExecute(Sender: TObject); begin var ArtistList:=ChinookDataModule.ArtistData.GetArtistList; UpdateListView<TAlbum>(AlbumsListView, ChinookDataModule.AlbumData.GetAlbumList, procedure (item: TListViewItem; album: TAlbum) begin item.Tag:= album.AlbumId; SetListItemText(item, 'AlbumId', album.AlbumId.ToString); SetListItemText(item, 'Title', album.Title); // поиск album.ArtistId // VVVV for var artist: TArtist in ArtistList do begin if artist.ArtistId = album.ArtistId then begin SetListItemText(item, 'ArtistName', artist.Name); Break; end; end; end ); end; Варик создать запрос и уже его обрабатывать, но тут я думаю будет больше гемора. Заметьте я тут не юзаю Spring4D. Почему?! Да просто не хотел подключать в демке другие библы Возможно в следущей версии подключу, а то циклами не удобно хотя и я изрядно попотел над длинным синтаксисом Spring Да еще я вынес обращение к GetArtistList наружу для оптимизации. Вот тут как раз она нужна. Если оставить внутри, то на каждую строчку альбома будет выполняться запрос всего списка артистов снова и снова. Тут мы видим во всей красе силу функционального стиля програмирования. Внутри анонимки мы обращемся ко списку ArtistList. Это называется замыкание. Гибко однако. |
Сообщ.
#11
,
|
|
|
складно пишешь, ток никуя не понятно)
|
Сообщ.
#12
,
|
|
|
Цитата vasya2019 @ складно пишешь, ток никуя не понятно) Дядка главное что я понел Возвращаясь к демо. Все пашет, но! Что если.. нам надо ввести например ArtistName и число альбомов выпущеных этим артистом? Как быть? Идеи? Ответ Все как и раньше меняем морду и пишем код: procedure TChinookMainForm.UpdateArtistsListViewActionExecute(Sender: TObject); begin var AlbumList:= ChinookDataModule.AlbumData.GetAlbumList; UpdateListView<TArtist>(ArtistsListView, ChinookDataModule.ArtistData.GetArtistList, procedure (item: TListViewItem; artist: TArtist) begin item.Tag:= artist.ArtistId; SetListItemText(item, 'ArtistId', artist.ArtistId.ToString); SetListItemText(item, 'Name', artist.Name); // подсчет числа альбомов у артиста // VVV var albumCount:= 0; for var album in AlbumList do begin if album.ArtistId = artist.ArtistId then Inc(albumCount); end; SetListItemText(item, 'AlbumCount', albumCount.ToString); end ); end; походу тут уже у нас не будет проблем с модификацией морды, че б я не придумал все изи решается. О! что если нам надо ваще другую морду делать? Как быть? Идеи? Объясняю. К примеру нам нужен график вместо листвью! |
Сообщ.
#13
,
|
|
|
Ответ
Отделяем кожу от морды Короче надо выносить всю логику в отдельный модуль так называемый ViewModel. У нас будет реализован шаблон MVVM (модель-вью-вьюмодель). Это один из паттернов програмирования таких как MVC, MVP. Чем они отличаются? Да хз я запуталси , вроде как толщиной промежуточного слоя между View и Model В одном случае он называется контролер, а с другом презентер. Кто такой презентер не знаю Где тут логика? Например в двух предыдуших примерах я вычислял значения полей для листвьюшек, и чем дальше тем больше будет логики! Мы еще не писали код редактирования данных |
Сообщ.
#14
,
|
|
|
Кто знает как настроить TListView, чтобы были заголовки у него?
|
Сообщ.
#15
,
|
|
|
Просмотрел кучу мобильных приложений и понял что не правильно делал его морду. Исходно я делал в десктоп стиле. В следующей версии я предоствлю новый дизайн. Век живи век учись
|