我正在执行一般功能/程序...
实际上,您似乎在追求一个完整的程序。您正在考虑将其作为一个整体来解决,这很模糊,这就是您有这么多问题的原因。你需要学会把这个任务分解成更小的部分,并更清楚地总结需求。当前形式的问题接近于题外话,它可能更适合SE Programmers。但由于这正好适合我的小巷,我想带你过去。
要求
- 有一组长度为 N 的值 X。
- 可以为该集合中的一个或多个值分配一个新值。
- 从旧值到新值的修改应在特定持续时间内分步执行。
- 这会在过渡期间产生中间值。
- 此转换是特定于值/索引的,即 X[0] 的持续时间可能与 X[1] 的持续时间不同。
- 必须先完成转换,然后才能分配另一个新值。
- 在进行转换时,可能会请求分配新值。
- 由此得出结论,新请求应存储在队列中,以便在转换完成时,可以从队列中拉出新值请求,从而产生新的转换。
我很确定这是对您问题的正确总结。
过渡
您建议使用计时器在每个间隔上执行总转换的片段是合理的。现在,有两种方法可以计算这些件:
- 将总转换划分为固定数量的小转换,将计时器的时间间隔设置为总持续时间除以该数字,并在每个时间间隔处理所有已处理的较小转换的总和。这是您在计算更改步骤时提出的建议。这种方法的缺点是双重的:
- Timer 的时间间隔是一个近似值,由于各种原因并不准确,其中一个原因是依赖于 Windows 消息传递模型,该模型的时间受到许多进程(包括您的进程)的影响,
- 因此可能会导致进展不顺利。
- 在每个时间间隔重新计算已处理过渡的部分。这样一来,无论下一个间隔是否需要两倍以上,进度都将始终顺利。
首选第二种解决方案,这将转化为您正在寻找的以下一般例程。让我们从假设一个项目开始:
function InBetweenValue(BeginValue, EndValue: Byte; Duration,
StartTick: Cardinal): Byte
var
Done: Single;
begin
Done := (GetTickCount - StartTick) / Duration;
if Done >= 1.0 then
Result := EndValue
else
Result := Round(BeginValue + (EndValue - BeginValue) * Done);
end;
使用具有固定间隔的计时器是个好主意吗?
使用这种方法,计时器的间隔不影响计算:在任何给定时间InBetweenValue 的结果都是正确的。 唯一需要 Timer 的事情是推动进度。如果您想要 67 Hz 的刷新率,请将其间隔设置为 15 毫秒。如果您想要 20 Hz 的刷新率,请将间隔设置为 50 毫秒。
性能
如果有很多值等待淡入淡出,会导致任何延迟吗?
不,不是出于隐含的原因。所有计算所需的时间可能取决于队列的大小,但这肯定不是一个重要因素。 (如果是这样,那么你的问题就更令人不安了)。由于错过或合并的 Windows 计时器消息,可能的“延迟”会以较低的刷新率表现出来,具体取决于计算机处理它正在执行的所有操作的繁忙程度。
数据存储
对值和时间使用动态数组(并在“StartFade”事件中填充它们并在淡入淡出完成后释放它们)是在黑暗中拍摄还是一个好的猜测?
我们先分析一下需要处理哪些数据。有一组任意长度的中间当前值,每个值都有自己的四个属性:开始值、结束值、过渡持续时间和过渡开始时间。因此,您可以选择:
- 存储 5 组:一组当前值和四组属性,或者
- 存储 1 组:一组当前值,其中每个值具有四个属性成员。
第一个解决方案在保持所有五组同步方面遇到了麻烦。第二个需要另一个维度。我更喜欢后者。
您是否使用数组或其他东西取决于您。选择您最熟悉的、符合目的的或与输入或所需输出最匹配的。选择静态数组还是动态数组取决于输入的可变性,并且不会对性能产生可衡量的差异。动态数组需要运行时长度管理,而静态数组则不需要。
但既然您无论如何都需要动态解决方案,那么我建议您跳出框框思考。例如,RTL 没有为数组提供默认的内置管理工具,但它确实有可以提供的集合类,例如TList.
对于这个答案的其余部分,我将假设使用 Object 作为元素并使用 List 来跟踪它们的决定。
设计
现在两个最紧迫的点已经解决了,可以制定设计了。
有一个包含项目的List,每个项目都有它的当前值和四个属性:开始、结束、持续时间和开始时间。每个项目都必须能够获得新的属性值。有一个基于属性计算当前值的公式。还有一个 Timer 可以自动执行多个这些计算。
此外,应该为一个项目存储多个转换命令。由于我们已经有一个带有成员的 Item,让我们也将这些命令添加为 Item 的成员。
缺少什么?不,我们走吧。
实施
我们需要:
- 具有两个成员的转换类型:结束值和持续时间,
- 这些转换的多个类型,最好具有队列特征,
- 具有六个成员的 Item 类型:开始值、结束值、持续时间、开始时间、当前值和转换,
- 此类项目列表的类型,
- 计算项目当前值的例程,
- 当当前值达到结束值时弹出新转换的例程,
- 执行此计算并弹出所有项目的例程,
- 一个计时器来驱动这个整体例程,
- 更新项目属性的例程。概括。我们需要设置所有属性的能力吗?过渡不是有所有需要的设置吗?
- 将这一切结合在一起的 Object 类型。
这应该可以帮助您设置代码的接口部分。流连忘返,并渴望开始编写实现代码。
在此我的试用,起源如上所述:
unit Modulation;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections, WinAPI.Windows,
VCL.ExtCtrls;
type
TTransition = record
EndValue: Byte;
Duration: Cardinal;
end;
TTransitions = class(TQueue<TTransition>);
TByte = class(TObject)
private
FBeginValue: Byte;
FCurrentValue: Byte;
FEndValue: Byte;
FDuration: Cardinal;
FStartTick: Cardinal;
FTransitions: TTransitions;
procedure PopTransition;
public
procedure AddTransition(ATransition: TTransition);
constructor Create;
destructor Destroy; override;
function HasTransition: Boolean;
function InTransition: Boolean;
procedure Recalculate;
property CurrentValue: Byte read FCurrentValue;
end;
TBytes = class(TObjectList<TByte>);
TByteModulator = class(TObject)
private
FItems: TBytes;
FOnProgress: TNotifyEvent;
FTimer: TTimer;
function Finished: Boolean;
function GetCurrentValue(Index: Integer): Byte;
function GetItemCount: Integer;
procedure SetItemCount(Value: Integer);
procedure Proceed(Sender: TObject);
protected
procedure DoProgress;
public
procedure AddTransition(Index: Integer; ATransition: TTransition);
constructor Create;
destructor Destroy; override;
property CurrentValues[Index: Integer]: Byte read GetCurrentValue; default;
property ItemCount: Integer read GetItemCount write SetItemCount;
property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
end;
implementation
{ TByte }
procedure TByte.AddTransition(ATransition: TTransition);
begin
if ATransition.Duration < 1 then
ATransition.Duration := 1;
FTransitions.Enqueue(ATransition);
Recalculate;
end;
constructor TByte.Create;
begin
inherited Create;
FTransitions := TTransitions.Create;
FDuration := 1;
end;
destructor TByte.Destroy;
begin
FTransitions.Free;
inherited Destroy;
end;
function TByte.HasTransition: Boolean;
begin
Result := FTransitions.Count > 0;
end;
function TByte.InTransition: Boolean;
begin
Result := FCurrentValue <> FEndValue;
end;
procedure TByte.PopTransition;
var
Transition: TTransition;
begin
Transition := FTransitions.Dequeue;
FBeginValue := FCurrentValue;
FEndValue := Transition.EndValue;
FDuration := Transition.Duration;
FStartTick := GetTickCount;
end;
procedure TByte.Recalculate;
var
Done: Single;
begin
Done := (GetTickCount - FStartTick) / FDuration;
if Done >= 1.0 then
begin
FCurrentValue := FEndValue;
if HasTransition then
PopTransition;
end
else
FCurrentValue := Round(FBeginValue + (FEndValue - FBeginValue) * Done);
end;
{ TByteModulator }
const
RefreshFrequency = 25;
procedure TByteModulator.AddTransition(Index: Integer;
ATransition: TTransition);
begin
FItems[Index].AddTransition(ATransition);
FTimer.Enabled := True;
end;
constructor TByteModulator.Create;
begin
inherited Create;
FItems := TBytes.Create(True);
FTimer := TTimer.Create(nil);
FTimer.Enabled := False;
FTimer.Interval := MSecsPerSec div RefreshFrequency;
FTimer.OnTimer := Proceed;
end;
destructor TByteModulator.Destroy;
begin
FTimer.Free;
FItems.Free;
inherited Destroy;
end;
procedure TByteModulator.DoProgress;
begin
if Assigned(FOnProgress) then
FOnProgress(Self);
end;
function TByteModulator.Finished: Boolean;
var
Item: TByte;
begin
Result := True;
for Item in FItems do
if Item.InTransition or Item.HasTransition then
begin
Result := False;
Break;
end;
end;
function TByteModulator.GetCurrentValue(Index: Integer): Byte;
begin
Result := FItems[Index].CurrentValue;
end;
function TByteModulator.GetItemCount: Integer;
begin
Result := FItems.Count;
end;
procedure TByteModulator.Proceed(Sender: TObject);
var
Item: TByte;
begin
for Item in FItems do
Item.Recalculate;
DoProgress;
FTimer.Enabled := not Finished;
end;
procedure TByteModulator.SetItemCount(Value: Integer);
var
I: Integer;
begin
for I := FItems.Count to Value - 1 do
FItems.Add(TByte.Create);
FItems.DeleteRange(Value, FItems.Count - Value);
end;
end.
还有一个小小的即插即用演示程序(注意标签只显示最后一个请求):
unit Unit2;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms,
VCL.ComCtrls, VCL.StdCtrls, Modulation;
type
TForm2 = class(TForm)
private
FBars: array of TProgressBar;
FLabels: array of TLabel;
FByteModulator: TByteModulator;
procedure FormClick(Sender: TObject);
procedure Progress(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
{ TForm2 }
const
Count = 10;
constructor TForm2.Create(AOwner: TComponent);
var
I: Integer;
begin
inherited Create(AOwner);
FByteModulator := TByteModulator.Create;
FByteModulator.ItemCount := Count;
FByteModulator.OnProgress := Progress;
SetLength(FBars, Count);
SetLength(FLabels, Count);
for I := 0 to Count - 1 do
begin
FBars[I] := TProgressBar.Create(Self);
FBars[I].SetBounds(10, 10 + 30 * I, 250, 25);
FBars[I].Smooth := True;
FBars[I].Max := High(Byte);
FBars[I].Parent := Self;
FLabels[I] := TLabel.Create(Self);
FLabels[I].SetBounds(270, 15 + 30 * I, 50, 25);
FLabels[I].Parent := Self;
end;
OnClick := FormClick;
end;
destructor TForm2.Destroy;
begin
FByteModulator.Free;
inherited Destroy;
end;
procedure TForm2.FormClick(Sender: TObject);
var
Transition: TTransition;
Index: Integer;
begin
Transition.EndValue := Random(High(Byte) + 1);
Transition.Duration := Random(3000);
Index := Random(Count);
FLabels[Index].Caption := Format('%d > %d @ %f',
[FByteModulator.CurrentValues[Index], Transition.EndValue,
Transition.Duration / MSecsPerSec]);
FByteModulator.AddTransition(Index, Transition);
end;
procedure TForm2.Progress(Sender: TObject);
var
I: Integer;
begin
for I := 0 to Count - 1 do
FBars[I].Position := FByteModulator.CurrentValues[I];
end;
initialization
Randomize;
end.
成功了。