Basically, I want the TextHint of my TEdits to disappear when the first character is entered and not when they receive focus, like the Edits on this Microsoft page: Sign in to your Microsoft account. Can someone please walk me through on how to achieve this?
Thank you in advance.
The built-in TEdit behavior doesn't allow this, but you can derive a new control from TEdit and override DoSetTextHint. The implementation should be similar to the internal method, but provide a value of 1 for wParam instead of 0.
This is a solution using an interceptor class:
unit EditInterceptor;
uses
Vcl.StdCtrls, System.SysUtils, Winapi.Messages, Windows;
type
TEdit = class(Vcl.StdCtrls.TEdit)
protected
procedure DoSetTextHint(const Value: string); override;
end;
implementation
uses
Vcl.Themes, Winapi.CommCtrl;
procedure TEdit.DoSetTextHint(const Value: string);
begin
if CheckWin32Version(5, 1) and StyleServices.Enabled and HandleAllocated then
SendTextMessage(Handle, EM_SETCUEBANNER, WPARAM(1), Value);
end;
end.
Make sure to place this unit in the interface uses clause after Vcl.StdCtrls.
Based on Uwe Raabe's answer, here is a procedure (for Delphi 2007, should work for newer versions of Delphi as well):
type
TCueBannerHideEnum = (cbhHideOnFocus, cbhHideOnText);
procedure TEdit_SetCueBanner(_ed: TEdit; const _s: WideString; _WhenToHide: TCueBannerHideEnum = cbhHideOnFocus);
const
EM_SETCUEBANNER = $1501;
var
wParam: Integer;
begin
case _WhenToHide of
cbhHideOnText: wParam := 1;
else // cbhHideOnFocus: ;
wParam := 0;
end;
SendMessage(_ed.Handle, EM_SETCUEBANNER, wParam, Integer(PWideChar(_s)));
end;
You call it like this:
constructor TForm1.Create(_Owner: TComponent);
begin
inherited;
TEdit_SetCueBanner(ed_HideOnFocus, 'hide on focus', cbhHideOnFocus);
TEdit_SetCueBanner(ed_HideOnText, 'hide on text', cbhHideOnText);
end;
It doesn't check for the Windows version though, you might want to add the if statement Uwe provided:
if CheckWin32Version(5, 1) and StyleServices.Enabled and _ed.HandleAllocated then
I just tested it with a project where I disabled runtime theming: It didn't work.
Related
I'm trying to achieve a simple drag and drop-panel, where a user can drop a file from windows explorer. The basic functionality is already working after I found this Thread.
Now I'm trying to change the color of the panel, while the user is dragging a file over it. I tried to use OnDragOver, but nothing happens. What am I doing wrong?
This is my current code:
unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ShellApi,
Vcl.ExtCtrls, Vcl.Imaging.pngimage;
type
TPanel = class(Vcl.ExtCtrls.TPanel)
protected
procedure WMDropFiles(var Message: TWMDropFiles); message WM_DROPFILES;
procedure CreateWnd; override;
procedure DestroyWnd; override;
end;
TfrmMain = class(TForm)
panFileDrop: TPanel;
lblFileName: TLabel;
procedure panFileDropDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TPanel.CreateWnd;
begin
inherited;
DragAcceptFiles(Handle, true);
end;
procedure TPanel.DestroyWnd;
begin
DragAcceptFiles(Handle, false);
inherited;
end;
procedure TPanel.WMDropFiles(var Message: TWMDropFiles);
var
c: integer;
fn: array[0..MAX_PATH-1] of char;
begin
c := DragQueryFile(Message.Drop, $FFFFFFFF, fn, MAX_PATH);
if c <> 1 then
begin
MessageBox(Handle, 'Too many files.', 'Drag and drop error', MB_ICONERROR);
Exit;
end;
if DragQueryFile(Message.Drop, 0, fn, MAX_PATH) = 0 then Exit;
frmMain.lblFileName.Caption := fn;
end;
procedure TfrmMain.panFileDropDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
panFileDrop.Color := $00d4d3d2;
end;
end.
The problem
Delphi's concept of Drag'n'drop is not related to COM Drag and drop at all.
Borland implemented a light-weight version for dragging and dropping within the same application.
This works great and very efficient, but does not support DnD operations between applications. COM drag and drop requires you to register a drop target with the OS and accept relevant mouse messages. At no point will a COM drag&drop ever generate an standard OnDragOver event.
I fear the documentation is quite misleading when it does not make clear this source of confusion.
You are mixing Windows message based code TPanel.WMDropFiles(var Message: TWMDropFiles) with Borland's implementation for intra-application use only: TfrmMain.panFileDropDragOver(...)
The two options exist in parallel universes.
If you want to do the COM way you need to go COM all the way.
The solution
The WMDropFiles option is still a 'light-weight' solution before you go full COM and need to implement IDropTarget and all the complexity that entails.
My answer to your question is to not invent your own drag and drop but to go on the intertubes and download: https://github.com/DelphiPraxis/The-Drag-and-Drop-Component-Suite-for-Delphi
This is the up to date version of Anders Melander's famous suite which used to be at: http://melander.dk/delphi/dragdrop/
This implements COM based drag and drop and solves all your problems in one go.
It is also a fine example of beautiful code in its own right.
Take special note of the demos. The shelldragdrop stuff should cover your use case.
Would you like to know more?
http://delphi.about.com/od/vclusing/a/dragdrop.htm
The default font of the object inspector is ridiculously small, esp on a high resolution screen.
Is there a way to make it bigger?
Yes there is and it's really easy.
You can alter any window in the IDE by creating a package and installing this in the IDE.
Because the bpl gets loaded into the main process of the Delphi IDE you can alter any IDE window's properties from there.
Code by Mike Fletcher
Create a new package and add the following unit:
unit AdjustOIFont;
interface
uses Vcl.Forms, Vcl.Controls, Vcl.Dialogs, Vcl.StdCtrls;
procedure Register;
implementation
function GetOIForm: TForm;
var
i: Integer;
begin
Result:= nil;
for i:= 0 to Screen.FormCount - 1 do begin
if Screen.Forms[i].Name = 'PropertyInspector' then begin
Result:= Screen.Forms[I];
Exit;
end;
end;
end;
function GetChildControl(AParent: TWinControl; AName: string): TWinControl;
var
i: Integer;
begin
Result:= nil;
for i:= 0 to AParent.ControlCount - 1 do begin
if AParent.Controls[i].Name = AName then begin
Result:= TWinControl(AParent.Controls[i]);
Exit;
end;
end;
end;
function GetOIControl: TCustomListBox;
var
OIForm: TForm;
begin
OIForm:= GetOIForm;
Result:= TCustomListBox(GetChildControl(GetChildControl(OIForm, 'Panel3'), 'PropList'));
end;
procedure Register;
var
OI: TListBox;
OIForm: TForm;
begin
OIForm:= GetOIForm;
OIForm.Font.Size:= 10;
OI:= TListBox(GetOIControl);
OI.Font.Size:= 10;
OI.ItemHeight:= 20;
end;
end.
Build the package and install.
The change will take effect immediately.
Knowing this trick it's also be easy to collect all the enumerated names in a stringlist and copy them to the clipboard.
These names can than be used to expand the code and fix the fonts of other IDE elements as well (e.g. the Structure pane).
Much better.
Works on Seattle and XE7.
One way to achieving this is by modifying registry like it is described in Malcolm Groves article here: http://www.malcolmgroves.com/blog/?p=1804
Another option is to use Delphi IDE Colorizer which is a third party application designed to greatly change appearance of Delphi IDE by changing fonts, colors, etc. You can find it here: https://github.com/RRUZ/Delphi-IDE-Colorizer
And if you perhaps also want to change syntax fonts and syntax highlighting you can also check Delphi IDE Theme Editor which is designed to change the appearance of code highlighting based on your desires. You can find it here: https://github.com/RRUZ/delphi-ide-theme-editor
I've searched around and the general answer seems to place
SomeEdit2.setFocus;
in SomeEdit1.OnExit event. I have tried this (Using Delphi Xe5, developing for iOS) and it causes the application to crash. The app does not throw an error, it just blanks out and crashes. I've tried placing the same code in other events but it does not work as expected. For example, when placed in SomeEdit1.OnChange event, when a user hits 'done' on the virtual keyboard - Focus is switched to the desired control, but the keyboard does not show and stops working properly.
What is the proper way to change focus inbetween controls when a user hits the 'done' button provided on the virtual keyboard?
You can not compare VCL-Control behaviour with FMX-Control behaviour, because sometimes they behave different - they should not, but they do.
In VCL you have an OnExit event and it occurs right after the focus has left the control. So this is an OnAfterExit event.
In FMX the OnExit event is fired before the focus gets away. So this is an OnBeforeExit.
procedure TControl.DoExit;
begin
if FIsFocused then
begin
try
if CanFocus and Assigned(FOnExit) then
FOnExit(Self);
FIsFocused := False;
Now, what has this to do with your current problem?
If you set the focus to another control inside the OnExit event, the current active control DoExit method gets called, which calls the OnExit event, and you have a perfect circle.
So you have several options to fix this
Bug Report
The best solution is to create a bug report and let emba fix this.
There is already a bug report 117752 with the same reason. So I posted the solution as a comment.
Patch FMX.Controls.pas
Copy FMX.Controls into your project source directory and patch the buggy code (just one line)
procedure TControl.DoExit;
begin
if FIsFocused then
begin
try
FIsFocused := False; // thats the place to be, before firering OnExit event
if CanFocus and Assigned(FOnExit) then
FOnExit(Self);
//FIsFocused := False; <-- buggy here
SetFocus to control
To set the focus in the OnExit you have to do some more work, because the message to change the focus to the next control is already queued. You must ensure that the focus change to the desired control take place after that already queued focus change message. The simplest approach is using a timer.
Here is an example FMX form with 3 edit controls and each of them has an OnExit event
unit MainForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Edit;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
EnsureActiveControl_Timer: TTimer;
procedure EnsureActiveControl_TimerTimer(Sender: TObject);
procedure Edit1Exit(Sender: TObject);
procedure Edit2Exit(Sender: TObject);
procedure Edit3Exit(Sender: TObject);
private
// locks the NextActiveControl property to prevent changes while performing the timer event
FTimerSwitchInProgress: Boolean;
FNextActiveControl: TControl;
procedure SetNextActiveControl(const Value: TControl);
protected
property NextActiveControl: TControl read FNextActiveControl write SetNextActiveControl;
public
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.Edit1Exit(Sender: TObject);
begin
NextActiveControl := Edit3;
end;
procedure TForm1.Edit2Exit(Sender: TObject);
begin
NextActiveControl := Edit1;
end;
procedure TForm1.Edit3Exit(Sender: TObject);
begin
NextActiveControl := Edit2;
end;
procedure TForm1.EnsureActiveControl_TimerTimer(Sender: TObject);
begin
EnsureActiveControl_Timer.Enabled := False;
FTimerSwitchInProgress := True;
try
if (Self.ActiveControl <> NextActiveControl) and NextActiveControl.CanFocus then
NextActiveControl.SetFocus;
finally
FTimerSwitchInProgress := False;
end;
end;
procedure TForm1.SetNextActiveControl(const Value: TControl);
begin
if FTimerSwitchInProgress
or (FNextActiveControl = Value)
or (Assigned(Value) and not Value.CanFocus)
or (Self.ActiveControl = Value)
then
Exit;
FNextActiveControl := Value;
EnsureActiveControl_Timer.Enabled := Assigned(FNextActiveControl);
end;
end.
How to add support of HTML help files (.chm) on Delphi XE2? We need to use A-links (A-keywords) on HelpContext property of every control to lookup help pages. Delphi XE2 has native support of HTML help files by unit HTMLHelpViewer. But how to use it?
It's not hard with F1 jump to a context.
Select Edit1 and press F1 . Help opens and Overview.htm is shown.
Prerequisite.
Edit1 Help settings:
sample.chm source settings.
sample.ali
IDH_Overview=Overview.htm
IDH_welcom=FirstTopic.htm
IDH_UsingtheMenus=Overview.htm
sample.h
#define IDH_Creating_Projects_and_Topics 1005
#define IDH_Overview 1003
#define IDH_UsingtheMenus 1009
Unit1.pas
unit Unit1;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, HTMLHelpViewer, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
HHALINKLOOKUP: TButton;
JumpAnchor: TButton;
Edit1: TEdit;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure HHALINKLOOKUPClick(Sender: TObject);
procedure JumpAnchorClick(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
hpPath : string;
link : HH_AKLINK;
procedure TForm1.FormCreate(Sender: TObject);
begin
hpPath := ExtractFilePath(Application.ExeName) +
'HelpFile\sample.chm';
Application.HelpFile := hpPath;
end;
procedure TForm1.HHALINKLOOKUPClick(Sender: TObject);
var
link : HH_AKLINK;
szUrl,szKey,szMsgText,szMsgTitle,szWindow : AnsiString;
begin
szKey := Edit1.Text; // 'UsingtheMenus';
szUrl :='Overview.htm';
szMsgText :='Error: Can''t find "'+Edit1.Text+'"!';
szMsgTitle :='Error: HH_ALINK_LOOKUP';
szWindow :='main';
with link do begin
cbStruct := sizeof(HH_AKLINK) ;
fReserved := False;
pszKeywords := PChar(szKey);
pszUrl := nil;
pszMsgText := PChar(szMsgText);
pszMsgTitle := PChar(szMsgTitle);
pszWindow := PChar(szWindow);
fIndexOnFail:= False;
end;
HtmlHelpW(0, hpPath+'>main', HH_DISPLAY_TOPIC, DWORD_PTR(nil));
HtmlHelpW(0, hpPath, HH_ALINK_LOOKUP, DWORD_PTR(#link));
end;
procedure TForm1.JumpAnchorClick(Sender: TObject);
begin
HtmlHelpW(0, hpPath+'::/Overview.htm#'+Edit1.Text+'>main', HH_DISPLAY_TOPIC, DWORD(nil));
end;
end.
Here is a ready to use sample.chm and the source Download
There is a trick how to easily, to jump, not only to the .htm file but jumps directly to an anchor.
Change sample.ali
IDH_Overview=Overview.htm
IDH_welcom=FirstTopic.htm
IDH_UsingtheMenus=Overview.htm#UsingtheMenus
Insert an anchor at the place, you want to jump to in Overview.htm
[...]
<A NAME="UsingtheMenus" </A>
<P><STRONG>Using the Menus and Toolbars</STRONG>
<P>The menus and toolbars provide a complete set of tools
[...]
Now it is possible with F1, jump directly to the desired point in overview.htm.
I suspect that to use A-links you need to do the following:
Assign an Application.OnHelp handler as described below.
Assign Application.HelpFile during program startup.
Call Application.HelpKeyword if you wish to invoke the help system with an A-link.
Set the HelpKeyword property for any GUI controls that you wish to respond to context sensitive F1 key presses.
The OnHelp handler looks like this:
function TMainForm.ApplicationHelp(Command: Word;
Data: THelpEventData; var CallHelp: Boolean): Boolean;
var
Link: THH_AKLink;
ALink: string;
begin
CallHelp := False;
Result := True;
//argh, WinHelp commands
case Command of
HELP_COMMAND:
begin
ZeroMemory(#Link, SizeOf(Link));
Link.cbStruct := SizeOf(Link);
ALink := PChar(Data); // we are going to re-purpose the keyword as an A-link
Link.pszKeywords := PChar(AnsiString(ALink)); // seems we have to pass a PAnsiChar ..
Link.fIndexOnFail := True;
HtmlHelp(GetDesktopWindow, Application.HelpFile, HH_ALINK_LOOKUP,
DWORD_PTR(#Link));
end;
end;
end;
The HtmlHelpViewer unit contains methods named LookupALink which do the same. But I don't see how they could ever be called.
The above approach is a little bit hacky because it interprets keywords as A-Links. If you want context sensitive help, I can't see what else you can do.
Not sure how Xe2 viewer works (I'm on 2007) but I just use Eric Granges port of the Microsoft HTML help API, which unsurprisingly, is called HTMLhelpAPI.pas.
You can call an Alink using the function
ChmShowTopic(const filename,atopic:string):HWND;
I am having a strange problem of using interface in different versions of Delphi. The following minimized code compiles and runs as expected in Delphi XE and higher but not in Delphi 7. Specificaly, it seems when compiling in Delphi 7, the function TForm1.Load: IMoleculeSubject; does not returns the correct result, i.e., the correct reference to the newly created instance. Could you help to comment about the reason and possible workaround? Many thanks!
uInterface.pas
unit uInterface;
interface
type
IMoleculeSubject = interface
['{BEB4425A-186C-45DF-9DCE-C7175DB0CA90}']
end;
TMoleculeSubject = class(TInterfacedObject, IMoleculeSubject)
end;
implementation
end.
uBusiness.pas
unit uBusiness;
interface
uses
uInterface;
type
TMoleculeDecorator = class(TMoleculeSubject)
private
FID: Integer;
public
property ID: Integer read FID;
constructor Create;
end;
implementation
{ TMoleculeDecorator }
constructor TMoleculeDecorator.Create;
begin
inherited Create;
FID := Random(100);
end;
end.
Unit1.pas
unit Unit1;
interface
uses
uInterface, uBusiness,
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
function Load: IMoleculeSubject;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
MolSubject: IMoleculeSubject;
begin
MolSubject := Load;
// The down-cast is to show the returned result is wrong in Delphi 7!
Caption := IntToStr(TMoleculeDecorator(MolSubject).ID);
end;
function TForm1.Load: IMoleculeSubject;
var
MolSubject: IMoleculeSubject;
begin
MolSubject := TMoleculeDecorator.Create;
Result := MolSubject;
end;
end.
The Load function works perfectly well in all versions of Delphi. The problem is your cast, which is what is known as an unsafe typecast. An unsafe typecast from an interface reference to an object has ill-defined behaviour in older versions of Delphi. However, the behaviour is well-defined in modern Delphi. The documentation says more.
So, the basic problem is that your expectations for the behaviour are not compatible with the Delphi 7 version of the language.
If you get the interface to return the ID you will find that the interface you are creating is as expected.
program InterfaceDemo;
{$APPTYPE CONSOLE}
uses
Classes;
type
IMyIntf = interface
function GetID: Integer;
end;
TImplementingObject = class(TInterfacedObject, IMyIntf)
private
FID: Integer;
function GetID: Integer;
public
constructor Create;
end;
{ TImplementingObject }
constructor TImplementingObject.Create;
begin
FID := Random(100);
Writeln(FID);
end;
function TImplementingObject.GetID: Integer;
begin
Result := FID;
end;
var
MyIntf: IMyIntf;
begin
Randomize;
MyIntf := TImplementingObject.Create;
Writeln(MyIntf.GetID);
Readln;
end.
It's rather unusual to ask for the implementing object from an interface. To do so suggests that there is a problem with your design. Should you really need to do so there are a few options:
In modern Delphi you can use the type-safe case with the as operator.
In older Delphi versions there are hacks that retrieve the implementing object: Casting a delphi interface to its implementation class without modifying the interface
You could add a function to the interface that returns the implementing object.
The latter option works in all versions of Delphi and does so without resorting to subterfuge.
Casting interfaces to objects is available since Delphi 2010. Where are workarounds for older Delphi versions, see for example How to cast a Interface to a Object in Delphi