Post by Carlos (Panels App Developer) on Mar 3, 2018 6:19:54 GMT
If you've read the Developer Getting Started guide (found here), then you have a good understanding of how to get your Panel to interact with the computer. This is data (key presses, usually) flowing from your tablet to the computer. What if you want data going in the opposite direction? What if you want your panel to do something whenever something happens in a game, or whenever a file is created in a folder?
WARNING: This does require some knowledge of a .Net language (C#, VB, etc.).
A DataFeed is a .Net Standard class that inherits from the DataFeed abstract class. To get this abstract class, you just need to install the PanelDataFeed nuget package (here, source here). This gives you the interfaces needed to make your custom data feed to communicate with your panel. The DataFeed abstract has two important abstract methods that need to be implemented. The first is the "RequiredSettings" method. This is called by the Panels Desktop application to ask your data feed about any settings that the user needs to provide for your data feed to work. If your data feed doesn't have any required settings, you can simply return an empty array here.
NOTE: For security reasons, your Datafeed will only be able to read from directories/files that are returned in the "RequiredSettings" method. If your Datafeed attempts to read from any directory that isn't explicitly stated in the RequiredSettings method, you will get a SecurityException.
The other important method is the Start method. This is called when the DataFeed is loaded. The method takes an IPanelCommunicator, which is an object that will let you send data to your panel. Once the start method is called, you can enter a loop, or write any event handling logic you'd like. Your DataFeed is running on a separate thread, and so you won't have to worry about blocking the main processing of the program. Do try to be reasonable with what your data feed is doing though, as poor performance will lead to people not wanting to use your panel.
A good example of what I'm describing above is the "Featured" Chrome Panel that comes with the Panels Desktop client. If you install and try out that panel, you'll see that the Panel is loading bookmarks from your computer. If you add a bookmark, that bookmark will show up in the panel. Remove a bookmark, and the bookmark will disappear from the panel. How is this done?
ChromeDataFeed
WARNING: This does require some knowledge of a .Net language (C#, VB, etc.).
DataFeed
A DataFeed is a .Net Standard class that inherits from the DataFeed abstract class. To get this abstract class, you just need to install the PanelDataFeed nuget package (here, source here). This gives you the interfaces needed to make your custom data feed to communicate with your panel. The DataFeed abstract has two important abstract methods that need to be implemented. The first is the "RequiredSettings" method. This is called by the Panels Desktop application to ask your data feed about any settings that the user needs to provide for your data feed to work. If your data feed doesn't have any required settings, you can simply return an empty array here.
NOTE: For security reasons, your Datafeed will only be able to read from directories/files that are returned in the "RequiredSettings" method. If your Datafeed attempts to read from any directory that isn't explicitly stated in the RequiredSettings method, you will get a SecurityException.
The other important method is the Start method. This is called when the DataFeed is loaded. The method takes an IPanelCommunicator, which is an object that will let you send data to your panel. Once the start method is called, you can enter a loop, or write any event handling logic you'd like. Your DataFeed is running on a separate thread, and so you won't have to worry about blocking the main processing of the program. Do try to be reasonable with what your data feed is doing though, as poor performance will lead to people not wanting to use your panel.
Case Study: Chrome Panel
ChromeDataFeed
The Chrome Data Feed I am using for the featured panel can be found here. If you take a look at the ChromePanelDataFeed.cs file, you'll see one class that implements the IDataFeed interface. Here's the implementation of the "RequiredSettings" method
Here, I'm just returning a list with one DataFeedSettingDeclaration which describes the setting that my DataFeed needs in order to run. That setting is the "Chrome Bookmark Location". Chrome saves its bookmarks in json format at a specific file location, and I want to watch that file. I also set a "Default Value" to where it should be. If the Panels Desktop application finds the file at the location, it won't prompt the user for the location. You can also think of this as your DataFeed "requesting" access to this location. If I didn't declare this "File" type setting, and attempted to read from that folder, I'd receive a SecurityException.
And here's the implementation of the Start method
First, I get the bookmark location for the Settings provided to me in the data feed context. These are the setting values that the user provided (or the default settings). Once I have the bookmark location, I'm just watching for any file changes to that file, and on file change, call the SendBookmarks method. The implementation for that is
There's some retry logic here, but basically I'm opening the file, reading the entire contents, and sending the contents to the panel with the
call. I'm also saving the previously sent package so I don't send the same thing twice.
NOTE: Anything sent via the "communicator" must be "Serializable". Unfortunately, things like "dynamic" types aren't, but strings are. Any custom type you mark as [Serializable] will also work.
That's all there is to this data feed. Now, how do we actually use it?
public IEnumerable<DataFeedSettingDeclaration> RequiredSettings()
{
var requiredSettings = new List<DataFeedSettingDeclaration>
{
new DataFeedSettingDeclaration
{
SettingName = "ChromeBookmarkLocation",
SettingType = SettingType.File,
DefaultValue = "%UserProfile%\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Bookmarks",
DisplayRequestForSetting = "Unable to locate Bookmarks File. Please provide path to Chrome Bookmarks file"
}
};
return requiredSettings;
}
Here, I'm just returning a list with one DataFeedSettingDeclaration which describes the setting that my DataFeed needs in order to run. That setting is the "Chrome Bookmark Location". Chrome saves its bookmarks in json format at a specific file location, and I want to watch that file. I also set a "Default Value" to where it should be. If the Panels Desktop application finds the file at the location, it won't prompt the user for the location. You can also think of this as your DataFeed "requesting" access to this location. If I didn't declare this "File" type setting, and attempted to read from that folder, I'd receive a SecurityException.
And here's the implementation of the Start method
public void Start(IDataFeedContext dataFeedContext, IPanelCommunicator panelCommunicator)
{
var bookmarkLocation = dataFeedContext.GetSettings()["ChromeBookmarkLocation"].SettingValue;
bookmarkLocation = Environment.ExpandEnvironmentVariables(bookmarkLocation);
SendBookmarks(bookmarkLocation, panelCommunicators);
while (true)
{
SendBookmarks(bookmarkLocation, panelCommunicators);
Thread.Sleep(1000);
}
}
First, I get the bookmark location for the Settings provided to me in the data feed context. These are the setting values that the user provided (or the default settings). Once I have the bookmark location, I'm just watching for any file changes to that file, and on file change, call the SendBookmarks method. The implementation for that is
private void SendBookmarks(string bookmarkLocation, IPanelCommunicator communicator)
{
var retryCount = 0;
while (retryCount < 5)
{
try
{
using (FileStream stream = File.Open(bookmarkLocation, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (StreamReader reader = new StreamReader(stream))
{
var bookmarksJson = reader.ReadToEnd();
if (bookmarksJson != _alreadySent)
{
communicator.SendMessageToPanel(bookmarksJson);
_alreadySent = bookmarksJson;
}
break;
}
}
}
catch (IOException e)
{
retryCount++;
}
}
}
There's some retry logic here, but basically I'm opening the file, reading the entire contents, and sending the contents to the panel with the
communicator.SendMessageToPanel(bookmarksJson)
call. I'm also saving the previously sent package so I don't send the same thing twice.
NOTE: Anything sent via the "communicator" must be "Serializable". Unfortunately, things like "dynamic" types aren't, but strings are. Any custom type you mark as [Serializable] will also work.
That's all there is to this data feed. Now, how do we actually use it?
Receiving Data Feed Messages in your Panel.
Any data that's sent to your panel via a data feed can be received via JavaScript in your actual panel. The Panels App will check to see if there's a function on the window object named
and call that function. As long as you make sure to add that function with that same name on the root window object, the Panels App will forward all received DataFeed events to that JavaScript function. At that point, it's just a matter of handling the events that come from your data feed.
The only missing piece to understand this is "How do I actually associate a DataFeed with my Panel?". Well, I tried to make that as simple as possible. When you're creating your .panelpkg file, you can optionally include a "data_feeds" folder (at the root level of the zip file), and include the compiled dll in there. Any DLLs in the data_feeds folder will be scanned by the Panels Desktop application, and any class that implements the IDataFeed interface will be associated with your panel.
This is hopefully enough to get you started with working with DataFeeds. If you have any questions, feel free to leave a comment here or shoot me a message at panelsapphelp@gmail.com
Thanks, and happy coding!
PanelsApp.ReceiveData(event)
and call that function. As long as you make sure to add that function with that same name on the root window object, the Panels App will forward all received DataFeed events to that JavaScript function. At that point, it's just a matter of handling the events that come from your data feed.
The only missing piece to understand this is "How do I actually associate a DataFeed with my Panel?". Well, I tried to make that as simple as possible. When you're creating your .panelpkg file, you can optionally include a "data_feeds" folder (at the root level of the zip file), and include the compiled dll in there. Any DLLs in the data_feeds folder will be scanned by the Panels Desktop application, and any class that implements the IDataFeed interface will be associated with your panel.
This is hopefully enough to get you started with working with DataFeeds. If you have any questions, feel free to leave a comment here or shoot me a message at panelsapphelp@gmail.com
Thanks, and happy coding!