I'm moving a TMemo object left and right in my GUI application. The problem is, is that the letters in my TMemo are flickering as soon as the movement starts.
I've looked this up, and, apparently, setting the DoubleBuffering property of my main form should've helped me, but it didn't. So I tried setting that property to true on all objects that were moving, but flickering was still present.
Are there any ways to achieve flicker-free animations of GUI components in Lazarus? I'm a novice in Lazarus, so I'm kind of blindly googling for solutions right now. I would really appreciate some help.
To provide further context, here's how I animate my TMemo: I've got a TTimer with an interval value of 10, and its OnTimer event moves my TMemo left and right contiguously. To make the movement slightly smoother, I added a simple cosine interpolation function.
In the end here's the code:
procedure TServerSideForm.ControlPanelHideTimerTimer(Sender: TObject);
begin
if (hideAnimVal < 1) then
begin
hideAnimVal := hideAnimVal + 0.025;
end
else
begin
MemoHideTimer.Enabled:=false;
end;
// hideStart - starting position of my TMemo, hideEnd - end position of my TMemo
hideCurr := Round(CosineInterpolation(hideStart, hideEnd, hideAnimVal));
Memo.Left:=hideCurr;
end;
Cosine interpolation:
function CosineInterpolation(Val1, Val2, Angle: Double): Double;
var
Percent: Double;
begin
Percent := (1-Cos(Angle*PI))/2;
Result := (Val1 * (1 - Percent) + Val2 * Percent);
end;
I would try to move an image instead:
var
Memo1dc: hdc;
Cnv: TCanvas;
Rct: TRect;
implementation
procedure TForm1.MemoHideTimerTimer(Sender: TObject);
begin
if Memo1.Visible then
begin
Memo1dc := GetDC(Memo1.Handle);
Cnv.Handle := Memo1dc;
Rct.Height := Memo1.Height;
Rct.Width := Memo1.Width;
Image1.Left := Memo1.Left;
Image1.Top := Memo1.Top;
Image1.Width := Memo1.Width;
Image1.Height := Memo1.Height;
Image1.Canvas.CopyRect(Rct, Cnv, Rct);
Memo1.Visible := False;
Image1.Visible := True;
end;
if (hideAnimVal < 1) then
begin
hideAnimVal := hideAnimVal + 0.025;
end
else
begin
MemoHideTimer.Enabled := False;
end;
// hideStart - starting position of my TMemo, hideEnd - end position of my TMemo
hideCurr := Round(CosineInterpolation(hideStart, hideEnd, hideAnimVal));
Image1.Left := hideCurr;
if MemoHideTimer.Enabled = False then
begin
Memo1.Left := Image1.Left;
Memo1.Visible := True;
Image1.Visible := False;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Cnv := TCanvas.Create;
end;
program GameMain;
uses SwinGame, sgTypes;
function buttonClicked(p1, Next_PARAM_thingie: Single; W, lastOne: Integer): Boolean;
var blah, blee: Single; _r_, BTMOB: Single;
begin blah := MouseX(); blee := MouseY(); _r_ := p1 + W; BTMOB := Next_PARAM_thingie + lastOne; result := false;
if MouseClicked( LeftButton ) then
begin
if (blah >= p1) and (blah <= _r_) then
begin result := true;
end;
end;
end;
procedure Main();
var
clr: Color;
begin
OpenGraphicsWindow('Test Program for Button Click Code', 800, 600);
ShowSwinGameSplashScreen();
clr := ColorWhite;
repeat
clearScreen(clr);
drawframerate(0,0);
fillRectangle(ColorGrey, 50, 50, 100, 30);
drawtext('Click Me', ColorBlack, 'arial.ttf', 14, 55, 55);
RefreshScreen();
Processevents();
if buttonClicked(50, 50, 100, 30) then
begin
clr := RandomRGBcolor(255);
end;
until WindowCloseRequested();
end;
begin
main();
end.
I have been trying to figure out what does what but it isn't going to well. I could use some help trying to figure out what each of these random words do so I can change the name so the code is more understanding
The first thing that I would do is work through removing things that are unneeded. In the code sample given, BTMOB is entirely unused, so I would remove it and the code that sets its value. With BTMOB removed, the lastOne parameter is no longer needed, so it goes away.
Keep chipping away things that don't belong at all and using whatever context clues are available to give things that are used more meaningful names. There will be some things that you may not be able to guess just by analyzing the code and potentially not even through runtime debugging, but you should be able to make it far more readable. Here's an example of how buttonClicked might look after the first pass (you'd also have to change the code that calls it to no longer pass the unused parameters that were removed).
function buttonClicked(p1: Single; W: Integer): Boolean;
var posX: Single; _r_: Single;
begin posX := MouseX(); _r_ := p1 + W; result := false;
if MouseClicked( LeftButton ) then
begin
if (posX >= p1) and (posX <= _r_) then
begin result := true;
end;
end;
end;
I need a simple thing: have advance and normal installation buttons. For normal case it is all simple - I used default next button with some logic on NextButtonClick to set a condition variable and skeep some pages using ShouldSkipPage . Yet for advanced setup I created a new button and all I need it to do on click is to open next installer page:
procedure CurPageChanged(CurPageID : Integer);
begin
if CurPageID = wpWelcome then begin
AdvancedButton := TButton.Create(WizardForm);
AdvancedButton.Caption := 'Advanced Install';
AdvancedButton.Left := WizardForm.InfoAfterPage.Left + 10;
AdvancedButton.Top := WizardForm.InfoAfterPage.Height + 88;
AdvancedButton.Parent := WizardForm.NextButton.Parent;
# AdvancedButton.OnClick := What shall I call to open say next page (or some page by given PageID value)
end
else begin
AdvancedButton.Visible := False;
end;
end;
So What shall I call to open say next page (or some page by given PageID value) on my button click (could not find any NextPage or some SetPage function in Inno API)?
There is no such thing as "Direct jump to page" in Inno Setup.
All you need is to quietly skip certain pages in 'Advanced mode'.
Simply do the same as in regular installer. Set one variable for holding 'Advanced mode'. After clicking the Advance button:
[Code]
var
IsAdvanced: Boolean;
procedure AdvancedButtonClick(Sender: TObject);
begin
IsAdvanced := True;
WizardForm.NextButton.OnClick(nil);
end;
procedure InitializeWizard;
var
AdvancedButton: TNewButton;
begin
// not necessary; just for sure
IsAdvanced := False;
// create the button
AdvancedButton := TNewButton.Create(WizardForm);
AdvancedButton.Caption := 'Advanced Install';
AdvancedButton.Left := WizardForm.InfoAfterPage.Left + 10;
AdvancedButton.Top := WizardForm.InfoAfterPage.Height + 88;
AdvancedButton.Parent := WizardForm.NextButton.Parent;
AdvancedButton.OnClick := #AdvancedButtonClick;
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
// the <page where you want to end up> fill with the wizard page constant
// of the page where you want to end up, e.g. wpReady
Result := IsAdvanced and (PageID <> <page where you want to end up>);
end;
With this logic you can simulate quiet 'jump' to certain page.
Please see the attached screenshot which illustrates a TToolBar from one of my programs:
Notice the last two images of the Toolbar, they are disabled. The way they have been drawn to appear disabled is not very appealing, in fact in the Delphi IDE some of the images look the same.
The issue I have with it is I want my application to look a lot cleaner. The way the disabled items are drawn doesn't look very good. The TToolBar allows to set a disabled TImageList, I tried making my images black & white but they didn't look right, and would rather not have to always make the images black and white (time and effort). This problem also shows in my menus and popup menus, which don't allow for disabled images anyway.
Is there a way to paint the disabled items to look better on the eye?
If possible I would rather not look to use 3rd Party Controls. I know the Jedi Components allow disabled images for the menu etc, but would prefer a way to not resort too 3rd Party Components, when possible I would much prefer to use the standard issue VCL, especially as sometimes I use the TActionMainMenuBar to draw Office Style menus, which match the TToolBar when DrawingStyle is set to gradient.
EDIT
I have accepted RRUZ's answer, is it possible though to accept David's answer as well, both are very good answers and would like the answer to be shared between them if possible.
Thanks.
Sometime Ago i wrote a patch to fix this behavior. the key is patch the code of the TCustomImageList.DoDraw function, the technique used is similar to the used by the delphi-nice-toolbar app, but instead of patch a bpl IDE in this case we patch the function in memory.
Just include this unit in your project
unit uCustomImageDrawHook;
interface
uses
Windows,
SysUtils,
Graphics,
ImgList,
CommCtrl,
Math;
implementation
type
TJumpOfs = Integer;
PPointer = ^Pointer;
PXRedirCode = ^TXRedirCode;
TXRedirCode = packed record
Jump: Byte;
Offset: TJumpOfs;
end;
PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
TAbsoluteIndirectJmp = packed record
OpCode: Word;
Addr: PPointer;
end;
TCustomImageListHack = class(TCustomImageList);
var
DoDrawBackup : TXRedirCode;
function GetActualAddr(Proc: Pointer): Pointer;
begin
if Proc <> nil then
begin
if (Win32Platform = VER_PLATFORM_WIN32_NT) and (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
Result := PAbsoluteIndirectJmp(Proc).Addr^
else
Result := Proc;
end
else
Result := nil;
end;
procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
var
n: DWORD;
Code: TXRedirCode;
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
if ReadProcessMemory(GetCurrentProcess, Proc, #BackupCode, SizeOf(BackupCode), n) then
begin
Code.Jump := $E9;
Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
WriteProcessMemory(GetCurrentProcess, Proc, #Code, SizeOf(Code), n);
end;
end;
procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
var
n: Cardinal;
begin
if (BackupCode.Jump <> 0) and (Proc <> nil) then
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
WriteProcessMemory(GetCurrentProcess, Proc, #BackupCode, SizeOf(BackupCode), n);
BackupCode.Jump := 0;
end;
end;
procedure Bitmap2GrayScale(const BitMap: TBitmap);
type
TRGBArray = array[0..32767] of TRGBTriple;
PRGBArray = ^TRGBArray;
var
x, y, Gray: Integer;
Row : PRGBArray;
begin
BitMap.PixelFormat := pf24Bit;
for y := 0 to BitMap.Height - 1 do
begin
Row := BitMap.ScanLine[y];
for x := 0 to BitMap.Width - 1 do
begin
Gray := (Row[x].rgbtRed + Row[x].rgbtGreen + Row[x].rgbtBlue) div 3;
Row[x].rgbtRed := Gray;
Row[x].rgbtGreen := Gray;
Row[x].rgbtBlue := Gray;
end;
end;
end;
//from ImgList.GetRGBColor
function GetRGBColor(Value: TColor): DWORD;
begin
Result := ColorToRGB(Value);
case Result of
clNone:
Result := CLR_NONE;
clDefault:
Result := CLR_DEFAULT;
end;
end;
procedure New_Draw(Self: TObject; Index: Integer; Canvas: TCanvas; X, Y: Integer; Style: Cardinal; Enabled: Boolean);
var
MaskBitMap : TBitmap;
GrayBitMap : TBitmap;
begin
with TCustomImageListHack(Self) do
begin
if not HandleAllocated then Exit;
if Enabled then
ImageList_DrawEx(Handle, Index, Canvas.Handle, X, Y, 0, 0, GetRGBColor(BkColor), GetRGBColor(BlendColor), Style)
else
begin
GrayBitMap := TBitmap.Create;
MaskBitMap := TBitmap.Create;
try
GrayBitMap.SetSize(Width, Height);
MaskBitMap.SetSize(Width, Height);
GetImages(Index, GrayBitMap, MaskBitMap);
Bitmap2GrayScale(GrayBitMap);
BitBlt(Canvas.Handle, X, Y, Width, Height, MaskBitMap.Canvas.Handle, 0, 0, SRCERASE);
BitBlt(Canvas.Handle, X, Y, Width, Height, GrayBitMap.Canvas.Handle, 0, 0, SRCINVERT);
finally
GrayBitMap.Free;
MaskBitMap.Free;
end;
end;
end;
end;
procedure HookDraw;
begin
HookProc(#TCustomImageListHack.DoDraw, #New_Draw, DoDrawBackup);
end;
procedure UnHookDraw;
begin
UnhookProc(#TCustomImageListHack.DoDraw, DoDrawBackup);
end;
initialization
HookDraw;
finalization
UnHookDraw;
end.
and the result will be
I submitted a QC report for a related issue over a year ago, but that was for menus. I've never seen this for TToolbar since it is a wrapper to the common control and the drawing is handled by Windows.
However, the images you are seeing are clearly as result of the VCL calling TImageList.Draw and passing Enabled=False – nothing else looks that bad! Are you 100% sure this really is a TToolbar?
The fix will surely be to avoid TImageList.Draw and call ImageList_DrawIndirect with the ILS_SATURATE.
You may need to modify some VCL source. First find the location where the toolbar is being custom drawn and call this routine instead of the calls to TImageList.Draw.
procedure DrawDisabledImage(DC: HDC; ImageList: TCustomImageList; Index, X, Y: Integer);
var
Options: TImageListDrawParams;
begin
ZeroMemory(#Options, SizeOf(Options));
Options.cbSize := SizeOf(Options);
Options.himl := ImageList.Handle;
Options.i := Index;
Options.hdcDst := DC;
Options.x := X;
Options.y := Y;
Options.fState := ILS_SATURATE;
ImageList_DrawIndirect(#Options);
end;
An even better fix would be to work out why the toolbar is being custom drawn and find a way to let the system do it.
EDIT 1
I've looked at the Delphi source code and I'd guess that you are custom drawing the toolbar, perhaps because it has a gradient. I never even knew that TToolbar could handle custom drawing but I'm just a plain vanilla kind of guy!
Anyway, I can see code in TToolBar.GradientDrawButton calling the TImageList.Draw so I think the explanation above is on the right track.
I'm fairly sure that calling my DrawDisabledImage function above will give you better results. If could find a way to make that happen when you call TImageList.Draw then that would, I suppose, be the very best fix since it would apply wholesale.
EDIT 2
Combine the function above with #RRUZ's answer and you have an excellent solution.
Solution from #RRUZ dosn't work if you use LargeImages in ActionToolBar. I made changes to the #RRUZ code to work with LargeImages in ActionToolBar.
unit unCustomImageDrawHook;
interface
uses
Windows,
SysUtils,
Graphics,
ImgList,
CommCtrl,
Math,
Vcl.ActnMan,
System.Classes;
implementation
type
TJumpOfs = Integer;
PPointer = ^Pointer;
PXRedirCode = ^TXRedirCode;
TXRedirCode = packed record
Jump: Byte;
Offset: TJumpOfs;
end;
PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
TAbsoluteIndirectJmp = packed record
OpCode: Word;
Addr: PPointer;
end;
TCustomImageListHack = class(TCustomImageList);
TCustomActionControlHook = class(TCustomActionControl);
var
DoDrawBackup : TXRedirCode;
DoDrawBackup2 : TXRedirCode;
function GetActualAddr(Proc: Pointer): Pointer;
begin
if Proc <> nil then
begin
if (Win32Platform = VER_PLATFORM_WIN32_NT) and (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
Result := PAbsoluteIndirectJmp(Proc).Addr^
else
Result := Proc;
end
else
Result := nil;
end;
procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
var
n: SIZE_T;
Code: TXRedirCode;
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
if ReadProcessMemory(GetCurrentProcess, Proc, #BackupCode, SizeOf(BackupCode), n) then
begin
Code.Jump := $E9;
Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
WriteProcessMemory(GetCurrentProcess, Proc, #Code, SizeOf(Code), n);
end;
end;
procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
var
n: SIZE_T;
begin
if (BackupCode.Jump <> 0) and (Proc <> nil) then
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
WriteProcessMemory(GetCurrentProcess, Proc, #BackupCode, SizeOf(BackupCode), n);
BackupCode.Jump := 0;
end;
end;
procedure Bitmap2GrayScale(const BitMap: TBitmap);
type
TRGBArray = array[0..32767] of TRGBTriple;
PRGBArray = ^TRGBArray;
var
x, y, Gray: Integer;
Row : PRGBArray;
begin
BitMap.PixelFormat := pf24Bit;
for y := 0 to BitMap.Height - 1 do
begin
Row := BitMap.ScanLine[y];
for x := 0 to BitMap.Width - 1 do
begin
Gray := (Row[x].rgbtRed + Row[x].rgbtGreen + Row[x].rgbtBlue) div 3;
Row[x].rgbtRed := Gray;
Row[x].rgbtGreen := Gray;
Row[x].rgbtBlue := Gray;
end;
end;
end;
//from ImgList.GetRGBColor
function GetRGBColor(Value: TColor): DWORD;
begin
Result := ColorToRGB(Value);
case Result of
clNone:
Result := CLR_NONE;
clDefault:
Result := CLR_DEFAULT;
end;
end;
procedure New_Draw(Self: TObject; Index: Integer; Canvas: TCanvas; X, Y: Integer; Style: Cardinal; Enabled: Boolean);
var
MaskBitMap : TBitmap;
GrayBitMap : TBitmap;
begin
with TCustomImageListHack(Self) do
begin
if not HandleAllocated then Exit;
if Enabled then
ImageList_DrawEx(Handle, Index, Canvas.Handle, X, Y, 0, 0, GetRGBColor(BkColor), GetRGBColor(BlendColor), Style)
else
begin
GrayBitMap := TBitmap.Create;
MaskBitMap := TBitmap.Create;
try
GrayBitMap.SetSize(Width, Height);
MaskBitMap.SetSize(Width, Height);
GetImages(Index, GrayBitMap, MaskBitMap);
Bitmap2GrayScale(GrayBitMap);
BitBlt(Canvas.Handle, X, Y, Width, Height, MaskBitMap.Canvas.Handle, 0, 0, SRCERASE);
BitBlt(Canvas.Handle, X, Y, Width, Height, GrayBitMap.Canvas.Handle, 0, 0, SRCINVERT);
finally
GrayBitMap.Free;
MaskBitMap.Free;
end;
end;
end;
end;
procedure New_Draw2(Self: TObject; const Location: TPoint);
var
ImageList: TCustomImageList;
DrawEnabled: Boolean;
LDisabled: Boolean;
begin
with TCustomActionControlHook(Self) do
begin
if not HasGlyph then Exit;
ImageList := FindImageList(True, LDisabled, ActionClient.ImageIndex);
if not Assigned(ImageList) then Exit;
DrawEnabled := LDisabled or Enabled and (ActionClient.ImageIndex <> -1) or
(csDesigning in ComponentState);
ImageList.Draw(Canvas, Location.X, Location.Y, ActionClient.ImageIndex,
dsTransparent, itImage, DrawEnabled);
end;
end;
procedure HookDraw;
begin
HookProc(#TCustomImageListHack.DoDraw, #New_Draw, DoDrawBackup);
HookProc(#TCustomActionControlHook.DrawLargeGlyph, #New_Draw2, DoDrawBackup2);
end;
procedure UnHookDraw;
begin
UnhookProc(#TCustomImageListHack.DoDraw, DoDrawBackup);
UnhookProc(#TCustomActionControlHook.DrawLargeGlyph, DoDrawBackup2);
end;
initialization
HookDraw;
finalization
UnHookDraw;
end.
Take a look at this Delphi IDE fix. Maybe you can mimic it's implementation.
Use TActionToolbar , TActionmanager , Timagelist
Set action managers image list to a Timagelist. and set Disabledimages to another imagelist
I'm trying to iterate all the controls on a form and enable ClearType font smoothing. Something like this:
procedure TForm4.UpdateControls(AParent: TWinControl);
var
I: Integer;
ACtrl: TControl;
tagLOGFONT: TLogFont;
begin
for I := 0 to AParent.ControlCount-1 do
begin
ACtrl:= AParent.Controls[I];
// if ParentFont=False, update the font here...
if ACtrl is TWinControl then
UpdateControls(Ctrl as TWinControl);
end;
end;
Now, is there a easy way to check if ACtrl have a Font property so i can pass the Font.Handle to somethink like:
GetObject(ACtrl.Font.Handle, SizeOf(TLogFont), #tagLOGFONT);
tagLOGFONT.lfQuality := 5;
ACtrl.Font.Handle := CreateFontIndirect(tagLOGFONT);
Thank you in advance.
You use TypInfo unit, more specifically methods IsPublishedProp and GetOrdProp.
In your case, it would be something like:
if IsPublishedProp(ACtrl, 'Font') then
ModifyFont(TFont(GetOrdProp(ACtrl, 'Font')))
A fragment from one of my libraries that should put you on the right path:
function ContainsNonemptyControl(controlParent: TWinControl;
const requiredControlNamePrefix: string;
const ignoreControls: string = ''): boolean;
var
child : TControl;
iControl: integer;
ignored : TStringList;
obj : TObject;
begin
Result := true;
if ignoreControls = '' then
ignored := nil
else begin
ignored := TStringList.Create;
ignored.Text := ignoreControls;
end;
try
for iControl := 0 to controlParent.ControlCount-1 do begin
child := controlParent.Controls[iControl];
if (requiredControlNamePrefix = '') or
SameText(requiredControlNamePrefix, Copy(child.Name, 1,
Length(requiredControlNamePrefix))) then
if (not assigned(ignored)) or (ignored.IndexOf(child.Name) < 0) then
if IsPublishedProp(child, 'Text') and (GetStrProp(child, 'Text') <> '') then
Exit
else if IsPublishedProp(child, 'Lines') then begin
obj := TObject(cardinal(GetOrdProp(child, 'Lines')));
if (obj is TStrings) and (Unwrap(TStrings(obj).Text, child) <> '') then
Exit;
end;
end; //for iControl
finally FreeAndNil(ignored); end;
Result := false;
end; { ContainsNonemptyControl }
There's no need to use RTTI for this. Every TControl descendant has a Font property. At TControl level its visibility is protected but you can use this workaround to access it:
type
THackControl = class(TControl);
ModifyFont(THackControl(AParent.Controls[I]).Font);
One other thing worth mentioning. Every control has a ParentFont property, which - if set - allows the Form's font choice to ripple down to every control. I tend to make sure ParentFont is set true wherever possible, which also makes it easier to theme forms according to the current OS.
Anyway, surely you shouldn't need to do anything to enable ClearType smoothing? It should just happen automatically if you use a TrueType font and the user has enabled the Cleartype "effect".
Here's a C++Builder example of TOndrej's answer:
struct THackControl : TControl
{
__fastcall virtual THackControl(Classes::TComponent* AOwner);
TFont* Font() { return TControl::Font; };
};
for(int ControlIdx = 0; ControlIdx < ControlCount; ++ControlIdx)
{
((THackControl*)Controls[ControlIdx])->Font()->Color = clRed;
}