![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
Внедрение и линковка компонентовDelphi , Компоненты и Классы , Создание компонент
Оформил: DeeCo Автор: Горбань С.В. Модуль демонстрирует возможности по "Внедрению" и "Сцепке" компонентов. В основном все д/б понятно из подстрочных комментариев. Для чего нужно: Задача - содать специализированный LightWeight вариант TChart. Работа ведется несколькими программистами. ВСЕ элементы д/б объектами, а по возможности и самостоятельными компонентами. Например - полоса скроллинга по данным. Она должна быть либо "встроенной" (принадлежать базовому компоненту) либо внешней. Причем при работе (в приложении) различий быть не должно...Первый маленький элемент - полоса скроллинга по данным и контейнер для нее. Компонент вполне самостоятельный и вполне может быть полезен Вне контекста задачи. Примечания:
unit AltChartMain; interface {Заранее извиняюсь за цветовую гамму... Делайте как кому нравится :-)} {ВНИМАНИЕ!!!! Пример тестировался под D6, и меня предупредили, что в D5 нет SetSubComponent. Самому проверить негде, так что будте внимательны!} uses Windows, Messages, SysUtils, Classes, Controls, StdCtrls, ExtCtrls, Graphics, Math, MyMath; resourcestring SMinMaxError = 'Max ДОЛЖЕН быть больше Min. EMinMaxError.' + Chr(13) + Chr(13); type EMinMaxError = class(Exception); //Попытка задать Min > Max TGraphScrollKind = (skHorizontal, skVertical); TGraphScrollLayout = (slTop, slCenter, slBottom); //Полоса скроллинга по данным TGraphScroll = class(TGraphicControl) private FLineWidth: Integer; FLineColor: TColor; FSliderWidth: Integer; FSliderLength: Integer; FSliderColor: TColor; FHSC: Integer; //Horisontal Slider Center. Для ускорения отрисовки. FVSC: Integer; //Vertical Slider Center. Для ускорения отрисовки. FPosition: Integer; FSliderRect: TRect; //Это чтобы по быстрому определить, ткнули мы мышом по слайдеру или нет... FMin: Integer; FMax: Integer; FSliderCaptured: Boolean; FGraphScrollKind: TGraphScrollKind; //Слайдер зацепили мышом... FBegDragCoord: TPoint; //Коорд. мыша в момент "зацепа" FBegDragPos: Integer; //Position в момент "зацепа" FGraphScrollLayout: TGraphScrollLayout; procedure SetGeometry(const Index, Value: Integer); procedure SetColor(const Index: Integer; const Value: TColor); procedure SetValues(AMin, AMax, APosition: Integer); procedure RecalcGeometry; procedure SetMax(const Value: Integer); procedure SetMin(const Value: Integer); procedure SetPosition(const Index, Value: Integer); procedure SetGraphScrollKind(const Value: TGraphScrollKind); procedure SetGraphScrollLayout(const Value: TGraphScrollLayout); protected procedure Paint; override; procedure Resize; override; procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure ConstrainedResize(var MinWidth, MinHeight, MaxWidth, MaxHeight: Integer); override; procedure RequestAlign; override; function CanAutoSize(var NewWidth, NewHeight: Integer): Boolean; override; public constructor Create(AOwner: TComponent); override; published property Anchors; property Align; property AutoSize; property LineColor: TColor index 0 read FLineColor write SetColor; property SliderColor: TColor index 1 read FSliderColor write SetColor; property LineWidth: Integer index 0 read FLineWidth write SetGeometry; property SliderWidth: Integer index 1 read FSliderWidth write SetGeometry; property SliderLength: Integer index 2 read FSliderLength write SetGeometry; property Position: Integer index 0 read FPosition write SetPosition; property Min: Integer read FMin write SetMin; property Max: Integer read FMax write SetMax; property Kind: TGraphScrollKind read FGraphScrollKind write SetGraphScrollKind; property Layout: TGraphScrollLayout read FGraphScrollLayout write SetGraphScrollLayout; end; //Компонент - контейнер TModContainer = class(TPanel) private FComponent: TGraphScroll; procedure CreateComponent; procedure SetComponent(const Value: TGraphScroll); protected procedure Notification(AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; published property Component: TGraphScroll read FComponent write SetComponent; end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TGraphScroll, TModContainer]); end; { TGraphScroll } constructor TGraphScroll.Create(AOwner: TComponent); begin inherited Create(AOwner); //"сетапим" компонент... FLineWidth := 3; FLineColor := clNavy; FSliderWidth := 7; FSliderLength := 40; FSliderColor := clTeal; FMax := 100; FPosition := 30; Width := 200; Height := 11; //Странно, но значения меньше 10 НЕ принимаются! Почему? Кто объяснит дремучему? Align := alBottom; RecalcGeometry; end; procedure TGraphScroll.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; if InRect(X, Y, FSliderRect) then begin FSliderCaptured := True; FBegDragCoord.X := X; FBegDragCoord.Y := Y; FBegDragPos := Position; end; end; procedure TGraphScroll.MouseMove(Shift: TShiftState; X, Y: Integer); begin inherited; if FSliderCaptured then if Kind = skHorizontal then Position := FBegDragPos + Round((X - FBegDragCoord.X) * (Max - Min) / Width) else Position := FBegDragPos + Round((Y - FBegDragCoord.Y) * (Max - Min) / Height); end; procedure TGraphScroll.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; FSliderCaptured := False; Refresh; end; procedure TGraphScroll.RecalcGeometry; var WorkZone: Integer; begin //Гммм... если кто-нибудь сможет упростить эти монструозные формулы - буду благодарен... //Однако будте внимательны! //If по Kind'у меня уже достал... Нужно как-то более гибко... if Kind = skHorizontal then begin WorkZone := Width - SliderLength - SliderWidth - 3; //Левый край FSliderRect.Left := Round(WorkZone * (FPosition - FMin) / (FMax - FMin)) + SliderWidth div 2 + 2; //Правый край FSliderRect.Right := FSliderRect.Left + SliderLength; //Горизонтальный центр слайдера (нужен для рисования риски) FHSC := EnsureRange(FSliderRect.Left + Floor(SliderLength / 2), 0, Width - 1); //"Вертикальные" параметры. Зависят от Layout. case Layout of //ВНИМАНИЕ!!!! Может кому пригодится! У нас есть св-во Max, а нам нужна ф-ия Max из //модуля Math. Поэтому - Math.Max. Вроде-бы просто, но какую я шишку год назад набил на этом... slTop: FVSC := Math.Max(SliderWidth, LineWidth) div 2; slCenter: FVSC := Height div 2; slBottom: FVSC := Height - Math.Max(SliderWidth, LineWidth) div 2 - 2; end; //Верх бегунка FSliderRect.Top := FVSC - SliderWidth div 2; //Низ бегунка FSliderRect.Bottom := FSliderRect.Top + SliderWidth; end else begin WorkZone := Height - SliderLength - SliderWidth - 3; //Верх бегунка FSliderRect.Top := Round(WorkZone * (FPosition - FMin) / (FMax - FMin)) + SliderLength div 2 + 2; //Низ бегунка FSliderRect.Bottom := FSliderRect.Top + SliderLength; //Горизонтальный центр (при skVertical становится Вертикальным Центром) слайдера (нужен для рисования риски) FHSC := EnsureRange(FSliderRect.Top + Floor(SliderLength / 2), 0, Height - 1); //"Вертикальные" параметры. Зависят от Layout. case Layout of //ВНИМАНИЕ!!!! Может кому пригодится! У нас есть св-во Max, а нам нужна ф-ия Max из //модуля Math. Поэтому - Math.Max. Вроде-бы просто, но какую я шишку год назад набил на этом... slTop: FVSC := Math.Max(SliderWidth, LineWidth) div 2; slCenter: FVSC := Width div 2; slBottom: FVSC := Width - Math.Max(SliderWidth, LineWidth) div 2 - 2; end; //Левый край бегунка FSliderRect.Left := FVSC - SliderWidth div 2; //Правый край бегунка FSliderRect.Right := FSliderRect.Left + SliderWidth; end; end; procedure TGraphScroll.Paint; var LWD2: Integer; //LineWidth div 2// begin //Предложения по "украшательству" компонента принимаются с радостью, но только не в ущерб СКОРОСТИ //Предложения, как избавиться от мерцания, принимаются ВНЕ очереди! //С удовольствием выслушаю предложения, как избавиться от If'ов по Kind'у. Уж больно громоздко... LWD2 := LineWidth div 2 + 1; //При рисовании толстой линии ее концы скругляются "наружу", чтобы их НЕ //подрезать (красиво выглядит), даем для них отступ... with Canvas do begin //Рисуем линию. Без комментариев... Pen.Width := LineWidth; Pen.Color := LineColor; if Kind = skHorizontal then begin MoveTo(LWD2, FVSC); //0 + ширина линии | Так получаются скругленные концы LineTo(Width - LWD2 - 1, FVSC); //ширина - ширина линии | end else begin MoveTo(FVSC, LWD2); //0 + ширина линии | Так получаются скругленные концы LineTo(FVSC, Height - LWD2 - 1); //ширина - ширина линии | end; //Рисуем "слайдер" (бегунок, он же ползунок, по буржуйски - Slider). Без комментариев... Pen.Width := SliderWidth; Pen.Color := SliderColor; if Kind = skHorizontal then begin MoveTo(FSliderRect.Left, FVSC); LineTo(FSliderRect.Right, FVSC); end else begin MoveTo(FVSC, FSliderRect.Top); LineTo(FVSC, FSliderRect.Bottom); end; //Рисуем центральную риску на бегунке. Pen.Width := 1; if FSliderCaptured then //Если бегунок "захвачен" (двигается мышом...) Pen.Color := clRed //Рисуем красным цветом else Pen.Color := clBlack; //Если нет - черным... if Kind = skHorizontal then begin MoveTo(FHSC, FSliderRect.Top); LineTo(FHSC, FSliderRect.Bottom); end else begin MoveTo(FSliderRect.Left, FHSC); LineTo(FSliderRect.Right, FHSC); end; end; end; procedure TGraphScroll.Resize; begin //При изменении размера надо пересчитать все переменные, используемы для отрисовки компонента... inherited Resize; RecalcGeometry; Refresh; end; procedure TGraphScroll.SetColor(const Index: Integer; const Value: TColor); begin //Все стандартно... case Index of 0: FLineColor := Value; 1: FSliderColor := Value; end; Refresh; end; procedure TGraphScroll.SetGeometry(const Index, Value: Integer); begin //Тоже стандартно... case Index of 0: FLineWidth := Value; 1: FSliderWidth := Value; 2: FSliderLength := Value; end; RecalcGeometry; Refresh; end; procedure TGraphScroll.SetGraphScrollKind(const Value: TGraphScrollKind); var Tmp: Integer; begin if FGraphScrollKind <> Value then //Если НЕ текущее значение begin FGraphScrollKind := Value; //Присвоим новое... if not (csLoading in ComponentState) and //Если не в состоянии загрузки И //Выравнивание alNone или alCustom или alClient ((Align = alNone) or (Align = alCustom) or (Align = alClient)) then begin //"Переворачиваем" компонент (меняем местами высоту и ширину...) Tmp := Height; Height := Width; Width := Tmp; end; end; RecalcGeometry; Refresh; end; procedure TGraphScroll.SetGraphScrollLayout( const Value: TGraphScrollLayout); begin //Процедура смены Layout'а. Все просто... Что такое Layout - смотри TLabel FGraphScrollLayout := Value; RecalcGeometry; Refresh; end; procedure TGraphScroll.SetMax(const Value: Integer); begin SetValues(FMin, Value, FPosition); end; procedure TGraphScroll.SetMin(const Value: Integer); begin SetValues(Value, FMax, FPosition); end; procedure TGraphScroll.SetPosition(const Index, Value: Integer); begin SetValues(FMin, FMax, Value); end; procedure TGraphScroll.SetValues(AMin, AMax, APosition: Integer); begin if AMax < AMin then //Максимум ДОЛЖЕН быть больше минимума raise EMinMaxError.Create(SMinMaxError + 'TGraphScroll.SetValues'); FMin := AMin; FMax := AMax; FPosition := EnsureRange(APosition, FMin, FMax); RecalcGeometry; Refresh; end; procedure TGraphScroll.ConstrainedResize(var MinWidth, MinHeight, MaxWidth, MaxHeight: Integer); //Перекрыв этот метод TControl можно задать мин и макс. р-ры компонента. //В нашем случае - компонент не может быть ниже ширины Math.Max(LineWidth, SliderWidth); //И уже MinWidth:=SliderLength+2*LineWidth+2*SliderWidth; //ЕСЛИ вертикально расположенный - наоборот... begin if Kind = skHorizontal then begin MinWidth := SliderLength + 2 * LineWidth + 2 * SliderWidth; MinHeight := Math.Max(LineWidth, SliderWidth); end else begin MinWidth := Math.Max(LineWidth, SliderWidth); MinHeight := SliderLength + 2 * LineWidth + 2 * SliderWidth; end; end; procedure TGraphScroll.RequestAlign; begin inherited; //Меняем тип Kind'а при изменении выравнивания. if ((Align = alTop) or (Align = alBottom)) and (Kind <> skHorizontal) then Kind := skHorizontal; if ((Align = alLeft) or (Align = alRight)) and (Kind <> skVertical) then Kind := skVertical; end; function TGraphScroll.CanAutoSize(var NewWidth, NewHeight: Integer): Boolean; begin //Перекрываем унаследованную "автосайзилку". Код слизан с TImage и поэтому работает :-) Result := True; if not (csDesigning in ComponentState) or (LineWidth > 0) and (SliderWidth > 0) then begin if (Align in [alNone, alLeft, alRight]) and (Kind = skVertical) then NewWidth := Math.Max(LineWidth, SliderWidth); if (Align in [alNone, alTop, alBottom]) and (Kind <> skVertical) then NewHeight := Math.Max(LineWidth, SliderWidth); end; end; { TModContainer } constructor TModContainer.Create(AOwner: TComponent); begin inherited Create(AOwner); //Ну, это святое... Width := 400; Height := 150; CreateComponent; //Создание к-та собрано в процедуру, так как используется еще и в SetComponent end; procedure TModContainer.CreateComponent; begin FComponent := TGraphScroll.Create(Self); //Создаем к-т FComponent.Name := 'IntCnt'; //Даем ему имя (необязательно...) FComponent.SetSubComponent(True); //Устанавливаем флаг "SubComponent" FComponent.FreeNotification(Self); //Хотим получать уведомление об уничтожении FComponent.Parent := Self; //ВАЖНО!!!! Ставим себя "Родителем" FComponent.Width := Width - 20; //Располагаем и образмериваем... FComponent.Top := Height - 20; // ------//------- FComponent.Left := 10; // ------//------- // FComponent.Anchors:=[akBottom, akLeft, akRight]; //А вот с якорями пока решения нету. //Ставим "ручками" в DesignTime //Суть прикола такова - "якоря" цепляются раньше, чем загружаются размеры контейнерного компонента //из файла формы. (ВСЕ креэйты отрабатваю раньше загрузки). Как я понял: контейнерный компонент создается //с размерами Width:=400; Height:=150; , на нем создается FComponent, который цепляется якорями, а затем //читаются данные из файла формы, например Width:=800; - Результат - внедренные к-ты с установленными akLeft+akRight или //akTop+akBottom растягиваются (сжимаются) при КАЖДОЙ загрузке формы в Design Time. //В Ран тайм все нормально... но... end; procedure TModContainer.Notification(AComponent: TComponent; Operation: TOperation); //*Fox* Процедура отслеживающая удаление встроенных объектов //См. справку "Creating properties for subcomponents" begin inherited Notification(AComponent, Operation); //Ну, это святое... //Если "наш" компонент и его удаляют if (AComponent = FComponent) and (Operation = opRemove) then FComponent := nil; //Обнулим линк на него... end; procedure TModContainer.SetComponent(const Value: TGraphScroll); //*Fox* Процедура ответственная за "линковку" FComponent //Если линкуем внешний скроллер - внутренний высвобождается //Если удаляем внешний (присваиваем nil) - создается внутрений //См. справку "Creating properties for subcomponents" begin if Value <> FComponent then //Если предлагают НЕ то, что уже есть... begin if Value <> nil then //Если линкуем внешний begin if (FComponent <> nil) and (FComponent.Owner = Self) then //Если сейчас НЕ пустой и Свой FComponent.Free; //Удалим его FComponent := Value; //Прицепим то, что предлагают... FComponent.FreeNotification(Self); //Хотим получать уведомление об уничтожении end else //Если удаляем внешний (присв. nil) begin if FComponent.Owner <> Self then //Если убрали внешний - создадим внутренний CreateComponent; end; end; end; end. Скачать пример: AltChart.zip (11 K) Программный проект на языке Delphi, демонстрирующий создание и использование пользовательского компонента Код включает несколько классов:
Код включает различные процедуры и функции, включая:
Код также включает несколько констант и enum:
В целом, этот проект демонстрирует, как создавать пользовательский компонент в Delphi и использовать его в приложении. Некоторые потенциальные улучшения:
Замечание: предоставленный zip-файл содержит проект Delphi с исходным кодом, но он может не быть совместимым с всеми версиями Delphi. Вот описание статьи на русском языке в одном предложении: В статье рассматривается пример разработки компонента TGraphScroll для отображения полосы скроллинга по данным, а также создания контейнера TModContainer для управления этим компонентом. Комментарии и вопросыПолучайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта. :: Главная :: Создание компонент ::
|
||||
©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007 |