关于音量控制(100分)

  • 主题发起人 主题发起人 monster
  • 开始时间 开始时间
M

monster

Unregistered / Unconfirmed
GUEST, unregistred user!
查得:waveOutSetVolume函数设置指定波形音频设备的音量等级:

MMRESULT waveOutSetVolume(
HWAVEOUT hwo,
DWORD dwVolume
);

其第一个参数说明为:
hwo:打开的波形音频设备句柄或标示。
可是我怎么知道哪个“波形音频设备”正被打开?即:如何确定hwo参数,填0..2不好用的说!!
 
hwo可以用TMediaPlayer.DeviceID,如果你完全不用MediaPlayer的话……
还是用吧,省些麻烦。
但我不知道为什么直接用MediaPlayer.DeviceID不行,用0倒是行(在播放Wave文件
和AVI文件的时候。)
 
unit AMixer;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
MMSystem

(* TAudioMixer v1.1 (FREEWARE component)
* ----------------
* Released 16 Nov 1998
* e-mail: vkovalcik@iname.com
* WWW: http://www.geocities.com/SiliconValley/Hills/1335/
so be sure to check TAudioMixer.MixerCount first. *)

type
TAudioMixer=class;

TPListFreeItemNotify=procedure (Pntr:Pointer) of object;
TMixerChange=procedure (Sender:TObject;MixerH:HMixer;ID:Integer) of object;

TPointerList=class(TObject)
private
FOnFreeItem:TPListFreeItemNotify;
Items:Tlist;
protected
function GetPointer (Ind:Integer):Pointer;
function GetCount :integer;
public
constructor Create;
destructor Destroy;
override;
procedure Clear;
procedure Add (Pntr:Pointer);
property Count:Integer read GetCount;
property Pointer[Ind:Integer]:Pointer read GetPointer;
default;
property OnFreeItem:TPListFreeItemNotify read FOnFreeItem write FOnFreeItem;
end;


TMixerControls=class(TObject)
private
heap:pointer;
FControls:TPointerList;
protected
function GetControl (Ind:Integer):PMixerControl;
function GetCount:Integer;
public
constructor Create (AMixer:TAudioMixer;AData:TMixerLine);
destructor Destroy;
override;
property Control[Ind:Integer]:PMixerControl read GetControl;
default;
property Count:Integer read GetCount;
end;


TMixerConnection=class(TObject)
private
XMixer:TAudioMixer;
FData:TMixerLine;
FControls:TMixerControls;
public
constructor Create (AMixer:TAudioMixer;AData:TMixerLine);
destructor Destroy;
override;
property Controls:TMixerControls read FControls;
property Data:TMixerLine read FData;
end;


TMixerConnections=class(TObject)
private
XMixer:TAudioMixer;
FConnections:TPointerList;
protected
proceduredo
FreeItem (Pntr:Pointer);
function GetConnection (Ind:Integer):TMixerConnection;
function GetCount:Integer;
public
constructor Create (AMixer:TAudioMixer;AData:TMixerLine);
destructor Destroy;
override;
property Connection[Ind:Integer]:TMixerConnection read GetConnection;
default;
property Count:Integer read GetCount;
end;


TMixerDestination=class(TObject)
private
XMixer:TAudioMixer;
FData:TMixerLine;
FControls:TMixerControls;
FConnections:TMixerConnections;
public
constructor Create (AMixer:TAudioMixer;AData:TMixerLine);
destructor Destroy;
override;
property Connections:TMixerConnections read FConnections;
property Controls:TMixerControls read FControls;
property Data:TMixerLine read FData;
end;


TMixerDestinations=class(TObject)
private
FDestinations:TPointerList;
protected
function GetDestination (Ind:Integer):TMixerDestination;
proceduredo
FreeItem (Pntr:Pointer);
function GetCount:Integer;
public
constructor Create (AMixer:TAudioMixer);
destructor Destroy;
override;
property Count:Integer read GetCount;
property Destination[Ind:Integer]:TMixerDestination read GetDestination;
default;
end;


TAudioMixer = class(TComponent)
private
XWndHandle:HWnd;

