Now that we know how to work with Persistent Resources, Transient Resources are going to be a walk in the park.

Transient Resources have many things in common with Persistent Resources. They come in both Data and Stream types. They are one-way. Also their network behaviour is much the same as Persistent Resources. ie. Transient Data Resources are sent via UDP Point-to-Point whereas Transient Stream Resources are notified via UDP Point-to-Point but the value is actually transmitted via TCP.

Where they differ is mostly around the details of how they are sent, and how much work you have to do vs how much the framework does for you. Remember with Persistent Resources you define them at design-time, and then other apps can subscribe to them. When you update them, you don’t know how many people are being notified of the change.

With Transient Resources, they are defined in code and you send them to a specific receiver. Want a resource to go to three apps? Then you send it three times, once to each app.

Let’s update our current apps to include some Transient Resources. In our case we’re going to use it to allow App2 to send resources back to App1 (so far App1 has been the sender and App2 the receiver). Open up the main form of App2, and add a TEdit. Right-click on the TEdit and add a TEditButton, then set the EditButton’s Text property to Send Transient. Set the TEdit’s TextPrompt property to ‘ReplyText’. Here’s what my form looks like:

Double-click on the Send Transient button and enter the following code.

For Delphi:

procedure TForm4.EditButton1Click(Sender: TObject); begin TetheringAppProfile1.SendString(TetheringManager1.RemoteProfiles.First, 'ReplyText', Edit1.Text); end; 1 2 3 4 5 6 procedure TForm4 . EditButton1Click ( Sender : TObject ) ; begin TetheringAppProfile1 . SendString ( TetheringManager1 . RemoteProfiles . First , 'ReplyText' , Edit1 . Text ) ; end ;

For C++:

void __fastcall TForm2::EditButton1Click(TObject *Sender) { TetheringAppProfile1->SendString( TetheringManager1->RemoteProfiles->First(), "ReplyText", Edit1->Text); } 1 2 3 4 5 6 7 void __fastcall TForm2 :: EditButton1Click ( TObject * Sender ) { TetheringAppProfile1 -> SendString ( TetheringManager1 -> RemoteProfiles -> First ( ) , "ReplyText" , Edit1 -> Text ) ; }

We’re calling SendString on our TetheringAppProfile. It takes three parameters:

AProfile – a TTetheringProfileInfo record which identifies the remote TetheringAppProfile that you want to send the resource to. The TetheringManager component contains a list of all remote AppProfiles we are connected to in the RemoteProfiles property and in this case we are simply selecting the first one. You could easily imagine doing a for..in over this collection and sending to multiple.

