Home > Articles > Programming > General Programming/Other Languages

📄 Contents

  1. Defining Properties
  2. An Example of DefineProperty()
  3. TddgWaveFile: An Example of DefineBinaryProperty()
  • Print
  • + Share This
From the author of

TddgWaveFile: An Example of DefineBinaryProperty()

We mentioned earlier that a good time to use DefineBinaryProperty() is when you need to store graphic or sound information along with a component. In fact, VCL uses this technique for storing images associated with components—the Glyph of a TBitBtn, for example, or the Icon of a TForm. In this section, you'll learn how to use this technique when storing the sound associated with the TddgWaveFile component.

The DefineProperties() method for TddgWaveFile is as follows:

procedure TddgWaveFile.DefineProperties(Filer: TFiler);
{ Defines binary property called "Data" for FData field. }
{ This allows FData to be read from and written to DFM file. }

 function DoWrite: Boolean;
 begin
  if Filer.Ancestor <> nil then
   Result := not (Filer.Ancestor is TddgWaveFile) or
    not Equal(TddgWaveFile(Filer.Ancestor))
  else
   Result := not Empty;
 end;

begin
 inherited DefineProperties(Filer);
 Filer.DefineBinaryProperty('Data', ReadData, WriteData, DoWrite);
end;

This method defines a binary property called Data, which is read and written using the component's ReadData() and WriteData() methods. Additionally, data is written only if the return value of DoWrite() is True. (You'll learn more about DoWrite() in just a moment.)

The ReadData() and WriteData() methods are defined as follows:

procedure TddgWaveFile.ReadData(Stream: TStream);
{ Reads WAV data from DFM stream. }
begin
 LoadFromStream(Stream);
end;

procedure TddgWaveFile.WriteData(Stream: TStream);
{ Writes WAV data to DFM stream }
begin
 SaveToStream(Stream);
end;

As you can see, there isn't much to these methods; they simply call the LoadFromStream() and SaveToStream() methods, which are also defined by the TddgWaveFile component. The LoadFromStream() method is as follows:

procedure TddgWaveFile.LoadFromStream(S: TStream);
{ Loads WAV data from stream S. This procedure will free }
{ any memory previously allocated for FData. }
begin
 if not Empty then
  FreeMem(FData, FDataSize);
 FDataSize := 0;
 FData := AllocMem(S.Size);
 FDataSize := S.Size;
 S.Read(FData^, FDataSize);
end;

This method first checks whether memory has been previously allocated by testing the value of the FDataSize field. If it's greater than zero, the memory pointed to by the FData field is freed. At that point, a new block of memory is allocated for FData, and FDataSize is set to the size of the incoming data stream. The contents of the stream are then read into the FData pointer.

The SaveToStream() method is much simpler; it's defined as follows:

procedure TddgWaveFile.SaveToStream(S: TStream);
{ Saves WAV data to stream S. }
begin
 if FDataSize > 0 then
  S.Write(FData^, FDataSize);
end;

This method writes the data pointed to by pointer FData to TStream S.

The local DoWrite() function inside the DefineProperties() method determines whether the Data property needs to be streamed. Of course, if FData is empty, there's no need to stream data. Additionally, you must take extra measures to ensure that your component works correctly with form inheritance: You must check whether the Ancestor property for Filer is non-nil. If it is, and it points to an ancestor version of the current component, you must check whether the data you're about to write is different from the ancestor. If you don't perform these additional tests, a copy of the data (the wave file, in this case) will be written in each of the descendant forms, and changes to the ancestor's wave file won't be copied to the descendant forms.

Listing 2 shows Wavez.pas, which includes the complete source code for the component.

Listing 2—Wavez.pas Illustrates a Component Encapsulating a Wave File

unit Wavez;

interface

uses
 SysUtils, Classes;

type
 { Special string "descendant" used to make a property editor. }
 TWaveFileString = type string;

 EWaveError = class(Exception);

 TWavePause = (wpAsync, wpsSync);
 TWaveLoop = (wlNoLoop, wlLoop);

 TddgWaveFile = class(TComponent)
 private
  FData: Pointer;
  FDataSize: Integer;
  FWaveName: TWaveFileString;
  FWavePause: TWavePause;
  FWaveLoop: TWaveLoop;
  FOnPlay: TNotifyEvent;
  FOnStop: TNotifyEvent;
  procedure SetWaveName(const Value: TWaveFileString);
  procedure WriteData(Stream: TStream);
  procedure ReadData(Stream: TStream);
 protected
  procedure DefineProperties(Filer: TFiler); override;
 public
  destructor Destroy; override;
  function Empty: Boolean;
  function Equal(Wav: TddgWaveFile): Boolean;
  procedure LoadFromFile(const FileName: String);
  procedure LoadFromStream(S: TStream);
  procedure Play;
  procedure SaveToFile(const FileName: String);
  procedure SaveToStream(S: TStream);
  procedure Stop;
 published
  property WaveLoop: TWaveLoop read FWaveLoop write FWaveLoop;
  property WaveName: TWaveFileString read FWaveName write SetWaveName;
  property WavePause: TWavePause read FWavePause write FWavePause;
  property OnPlay: TNotifyEvent read FOnPlay write FOnPlay;
  property OnStop: TNotifyEvent read FOnStop write FOnStop;
 end;