FDestinations:TMixerDestinations;
FMixersCount:Integer;
FMixerHandle:HMixer;
FMixerID:Integer;
FMixerCaps:TMixerCaps;
FOnLineChange:TMixerChange;
FOnControlChange:TMixerChange;
protected
procedure SetMixerID (Value:Integer);
procedure MixerCallBack (var Msg:TMessage);
procedure CloseMixer;
published
constructor Create (AOwner:TComponent);
override;
destructor Destroy;
override;
property MixerID:Integer read FMixerID write SetMixerID;
{Opened mixer - value must be in range 0..MixersCount-1
If no mixer is opened this value is -1}
property OnLineChange:TMixerChange read FOnLineChange write FOnLineChange;
property OnControlChange:TMixerChange read FOnControlChange write FOnControlChange;
public
function GetVolume (ADestination,AConnection:Integer;var LeftVol,RightVol,Mute:Integer;var VolDisabled,MuteDisabled:Boolean):Boolean;
{This function return volume of selected Destination and Connection.
ADestination must be from range 0..Destinations.Count-1
AConnection must be in range 0..Destinations[ADestination].Connections.Count-1
If you want to read master volume of some Destination, you have to
set AConnection to -1.
If LeftVol, RightVol or Mute is not supported by queried connection,
it's return value will be -1.

LeftVol and RightVol are in range 0..65536

If Mute is non-zero then
the connection is silent.
If specified line is recording source then
Mute specifies if programs will
record from this connection (it is copy of "Select" Checkbox in
standard Windows Volume Control program)
VolDisabled or MuteDisabled is True when you cannot apply settings to this
control (but can read it).

Return value of the function is True if no error has occured,
otherwise it returns False.}
function SetVolume (ADestination,AConnection:Integer;LeftVol,RightVol,Mute:Integer):Boolean;
{This function sets volume.
If you set RightVol to -1 and connection is stereo then
LeftVol will be
copied to RightVol.
If LeftVol or Mute is -1 then
this value will not be set.

Return value is True if ADestination and AConnection ar correct, otherwise False.}
property Destinations:TMixerDestinations read FDestinations;
{Ind must be in range 0..DestinationsCount-1}
property MixerCaps:TMixerCaps read FMixerCaps;
property MixerCount:Integer read FMixersCount;
{Number of mixers present in system;
mostly 1}
property MixerHandle:HMixer read FMixerHandle;
{Handle of opened mixer}
end;


procedure Register;

implementation

{------------}
{TPointerList}
{------------}

constructor TPointerList.Create;
begin

items:=tlist.create;
end;


destructor TPointerList.Destroy;
begin

Clear;
items.destroy;
end;


procedure TPointerList.Add (Pntr:Pointer);
begin

items.add(Pntr);
end;


function TPointerList.GetPointer (Ind:Integer):Pointer;
begin

result:=items[Ind];
end;


procedure TPointerList.Clear;
var i:integer;
begin

for i:=0 to items.count-1do
begin

If Assigned (FOnFreeItem) then

FOnFreeItem (items)
end;

items.clear;
end;


function TPointerList.GetCount:Integer;
begin

result:=items.count;
end;


{--------------}
{TMixerControls}
{--------------}

constructor TMixerControls.Create (AMixer:TAudioMixer;AData:TMixerLine);
var MLC:TMixerLineControls;
A,B:Integer;
P:PMixerControl;
begin

FControls:=TPointerList.Create;
MLC.cbStruct:=SizeOf(MLC);
MLC.dwLineID:=AData.dwLineID;
MLC.cControls:=AData.cControls;
MLC.cbmxctrl:=SizeOf(TMixerControl);
GetMem (P,SizeOf(TMixerControl)*AData.cControls);
heap:=P;
MLC.pamxctrl:=P;
A:=MixerGetLineControls(AMixer.MixerHandle,@MLC, MIXER_GETLINECONTROLSF_ALL);
If A=MMSYSERR_NOERROR then

begin

For B:=0 to AData.cControls-1do

begin

FControls.Add (P);
Inc (P);
end;

end;

end;


destructor TMixerControls.Destroy;
begin

FControls.free;
freemem(heap);
inherited;
end;


