I've been stretching my brain trying to create a loading screen in Delphi but I just can't find help anywhere.
I am creating a game for a school project and I would like to implement a form that mimics a loading screen.
I want to move a shape across the screen and I want it to leave behind a trail (imitate a progress bar). I know you use timer to smooth it's progression but I'm not sure about how to use a timer correctly with a shape.
I would appreciate it if anyone would show me what code/functions I have to use to do this.
Sincerely,
Kuzon.
To move a shape with a timer and leave a trail:
Each time the timer event fires, adjust the shape position.
The trail is also made with a shape here, by adding the width each timer tick.
unit MoveShape;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;
type
TFormMoveShape = class(TForm)
Shape1: TShape;
Timer1: TTimer;
Shape2: TShape;
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
FormMoveShape: TFormMoveShape;
implementation
{$R *.dfm}
const
cMoveIncrement = 2;
procedure TFormMoveShape.Timer1Timer(Sender: TObject);
begin
if (Shape1.Left + Shape1.Width < Self.ClientWidth - cMoveIncrement) then
begin
Shape1.Left := Shape1.Left + cMoveIncrement;
Shape2.Width := Shape2.Width + cMoveIncrement;
end
else
begin
Shape1.Left := 8;
Shape2.Width := 8;
end;
end;
end.
object FormMoveShape: TFormMoveShape
Left = 0
Top = 0
Caption = 'Form27'
ClientHeight = 336
ClientWidth = 635
Color = clBtnFace
DoubleBuffered = True
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Shape2: TShape
Left = 8
Top = 112
Width = 8
Height = 41
Brush.Color = clAqua
Shape = stRoundRect
end
object Shape1: TShape
Left = 8
Top = 112
Width = 137
Height = 41
Shape = stRoundRect
end
object Timer1: TTimer
Interval = 50
OnTimer = Timer1Timer
Left = 512
Top = 24
end
end
Related
I want to dynamically create some buttons in a TScrollBox (that has VertScrollBar.Vissible= False).
I want to programmatically bring some of those buttons in view, so I would like to use something like:
ScrollBox.VertScrollBar.Position:= i; //Does not work
However, the box won't scroll to the indicated position unless the VertScrollBar.Vissible= True.
Note: ScrollBy() works, but I don't want to use that.
How to circumvent this behavior?
(A "solution" would be to let the scrollbars visible and hide them outside the screen (place the scrollbox in a panel))
Code:
unit UnitVert;
interface
uses
System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm3 = class(TForm)
ScrollBox1: TScrollBox;
Button1: TButton;
Button2: TButton;
procedure Button2Click(Sender: TObject);
end;
var
Form3: TForm3;
implementation {$R *.dfm}
procedure TForm3.Button2Click(Sender: TObject);
begin
ScrollBox1.VertScrollBar.Position:= -20;
//ScrollBox1.ScrollBy(0, -20); //Works
end;
end.
object Form3: TForm3
Left = 0
Top = 0
Caption = 'Form3'
ClientHeight = 336
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object ScrollBox1: TScrollBox
Left = 176
Top = 75
Width = 283
Height = 203
HorzScrollBar.Visible = False
VertScrollBar.Visible = False
TabOrder = 0
object Button1: TButton
Left = 188
Top = 132
Width = 123
Height = 99
Caption = 'Dummy'
TabOrder = 0
end
end
object Button2: TButton
Left = 26
Top = 50
Width = 111
Height = 51
Caption = 'Test'
TabOrder = 1
OnClick = Button2Click
end
end
Works as expected
Cannot reproduce your issue with D7 on Win7:
Scrollbox1.HorzScrollBar.Visible:= FALSE;
Scrollbox1.VertScrollBar.Visible:= FALSE;
Scrollbox1.ScrollBy( -30, -45 );
...moves the viewport 30 px to the left and 45 px to the top. Also note that the first parameter is X (horizontal) and the second parameter is Y (vertical) - actually anything I've seen in life was always in the X,Y order.
Why invisible scrollbars won't work
The method TWinControl.ScrollBy() includes this code:
IsVisible := (FHandle <> 0) and IsWindowVisible(FHandle);
if IsVisible then ScrollWindow(FHandle, DeltaX, DeltaY, nil, nil);
...which means: it is essentially using the WinAPI's ScrollWindow() function. Changing the position of one of the scrollbars executes TControlScrollBar.SetPosition(), which in turn calls .ScrollBy() again for just one dimension:
OldPos := FPosition;
if Kind = sbHorizontal then
FControl.ScrollBy(OldPos - Value, 0) else
FControl.ScrollBy(0, OldPos - Value);
...and since this time the parent control is the scrollbar (not the scrollbox) its invisibility prevents the WinAPI function from being called. Content wise there's no gain in using scrollbars - they just conveniently remember what you already scrolled.
I hate the title of this question. Anyway:
If you call TForm.Show with a custom theme (Windows10 Dark in this case), then close that form, then change the theme to the system Windows theme, then change back to the Windows10 Dark theme, and finally call TForm.Show on that form again, the border renders incorrectly and certain controls do not render properly, like a TComboBox.
I have a test project below, and a "fix" of sorts. But I do not like my fix and the reason for this question is that I do not really understand what is happening here that causes the form to render incorrectly only if it was hidden while the theme changed, and only if the theme is changed away from, and then back to, Windows10 Dark.
My fix is to track the theme change. If the condition I describe above occurs, I intercept the CM_SHOWINGCHANGED message, ignore it, then force the window to be recreated and then process the inherited CM_SHOWINGCHANGED the next time around. It is a very brittle fix and obviously not the way to go, so I am hoping someone can show me what is actually happening so I can fix it "for real."
Incidentally, I have submitted this as a bug to Embarcadero already. https://quality.embarcadero.com/browse/RSP-33977
Here is the test code. You'll need to add Windows10 Dark to the application's styles, obviously.
unit Unit22;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Unit23, Vcl.Themes;
type
TForm22 = class(TForm)
Panel1: TPanel;
ComboBox1: TComboBox;
RadioGroup1: TRadioGroup;
ButtonShow: TButton;
Memo1: TMemo;
procedure ButtonShowClick(Sender: TObject);
procedure RadioGroup1Click(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ Private declarations }
FAllowChange: Boolean;
public
{ Public declarations }
end;
var
Form22: TForm22;
implementation
{$R *.dfm}
procedure TForm22.ButtonShowClick(Sender: TObject);
begin
Form23.Show;
end;
procedure TForm22.FormShow(Sender: TObject);
begin
if StyleServices.Name = 'Windows10 Dark' then
RadioGroup1.ItemIndex := 1
else
RadioGroup1.ItemIndex := 0;
FAllowChange := True;
end;
procedure TForm22.RadioGroup1Click(Sender: TObject);
begin
if not FAllowChange then
exit;
if RadioGroup1.ItemIndex = 0 then
TStyleManager.SetStyle('Windows');
if RadioGroup1.ItemIndex = 1 then
TStyleManager.SetStyle('Windows10 Dark');
end;
end.
Unit 22 DPR:
object Form22: TForm22
Left = 0
Top = 0
ActiveControl = Memo1
Caption = 'Form22'
ClientHeight = 305
ClientWidth = 511
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 511
Height = 305
Align = alClient
BevelEdges = []
BevelOuter = bvNone
Caption = 'Panel1'
ShowCaption = False
TabOrder = 0
object ComboBox1: TComboBox
Left = 16
Top = 8
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 0
TabOrder = 0
Text = 'one'
Items.Strings = (
'one'
'two'
'three')
end
object RadioGroup1: TRadioGroup
Left = 16
Top = 48
Width = 185
Height = 105
Caption = 'RadioGroup1'
Items.Strings = (
'windows'
'dark')
TabOrder = 1
OnClick = RadioGroup1Click
end
object ButtonShow: TButton
Left = 16
Top = 159
Width = 75
Height = 25
Caption = 'ButtonShow'
TabOrder = 2
OnClick = ButtonShowClick
end
object Memo1: TMemo
Left = 207
Top = 8
Width = 274
Height = 281
Lines.Strings = (
'Always start in dark.'
''
'Steps to reproduce:'
'1.'#9'Click ButtonShow.'
'2.'#9'Close the window that opened.'
'3.'#9'Click Windows (change to system them).'
'4.'#9'Click Dark (change back to dark VCL style).'
'5.'#9'Click ButtonShow again. The controls are '
'not properly painted. Combobox text is black and form '
'is wrong until resize.'
''
'Hacky fix:'
'1.'#9'Click ButtonShow.'
'2.'#9'Check the '#8220'Fix'#8221' button in the window that '
'opened, then close it.'
'3.'#9'Click Windows (change to system)'
'4.'#9'Click Dark (change back to vcl dark)'
'5.'#9'Click ButtonShow. See comments in source.'
'')
ReadOnly = True
TabOrder = 3
end
end
end
Unit23:
unit Unit23;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Themes;
type
TForm23 = class(TForm)
Panel1: TPanel;
ComboBox1: TComboBox;
ComboBox2: TComboBox;
ComboBox3: TComboBox;
Button1: TButton;
CheckBox1: TCheckBox;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
FFixing: Boolean;
FNeedFix: String;
FShowedStyle: String;
protected
procedure DoShow; override;
public
{ Public declarations }
procedure CMStyleChanged(var Message: TMessage); message CM_STYLECHANGED;
procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
end;
var
Form23: TForm23;
implementation
{$R *.dfm}
procedure TForm23.Button1Click(Sender: TObject);
begin
PostMessage(Handle, CM_RECREATEWND, 0, 0);
end;
procedure TForm23.CMShowingChanged(var Message: TMessage);
var
DoFix: Boolean;
begin
if not Showing then
inherited
else
begin
// if the theme changed away from dark, then back to dark, while we were
// not visible, then we need to force the window to be recreated again
// before showing.
// This is a really bad hack but basically I am just preventing the
// normal response to CMShowingChanged and then setting up a message
// queue that will recreate the window and then process the CM_SHOWINGCHANGED
// message again. This will probably break the universe but it appears to work
// in this test.
FShowedStyle := StyleServices.Name;
Panel1.Caption := FShowedStyle;
DoFix := not FFixing and (FNeedFix <> '') and (FNeedFix = FShowedStyle);
FNeedFix := '';
if DoFix and CheckBox1.Checked then
begin
FFixing := True;
// SendMessage(Handle, WM_SETREDRAW, Winapi.Windows.WPARAM(LongBool(False)), 0);
PostMessage(Handle, CM_RECREATEWND, 0, 0);
// PostMessage(Handle, CM_SHOWINGCHANGED, Message.WParam, Message.LParam);
// do not allow inherited.
end else
begin
FFixing := False;
inherited;
end;
end;
end;
procedure TForm23.CMStyleChanged(var Message: TMessage);
begin
FNeedFix := FShowedStyle;
inherited;
end;
procedure TForm23.DoShow;
var
DoFix: Boolean;
begin
inherited;
exit;
end;
end.
Unit23 DPR:
object Form23: TForm23
Left = 0
Top = 0
Caption = 'Form23'
ClientHeight = 253
ClientWidth = 360
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 360
Height = 253
Align = alClient
Alignment = taRightJustify
BevelEdges = []
BevelOuter = bvNone
Caption = 'Panel1'
TabOrder = 0
object ComboBox1: TComboBox
Left = 16
Top = 32
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 0
TabOrder = 0
Text = 'one'
Items.Strings = (
'one'
'two'
'three')
end
object ComboBox2: TComboBox
Left = 16
Top = 59
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 1
TabOrder = 1
Text = 'two'
Items.Strings = (
'one'
'two'
'three')
end
object ComboBox3: TComboBox
Left = 16
Top = 86
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 2
TabOrder = 2
Text = 'three'
Items.Strings = (
'one'
'two'
'three')
end
object Button1: TButton
Left = 16
Top = 136
Width = 75
Height = 25
Caption = 'RecreateWnd'
TabOrder = 3
OnClick = Button1Click
end
object CheckBox1: TCheckBox
Left = 16
Top = 167
Width = 273
Height = 17
Caption = 'Fix with CM_SHOWINGCHANGED hack'
TabOrder = 4
end
end
end
I have a TStringGrid, however if I click and drag on it, it can be panned vertically and horizontally, I don't want the user to be able to do this, how can I stop this from happening?
You can use the OnTopLeftChanged event to catch whenever any sort of "scrolling" has occurred, and decide how to proceed. If you don't want user to go out of range in certain circumstances, you can reset the range as needed. Here's a rough example...
uStringGridTestMain.dfm:
object frmStringGridTestMain: TfrmStringGridTestMain
Left = 0
Top = 0
Caption = 'String Grid Test'
ClientHeight = 416
ClientWidth = 738
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object StringGrid1: TStringGrid
Left = 72
Top = 32
Width = 513
Height = 329
Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine]
TabOrder = 0
OnTopLeftChanged = StringGrid1TopLeftChanged
ColWidths = (
64
64
64
64
64)
RowHeights = (
24
24
24
24
24)
end
end
uStringGridTestMain.pas:
unit uStringGridTestMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids;
type
TfrmStringGridTestMain = class(TForm)
StringGrid1: TStringGrid;
procedure StringGrid1TopLeftChanged(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmStringGridTestMain: TfrmStringGridTestMain;
implementation
{$R *.dfm}
procedure TfrmStringGridTestMain.FormCreate(Sender: TObject);
begin
StringGrid1.Align:= alClient;
//Let's put a big scroll in both directions...
StringGrid1.RowCount:= 50;
StringGrid1.ColCount:= 50;
end;
procedure TfrmStringGridTestMain.StringGrid1TopLeftChanged(Sender: TObject);
begin
//You can change the "current" cell...
StringGrid1.Row:= 1;
StringGrid1.Col:= 1;
//Or you can change the scrolled cell on top-left...
StringGrid1.TopRow:= 1;
StringGrid1.LeftCol:= 1;
end;
end.
To prevent panning on drag you can set the TouchTracking property of TStringGrid to TBehaviorBoolean.False
RichEdit control stop drawing text when it became a parent for other control.
Is this a feature or a bug?
Is it possible to make RichEdit to be a parent for other control?
Check out next app:
-- Form1.dfm ---
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 282
ClientWidth = 418
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 24
Top = 8
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object RichEdit1: TRichEdit
Left = 16
Top = 72
Width = 145
Height = 105
Font.Charset = RUSSIAN_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
Lines.Strings = (
'RichEdit1')
ParentFont = False
TabOrder = 1
end
end
-- Form1.dfm ---
--- Unit1.pas ---
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;
type
TForm3 = class(TForm)
Button1: TButton;
RichEdit1: TRichEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.Button1Click(Sender: TObject);
begin
Button1.Parent := RichEdit1;
RichEdit1.Invalidate;
end;
end.
--- Unit1.pas ---
Test under Delphi XE5 + Win 7.
I want to create RichEdit with Edit button like this
This is the result that I want to get - RichEdit with DropDown Editor:
Use an interposer class that handles the WM_PAINT message like so:
type
TRichEdit = class(Vcl.ComCtrls.TRichEdit)
protected
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
end;
procedure TRichEdit.WMPaint(var Message: TWMPaint);
begin
DefaultHandler(Message);
end;
For reasons lost in the mists of time, TCustomRichEdit does some special handling of WM_PAINT that was only actually needed for the original version of the rich edit DLL. Moreover, this special handling breaks normal painting when another control is parented to the rich edit. As such, fixing the issue requires re-establishing standard VCL/Windows paint handling, which is what the code above does.
That said, I doubt nesting a button inside a rich edit is really what you want - the text won't wrap around it, for example.
Is it possible to do LiveBinding between controls, i.e. take 2 edit boxes and get the result of adding their contents together into a label. I'm sure it is, I just don't know where to start
Thanks
Have a look at the samples. SVN repository URL: https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE2/LiveBindings
An example:
----- Unit1.dfm -----
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 286
ClientWidth = 426
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 8
Top = 62
Width = 48
Height = 13
Caption = 'Edit1Edit2'
end
object Edit1: TEdit
Left = 8
Top = 8
Width = 121
Height = 21
TabOrder = 0
Text = 'Edit1'
OnChange = EditChange
end
object Edit2: TEdit
Left = 8
Top = 35
Width = 121
Height = 21
TabOrder = 1
Text = 'Edit2'
OnChange = EditChange
end
object BindingsList1: TBindingsList
Methods = <>
OutputConverters = <>
UseAppManager = True
Left = 20
Top = 5
object BindExpressionLabel11: TBindExpression
Category = 'Binding Expressions'
ControlComponent = Label1
SourceComponent = BindScope1
SourceExpression = 'Edit1.Text + Edit2.Text'
ControlExpression = 'Caption'
NotifyOutputs = False
Direction = dirSourceToControl
end
end
object BindScope1: TBindScope
Left = 192
Top = 16
end
end
----- Unit1.pas -----
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.Bind.EngExt, Vcl.Bind.DBEngExt,
System.Rtti, System.Bindings.Outputs, Vcl.Bind.Editors, Data.Bind.Components,
Vcl.StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
BindingsList1: TBindingsList;
BindExpressionLabel11: TBindExpression;
BindScope1: TBindScope;
procedure EditChange(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
System.Bindings.Helper;
procedure TForm1.EditChange(Sender: TObject);
begin
TBindings.Notify(Sender, 'Text');
end;
end.
How to use the IDE designer to produce the result:
put two edits (Edit1, Edit2), a label (Label1) and a TBindScope (BindScope1) on your form (Form1).
create an event handler for both edits' OnChange event (EditChange).
select Label1, expand the drop-down of LiveBindings property, select 'New Live Binding...', select TBindExpression
edit properties of the newly created BindExpressionLabel11: assign Caption to ControlExpression, BindScope1 to SourceComponent, Edit1.Text + Edit2.Text to SourceExpression
The sample project at the (Default) location of:
C:\Users\Public\Documents\RAD Studio\9.0\Samples\Delphi\LiveBinding\Components\bindexpression\fmx\BindExpressionSampleProject.dproj
does precisely that.
You don't need to TBindScope to bind components together. Say you have edit1 and edit2 on the form. If you set edit2 BindingSource to edit1 it will be link to changes to edit1