{"id":1989,"date":"2015-06-03T13:00:23","date_gmt":"2015-06-03T03:00:23","guid":{"rendered":"http:\/\/www.malcolmgroves.com\/blog\/?p=1989"},"modified":"2015-06-02T13:36:04","modified_gmt":"2015-06-02T03:36:04","slug":"app-tethering-remote-actions","status":"publish","type":"post","link":"http:\/\/www.malcolmgroves.com\/blog\/?p=1989","title":{"rendered":"App Tethering : Remote Actions"},"content":{"rendered":"<p><a href=\"http:\/\/www.malcolmgroves.com\/blog\/?p=1891\">After the last few posts, you should be getting comfortable sharing data between multiple apps<\/a>. That leaves us the last of our\u00a0original four conceptual areas to cover off : Actions.<\/p>\n<p>Just as App Tethering allows you to share pieces of data out to remote applications, it also allows you to share TActions (strictly speaking, any TCustomAction descendant, but I&#8217;ll use TAction for short).\u00a0Right now you&#8217;ve possibly\u00a0got at least one button or menu item connected to a TAction somewhere in your app. More likely you have dozens of them. Shared Actions let you expose these TActions unchanged, so they can be triggered from remote apps as well.<!--more--><\/p>\n<p>It&#8217;s probably easiest to add one first before we discuss further, so bring up App1 and add a TActionList to the main form. Double-click on it to bring up the ActionList Editor and add a new Action. Set the Action&#8217;s Name to actReset\u00a0and\u00a0its Text to Reset. In this Action we&#8217;re going to reset all the components we&#8217;ve been using to display the various shared data resources over the last few posts. In the Action&#8217;s OnExecute event add the following code.<\/p>\n<p>For Delphi:<\/p>\n<pre class=\"lang:delphi decode:true\">procedure TForm3.actResetExecute(Sender: TObject);\r\nbegin\r\n  Edit1.Text := '';\r\n  Label2.Text := '';\r\nend;<\/pre>\n<p>For C++:<\/p>\n<pre class=\"lang:c++ decode:true \">void __fastcall TForm1::actResetExecute(TObject *Sender)\r\n{\r\n  Edit1-&gt;Text = \"\";\r\n  Label2-&gt;Text = \"\";\r\n}<\/pre>\n<p>In the action&#8217;s OnUpdate event, put the following code.<\/p>\n<p>For Delphi:<\/p>\n<pre class=\"lang:delphi decode:true\">procedure TForm3.actResetUpdate(Sender: TObject);\r\nbegin\r\n  actReset.Enabled := (Edit1.Text &lt;&gt; '') OR\r\n                      (Label2.Text &lt;&gt; '');\r\nend;<\/pre>\n<p>For C++:<\/p>\n<pre class=\"lang:c++ decode:true \">void __fastcall TForm1::actResetUpdate(TObject *Sender)\r\n{\r\n  actReset-&gt;Enabled = (Edit1-&gt;Text != \"\") ||\r\n                      (Label2-&gt;Text != \"\");\r\n}<\/pre>\n<p>Lastly, drop a button on the form and connect its Action property to your newly created actReset.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-1992 size-full\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.1.png\" alt=\"\" width=\"427\" height=\"223\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.1.png 427w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.1-300x157.png 300w\" sizes=\"auto, (max-width: 427px) 100vw, 427px\" \/><\/p>\n<p>So, we&#8217;re clearing Edit1 and Label2, but the action (and by extension the button) is only enabled if those control are not already clear.<\/p>\n<p>If you&#8217;ve ever used Actions before, hopefully nothing there is surprising (well, except perhaps the fact that I couldn&#8217;t come up with a better scenario than clearing the controls).\u00a0Importantly, nothing there has anything to do with App Tethering: actReset is a purely local TAction.<\/p>\n<p>How do we share it with our remote App? In App1, go to your TetheringAppProfile1 component. Earlier we saw that it had a Resources property that contained all the resources it was sharing with remote apps. You should also see that it has an Actions property. Bring up its property editor and add a new TLocalAction. Set its Name to lactReset, and point its Action property to actReset. You&#8217;ll note it has a Kind property, just like our persistent resources, and like those, we&#8217;ll set it to Shared.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1998\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.2.1.png\" alt=\"tethering6.2.1\" width=\"290\" height=\"289\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.2.1.png 290w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.2.1-150x150.png 150w\" sizes=\"auto, (max-width: 290px) 100vw, 290px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>That&#8217;s all we need to do in App1. In App2, what we do depends on how you want to trigger this action. If all you want to do is trigger it from code, then drop a TButton onto App2&#8217;s main form and in the OnClick event write the following code.<\/p>\n<p>For Delphi:<\/p>\n<pre class=\"lang:delphi decode:true \">procedure TForm4.Button2Click(Sender: TObject);\r\nbegin\r\n  TetheringAppProfile1.RunRemoteAction(TetheringManager1.RemoteProfiles.First,\r\n                                       'lactReset');\r\nend;<\/pre>\n<p>For C++:<\/p>\n<pre class=\"lang:c++ decode:true \">void __fastcall TForm2::Button2Click(TObject *Sender)\r\n{\r\n  TetheringAppProfile1-&gt;RunRemoteAction(TetheringManager1-&gt;RemoteProfiles-&gt;First(),\r\n                                        \"lactReset\");\r\n}<\/pre>\n<p>Provided the remote profile we specify in the first parameter has a TLocalAction named &#8216;lactReset&#8217;, and provided the connected TAction is enabled, it will be executed.<\/p>\n<p>If that&#8217;s all we want to do, then we&#8217;re done. However, we may want a TAction in App2 that we can connect to Buttons, Menu Items, etc that acts as a proxy to the action in App1. In that case, add a TActionList to App2 and create a new TAction in it. This time don&#8217;t give it an OnExecute or OnUpdate event. This action will be our local proxy, but doesn&#8217;t need any execution logic as that will be happening remotely.<\/p>\n<p>Next, add a\u00a0TLocalAction to App2&#8217;s TetheringAppProfile.Actions property. Set its Name to lactReset (to match the one in App1), set its Kind to Mirror and lastly point its Action property at the new TAction you just created.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1999\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.3.png\" alt=\"tethering6.3\" width=\"286\" height=\"288\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.3.png 286w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.3-150x150.png 150w\" sizes=\"auto, (max-width: 286px) 100vw, 286px\" \/><\/p>\n<p>Lastly, clear out the button event handler you just wrote to call RunRemoteAction (or alternately, add another button) and connect its Action property to your newly created TAction.<\/p>\n<p>I realise all that might be a little confusing, what with every second word ending in Action, so at a high level here&#8217;s what we have:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2001\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/flow.jpg\" alt=\"flow\" width=\"372\" height=\"331\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/flow.jpg 372w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/flow-300x267.jpg 300w\" sizes=\"auto, (max-width: 372px) 100vw, 372px\" \/><\/p>\n<p>In App2 we have a TAction, which is connected to lactReset, our TLocalAction. This TLocalAction is actually a mirror of a shared TLocalAction in App1, which is connected to a TAction called actReset which contains the actual logic that will run.<\/p>\n<p>Start both apps and click Connect in App1. You should then be able to\u00a0share some text resources both ways, and then click your button in App2 and see the label and edit box in App1 get cleared.<\/p>\n<p>On the network, this is actually pretty simple.\u00a0This is not like a remote procedure call where we need to marshall\\unmarshall parameters and return values. All we&#8217;re doing is triggering an Action, so the action name is all that is sent over the wire. In the image below you can see the RUN_ACTION constant in the network packet, followed by the action name, lactReset.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2012\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.4.png\" alt=\"tethering6.4\" width=\"400\" height=\"156\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.4.png 400w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.4-300x117.png 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/p>\n<p>The following packet is the RES_RUNOK acknowledgement coming back.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2013\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.5.png\" alt=\"tethering6.5\" width=\"400\" height=\"135\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.5.png 400w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.5-300x101.png 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/p>\n<p>One last thing on Actions before we end for the day. When you added the very first Action to your TetheringAppProfile1 component in App1, I got you to set the name to lactReset and the Kind to Shared.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1998\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.2.1.png\" alt=\"tethering6.2.1\" width=\"290\" height=\"289\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.2.1.png 290w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/05\/tethering6.2.1-150x150.png 150w\" sizes=\"auto, (max-width: 290px) 100vw, 290px\" \/><\/p>\n<p>Notice the very bottom property, NotifyUpdates. If you are using RAD Studio XE8 Update 1 (or later), this indicates whether you want the remote actions to be updated when the local TAction changes. For example, in our case in actReset&#8217;s OnUpdate event, we are disabling it if there is no text in the edit box and\u00a0the label, and enabling it if there is text in either of them. If you set NotifyUpdates to True, this change in the enabled property will be sent to the remote actions, so our button in App2 will enable and disable in sync with the button in App1.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2016\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.6.png\" alt=\"tethering6.6\" width=\"456\" height=\"531\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.6.png 456w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.6-258x300.png 258w\" sizes=\"auto, (max-width: 456px) 100vw, 456px\" \/><\/p>\n<p>NotifyUpdates defaults to False, as depending on the Action this could end up being quite chatty over the network.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2017\" src=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.7.png\" alt=\"tethering6.7\" width=\"458\" height=\"535\" srcset=\"http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.7.png 458w, http:\/\/www.malcolmgroves.com\/blog\/wp-content\/uploads\/2015\/06\/tethering6.7-257x300.png 257w\" sizes=\"auto, (max-width: 458px) 100vw, 458px\" \/><\/p>\n<p>So now you&#8217;ve done enough to build some pretty useful apps. You can find and connect your apps, share all sorts of data, and trigger actions.<\/p>\n<p>We&#8217;ve only run App1 and App2 on Windows so far, but they are Multi-Device apps, so try running them on some mixture of Windows, Android, iOS and OSX. Provided the machines are all on the same subnet, the code should work as is, and if\u00a0not, <a href=\"http:\/\/www.malcolmgroves.com\/blog\/?p=1854\">try passing in the Target details to AutoConnect as we looked at way back in the second article<\/a>.<\/p>\n<p>Next up, I think it&#8217;s time to come good on a promise I made earlier. I said there were three ways to find other apps, but so far we&#8217;ve only discussed one: AutoConnect. In the next article I&#8217;ll fix that.<\/p>\n<p>You can get the source for the sample apps at this stage of the series, in both <a href=\"https:\/\/github.com\/malcolmgroves\/tethering_series_cpp\/releases\/tag\/6.RemoteActions\" target=\"_blank\">C++<\/a> and <a href=\"https:\/\/github.com\/malcolmgroves\/tethering_series_delphi\/releases\/tag\/6.RemoteActions\" target=\"_blank\">Delphi<\/a>, but to really understand what is going on there is no substitute for building the apps yourself. They aren&#8217;t that big.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>After the last few posts, you should be getting comfortable sharing data between multiple apps. That leaves us the last of our\u00a0original four conceptual areas to cover off : Actions. Just as App Tethering allows you to share pieces of data out to remote applications, it also allows you to share TActions (strictly speaking, any [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[113,119],"tags":[48],"class_list":["post-1989","post","type-post","status-publish","format-standard","hentry","category-apptethering","category-distributed-systems","tag-embarcadero"],"_links":{"self":[{"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1989","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1989"}],"version-history":[{"count":17,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1989\/revisions"}],"predecessor-version":[{"id":2027,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1989\/revisions\/2027"}],"wp:attachment":[{"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1989"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1989"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.malcolmgroves.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1989"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}