function TMixerControls.GetControl (Ind:Integer):PMixerControl;
begin

Result:=FControls.Pointer[Ind];
end;


function TMixerControls.GetCount:Integer;
begin

Result:=FControls.Count;
end;


{----------------}
{TMixerConnection}
{----------------}

constructor TMixerConnection.Create (AMixer:TAudioMixer;AData:TMixerLine);
begin

FData:=AData;
XMixer:=AMixer;
FControls:=TMixerControls.Create (AMixer,AData);
end;


destructor TMixerConnection.Destroy;
begin

FControls.Free;
inherited;
end;


{-----------------}
{TMixerConnections}
{-----------------}

constructor TMixerConnections.Create (AMixer:TAudioMixer;AData:TMixerLine);
var A,B:Integer;
ML:TMixerLine;
begin

XMixer:=AMixer;
FConnections:=TPointerList.Create;
FConnections.OnFreeItem:=Dofreeitem;
ML.cbStruct:=SizeOf(TMixerLine);
ML.dwDestination:=AData.dwDestination;
For A:=0 to AData.cConnections-1do

begin

ML.dwSource:=A;
B:=MixerGetLineInfo (AMixer.MixerHandle,@ML,MIXER_GETLINEINFOF_SOURCE);
If B=MMSYSERR_NOERROR then

FConnections.Add(Pointer(TMixerConnection.Create (XMixer,ML)));
end;

end;


destructor TMixerConnections.Destroy;
begin

FConnections.Free;
inherited;
end;


procedure TMixerConnections.DoFreeItem (Pntr:Pointer);
begin

TMixerConnection(Pntr).Free;
end;


function TMixerConnections.GetConnection (Ind:Integer):TMixerConnection;
begin

Result:=FConnections.Pointer[Ind];
end;


function TMixerConnections.GetCount:Integer;
begin

Result:=FConnections.Count;
end;


{-----------------}
{TMixerDestination}
{-----------------}

constructor TMixerDestination.Create (AMixer:TAudioMixer;AData:TMixerLine);
begin

FData:=AData;
XMixer:=AMixer;
FConnections:=TMixerConnections.Create (XMixer,FData);
FControls:=TMixerControls.Create (XMixer,AData);
end;


destructor TMixerDestination.Destroy;
begin

Fcontrols.free;
FConnections.Free;
inherited;
end;


{------------------}
{TMixerDestinations}
{------------------}

constructor TMixerDestinations.Create (AMixer:TAudioMixer);
var A,B:Integer;
ML:TMixerLine;
begin

FDestinations:=TPointerList.Create;
FDestinations.OnFreeItem:=DoFreeItem;
For A:=0 to AMixer.MixerCaps.cDestinations-1do

begin

ML.cbStruct:=SizeOf(TMixerLine);
ML.dwDestination:=A;
B:=MixerGetLineInfo (AMixer.MixerHandle,@ML,MIXER_GETLINEINFOF_DESTINATION);
If B=MMSYSERR_NOERROR then

FDestinations.Add(Pointer(TMixerDestination.Create (AMixer,ML)));
end;

end;


procedure TMixerDestinations.DoFreeItem (Pntr:Pointer);
begin

TMixerDestination(Pntr).Free;
end;


destructor TMixerDestinations.Destroy;
begin

FDestinations.Free;
inherited;
end;


function TMixerDestinations.GetDestination (Ind:Integer):TMixerDestination;
begin

Result:=FDestinations.Pointer[Ind];
end;


function TMixerDestinations.GetCount:Integer;
begin

Result:=FDestinations.Count;
end;


{-----------}
{TAudioMixer}
{-----------}

constructor TAudioMixer.Create (AOwner:TComponent);
begin

inherited Create (AOwner);
FDestinations:=nil;
XWndHandle:=AllocateHWnd (MixerCallBack);
FMixersCount:=mixerGetNumDevs;
FMixerID:=-1;
If FMixersCount>0 then

SetMixerID (0);
end;


destructor TAudioMixer.Destroy;
begin

closemixer;
if XWndHandle<>0 then

DeAllocateHwnd(XWndHandle);
inherited;
end;


