I’m considering switching from the abandoned TCoolTrayIcon to Delphi’s own TTrayIcon. The only thing that I’m missing is OnMouseEnter and OnMouseExit (≘OnMouseLeave) that I need in my case.
Is there an easy way to add these events to TTrayIcon?
(CoolTrayIcon does this with a timer ... I'm not sure if that's really the best solution)
Although nobody seemed to be really interested in an actual solution to this question, I guess it would be fair to post my solution anyway, in case somebody else ever searches for the same problem.
It was way less coding than I initially expected.
unit TrayIconEx;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.ExtCtrls;
type
TTrayIconEx = class(TTrayIcon)
private
CursorPosX, CursorPosY: Integer;
FOnMouseEnter: TNotifyEvent;
FOnMouseLeave: TNotifyEvent;
EnterLeaveTimer: TTimer;
procedure EnterLeaveEvent(Sender: TObject);
protected
procedure WindowProc(var Msg: TMessage); override;
procedure MouseEnter; virtual;
procedure MouseLeave; virtual;
public
constructor Create(AOwner: TComponent); override;
published
property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Custom', [TTrayIconEx]);
end;
constructor TTrayIconEx.Create(AOwner: TComponent);
begin
inherited;
EnterLeaveTimer := TTimer.Create(Self);
EnterLeaveTimer.Enabled := False;
EnterLeaveTimer.Interval := 200;
EnterLeaveTimer.OnTimer := EnterLeaveEvent;
end;
procedure TTrayIconEx.WindowProc(var Msg: TMessage);
var
p: TPoint;
begin
if Assigned(FOnMouseEnter) or Assigned(FOnMouseLeave) then
begin
if (Msg.Msg = WM_SYSTEM_TRAY_MESSAGE) and (Msg.lParam = WM_MOUSEMOVE) then
begin
GetCursorPos(p);
CursorPosX := p.X;
CursorPosY := p.Y;
if not EnterLeaveTimer.Enabled then
MouseEnter();
end;
end;
inherited WindowProc(Msg);
end;
procedure TTrayIconEx.EnterLeaveEvent(Sender: TObject);
var
p: TPoint;
begin
//Win7+ supports Shell_NotifyIconGetRect(), but to support Vista and XP a workaround is required.
//-> If the position differs from the last captured position in MouseMove, then the cursor was moved away.
GetCursorPos(p);
if (CursorPosX <> p.X) or (CursorPosY <> p.Y) then
MouseLeave();
end;
procedure TTrayIconEx.MouseEnter;
begin
if Assigned(FOnMouseEnter) then
FOnMouseEnter(Self);
EnterLeaveTimer.Enabled := True;
end;
procedure TTrayIconEx.MouseLeave;
begin
EnterLeaveTimer.Enabled := False;
if Assigned(FOnMouseLeave) then
FOnMouseLeave(Self);
end;
end.
If all you want is a more advanced hint then you can Personalization its own hint like any other string with special character such as Enter and etc like this :
edit1.hint:='first row'+#13+'second row'+#13+#13+'last row';
Related
What I'm basically trying to create is a component that inherits from TScrollBox. That component has a TGroupBox and inside it a TFlowPanel. What I need is when I double click this component, a TCollection-like editor appears where I can add components (TFiltros) that will be children of that TFlowPanel. The problem is that I want those components to be named, such that I can directly access them via code, kinda like a TClientDataSet, where you add fields and they appear in your code.
I've managed to make it almost work by overriding GetChildren and making it return the children of the TFlowPanel. That also required me to make TFiltros's owner be the Form which they are in. It shows in the Structure panel as children (even tho they are not direct children) and also saves it in the DFM, but when I close the form and open it again, it fails to load the data back from the DFM, throwing an Access Violation. I have no idea how to override the loading to properly set the children.
Any help in how I can fix that, or even different ideas would be really nice. I'm new to creating Delphi components.
My current code which is heavily inspired in this question:
unit uFiltros;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Forms, StdCtrls,
ClRelatorio, Math, DesignEditors, DesignIntf, System.Generics.Collections;
type
TFiltrosEditor = class(TComponentEditor)
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): String; override;
function GetVerbCount: Integer; override;
end;
TFiltros = class(TScrollingWinControl)
private
FChilds: TList<TComponent>;
FGroupBox: TGroupBox;
FFlowPanel: TFlowPanel;
FWidth: Integer;
procedure OnFlowPanelResize(Sender: TObject);
procedure SetWidth(AWidth: Integer);
public
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
function GetChildOwner: TComponent; override;
constructor Create(AOwner: TComponent); override;
property Childs: TList<TComponent> read FChilds;
published
property Width: Integer read FWidth write SetWidth;
end;
TClFiltro = class(TFiltro)
private
FFiltros: TFiltros;
protected
procedure SetParent(AParent: TWinControl); override;
public
constructor Create(AOwner: TComponent; AFiltros: TFiltros); reintroduce;
function GetParentComponent: TComponent; override;
function HasParent: Boolean; override;
property Parent: TWinControl write SetParent;
end;
TFiltroItem = class(TCollectionItem)
private
FFiltro: TClFiltro;
protected
function GetDisplayName: String; override;
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
published
property Filtro: TClFiltro read FFiltro write FFiltro;
end;
TFiltrosCollection = class(TOwnedCollection)
private
FDesigner: IDesigner;
public
property Designer: IDesigner read FDesigner write FDesigner;
end;
procedure Register;
implementation
uses Dialogs, ClFuncoesBase, Vcl.Graphics, ColnEdit;
procedure Register;
begin
RegisterClass(TClFiltro);
RegisterNoIcon([TClFiltro]);
RegisterComponents('Cl', [TFiltros]);
RegisterComponentEditor(TFiltros, TFiltrosEditor);
end;
{ TFiltroItem }
constructor TFiltroItem.Create(Collection: TCollection);
begin
inherited;
if Assigned(Collection) then
begin
FFiltro := TClFiltro.Create(TFiltros(Collection.Owner).Owner, TFiltros(Collection.Owner));
FFiltro.Name := TFiltrosCollection(Collection).Designer.UniqueName(TClFiltro.ClassName);
FFiltro.Parent := TFiltros(Collection.Owner).FFlowPanel;
FFiltro.Margins.Top := 1;
FFiltro.Margins.Bottom := 1;
FFiltro.AlignWithMargins := True;
//FFiltro.SetSubComponent(True);
end;
end;
destructor TFiltroItem.Destroy;
begin
FFiltro.Free;
inherited;
end;
function TFiltroItem.GetDisplayName: String;
begin
Result := FFiltro.Name;
end;
{ TFiltros }
constructor TFiltros.Create(AOwner: TComponent);
begin
inherited;
FChilds := TList<TComponent>.Create;
// Configurações ScrollBox
Align := TAlign.alRight;
AutoScroll := False;
AutoSize := True;
//Configurações GroupBox
FGroupBox := TGroupBox.Create(Self);
FGroupBox.Parent := Self;
FGroupBox.Caption := ' Fil&tros ';
FGroupBox.Font.Style := [fsBold];
//Configurações FlowPanel
FFlowPanel := TFlowPanel.Create(FGroupBox);
FFlowPanel.Parent := FGroupBox;
FFlowPanel.Top := 15;
FFlowPanel.Left := 2;
FFlowPanel.AutoSize := True;
FFlowPanel.FlowStyle := TFlowStyle.fsRightLeftTopBottom;
FFlowPanel.Caption := '';
FFlowPanel.OnResize := OnFlowPanelResize;
FFlowPanel.BevelOuter := TBevelCut.bvNone;
end;
function TFiltros.GetChildOwner: TComponent;
begin
Result := FFlowPanel;
end;
procedure TFiltros.GetChildren(Proc: TGetChildProc; Root: TComponent);
var I: Integer;
begin
// inherited;
for I := 0 to FChilds.Count - 1 do
Proc(TComponent(FChilds[I]));
end;
procedure TFiltros.OnFlowPanelResize(Sender: TObject);
begin
FGroupBox.Width := FFlowPanel.Width + 4;
FGroupBox.Height := Max(FFlowPanel.Height + 17, Height);
VertScrollBar.Range := FGroupBox.Height;
FWidth := FFlowPanel.Width;
end;
procedure TFiltros.SetWidth(AWidth: Integer);
begin
FFlowPanel.Width := AWidth;
FWidth := FFlowPanel.Width;
OnFlowPanelResize(Self);
end;
{ TFiltrosEditor }
procedure TFiltrosEditor.ExecuteVerb(Index: Integer);
var LCollection: TFiltrosCollection;
I: Integer;
begin
LCollection := TFiltrosCollection.Create(Component, TFiltroItem);
LCollection.Designer := Designer;
for I := 0 to TFiltros(Component).Childs.Count - 1 do
with TFiltroItem.Create(nil) do
begin
FFiltro := TClFiltro(TFiltros(Component).Childs[I]);
Collection := LCollection;
end;
ShowCollectionEditorClass(Designer, TCollectionEditor, Component, LCollection, 'Filtros');
end;
function TFiltrosEditor.GetVerb(Index: Integer): String;
begin
Result := 'Editar filtros...';
end;
function TFiltrosEditor.GetVerbCount: Integer;
begin
Result := 1;
end;
{ TClFiltro }
constructor TClFiltro.Create(AOwner: TComponent; AFiltros: TFiltros);
begin
inherited Create(AOwner);
FFiltros := AFiltros;
end;
function TClFiltro.GetParentComponent: TComponent;
begin
Result := FFiltros;
end;
function TClFiltro.HasParent: Boolean;
begin
Result := Assigned(FFiltros);
end;
procedure TClFiltro.SetParent(AParent: TWinControl);
begin
if Assigned(AParent) then
FFiltros.FChilds.Add(Self)
else
FFiltros.FChilds.Remove(Self);
inherited;
end;
end.
I've finally managed to do it. It required a combination of TOwnedCollection and overriding GetChildren and GetParentComponent.
Basically what I've learned (and you can correct me if I'm wrong), is the following:
For a component to be shown in the Structure tab at all, the Owner of that component has to be the form. So the first thing was to create TFiltro with that owner.
GetParentComponent defines where in the Structure tree the component is going to reside in, it doesn't necessarily have to be the actual parent. So the second thing was to make GetParentComponent of the TFiltro return the TScrollBox but set the actual parent to be the TFlowPanel.
Now, as the parent of TFiltro no longer is the form, it won't save it to the DFM, because TFlowPanel is the actual parent but is not defined as a subcomponent. Overriding GetChildren in the TScrollBox and making it return every TFiltro solves this, and it is now saved in the DFM as a child.
But now, for the TFiltro to be properly read back from the DFM and be set again accordingly, it has to be a published value in an item inside the TOwnedCollection, which itself is a published value in the TScrollBox. Then, make the TCollectionItem published value's set function define the parent of the TFiltro to be the TFlowPanel.
The article which helped me the most in achieving this is available in the WayBack machine.
Basically I have this problem: CapsLock password message in TEdit visually fails with VCL Styles.
What I want to do is not to solve the problem as shown in the answer or the comments.
I want to disable that ugly hint window entirely. and instead show an image letting the user know that the caps are locked.
like this
I found the solution to my problem, It involves a hack that I would rather not use.
It goes like this.
Override WndProc.
code
type
TEdit = class (Vcl.StdCtrls.TEdit)
protected
procedure WndProc(var Message: TMessage); override;
end;
Intercept the EM_SHOWBALLOONTIPmessage and you are done
code
procedure TEdit.WndProc(var Message: TMessage);
begin
if Message.Msg = EM_SHOWBALLOONTIP then
showmessage('Do your thing.')
else
inherited;
end;
For more information check the MSDN documentation:
How do I suppress the CapsLock warning on password edit controls?
This is a descendant of TEdit that would allow to suppress the CapsLock warning on password edit controls, if a certain FOnPasswordCaps events are assigned with PasswordChar <> #0
unit NCREditUnit;
interface
uses
Vcl.StdCtrls,
vcl.Controls,
Winapi.Messages,
System.Classes;
type
TNCREdit = class(TEdit)
private
FOnPasswordCapsLocked: TNotifyEvent;
FIsCapsLocked: boolean;
FOnPasswordCapsFreed: TNotifyEvent;
FBlockCapsBalloonTip: boolean;
FValuePasswordChrOnCaps: boolean;
procedure SetOnPasswordCapsEvents;
procedure SetOnPasswordCapsFreed(const aValue: TNotifyEvent);
procedure SetOnPasswordCapsLocked(const aValue: TNotifyEvent);
protected
procedure WndProc(var Message: TMessage); override;
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure DoEnter; override;
procedure DoExit; override;
published
property BlockCapsBalloonTip: boolean read FBlockCapsBalloonTip write FBlockCapsBalloonTip default False;
property ValuePasswordChrOnCaps: boolean read FValuePasswordChrOnCaps write FValuePasswordChrOnCaps default True;
//... The usual property declaration of TEdit
property OnPasswordCapsLocked: TNotifyEvent read FOnPasswordCapsLocked write SetOnPasswordCapsLocked;
property OnPasswordCapsFreed: TNotifyEvent read FOnPasswordCapsFreed write SetOnPasswordCapsFreed;
end;
implementation
uses
Winapi.CommCtrl,
Winapi.Windows;
{ TNCREdit }
procedure TNCREdit.DoEnter;
begin
inherited;
if FBlockCapsBalloonTip then
begin
FIsCapsLocked := Odd(GetKeyState(VK_CAPITAL));
SetOnPasswordCapsEvents;
end;
end;
procedure TNCREdit.DoExit;
begin
if FBlockCapsBalloonTip and (FIsCapsLocked) then
begin
FIsCapsLocked := False;
SetOnPasswordCapsEvents;
end;
inherited;
end;
procedure TNCREdit.KeyUp(var Key: Word; Shift: TShiftState);
begin
if Key = VK_CAPITAL then
FIsCapsLocked := not FIsCapsLocked;
SetOnPasswordCapsEvents;
inherited;
end;
procedure TNCREdit.SetOnPasswordCapsEvents;
begin
if FIsCapsLocked then
begin
if Assigned(FOnPasswordCapsLocked) and
((self.PasswordChar <> #0) or ( not FValuePasswordChrOnCaps)) then
begin
FOnPasswordCapsLocked(Self);
end;
end
else
begin
if Assigned(FOnPasswordCapsLocked) and
((self.PasswordChar <> #0) or ( not FValuePasswordChrOnCaps)) then
begin
FOnPasswordCapsFreed(Self);
end;
end;
end;
procedure TNCREdit.SetOnPasswordCapsFreed(const aValue: TNotifyEvent);
begin
FOnPasswordCapsFreed := aValue;
FBlockCapsBalloonTip := True;
end;
procedure TNCREdit.SetOnPasswordCapsLocked(const aValue: TNotifyEvent);
begin
FOnPasswordCapsLocked := aValue;
FBlockCapsBalloonTip := True;
end;
procedure TNCREdit.WndProc(var Message: TMessage);
begin
if (Message.Msg = EM_SHOWBALLOONTIP) and FBlockCapsBalloonTip then Exit;
inherited;
end;
end.
Mr Kobik made a very elegant piece of code that I think PasteBin should not be trusted to host, so I decided to add it here.
From what I understood it lets you handle TPasswordCapsLockState in one event handler that is fired when the TPasswordEdit receives focus, loses focus, CapsLock key pressed while on focus and an optional firing when PasswordChar is changed.
Using this approach I could use the OnPasswordCapsLock event to show/hide the image in my question instead of forcing the consumer of the component to use two event handlers for each state (very clever by the way and less error prone).
also as long as LNeedHandle := FBlockCapsBalloonTip and IsPassword; is True I have another added feature to TPasswordEdit which is the handling of OnEnter and OnExit in OnPasswordCapsLock as well,
So what can I say Mr Kobik Je vous tire mon chapeau.
type
TPasswordCapsLockState = (pcsEnter, pcsExit, pcsKey, pcsSetPasswordChar);
TPasswordCapsLockEvent = procedure(Sender: TObject;
Locked: Boolean; State: TPasswordCapsLockState) of object;
TPasswordEdit = class(TCustomEdit)
private
FIsCapsLocked: boolean;
FBlockCapsBalloonTip: boolean;
FOnPasswordCapsLock: TPasswordCapsLockEvent;
protected
procedure WndProc(var Message: TMessage); override;
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure DoEnter; override;
procedure DoExit; override;
procedure HandlePasswordCapsLock(State: TPasswordCapsLockState); virtual;
function GetIsPassword: Boolean; virtual;
public
property IsPassword: Boolean read GetIsPassword;
published
property BlockCapsBalloonTip: boolean read FBlockCapsBalloonTip write FBlockCapsBalloonTip default False;
//... The usual property declaration of TEdit
property OnPasswordCapsLock: TPasswordCapsLockEvent read FOnPasswordCapsLock write FOnPasswordCapsLock;
end;
implementation
function TPasswordEdit.GetIsPassword: Boolean;
begin
Result := ((PasswordChar <> #0) or
// Edit control can have ES_PASSWORD style with PasswordChar == #0
// if it was creaed with ES_PASSWORD style
(HandleAllocated and (GetWindowLong(Handle, GWL_STYLE) and ES_PASSWORD <> 0)));
end;
procedure TPasswordEdit.HandlePasswordCapsLock;
var
LNeedHandle: Boolean;
begin
LNeedHandle := FBlockCapsBalloonTip and IsPassword;
if LNeedHandle then
begin
FIsCapsLocked := Odd(GetKeyState(VK_CAPITAL));
if Assigned(FOnPasswordCapsLock) then
FOnPasswordCapsLock(Self, FIsCapsLocked, State);
end;
end;
procedure TPasswordEdit.DoEnter;
begin
inherited;
HandlePasswordCapsLock(pcsEnter);
end;
procedure TPasswordEdit.DoExit;
begin
inherited;
HandlePasswordCapsLock(pcsExit);
end;
procedure TPasswordEdit.KeyUp(var Key: Word; Shift: TShiftState);
begin
inherited;
if Key = VK_CAPITAL then
HandlePasswordCapsLock(pcsKey);
end;
procedure TPasswordEdit.WndProc(var Message: TMessage);
begin
if (Message.Msg = EM_SHOWBALLOONTIP) and FBlockCapsBalloonTip and IsPassword then
Exit;
// Optional - if password char was changed
if (Message.Msg = EM_SETPASSWORDCHAR) and Self.Focused then
HandlePasswordCapsLock(pcsSetPasswordChar);
inherited;
end;
I'm trying to create a VCL component, that lets you insert multiple TImages of different sizes as properties.
I was told to best use a TObjectList ( Delphi component with a variable amount of TPictures ), but now I'm struggling to make the single TPictures assignable in the Property editor.
What i have at the moment: (it compiles)
unit ImageMultiStates;
interface
uses
Vcl.Graphics, Vcl.StdCtrls, System.SysUtils, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Forms, Generics.Collections;
type
TImageMultiStates = class(TImage)
private
FPictures: TObjectList<TPicture>;
procedure SetPicture(Which: Integer; APicture: TPicture);
function GetPicture(Which: Integer): TPicture;
public
Count: integer;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Activate(Which: Integer);
published
// property Pictures: TObjectList<TPicture> read GetPicture write SetPicture;
// property Pictures[Index: Integer]: TObjectList<TPicture> read GetPicture write SetPicture;
property Pictures: TObjectList<TPicture> read FPictures write FPictures;
end;
procedure Register;
implementation
constructor TImageMultiStates.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FPictures := TObjectList<TPicture>.Create;
end;
destructor TImageMultiStates.Destroy;
begin
FPictures.Free;
inherited Destroy;
end;
procedure TImageMultiStates.SetPicture(Which: Integer; APicture: TPicture);
begin
FPictures[Which] := APicture;
if Which=0 then
Picture.Assign(APicture);
end;
function TImageMultiStates.GetPicture(Which: Integer): TPicture;
begin
Result := FPictures[Which];
end;
procedure TImageMultiStates.Activate(Which: Integer);
begin
Picture.Assign(FPictures[Which]);
end;
procedure Register;
begin
RegisterComponents('Standard', [TImageMultiStates]);
end;
end.
What doesn't work is the final result in the PropertyEditor. It shows one single item named "Pictures", with the value "(TObjectList)". Clicking it doesn't do anything, i don't get a proper editor. Other ideas for the line in question have been commented out, they bring other errors:
The first one throws the compiler error "E2008 Incompatible Types", The second one throws "Published property 'Pictures' can not be of type ARRAY".
The IDE has no idea how to edit a TObjectList at design-time, and the DFM streaming system has no idea how to stream a TObjectList. You would have to implement a custom property editor and custom streaming logic. While that is certainly possible, it is a LOT of work.
What you are attempting to do is better handled by using System.Classes.TCollection instead. Both the IDE and the DFM streaming system have built-in support for handling TCollection editing and streaming automatically for you.
Try something more like this:
unit ImageMultiStates;
interface
uses
System.Classes, Vcl.Controls, Vcl.ExtCtrls, Vcl.Graphics;
type
TImagePictureItem = class(TCollectionItem)
private
FPicture: TPicture;
procedure PictureChanged(Sender: TObject);
procedure SetPicture(Value: TPicture);
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
published
property Picture: TPicture read FPicture write SetPicture;
end;
TImagePictureEvent = procedure(Sender: TObject; Index: Integer) of object;
TImagePictures = class(TOwnedCollection)
private
FOnPictureChange: TImagePictureEvent;
function GetPicture(Index: Integer): TImagePictureItem;
procedure SetPicture(Index: Integer; Value: TImagePictureItem);
protected
procedure Update(Item: TCollectionItem); override;
public
constructor Create(Owner: TComponent); reintroduce;
property Pictures[Index: Integer]: TImagePictureItem read GetPicture write SetPicture; default;
property OnPictureChange: TImagePictureEvent read FOnPictureChange write FOnPictureChange;
end;
TImageMultiStates = class(TImage)
private
FActivePicture: Integer;
FPictures: TImagePictures;
function GetPicture(Index: Integer): TPicture;
procedure PictureChanged(Sender: TObject; Index: Integer);
procedure SetActivePicture(Index: Integer);
procedure SetPicture(Index: Integer; Value: TPicture);
procedure SetPictures(Value: TImagePictures);
protected
procedure Loaded; override;
public
constructor Create(Owner: TComponent); override;
function Count: integer;
property Pictures[Index: Integer]: TPicture read GetPicture write SetPicture;
published
property ActivePicture: Integer read FActivePicture write SetActivePicture default -1;
property Picture stored False;
property Pictures: TImagePictures read FPictures write SetPictures;
end;
procedure Register;
implementation
{ TImagePictureItem }
constructor TImagePictureItem.Create(Collection: TCollection);
begin
inherited Create(Collection);
FPicture := TPicture.Create;
FPicture.OnChange := PictureChanged;
end;
destructor TImagePictureItem.Destroy;
begin
FPicture.Free;
inherited;
end;
procedure TImagePictureItem.PictureChanged(Sender: TObject);
begin
Changed(False);
end;
procedure TImagePictureItem.SetPicture(Value: TPicture);
begin
FPicture.Assign(Value);
end;
{ TImagePictures }
constructor TImagePictures.Create(Owner: TComponent);
begin
inherited Create(Owner, TImagePictureItem);
end;
function TImagePictures.GetPicture(Index: Integer): TImagePictureItem;
begin
Result := TImagePictureItem(inherited GetItem(Index));
end;
procedure TImagePictures.SetPicture(Index: Integer; Value: TImagePictureItem);
begin
inherited SetItem(Index, Value);
end;
procedure TImagePictures.Update(Item: TCollectionItem);
begin
if Assigned(FOnPictureChange) then
begin
if Item <> nil then
FOnPictureChange(Self, Item.Index)
else
FOnPictureChange(Self, -1);
end;
end;
{ TImageMultiStates }
constructor TImageMultiStates.Create(Owner: TComponent);
begin
inherited Create(Owner);
FPictures := TImagePictures.Create(Self);
FPictures.OnPictureChange := PictureChanged;
FActivePicture := -1;
end;
procedure TImageMultiStates.Loaded;
begin
inherited;
PictureChanged(nil, FActivePicture);
end;
function TImageMultiStates.Count: Integer;
begin
Result := FPictures.Count;
end;
procedure TImageMultiStates.PictureChanged(Sender: TObject; Index: Integer);
begin
if (FActivePicture <> -1) and ((Index = -1) or (Index = FActivePicture)) then
Picture.Assign(GetPicture(FActivePicture));
end;
function TImageMultiStates.GetPicture(Index: Integer): TPicture;
begin
Result := FPictures[Index].Picture;
end;
procedure TImageMultiStates.SetPicture(Index: Integer; Value: TPicture);
begin
FPictures[Index].Picture.Assign(Value);
end;
procedure TImageMultiStates.SetActivatePicture(Value: Integer);
begin
if FActivePicture <> Value then
begin
if ComponentState * [csLoading, csReading] = [] then
Picture.Assign(GetPicture(Value));
FActivePicture := Value;
end;
end;
procedure Register;
begin
RegisterComponents('Standard', [TImageMultiStates]);
// the inherited TImage.Picture property is published, and you cannot
// decrease the visibility of an existing property. However, if you move
// this procedure into a separate design-time package, you can then use
// DesignIntf.UnlistPublishedProperty() to hide the inherited
// Picture property at design-time, at least:
//
// UnlistPublishedProperty(TImageMultiStates, 'Picture');
//
// Thus, users are forced to use the TImageMultiStates.Pictures and
// TImageMultiStates.ActivePicture at design-time. The inherited
// Picture property will still be accessible in code at runtime, though...
end;
end.
I have developed two unit files (.pas) and i have 2 different components
the first one is MyPCButton and second is MyPanel
MyPCButton.pas uses MyPanel.pas
The Problem is when i try to put them in seperate packages, when i install the MyPCButton component which is using MyPanel.pas, the package installs both of them in the same package, if i install the MyPanel.pas first then MyPCButton package refuses the install and says "couldn't create output file for MyPanel.bpl in the package output directory"
I have placed MyPanel in interface section and i have placed it in implementation section but i still get the same error,
What i wanna do is install them sepeerately in their own packages
MyPCButton.pas :
unit MyPCButton;
interface
uses
Winapi.Windows,Winapi.Messages,System.SysUtils, System.Classes, Vcl.Controls, Vcl.ExtCtrls,
Vcl.Imaging.PngImage,Vcl.Graphics,Types,cxGraphics;
type
TMyPCButton=class (TCustomControl)
private
FCaption:TCaption;
FIcon:TPngImage;
FIconIndex:integer;
FIconList:TcxImageList;
FIconWidth:integer;
FIconLeftMargin,FIconRightMargin:integer;
FCloseIconList:TcxImageList;
FCloseIconWidth:integer;
FCloseIconLeftMargin,FCloseIconRightMargin:integer;
FFont:TFont;
FColorDefault,FColorDefaultFont:TColor;
FColorHover,FColorHoverFont:TColor;
FColorActive,FColorActiveFont:TColor;
FCaptionWidth:integer;
FMaximumCaptionWidth:integer;
FState:Byte;
FCurCloseIconState:integer;
FActive: Boolean;
FBuffer: TBitmap;
R3:TRect;
FOnClick,FOnCloseClick: TNotifyEvent;
FOnActivate,FOnDeactivate:TNotifyEvent;
FFocused:Boolean;
FGroupNo: integer;
procedure SetIconIndex(const Value:Integer);
procedure SetIconList(const Value:TcxImageList);
procedure SetCaption(const Value: TCaption);
procedure SetCloseIconList(const Value: TcxImageList);
procedure SetAutoSize;
procedure WndProc(var Message: TMessage); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); override;
procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
procedure SetActive(const Value: Boolean);
procedure SwapBuffers;
procedure CheckGroupNo;
protected
procedure Paint; override;
property Canvas;
procedure DoEnter; override;
procedure DoExit; override;
procedure KeyDown(Sender: TObject; var Key: Word;Shift: TShiftState);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetTotalWidth:integer;
function GetLeftSpace:integer;
function GetRightSpace:integer;
procedure SetPositionInPanel;
procedure SetActiveAfterClose;
published
property Caption:TCaption read FCaption write SetCaption;
property IconIndex:integer Read FIconIndex write SetIconIndex;
property IconList:TcxImageList Read FIconList write SetIconList;
property CloseIconList:TcxImageList Read FCloseIconList write SetCloseIconList;
property Active:Boolean read FActive write SetActive;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
property OnCloseClick: TNotifyEvent read FOnCloseClick write FOnCloseClick;
property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
property OnDeActivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
property GroupNo:integer read FGroupNo write FGroupNo;
property TabStop;
property Align;
end;
procedure Register;
implementation
uses Math,pvalues,pfunctions,MyPanel;
function IsIntInInterval(x, xmin, xmax: integer): boolean; inline;
begin
IsIntInInterval := (xmin <= x) and (x <= xmax);
end;
function PointInRect(const Point: TPoint; const Rect: TRect): boolean; inline;
begin
PointInRect := IsIntInInterval(Point.X, Rect.Left, Rect.Right) and
IsIntInInterval(Point.Y, Rect.Top, Rect.Bottom);
end;
procedure Register;
begin
RegisterComponents('MyComponents', [TMyPCButton]);
end;
procedure TMyPCButton.CheckGroupNo;
var
i:integer;
begin
for i:=0 to Parent.ControlCount-1 do
begin
if (Parent.Controls[i] is TMyPCButton) then
begin
if ((Parent.Controls[i] as TMyPCButton).Active) and
((Parent.Controls[i] as TMyPCButton).Name<>Self.Name) and
((Parent.Controls[i] as TMyPCButton).GroupNo=Self.GroupNo)
then
(Parent.Controls[i] as TMyPCButton).Active:=False;
end;
end;
end;
constructor TMyPCButton.Create(AOwner: TComponent);
begin
inherited;
if _V_RegValuesInitated=false then
_P_RegValuesInitate;
FFocused:=false;
FBuffer := TBitmap.Create;
Height:=30;
Width:=50;
FFont:=TFont.Create;
FFont.Assign(R_BtnTB.VFont);
FBuffer.Canvas.Font.Assign(FFont);
FState:=0;
FCurCloseIconState:=-1;
FIconIndex:=-1;
FIconWidth:=16;
FIconLeftMargin:=5;
FIconRightMargin:=5;
FCloseIconWidth:=17;
FCloseIconLeftMargin:=16;
FCloseIconRightMargin:=6;
FCaptionWidth:=0;
FMaximumCaptionWidth:=160;
OnKeyDown:=KeyDown;
end;
destructor TMyPCButton.Destroy;
begin
inherited;
FreeAndNil(FIcon);
FreeAndNil(FBuffer);
end;
procedure TMyPCButton.DoEnter;
begin
inherited;
if FActive=false then
FState:=1;
FFocused:=true;
paint;
end;
procedure TMyPCButton.DoExit;
begin
inherited;
if FActive=false then
FState:=0
else
FState:=2;
FFocused:=false;
paint;
end;
function TMyPCButton.GetLeftSpace: integer;
begin
Result:=Parent.Left;
end;
function TMyPCButton.GetRightSpace: integer;
begin
Result:=GetTotalWidth-Parent.Width;
end;
function TMyPCButton.GetTotalWidth: integer;
begin
Result:=Self.Left+Self.Width;
end;
procedure TMyPCButton.SetPositionInPanel;
var
TotalWidth,LeftSpace,RightSpace:integer;
begin
if (Owner is TMyPanel) then
begin
if (Owner as TMyPanel).Parent is TMyPanel then
begin
LeftSpace:=GetLeftSpace;
if LeftSpace<0 then
LeftSpace:=LeftSpace*-1;
RightSpace:=GetRightSpace;
TotalWidth:=GetTotalWidth;
if (TotalWidth-LeftSpace)<Self.Width then
Parent.Left:=Parent.Left+(((TotalWidth-LeftSpace)-Self.Width)*-1)
else
if TotalWidth-LeftSpace>(Parent).Parent.Width then
begin
Parent.Left:=Parent.Left-(TotalWidth-LeftSpace-(Parent).Parent.Width);
end;
end;
end;
end;
procedure TMyPCButton.SetActiveAfterClose;
var
VControlCount,VPosition:integer;
begin
if (Parent is TMyPanel) then
begin
VControlCount:=Parent.ControlCount;
if VControlCount>1 then
begin
for VPosition:=0 to VControlCount-1 do
begin
if (Parent.Controls[VPosition] as TMyPCButton).Name=Self.Name then
break;
end;
if VPosition+1=Parent.ControlCount then
(Parent.Controls[VPosition-1] as TMyPCButton).Active:=true
else
(Parent.Controls[VPosition+1] as TMyPCButton).Active:=true;
end;
end;
end;
procedure TMyPCButton.KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if FActive=false then
SetActive(True);
if (Key=13) and (Assigned(FOnClick)) then FOnClick(Self);
paint;
end;
procedure TMyPCButton.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
var
SelfWidth:integer;
begin
inherited;
if (FActive=false) and PointInRect(point(X,Y),R3)=false then
begin
FState:=2;
SetActive(True);
paint;
end;
if PointInRect(point(X,Y),R3) then
begin
if (Assigned(FOnCloseClick)) then FOnCloseClick(Self);
SelfWidth:=Self.Width;
Width:=0;
Parent.Width:=Parent.Width-SelfWidth;
if FActive then
SetActiveAfterClose;
Self.Destroy;
end
else
begin
if (Assigned(FOnClick)) then FOnClick(Self);
end;
end;
procedure TMyPCButton.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
inherited;
if PointInRect(point(X,Y),R3)=false then
begin
if FState-1<>FCurCloseIconState then
begin
FCurCloseIconState:=FState-1;
paint;
end;
end
else
begin
if FState+1<>FCurCloseIconState then
begin
FCurCloseIconState:=FState+1;
paint;
end;
end;
end;
procedure TMyPCButton.Paint;
var
R2,R4:TRect;
ColorBackground,ColorFont:TColor;
begin
inherited;
if FBuffer.Canvas.Font.Name<>R_BtnTB.VFont.Name then
begin
FBuffer.Canvas.Font.Name:=R_BtnTB.VFont.Name;
end;
if FBuffer.Canvas.Font.Size<>R_BtnTB.VFont.Size then
begin
FBuffer.Canvas.Font.Size:=R_BtnTB.VFont.Size;
end;
if FBuffer.Canvas.Font.Quality<>R_BtnTB.VFont.Quality then
begin
FBuffer.Canvas.Font.Quality:=R_BtnTB.VFont.Quality;
end;
FBuffer.SetSize(Width,Height);
if FState=0 then
begin
ColorBackground:=R_BtnTB.DefaultColor;
ColorFont:=R_BtnTB.DefaultFontColor
end;
if FState=1 then
begin
ColorBackground:=R_BtnTB.HoverColor;
ColorFont:=R_BtnTB.HoverFontColor
end;
if FState=2 then
begin
ColorBackground:=R_BtnTB.ActiveColor;
ColorFont:=R_BtnTB.ActiveFontColor
end;
FBuffer.Canvas.Brush.Color:=ColorBackground;
FBuffer.Canvas.Font.Color:=ColorFont;
FBuffer.Canvas.FillRect(ClientRect);
if ((Assigned(FIconList)) and (FIconIndex>-1)) then
begin
FIconList.Draw(FBuffer.Canvas,FIconLeftMargin,(ClientHeight div 2)-(FIconList.Height div 2),FIconIndex);
end;
R2.Top:=(ClientHeight div 2)-(FBuffer.Canvas.TextHeight(FCaption) div 2);
R2.Height:=ClientHeight;
R2.Left:=FIconLeftMargin+FIconWidth+FIconRightMargin;
R2.Width:=FCaptionWidth;
DrawText(FBuffer.Canvas.Handle, PChar(FCaption), -1, R2,DT_LEFT);
if Assigned(FCloseIconList) then
begin
R3.Top:=0;
R3.Left:=ClientWidth-FCloseIconWidth;
R3.Height:=ClientHeight;
R3.Width:=FCloseIconWidth;
FCloseIconList.Draw(FBuffer.Canvas,R3.Left+(FCloseIconList.Width div 2),(R3.Height div 2)-(FCloseIconList.Height div 2),FCurCloseIconState);
end;
if FFocused then
begin
R4.Top:=1;
R4.Left:=1;
R4.Width:=ClientWidth-2;
R4.Height:=ClientHeight-2;
DrawFocusRect(FBuffer.Canvas.Handle,R4);
end;
SwapBuffers;
end;
procedure TMyPCButton.SetActive(const Value: Boolean);
var
MyPoint:TPoint;
begin
if FActive<>Value then
begin
FActive := Value;
if FActive then
begin
CheckGroupNo;
FState:=2;
SetPositionInPanel;
if Assigned(FOnActivate) then FOnActivate(Self);
end
else
begin
MyPoint := ScreenToClient(Mouse.CursorPos);
if PtInRect(ClientRect, MyPoint) then
FState:=1
else
FState:=0;
if Assigned(FOnDeactivate) then FOnDeactivate(Self);
end;
paint;
end;
end;
procedure TMyPCButton.SetAutoSize;
begin
FCaptionWidth:=FBuffer.Canvas.TextWidth(FCaption);
if FCaptionWidth>160 then
FCaptionWidth:=FMaximumCaptionWidth;
Width:=FIconLeftMargin+FIconWidth+FIconRightMargin+FCaptionWidth+FCloseIconLeftMargin+FCloseIconWidth-12+FCloseIconRightMargin;
end;
procedure TMyPCButton.SetCaption(const Value: TCaption);
begin
inherited;
FCaption := Value;
SetAutoSize;
paint;
end;
procedure TMyPCButton.SetCloseIconList(const Value: TcxImageList);
begin
FCloseIconList := Value;
paint;
end;
procedure TMyPCButton.SetIconIndex(const Value: Integer);
begin
FIconIndex:=Value;
paint;
end;
procedure TMyPCButton.SetIconList(const Value: TcxImageList);
begin
FIconList:=Value;
paint;
end;
procedure TMyPCButton.WndProc(var Message: TMessage);
begin
inherited;
case Message.Msg of
CM_MOUSEENTER:
begin
if FActive=False then
begin
FState:=1;
Paint;
end;
end;
CM_MOUSELEAVE:
begin
if FActive=False then
begin
FState:=0;
FCurCloseIconState:=-1;
Paint;
end
else
begin
FState:=2;
FCurCloseIconState:=-1;
Paint;
end;
end;
WM_ERASEBKGND:
Message.Result := 1;
end;
end;
procedure TMyPCButton.SwapBuffers;
begin
BitBlt(Canvas.Handle, 0, 0, Width, Height, FBuffer.Canvas.Handle, 0, 0, SRCCOPY);
end;
end.
MyPanel.pas
unit MyPanel;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.ExtCtrls;
type
TMyPanel = class(TPanel)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyComponents', [TMyPanel]);
end;
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited;
end;
destructor TMyPanel.Destroy;
begin
inherited;
end;
end.
There is nothing particularly special about what you are trying to do but your implementation and the way you describe installing pas files into packages suggests that you are perhaps not clear on how to go about it.
First of all, it has long been considered bad practice to combine components and IDE registration in the same package.
You should implement your components in a Runtime-only Package (or packages). You then have a Design-Time Package which includes the corresponding Runtime package in it's requires list.
The Design-Time Package also contains (typically) a single unit which uses the units containing your components and implements the Register function.
You then install the Design-Time Package into the IDE, which will load the Runtime Packages as needed to register the components.
Within that overall approach, you still have a number of options for how to organise your packages.
You could have separate runtime packages for each control and separate designtime packages to install each control separately, but it sounds like you will then have a lot of dependencies between your packages which can quickly become problematic to unravel and create dependencies in the order in which you build and install your packages.
In your case since your two components have this dependency on each other, it sounds like it would make most sense to keep those components in one single runtime package and have a single designtime package to install all the components from that runtime package.
If you really wanted to you could still have separate design-time packages to install each control individually from that single runtime package, but I really don't see any advantage in doing that in your case (unless there are further complications or consideration which are not apparent from your question).
it was my mistake
i had changed the default dcp output directory and apparently i had to add the new path in library path, so now i am able to put/install the .pas files in their own packages
I am writing this question for Delphi 2007, but I'm pretty sure that this is a common problem in all kind of languages.
So, I have a project where I need to keep informations about the old and new value of certain fields (which are given in the BeforePost event of the dataset I'm working with) and use them in the AfterPost event.
For now, I have been using global variables, but there is already so many of them in the project that this is becoming a real issue when it comes to managing documentation and/or comments.
Basically, I am asking if there is a better way (in Delphi 2007 or in general) to keep the informations from the BeforePost event of a Dataset and get them back in the AfterPost event.
first create a new Custom Data Source
TDataRecord = array of record
FieldName: string;
FieldValue: Variant;
end;
TMyDataSource = class(TDataSource)
private
LastValues: TDataRecord;
procedure MyDataSourceBeforePost(DataSet: TDataSet);
procedure SetDataSet(const Value: TDataSet);
function GetDataSet: TDataSet;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetLastValue(FieldName: string): Variant;
property MyDataSet: TDataSet read GetDataSet write SetDataSet;
end;
{ TMyDataSource }
constructor TMyDataSource.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
destructor TMyDataSource.Destroy;
begin
SetLength(LastValues, 0);
inherited Destroy;
end;
function TMyDataSource.GetDataSet: TDataSet;
begin
Result := DataSet;
end;
procedure TMyDataSource.SetDataSet(const Value: TDataSet);
begin
DataSet := Value;
DataSet.BeforePost := MyDataSourceBeforePost;
end;
procedure TMyDataSource.MyDataSourceBeforePost(DataSet: TDataSet);
var
i: integer;
begin
SetLength(LastValues, DataSet.FieldCount);
for i:=0 to DataSet.FieldCount-1 do
begin
LastValues[i].FieldName := DataSet.Fields.Fields[i].FieldName;
LastValues[i].FieldValue := DataSet.Fields.Fields[i].OldValue;
end;
end;
function TMyDataSource.GetLastValue(FieldName: string): Variant;
var
i: integer;
begin
Result := Null;
for i:=0 to Length(LastValues)-1 do
if SameText(FieldName, LastValues[i].FieldName) then
begin
Result := LastValues[i].FieldValue;
break;
end;
end;
and after override application Data Source
TForm1 = class(TForm)
private
MyDataSource: TMyDataSource;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ADOQuery1.Active := true;
MyDataSource := TMyDataSource.Create(Self);
MyDataSource.MyDataSet := ADOQuery1;
DBGrid1.DataSource := MyDataSource;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MyDataSource.Free;
end;
procedure TForm1.ADOQuery1AfterPost(DataSet: TDataSet);
var
AValue: Variant;
begin
AValue := MyDataSource.GetLastValue('cname');
if not VarIsNull(AValue) then;
end;