- Defining Properties
- An Example of DefineProperty()
- TddgWaveFile: An Example of DefineBinaryProperty()
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 componentsthe 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 2Wavez.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.