procedure TAudioMixer.CloseMixer;
begin

If FMixerID>=0 then

begin

mixerClose (FMixerHandle);
FMixerID:=-1;
end;

FDestinations.Free;
FDestinations:=nil;
end;


procedure TAudioMixer.SetMixerID (Value:Integer);
begin

If Value>=FMixersCount then

Exit;
CloseMixer;
If Value>=0 then

If mixerOpen (@FMixerHandle,Value,XWndHandle,0,CALLBACK_WINDOW OR MIXER_OBJECTF_MIXER)=MMSYSERR_NOERROR then

begin

FMixerID:=Value;
mixerGetDevCaps (MixerID,@FMixerCaps,SizeOf (TMixerCaps));
FDestinations:=TMixerDestinations.Create (Self);
end;

end;


procedure TAudioMixer.MixerCallBack (var Msg:TMessage);
begin

case Msg.Msg of
MM_MIXM_LINE_CHANGE:
If Assigned (OnLineChange) then

OnLineChange (Self,Msg.wParam,Msg.lParam);
MM_MIXM_CONTROL_CHANGE:
If Assigned (OnControlChange) then

OnControlChange (Self,Msg.wParam,Msg.lParam);
end;

end;


function TAudioMixer.GetVolume (ADestination,AConnection:Integer;var LeftVol,RightVol,Mute:Integer;var VolDisabled,MuteDisabled:Boolean):Boolean;
var MD:TMixerDestination;
MC:TMixerConnection;
Cntrls:TMixerControls;
MCD:TMixerControlDetails;
Cntrl:PMixerControl;
A,B:Integer;
ML:TMixerLine;
details:array [0..30] of Integer;
begin

Result:=False;
MD:=Destinations[ADestination];
If MD<>nil then

begin

If AConnection=-1 then

begin

Cntrls:=MD.Controls;
ML:=MD.Data;
end
else

begin

MC:=MD.Connections[AConnection];
If MC<>nil then

begin

Cntrls:=MC.Controls;
ML:=MC.Data;
end
else

Cntrls:=nil;
end;

If Cntrls<>nil then

begin

A:=0;
Result:=True;
LeftVol:=-1;
RightVol:=-1;
Mute:=-1;
while ((LeftVol=-1) OR (Mute=-1)) AND (A<Cntrls.Count)do

begin

Cntrl:=Cntrls[A];
If Cntrl<>nil then

begin

If ((Cntrl.dwControlType=MIXERCONTROL_CONTROLTYPE_VOLUME) OR
(Cntrl.dwControlType=MIXERCONTROL_CONTROLTYPE_MUTE)) AND
(Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_MULTIPLE<>MIXERCONTROL_CONTROLF_MULTIPLE)
then

begin

MCD.cbStruct:=SizeOf(TMixerControlDetails);
MCD.dwControlID:=Cntrl.dwControlID;
If Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_UNIFORM>0 then

MCD.cChannels:=1
else

MCD.cChannels:=ML.cChannels;
MCD.cMultipleItems:=0;
MCD.cbDetails:=SizeOf(Integer);
MCD.paDetails:=@details;
B:=mixerGetControlDetails (FMixerHandle,@MCD,MIXER_GETCONTROLDETAILSF_VALUE);
If B=MMSYSERR_NOERROR then

begin

If (Cntrl.dwControlType=MIXERCONTROL_CONTROLTYPE_VOLUME) AND (LeftVol=-1) then

begin

VolDisabled:=Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_DISABLED>0;
If not VolDisabled then

begin

LeftVol:=details[0];
If MCD.cChannels>1 then

RightVol:=Details[1];
end;

end
else

If (Cntrl.dwControlType=MIXERCONTROL_CONTROLTYPE_MUTE) AND (Mute=-1) then

begin

MuteDisabled:=Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_DISABLED>0;
If not MuteDisabled then

begin

If Details[0]<>0 then

Mute:=1
else

Mute:=0;
end;

end;

end;

end;

end;

Inc (A);
end;

If Mute=-1 then

begin

If AConnection<>-1 then

begin

Cntrls:=MD.Controls;
ML:=MD.Data;
If Cntrls<>nil then