implementation

uses MMSystem, Windows;

{ TddgWaveFile }

destructor TddgWaveFile.Destroy;
{ Ensures that any allocated memory is freed }
begin
 if not Empty then
  FreeMem(FData, FDataSize);
 inherited Destroy;
end;

function StreamsEqual(S1, S2: TMemoryStream): Boolean;
begin
 Result := (S1.Size = S2.Size) and CompareMem(S1.Memory, S2.Memory, S1.Size);
end;

procedure TddgWaveFile.DefineProperties(Filer: TFiler);
{ Defines binary property called "Data" for FData field. }
{ This allows FData to be read from and written to DFM file. }

 function DoWrite: Boolean;
 begin
  if Filer.Ancestor <> nil then
   Result := not (Filer.Ancestor is TddgWaveFile) or
    not Equal(TddgWaveFile(Filer.Ancestor))
  else
   Result := not Empty;
 end;

begin
 inherited DefineProperties(Filer);
 Filer.DefineBinaryProperty('Data', ReadData, WriteData, DoWrite);
end;

function TddgWaveFile.Empty: Boolean;
begin
 Result := FDataSize = 0;
end;

function TddgWaveFile.Equal(Wav: TddgWaveFile): Boolean;
var
 MyImage, WavImage: TMemoryStream;
begin
 Result := (Wav <> nil) and (ClassType = Wav.ClassType);
 if Empty or Wav.Empty then
 begin
  Result := Empty and Wav.Empty;
  Exit;
 end;
 if Result then
 begin
  MyImage := TMemoryStream.Create;
  try
   SaveToStream(MyImage);
   WavImage := TMemoryStream.Create;
   try
    Wav.SaveToStream(WavImage);
    Result := StreamsEqual(MyImage, WavImage);
   finally
    WavImage.Free;
   end;
  finally
   MyImage.Free;
  end;
 end;
end;

procedure TddgWaveFile.LoadFromFile(const FileName: String);
{ Loads WAV data from FileName. Note that this procedure }
{ does not set the WaveName property. }
var
 F: TFileStream;
begin
 F := TFileStream.Create(FileName, fmOpenRead);
 try
  LoadFromStream(F);
 finally
  F.Free;
 end;
end;

procedure TddgWaveFile.LoadFromStream(S: TStream);
{ Loads WAV data from stream S. This procedure will free }
{ any memory previously allocated for FData. }
begin
 if not Empty then
  FreeMem(FData, FDataSize);
 FDataSize := 0;
 FData := AllocMem(S.Size);
 FDataSize := S.Size;
 S.Read(FData^, FDataSize);
end;

procedure TddgWaveFile.Play;
{ Plays the WAV sound in FData using the parameters found in }
{ FWaveLoop and FWavePause. }
const
 LoopArray: array[TWaveLoop] of DWORD = (0, SND_LOOP);
 PauseArray: array[TWavePause] of DWORD = (SND_ASYNC, SND_SYNC);
begin
 { Make sure component contains data }
 if Empty then
  raise EWaveError.Create('No wave data');
 if Assigned(FOnPlay) then FOnPlay(Self);  // fire event
 { attempt to play wave sound }
 if not PlaySound(FData, 0, SND_MEMORY or PauseArray[FWavePause] or
          LoopArray[FWaveLoop]) then
  raise EWaveError.Create('Error playing sound');
end;

procedure TddgWaveFile.ReadData(Stream: TStream);
{ Reads WAV data from DFM stream. }
begin
 LoadFromStream(Stream);
end;

procedure TddgWaveFile.SaveToFile(const FileName: String);
{ Saves WAV data to file FileName. }
var
 F: TFileStream;
begin
 F := TFileStream.Create(FileName, fmCreate);
 try
  SaveToStream(F);
 finally
  F.Free;
 end;
end;

procedure TddgWaveFile.SaveToStream(S: TStream);
{ Saves WAV data to stream S. }
begin
 if not Empty then
  S.Write(FData^, FDataSize);
end;

procedure TddgWaveFile.SetWaveName(const Value: TWaveFileString);
{ Write method for WaveName property. This method is in charge of }
{ setting WaveName property and loading WAV data from file Value. }
begin
 if Value <> '' then begin
  FWaveName := ExtractFileName(Value);
  { don't load from file when loading from DFM stream }
  { because DFM stream will already contain data. }
  if (not (csLoading in ComponentState)) and FileExists(Value) then
   LoadFromFile(Value);
 end
 else begin
  { If Value is an empty string, that is the signal to free }
  { memory allocated for WAV data. }
  FWaveName := '';
  if not Empty then
   FreeMem(FData, FDataSize);
  FDataSize := 0;
 end;
end;

procedure TddgWaveFile.Stop;
{ Stops currently playing WAV sound }
begin
 if Assigned(FOnStop) then FOnStop(Self); // fire event
 PlaySound(Nil, 0, SND_PURGE);
end;

procedure TddgWaveFile.WriteData(Stream: TStream);
{ Writes WAV data to DFM stream }
begin
 SaveToStream(Stream);
end;

end.
  • + Share This
  • 🔖 Save To Your Account

Related Resources

There are currently no related titles. Please check back later.