Description – a string which is a unique name for the resource you are sending (much like the Name property of your Persistent Resource.

AString – cunningly named, this is a string containing the value of the resource you want to send.

SendString returns a boolean indicating whether the String was sent successfully.

On the receiver-side, let’s open up App1’s main form and add a label to hold our string once we receive it. Where do we receive it? Well, with our Persistent Resources, we received them in the Resource’s OnResourceReceived event. With Transient resources, we don’t have a Resource defined at design-time, so we use the TetheringAppProfile’s generic OnResourceReceived event:

For Delphi:

procedure TForm3.TetheringAppProfile1ResourceReceived(const Sender: TObject; const AResource: TRemoteResource); begin if AResource.Hint = 'ReplyText' then begin Label2.Text := AResource.Value.AsString; end; end; 1 2 3 4 5 6 7 8 procedure TForm3 . TetheringAppProfile1ResourceReceived ( const Sender : TObject ; const AResource : TRemoteResource ) ; begin if AResource . Hint = 'ReplyText' then begin Label2 . Text : = AResource . Value . AsString ; end ; end ;

For C++:

void __fastcall TForm1::TetheringAppProfile1ResourceReceived(TObject * const Sender, TRemoteResource * const AResource) { if (AResource->Hint == "ReplyText") { Label2->Text = AResource->Value.AsString; }; } 1 2 3 4 5 6 7 void __fastcall TForm1 :: TetheringAppProfile1ResourceReceived ( TObject * const Sender , TRemoteResource * const AResource ) { if ( AResource -> Hint == "ReplyText" ) { Label2 -> Text = AResource -> Value . AsString ; } ; }

The received resource is passed in as a parameter called AResource. As this OnResourceReceived event is going to run for all Transient Resources sent to this AppProfile, we first need to check which resource we are receiving. We match based on the name we passed in to SendString, and then grab the value much as we did for a Persistent Resource.

There is one other, optional event that comes into play here. Before the OnResourceReceived event runs, the TetheringAppProfile’s OnAcceptResource event will be called. This event gives you much more info about the resource, including the ProfileID of the sender. You could use this to look up the Profile in the TetheringManager’s RemoteProfiles list and make a decision about whether you want to accept it. Another of the parameters to this event is a boolean var parameter called AcceptResource, which defaults to True. If you decide you don’t want to accept a resource from a particular sender, set AcceptResource to False and your OnResourceReceieved event will never run. In this case, because AcceptResource defaults to True, we don’t need to implement that event, but feel free to play with accepting some resources and rejecting others.

So the flow of sending our Transient string Resource looks like this:

App 1 Network App 2 SendString(Profile, ‘ReplyText’, Edit1.Text Resource Updated (UDP Point to Point) TetheringAppProfile.OnAcceptResource TetheringAppProfile.OnResourceReceived

Run both apps, connect and then try sending a transient resource from App2 to App1.

Just to be complete, let’s do the same thing for a Transient Stream Resource.

Go back to App2 and add a TButton, a TImageControl and a TOpenDialog. Then put the following code into the TButton’s OnClick event.

For Delphi:

procedure TForm4.Button1Click(Sender: TObject); var LStream : TMemoryStream; begin if OpenDialog1.Execute then begin ImageControl2.LoadFromFile(OpenDialog1.FileName); LStream := TMemoryStream.Create; ImageControl2.Bitmap.SaveToStream(LStream); LStream.Position := 0; TetheringAppProfile1.SendStream(TetheringManager1.RemoteProfiles.First, 'ReplyImage', LStream); end; end; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 procedure TForm4 . Button1Click ( Sender : TObject ) ; var LStream : TMemoryStream ; begin if OpenDialog1 . Execute then begin ImageControl2 . LoadFromFile ( OpenDialog1 . FileName ) ; LStream : = TMemoryStream . Create ; ImageControl2 . Bitmap . SaveToStream ( LStream ) ; LStream . Position : = 0 ; TetheringAppProfile1 . SendStream ( TetheringManager1 . RemoteProfiles . First , 'ReplyImage' , LStream ) ; end ; end ;

For C++:

void __fastcall TForm2::Button1Click(TObject *Sender) { if (OpenDialog1->Execute()) { ImageControl2->LoadFromFile(OpenDialog1->FileName); TMemoryStream* LStream = new TMemoryStream; ImageControl2->Bitmap->SaveToStream(LStream); LStream->Position = 0; TetheringAppProfile1->SendStream( TetheringManager1->RemoteProfiles->First(), "ReplyImage", LStream); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void __fastcall TForm2 :: Button1Click ( TObject * Sender ) { if ( OpenDialog1 -> Execute ( ) ) { ImageControl2 -> LoadFromFile ( OpenDialog1 -> FileName ) ; TMemoryStream * LStream = new TMemoryStream ; ImageControl2 -> Bitmap -> SaveToStream ( LStream ) ; LStream -> Position = 0 ; TetheringAppProfile1 -> SendStream ( TetheringManager1 -> RemoteProfiles -> First ( ) , "ReplyImage" , LStream ) ; } }

The first part of this code where we browse for an image file, load it into the ImageControl and then save it to a stream is identical to the code we used in App1 back when we updated a Persistent Stream Resource. The last line is different of course, where we call SendStream to send it as a Transient Resource to a specific remote profile.

In our receiver (App1), we need to add another ImageControl and then update our TetheringAppProfile.OnResourceReceived event to handle this resource.

In Delphi:

procedure TForm3.TetheringAppProfile1ResourceReceived(const Sender: TObject; const AResource: TRemoteResource); begin if AResource.Hint = 'ReplyText' then begin Label2.Text := AResource.Value.AsString; end else if AResource.Hint = 'ReplyImage' then begin AResource.Value.AsStream.Position := 0; ImageControl2.Bitmap.LoadFromStream(AResource.Value.AsStream); end; end; 1 2 3 4 5 6 7 8 9 10 11 12 13 procedure TForm3 . TetheringAppProfile1ResourceReceived ( const Sender : TObject ; const AResource : TRemoteResource ) ; begin if AResource . Hint = 'ReplyText' then begin Label2 . Text : = AResource . Value . AsString ; end else if AResource . Hint = 'ReplyImage' then begin AResource . Value . AsStream . Position : = 0 ; ImageControl2 . Bitmap . LoadFromStream ( AResource . Value . AsStream ) ; end ; end ;

In C++:

void __fastcall TForm1::TetheringAppProfile1ResourceReceived(TObject * const Sender, TRemoteResource * const AResource) { if (AResource->Hint == "ReplyText") { Label2->Text = AResource->Value.AsString; } else if (AResource->Hint == "ReplyImage") { AResource->Value.AsStream->Position = 0; ImageControl2->Bitmap->LoadFromStream(AResource->Value.AsStream); }; } 1 2 3 4 5 6 7 8 9 10 void __fastcall TForm1 :: TetheringAppProfile1ResourceReceived ( TObject * const Sender , TRemoteResource * const AResource ) { if ( AResource -> Hint == "ReplyText" ) { Label2 -> Text = AResource -> Value . AsString ; } else if ( AResource -> Hint == "ReplyImage" ) { AResource -> Value . AsStream -> Position = 0 ; ImageControl2 -> Bitmap -> LoadFromStream ( AResource -> Value . AsStream ) ; } ; }

Again, once we determine which resource we’re receiving, the code to retrieve it from the AResource parameter and load it into the ImageControl is basically the same as our Persistent Stream Resource.

Here are our apps in action:

By this stage you can probably guess what the flow looks like for our Transient Stream Resource:

App 1 Network App 2 SendStream(Profile, ‘ReplyImage’, LStream); Resource Updated (UDP Point to Point)

Get Resource Value (TCP) Resource Value (TCP) TetheringAppProfile.OnAcceptResource TetheringAppProfile.OnResourceReceived

That will do us for Resources for now, however we will revisit them later in the series. We’ve now looked at Discovery, Pairing and Resources, so next up, let’s take a look at the last of these key App Tethering concepts : Actions.

You can get the source for the sample apps at this stage of the series, in both C++ and Delphi, but to really understand what is going on there is no substitute for building the apps yourself. They aren’t that big.