begin

A:=0;
while (Mute=-1) AND (A<Cntrls.Count)do

begin

Cntrl:=Cntrls[A];
If Cntrl.dwControlType AND MIXERCONTROL_CONTROLTYPE_MIXER=MIXERCONTROL_CONTROLTYPE_MIXER then

begin

MCD.cbStruct:=SizeOf(TMixerControlDetails);
MCD.dwControlID:=Cntrl.dwControlID;
If Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_UNIFORM>0 then

MCD.cChannels:=1
else

MCD.cChannels:=ML.cChannels;
If Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_MULTIPLE=MIXERCONTROL_CONTROLF_MULTIPLE then

MCD.cMultipleItems:=Cntrl.cMultipleItems
else

MCD.cMultipleItems:=0;
MCD.cbDetails:=4;
MCD.paDetails:=@Details;
B:=mixerGetControlDetails (FMixerHandle,@MCD,MIXER_GETCONTROLDETAILSF_VALUE);
If B=MMSYSERR_NOERROR then

Mute:=Details[AConnection];
end;

Inc (A);
end;

end;

end;

end;

If LeftVol=-1 then

VoldIsabled:=True;
If Mute=-1 then

MuteDisabled:=True;
end;

end;

end;


function TAudioMixer.SetVolume (ADestination,AConnection:Integer;LeftVol,RightVol,Mute:Integer):Boolean;
var MD:TMixerDestination;
MC:TMixerConnection;
Cntrls:TMixerControls;
MCD:TMixerControlDetails;
Cntrl:PMixerControl;
A:Integer;
ML:TMixerLine;
details:array [0..30] of Integer;
VolSet,MuteSet:Boolean;
begin

Result:=False;
MD:=Destinations[ADestination];
If MD<>nil then

begin

If AConnection=-1 then

begin

Cntrls:=MD.Controls;
ML:=MD.Data;
end
else

begin

MC:=MD.Connections[AConnection];
If MC<>nil then

begin

Cntrls:=MC.Controls;
ML:=MC.Data;
end
else

Cntrls:=nil;
end;

If Cntrls<>nil then

begin

A:=0;
VolSet:=LeftVol=-1;
MuteSet:=Mute=-1;
Result:=True;
while (not VolSet OR not MuteSet) AND (A<Cntrls.Count)do

begin

Cntrl:=Cntrls[A];
If Cntrl<>nil then

begin

If ((Cntrl.dwControlType=MIXERCONTROL_CONTROLTYPE_VOLUME) OR
(Cntrl.dwControlType=MIXERCONTROL_CONTROLTYPE_MUTE)) AND
(Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_MULTIPLE<>MIXERCONTROL_CONTROLF_MULTIPLE)
then

begin

MCD.cbStruct:=SizeOf(TMixerControlDetails);
MCD.dwControlID:=Cntrl.dwControlID;
If Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_UNIFORM>0 then

MCD.cChannels:=1
else

MCD.cChannels:=ML.cChannels;
MCD.cMultipleItems:=0;
MCD.cbDetails:=SizeOf(Integer);
MCD.paDetails:=@Details;
If (Cntrl.dwControlType=MIXERCONTROL_CONTROLTYPE_VOLUME) AND not VolSet then

begin

Details[0]:=LeftVol;
If RightVol=-1 then

Details[1]:=LeftVol
else

Details[1]:=RightVol;
VolSet:=True;
end
else

If (Cntrl.dwControlType=MIXERCONTROL_CONTROLTYPE_MUTE) AND not MuteSet then

begin

Details[0]:=Mute;
MuteSet:=True;
end;

mixerSetControlDetails (FMixerHandle,@MCD,MIXER_GETCONTROLDETAILSF_VALUE);
end;

end;

Inc (A);
end;

If not MuteSet then

begin

If AConnection<>-1 then

begin

Cntrls:=MD.Controls;
ML:=MD.Data;
If Cntrls<>nil then

begin

A:=0;
while not MuteSet AND (A<Cntrls.Count)do

begin

