:: MVP ::
|
|
:: RSS ::
|
|
|
Идея пришла во время решения задачи о выборке данных из БД и представлении ее пользователю в различных срезах
(далее фильтры), при этом фильтры заранее известны и реализовывать в интерфейсе средства динамической фильтрации
не нужно. Вопрос в данной задаче сводился к тому, как лучше реализовать переключение между различными фильтрами в
пользовательском интерфейсе.
Вариантов решения было несколько. Можно было воспользоваться RadioGroup, но если фильтров много, то он плохо
вписывается в пользовательский интерфейс (занимает много места), хотя принцип работы этого компонента подходит
для решения задачи как нельзя лучше. ComboBox тоже не совсем хорош, в нем нельзя сразу увидеть все имеющиеся фильтры
(даже когда он раскрыт, если фильтров достаточно много). TabSet уже лучше подходит к задаче, но организация
вкладок в компоненте не очень удобна - их нельзя расположить в несколько рядов, а "перемотка" вкладок маленькими кнопками
со стрелочками очень неудобна.
Наиболее оптимальный для решения задачи компонент – PageControl, с возможностью многострочной организации вкладок и
принципом работы, схожим с RadioGroup. Но небольшая ложка дегтя нашлась и тут. Вкладки в данной задаче совершенно ни к
чему. В самом деле, не создавать же DBGrid на каждой вкладки (в данном случае это излишняя ресурсоемкость),
когда можно, и даже нужно, обойтись всего одном. Сначала была идея обрабатывать событие PageControlChange и
перемещать DBGrid на активную вкладку путем изменения родителя с одновременной сменой фильтра. Решение вполне рабочее, но
все еще не устраивало наличие вкладок. И вот, после не долгих раздумий было решено от них избавиться, превратив PageControl
в подобие RadioGroup. Вот какой в результате получился код:
type
TPageControl = class(Vcl.ComCtrls.TPageControl)
protected
procedure ConstrainedResize(var MinWidth, MinHeight, MaxWidth,
MaxHeight: Integer); override;
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;
end;
{...}
implementation
{ TPageControl }
procedure TPageControl.WMPaint(var Msg: TWMPaint);
const
{$J+}
f: Boolean = False;
{$J-}
begin
inherited;
if not f then
begin
Perform(WM_SYSCOMMAND, SC_MINIMIZE, 0);
Perform(WM_SYSCOMMAND, SC_RESTORE, 0);
f := True;
end;
end;
procedure TPageControl.ConstrainedResize(var MinWidth, MinHeight, MaxWidth,
MaxHeight: Integer);
begin
inherited;
MinHeight := TabRect(0).Height * RowCount + 2;
MaxHeight := TabRect(0).Height * RowCount + 2;
end;
|
Обработчик ConstrainedResize понятен, высота компонента ограничивается суммарной высотой заголовков
вкладок, тем самым сами вкладки просто отсекаются. Немного интереснее с обработчиком WMPaint, он появился
в результате того, что нужно было применить изменения к компоненту сразу после запуска приложения, и это
оказалось единственным рабочим решением, которое удалось найти.
В качестве примера рассмотрим абстрактные бухгалтерские данные о приходах и расходах:
type
TRec = record
fId: Integer;
fType: string[6];
fDate: string;
fValue: Double;
end;
const
Values: array[0..29] of TRec = (
(fId: 1; fType: 'приход'; fDate: '01.01.2017'; fValue: 51234.25),
(fId: 2; fType: 'приход'; fDate: '02.02.2017'; fValue: 236241.11),
(fId: 3; fType: 'расход'; fDate: '05.02.2017'; fValue: 7785.5 ),
(fId: 4; fType: 'расход'; fDate: '05.02.2017'; fValue: 14751.75),
(fId: 5; fType: 'приход'; fDate: '08.02.2017'; fValue: 220541.07),
(fId: 6; fType: 'расход'; fDate: '15.02.2017'; fValue: 886.59),
(fId: 7; fType: 'приход'; fDate: '22.02.2017'; fValue: 55689.12),
(fId: 8; fType: 'приход'; fDate: '28.02.2017'; fValue: 53247.36),
(fId: 9; fType: 'расход'; fDate: '02.03.2017'; fValue: 4532.9 ),
(fId: 10; fType: 'расход'; fDate: '03.03.2017'; fValue: 84521.03),
(fId: 11; fType: 'приход'; fDate: '08.03.2017'; fValue: 154237.5 ),
(fId: 12; fType: 'приход'; fDate: '17.03.2017'; fValue: 45122.32),
(fId: 13; fType: 'приход'; fDate: '20.03.2017'; fValue: 225451.66),
(fId: 14; fType: 'расход'; fDate: '22.03.2017'; fValue: 33556.11),
(fId: 15; fType: 'расход'; fDate: '10.04.2017'; fValue: 55487.21),
(fId: 16; fType: 'приход'; fDate: '11.04.2017'; fValue: 547783.12),
(fId: 17; fType: 'приход'; fDate: '19.04.2017'; fValue: 15562.3 ),
(fId: 18; fType: 'расход'; fDate: '22.04.2017'; fValue: 85462.32),
(fId: 19; fType: 'расход'; fDate: '01.05.2017'; fValue: 4452.45),
(fId: 20; fType: 'приход'; fDate: '11.05.2017'; fValue: 144325.1 ),
(fId: 21; fType: 'расход'; fDate: '13.05.2017'; fValue: 45322.54),
(fId: 22; fType: 'приход'; fDate: '19.05.2017'; fValue: 14895.99),
(fId: 23; fType: 'приход'; fDate: '23.05.2017'; fValue: 54795.32),
(fId: 24; fType: 'расход'; fDate: '29.05.2017'; fValue: 1255.5 ),
(fId: 25; fType: 'приход'; fDate: '06.06.2017'; fValue: 77652.1 ),
(fId: 26; fType: 'приход'; fDate: '13.06.2017'; fValue: 77892.12),
(fId: 27; fType: 'расход'; fDate: '18.06.2017'; fValue: 7451.22),
(fId: 28; fType: 'расход'; fDate: '24.06.2017'; fValue: 15486.33),
(fId: 29; fType: 'приход'; fDate: '06.07.2017'; fValue: 150000 ),
(fId: 30; fType: 'приход'; fDate: '17.07.2017'; fValue: 15323.6 )
);
|
Заполним этими данными таблицу:
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
Tab: TTabSheet;
begin
// Добавление полей в ClientDataSet
ClientDataSet.Close;
ClientDataSet.FieldDefs.Clear;
with ClientDataSet.FieldDefs.AddFieldDef do
begin
DataType := ftInteger;
Name := 'ID';
Size := 0;
end;
with ClientDataSet.FieldDefs.AddFieldDef do
begin
DataType := ftString;
Name := 'TYPE';
Size := 6;
end;
with ClientDataSet.FieldDefs.AddFieldDef do
begin
DataType := ftDate;
Name := 'DATE';
Size := 0;
end;
with ClientDataSet.FieldDefs.AddFieldDef do
begin
DataType := ftFloat;
Name := 'VALUE';
Size := 0;
end;
ClientDataSet.CreateDataSet;
// Заполнение ClientDataSet
for i := Low(Values) to High(Values) do
begin
ClientDataSet.Append;
ClientDataSet.FieldByName('ID').AsInteger := Values[i].fId;
ClientDataSet.FieldByName('TYPE').AsString := Values[i].fType;
ClientDataSet.FieldByName('DATE').AsString := Values[i].fDate;
ClientDataSet.FieldByName('VALUE').AsFloat := Values[i].fValue;
ClientDataSet.Post;
end;
end;
|
Фильтры будут следующими - отображать все данные, только приходы или только расходы:
type
TFilter = record
Caption: string;
Filter: string;
end;
const
Filters: array[0..2] of TFilter = (
(Caption: 'Все'; Filter: ''),
(Caption: 'Приход'; Filter: '[TYPE]=''приход'''),
(Caption: 'Расход'; Filter: '[TYPE]=''расход''')
);
|
Теперь в событии PageControlChange остается просто поменять фильтр в DataSet. Результат - удобное
представление данных по нескольким (заранее предопределенным) фильтрам в пользовательском интерфейсе.
Из преимуществ:
- экономит много (и без того ценного) места на форме;
- сразу видны все возможные фильтры (при многострочной организации вкладок);
- удобно переключаться между различными фильтрами.
procedure TForm1.PageControlChange(Sender: TObject);
begin
ClientDataSet.Filtered := False;
if Filters[PageControl.ActivePageIndex].Filter <> '' then
begin
ClientDataSet.Filter := Filters[PageControl.ActivePageIndex].Filter;
ClientDataSet.Filtered := True;
end;
StatusBar.Panels[0].Text := 'Записей: ' +
IntToStr(ClientDataSet.RecordCount);
end;
|
На этом все, удачи!
.: Пример к данной статье :.
|
При использовании материала - ссылка на сайт обязательна
|
|