Cntrl:=Cntrls[A];
If Cntrl.dwControlType AND MIXERCONTROL_CONTROLTYPE_MIXER=MIXERCONTROL_CONTROLTYPE_MIXER then

begin

MCD.cbStruct:=SizeOf(TMixerControlDetails);
MCD.dwControlID:=Cntrl.dwControlID;
If Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_UNIFORM>0 then

MCD.cChannels:=1
else

MCD.cChannels:=ML.cChannels;
If Cntrl.fdwControl AND MIXERCONTROL_CONTROLF_MULTIPLE=MIXERCONTROL_CONTROLF_MULTIPLE then

MCD.cMultipleItems:=Cntrl.cMultipleItems
else

MCD.cMultipleItems:=0;
MCD.cbDetails:=4;
MCD.paDetails:=@Details;
MuteSet:=True;
mixerGetControlDetails (FMixerHandle,@MCD,MIXER_GETCONTROLDETAILSF_VALUE);
Details[AConnection]:=Mute;
mixerSetControlDetails (FMixerHandle,@MCD,MIXER_GETCONTROLDETAILSF_VALUE);
end;

Inc (A);
end;

end;

end;

end;

end;

end;

end;


procedure Register;
begin

RegisterComponents('Samples', [TAudioMixer]);
end;


end.
 
OopsWare:
谢谢您了!可惜,编译通不过,错误多多,我试着改掉几个明显的错误(while..then
),
但出现了更多的错误,你帮我再试试!谢谢!
 
我这个又简单,又好用,试试,一定行。


控制音量要用到waveOutSetVlume函数,此函数的声明在MMSystem.h单元中。
此单放在Delphi目录下的Source/rtl/win中

主意这儿用到两个函数waveOutSetVlume和waveOutGetVolume,不要用混,
我第一次用它们时就给搞混了,好费事。

程序如下:

三个TrackBar的属性设置:Max:=255;Frequency:=16;

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, mmsystem;

type
TForm1 = class(TForm)
TrackBar1: TTrackBar;
TrackBar2: TTrackBar;
TrackBar3: TTrackBar;
procedure TrackBar1Change(Sender: TObject);
procedure TrackBar2Change(Sender: TObject);
procedure TrackBar3Change(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;


var
Form1: TForm1;

implementation

{$R *.DFM}

//音量控制
procedure TForm1.TrackBar1Change(Sender: TObject);
var
t,v:Longint;
begin

// if a=0 then
Exit;
t:=TrackBar1.Position;
v:=(t shl 8)or(t shl 24);
waveOutSetVolume(0,v);
end;


//左声道控制
procedure TForm1.TrackBar2Change(Sender: TObject);
var
t,v:Longint;
begin

t:=TrackBar2.Position;
waveOutGetVolume(0,@v);
v:=v and $ffff0000 or (t shl 8);
waveOutSetVolume(0,v);
end;


//右声道控制
procedure TForm1.TrackBar3Change(Sender: TObject);
var
t,v:Longint;
begin

t:=TrackBar3.Position;
waveOutGetVolume(0,@v);
v:=v and $0000ffff or (t shl 24);
waveOutSetVolume(0,v);
end;


//获得当前音量值
procedure TForm1.FormCreate(Sender: TObject);
var
v:longint;
begin

waveOutGetVolume(0,@v);
TrackBar2.Position:=hi(v);
TrackBar3.Position:=hi(v shr 16);
if hi(v)>hi(v shr 16) then

TrackBar1.Position:=hi(v)
else

TrackBar1.Position:=hi(v shr 16);
end;


end.



函数说明:

waveOutSetVolume
第一个参数是波形文件输出设备标识符;
第二个参娄是音量大小。这是一个32位的整数,低16位表示左声道的音量,
高16位表示右声道的音量。

waveOutGetVolume
第一个参数是波形文件输出设备标识符;
第二个参数是一个32位整数的指针。


 
在msdn中有例子,我看了一段VB的关于控制系统主音量的程序,想改成delphi的,可惜有点问题,我一有时间会再调试。OopsWave的例子虽成,但太复杂了,其实很简单的几个API而已,要VB的源程序请留下email.
 
时间太久,强制结束。 wjiachun
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
后退
顶部