Wiki Navigation
- Loading...
How to write a plugin for MediaPortal in Visual Basic (.Net)
For instructions in C#, read How to write a plugin for MediaPortal in C#.
Introduction
MediaPortal is written in C#, and although you can write a plugin with any .NET language this tutorial will cover how to write a plugin in Visual Basic, using one of the following programming environments:
- Visual Studio 2008 (commercial)
- Visual Basic 2008 Express Edition (free, closed source)
- SharpDevelop 3.1 (open source)
Creating the MediaPortal Plugin Skeleton
MediaPortal supports several types of plugins. You can use each of these types to extend MediaPortal in a specific way. The plugin types are:
Plugin types
- Process plugins are plugins without any user interface, and do work in the background. Examples of process plugins:
- Caller id plugin
- LCD/VFD plugin
- Plugins remote controls
- Tag Reader Plugins are used for reading media tags from media files, for example from MP3 and WMA files.
- External Player Plugins are used for playing media using external applications, for example WinAmp, Foobar and iTunes.
- GUI (or Window) Plugins are the most interesting. As the name suggest they contain a user interface and allow the user to interact with them. Examples:
- My Music Plugin
- My TV Plugin
- My Pictures Plugin
In this tutorial we'll focus on how to create your own GUI plugin. Documentation of how to create another type of plugin may follow later.
Creating a new project
We start with creating a new project. Choose File > New Project (File > New > Solution in SharpDevelop) from the menu.
In Visual Studio and SharpDevelop, select the Visual Basic tree-item. (Express Edition users have no choice of language.) Now select the Class Library template.
Fill in a Name, when using Visual Studio specify a location, and press OK (or Create in SharpDevelop).
A new project for our plugin has now been created. If you are using Visual Basic, save your project now using File > Save All. (If you don't, your plugin will be compiled in a temporary location.)
Adding references to the MediaPortal interfaces
Next we'll need to tell it that this is going to be a plugin for MediaPortal. To do this we'll add a reference to the MediaPortal core.dll and dialogs.dll which defines all the interfaces needed. We will also add a reference to the Windows Forms dll.
Choose Project > Add Reference.
On the .NET tabsheet (GAC in SharpDevelop), select System.Windows.Forms. SharpDevelop users now press the Select button to add the reference. Visual Studio and Visual C# users press OK, and have to re-open the Add Reference dialog to add the next reference.
Now go to the Browse tabsheet (.NET Assembly Browser in SharpDevelop), and select the core.dll file which you can find in your MediaPortal folder.
Now go to the Browse tabsheet (.NET Assembly Browser in SharpDevelop), and select the dialogs.dll file which you can find in your MediaPortal\plugins\windows folder.
Next in the source code add
- Imports System
Imports System.Windows.Forms
Imports MediaPortal.GUI.Library
Imports MediaPortal.Dialogs
The code should now look more or less like this:
Imports System Imports System.Windows.Forms Imports MediaPortal.GUI.Library Imports MediaPortal.Dialogs Namespace OurPlugin Public Class Class1 End Class End Namespace
Implementing GUIWindow and ISetupForm
To make our plugin a GUI plugin for MediaPortal, we need to derive it from GUIWindow and implement the ISetupForm interface. The GUIWindow class in MediaPortal is the basic class for a screen. It knows how to load/render a skin file and handle user actions. By implementing the ISetupForm interface MediaPortal will recognize this class as a new plugin. Using this interface we can tell MP about the plugin name, type etc. Both will be discussed in more detail later.
Each window / screen has it's own unique id. This example uses 5678, use another code for your plugins. The same id should be in the skin file.
Change the code so it looks like this:
Imports System Imports System.Windows.Forms Imports MediaPortal.GUI.Library Imports MediaPortal.Dialogs Namespace OurPlugin Public Class Class1 Inherits GUIWindow Implements ISetupForm Public Sub New() End Sub ' With GetID it will be an window-plugin / otherwise a process-plugin ' Enter the id number here again Public Overrides Property GetID As Integer Get Return 5678 End Get Set End Set End Property ' Returns the name of the plugin which is shown in the plugin menu Public Function PluginName() As String Return "MyFirstPlugin" End Function ' Returns the description of the plugin is shown in the plugin menu Public Function Description() As String Return "My First Plugin" End Function ' Returns the author of the plugin which is shown in the plugin menu Public Function Author() As String Return "YourNameHere" End Function ' show the setup dialog Public Sub ShowPlugin() MessageBox.Show("Nothing to configure, this is just an example") End Sub ' Indicates whether plugin can be enabled/disabled Public Function CanEnable() As Boolean Return true End Function ' Get Windows-ID Public Function GetWindowId() As Integer ' WindowID of windowplugin belonging to this setup ' enter your own unique code Return 5678 End Function ' Indicates if plugin is enabled by default; Public Function DefaultEnabled() As Boolean Return true End Function ' indicates if a plugin has it's own setup screen Public Function HasSetup() As Boolean Return true End Function ''' <summary> ''' If the plugin should have it's own button on the main menu of MediaPortal then it ''' should return true to this method, otherwise if it should not be on home ''' it should return false ''' </summary> ''' <param name="strButtonText">text the button should have</param> ''' <param name="strButtonImage">image for the button, or empty for default</param> ''' <param name="strButtonImageFocus">image for the button, or empty for default</param> ''' <param name="strPictureImage">subpicture for the button or empty for none</param> ''' <returns>true : plugin needs it's own button on home ''' false : plugin does not need it's own button on home</returns> Public Function GetHome(ByRef strButtonText As String, _ ByRef strButtonImage As String, _ ByRef strButtonImageFocus As String, _ ByRef strPictureImage As String) As Boolean strButtonText = String.Empty strButtonImage = String.Empty strButtonImageFocus = String.Empty strPictureImage = String.Empty Return false End Function End Class End Namespace
Compiling the plugin
Time to see some action. Compile the plugin by choose Build > Build solution from the menu. Again you should see that compilation was successful with 0 errors.
Now we're going to test the plugin in MediaPortal!
Testing the plugin
MediaPortal will look for plugins in it's
plugin
subfolder. To get it to notice our new plugin, we have to copy it first. Copy the plugin from either
bin\release\OurPlugin.dll
or
bin\debug\OurPlugin.dll
underneath your project folder (this depends on your project settings), to
plugins\windows\OurPlugin.dll
underneath your MediaPortal application folder.
Now, start MediaPortal Setup and go to the plugins section. Guess what? Our plugin shows up!
Notice it's showing the plugin name, author, description as we told it and that we can enable/disable our plugin. Now let's try the setup. Select our plugin and hit the setup button and we see:
Well the basics are working. We got our plugin registered in MP, we can assign it a name, author, description and if needed we can add any setup screens for the plugin.
In the next chapter, we're going to add some GUI to the plugin so it actually becomes usefull.
Adding GUI to our plugin
To add a nice GUI to our plugin we'll need to do things:
- Create a skin file.
- Add some code to load the skin file.
Creating a skin file
All GUI in MediaPortal is defined in skin files. A skin file is a simple XML file which describes wha the GUI should look like. Although the details are very different, the idea is the same as for making a webpage in HTML. For more details see SkinArchitecture.
For creating a skin file we can use a text-editor like Notepad, or our programming environment (Visual Studio/SharpDevelop). A skin editor is in progress and you can use a preliminary version, but for now we'll do it by hand. Create a new file and enter the following code:
<window> <id>5678</id> <defaultcontrol>2</defaultcontrol> <allowoverlay>yes</allowoverlay> <controls> <control> <description>BackGround</description> <type>image</type> <id>1</id> <posX>0</posX> <posY>0</posY> <width>720</width> <height>576</height> <texture>background.png</texture> </control> <control> <description>an Image</description> <type>image</type> <id>1</id> <posX>75</posX> <posY>370</posY> <texture>hover_my videos.png</texture> </control> <control> <description>text label</description> <type>label</type> <id>1</id> <posX>250</posX> <posY>70</posY> <label>Some text</label> <font>font16</font> <align>right</align> <textcolor>ffffffff</textcolor> </control> <control> <description>Try Me</description> <type>button</type> <id>2</id> <posX>60</posX> <posY>97</posY> <label>Try Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>3</ondown> </control> <control> <description>Or Me</description> <type>button</type> <id>3</id> <posX>60</posX> <posY>131</posY> <label>Or Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>2</ondown> </control> </controls> </window>
Save the file in skin\Blue3\OurPlugin.xml underneath your MediaPortal application folder. Before discussing the xml, here's a picture of what this skin file produces:
The Skin file
Each skin file starts with the following section:
<window> <id>5678</id> <defaultcontrol>2</defaultcontrol> <allowoverlay>yes</allowoverlay> <controls>
Here you see again the id value we used earlier.
The <defaultcontrol> specifies which control will have the focus when the user enters the screen. As you can see in the screenshot our skin file has several controls:
- a blue background
- a label with the text ‘Some text'
- 2 buttons
- 1 picture showing a person with popcorn
Each control is specified in the xml file and each control has an <id>. In this case the default control is the control which has id 2, which is the 1st button
The <allowoverlay> specifies if MediaPortal is allowed to draw video/TV/music preview screens in the bottom right hand corner. By setting it to false you make sure there will be no preview windows drawn. When it's set to true, MP will show the preview window when it's playing a media file.
All the GUI controls are put between the <controls> and </controls> tags.
Image control
<control> <description>BackGround</description> <type>image</type> <id>1</id> <posX>0</posX> <posY>0</posY> <width>720</width> <height>576</height> <texture>background.png</texture> </control>
This section specifies the background control. The <description> tag is optional and only put here for clarity. Each control has a <type> tag which specifies the type of control. You can check references.xml for all types of controls. Since this is a background image we set it to type image.
Next is the <id> which we already mentioned before. I set it to 1, but you can use any (positive) number you want. The id will couple the skin file to the code, so if we later on want to check that a user pressed a button, the id will be needed. Therefore it's always a wise decision to make the control id's unique within a skin file
<posX> and <posY>: These give the x,y position of the upper left point of the image.
<width> and <height>: Give the width/height of the image.
<texture> specifies which image file to use. You can use .png, .jpg and .gif.
The image file should be in the skin's media folder. So if you for example are using the Blue3 skin then this image file should be stored in skin\Blue3\media underneath your MediaPortal application folder.
The following control specifies the image of our popcorn man:
<control> <description>an Image</description> <type>image</type> <id>1</id> <posX>75</posX> <posY>370</posY> <texture>hover_my videos.png</texture> </control>
It's basically the same as the background control. Notice we didn't fill in the <width> and <height> tags. If you don't supply this then MediaPortal simply will use the dimensions of the texture file itself.
Text Label Control
<control> <description>text label</description> <type>label</type> <id>1</id> <posX>250</posX> <posY>70</posY> <label>Some text</label> <font>font16</font> <align>right</align> <textcolor>ffffffff</textcolor> </control>
The text label control can be used to draw text on-screen. The type is label and you'll see the <posX> and <posY> tags again for specifying the position.
The <label> tag should contain the text to be presented. This can be normal text as shown above or a string id from the MediaPortal\Language(language)\strings.xml file.
<align> specifies how the text is aligned, can be left, centered or right.
<textcolor> specifies the color in AARRGGBB notation (hex).
Button Control
<control> <description>Try Me</description> <type>button</type> <id>2</id> <posX>60</posX> <posY>97</posY> <label>Try Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>3</ondown> </control> <control> <description>Or Me</description> <type>button</type> <id>3</id> <posX>60</posX> <posY>131</posY> <label>Or Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>2</ondown> </control>
These 2 controls represent the 2 buttons. The type here is button and you'll see the <posX>, <posY>, <label> tags again which behave the same as explained above.
Next are the <onleft>, <onright>, <onup> and <ondown> tags. These tags specify what happens when the user hits one of the cursor keys. For example if the user is on the top button and you press down, you would like to have the focus moved to the lower button. You can do this by editing the <ondown> tag of the 1st button. In there you place the <id> of the control which should get the focus. The same holds for the bottom button. If you press up, the focus should go to the upper button. Therefore we set the <onup> tag of the bottom button to the <id> of the top button.
For more detailed information on creating skin files, read the Skin documentation.
Modifying the code to use the skin file
Now that we have a skin file, we must load it from our plugin. This can be done by editing the source code and adding the following method:
Public Overloads Overrides Function Init() As Boolean Return Load(GUIGraphicsContext.Skin & "\ourplugin.xml") End Function
The Init() function will be called by MediaPortal when it wants to load your plugin. In there we simply ask it to load our xml file.
We also would like to have our plugin show up in the main menu of MediaPortal. For this we change the GetHome() method so it becomes:
Public Function GetHome(ByRef strButtonText As String, ByRef strButtonImage As String, _ ByRef strButtonImageFocus As String, ByRef strPictureImage As String) _ As Boolean Implements ISetupForm.GetHome strButtonText = PluginName() strButtonImage = [String].Empty strButtonImageFocus = [String].Empty strPictureImage = [String].Empty Return True End Function
Now recompile the plugin and copy it again to plugins\windows. Then go back to MediaPortal configuration and change the plugin display to Home menu but changing the option to 'Listed in Home'.
Start MediaPortal and voilà, our plugin shows in the Home menu:
Select the plugin and hit Enter. Your plugin appears!
Notice you can switch between the two buttons and that you can get back to the home screen by pressing Escape. Pretty neat huh? The buttons themselves don't do anything yet when pressed, but that's our next task.
Note: The graphic that appears when the menu entry for your plugin is highlighted, is hover_pluginname.png. This is independent from what is defined in the skin xml file.
Catching user actions
So we managed to get our plugin to show a screen, but it's still pretty useless. When you press a button nothing happens. This chapter will describe how you catch user actions like keypresses and some basic things of the GUIWindow class.
The GUIWindow class has several virtual methods which might be useful.
Protected Overrides Sub OnPageLoad() This method gets called just before your plugin is shown. You can use it to setup the screen like setting checkboxes, filling lists, etc, etc.
Protected Overrides Sub OnPageDestroy(ByVal NewWindowId As Integer) This method gets called when the user switches to another screen (it happens). You can use it to clean up, store any settings, etc. The newWindowId will contain the id of the screen the user is switching to.
Protected Overrides Sub OnShowContextMenu() This method gets called if the user presses the show context menu key/button. Normally this is F9 on the keyboard. You can use it to show context menu's you see all over MediaPortal.
Public Overrides Sub Process() When the user is interacting with your screen, this method will be called on a regular basis. You can use it to do some processing like refreshing (parts) of the screen.
Protected Overloads Overrides Sub OnClicked(ByVal controlId As Integer, ByVal control As GUIControl, ByVal actionType As MediaPortal.GUI.Library.Action.ActionType) This method gets called when the user presses a button or selects something from a list. controlId will contain the id of the control which is pressed. control will contain the GUIControl object. actionType will contain which action was done (like select, queue, delete, etc.).
Now this sounds like what we want next for our plugin, let's add some code when the buttons get clicked! First let's add 2 new member variables to the class which represent these buttons. This can be done with the following lines:
- <SkinControlAttribute(2)> Protected buttonOne As GUIButtonControl = Nothing
<SkinControlAttribute(3)> Protected buttonTwo As GUIButtonControl = Nothing
These 2 lines declare the two buttons. A button in the skin file is represented by the GUIButtonControl class. The attributes are needed to tell MediaPortal which button of the skin file the variable should represent. Remember the id tags of the two buttons. The upper one had <id>2</id> and the lower one had <id>3</id>. The ids are given as parameter to the attribute so the mapping is done automatically.
Note you can use this for any control, for example a label control could be mapped by:
- <SkinControlAttribute(1)> Protected lblText As GUILabelControl = Nothing
Next, we override the OnClicked() method:
Protected Overrides Sub OnClicked(ByVal controlId As Integer, _ ByVal control As GUIControl, _ ByVal actionType As Action.ActionType) If control Is buttonOne Then OnButtonOne End If If control Is buttonTwo Then OnButtonTwo End If MyBase.OnClicked(controlId, control, actionType) End Sub Private Sub OnButtonOne() End Sub Private Sub OnButtonTwo() End Sub
Now when you press button 1, the OnButtonOne() gets called. If you press button 2, the OnButtonTwo() gets called. To make sure this works, we are going to present a simple dialog.
Next change the OnButtonOne() and OnButtonTwo() into:
Private Sub OnButtonOne() Dim dlg As GUIDialogOK = CType(GUIWindowManager.GetWindow(CType(GUIWindow.Window.WINDOW_DIALOG_OK,Integer)),GUIDialogOK) dlg.SetHeading("Button has been pressed") dlg.SetLine(1, "You pressed button 1") dlg.SetLine(2, String.Empty) dlg.SetLine(3, String.Empty) dlg.DoModal(GUIWindowManager.ActiveWindow) End Sub Private Sub OnButtonTwo() Dim dlg As GUIDialogOK = CType(GUIWindowManager.GetWindow(CType(GUIWindow.Window.WINDOW_DIALOG_OK,Integer)),GUIDialogOK) dlg.SetHeading("Button has been pressed") dlg.SetLine(1, "You pressed button 2") dlg.SetLine(2, String.Empty) dlg.SetLine(3, String.Empty) dlg.DoModal(GUIWindowManager.ActiveWindow) End Sub
Now recompile your plugin (don't forget to copy it) and try again. Now when you press the first button you should see:
And when pressing the 2nd button:
For information about other dialogs see List of Dialogs.
Subversion
We need to tell developers a bit about using Subversion for their development, and the possibility to use plugins SVN at SourceForge. See also the forum thread.
Subversion is a version control system or concurrent versions system (CVS) that keeps programming source code, including all previous versions, in a central database. The most popular of these databases is offered free of charge by SourceForge. This is also where the MediaPortal code is kept, and where a repository for MediaPortal plugins has been created.
Why should you use version control, what are the advantages?
- multiple users
- fix regression bugs
- fix bugs in old versions
- continue development after abandoning
Quick overview:
- Get a SourceForge account.
- Request access by sending a message to hwahrmann, rtv or James.
- Install TortoiseSVN.
- Create a new subfolder, containing only the files that need to be send to SourceForge.
- On the context menu of the subfolder itself, select TortoiseSVN > Import.
- https://mp-plugins.svn.sourceforge.net/svnroot/mp-plugins/trunk/plugins/<pluginname>.
- Go to your project folder, and delete (or move to another folder) the files you just sent to SourceForge
- On the context menu of the project folder, select SVN Checkout
- Enter the same URL as used before (including pluginname subfolder)
- Regularly, for example at the end of every day, perform an SVN Commit on the folder to send your changes to the repository.
Distribution
Congratulations! You have now created your first plugin. Of course, if this were a "real" plugin, you would want to tell the world about it and make it available for everybody to use.
- Package your plugin, including source code, in a ZIP or RAR file.
- Go to the Plugins Repository, select the right folder for your plugin, and use the Submit File link to upload your plugin.
- Write documentation for your plugin on the community plugins page.
- Announce your plugin on the Plugins Development forum section.
Appendix A: complete source code of the plugin
Imports System Imports System.Windows.Forms Imports MediaPortal.GUI.Library Imports MediaPortal.Dialogs Namespace OurPlugin Public Class Class1 Inherits GUIWindow Implements ISetupForm <SkinControlAttribute(2)> _ Protected buttonOne As GUIButtonControl = Nothing <SkinControlAttribute(3)> _ Protected buttonTwo As GUIButtonControl = Nothing Public Sub New() MyBase.New End Sub ' With GetID it will be an window-plugin / otherwise a process-plugin ' Enter the id number here again Public Overrides Property GetID As Integer Get Return 5678 End Get Set End Set End Property ' Returns the name of the plugin which is shown in the plugin menu Public Function PluginName() As String Return "MyFirstPlugin" End Function ' Returns the description of the plugin is shown in the plugin menu Public Function Description() As String Return "My First plugin tutorial" End Function ' Returns the author of the plugin which is shown in the plugin menu Public Function Author() As String Return "Frodo" End Function ' show the setup dialog Public Sub ShowPlugin() MessageBox.Show("Nothing to configure, this is just an example") End Sub ' Indicates whether plugin can be enabled/disabled Public Function CanEnable() As Boolean Return true End Function ' Get Windows-ID Public Function GetWindowId() As Integer ' WindowID of windowplugin belonging to this setup ' enter your own unique code Return 5678 End Function ' Indicates if plugin is enabled by default; Public Function DefaultEnabled() As Boolean Return true End Function ' indicates if a plugin has it's own setup screen Public Function HasSetup() As Boolean Return true End Function ''' <summary> ''' If the plugin should have it's own button on the main menu of Mediaportal then it ''' should return true to this method, otherwise if it should not be on home ''' it should return false ''' </summary> ''' <param name="strButtonText">text the button should have</param> ''' <param name="strButtonImage">image for the button, or empty for default</param> ''' <param name="strButtonImageFocus">image for the button, or empty for default</param> ''' <param name="strPictureImage">subpicture for the button or empty for none</param> ''' <returns>true : plugin needs it's own button on home ''' false : plugin does not need it's own button on home</returns> Public Function GetHome(ByRef strButtonText As String, _ ByRef strButtonImage As String, _ ByRef strButtonImageFocus As String, _ ByRef strPictureImage As String) As Boolean strButtonText = PluginName strButtonImage = String.Empty strButtonImageFocus = String.Empty strPictureImage = String.Empty Return true End Function Public Overrides Function Init() As Boolean Return Load((GUIGraphicsContext.Skin + "\ourplugin.xml")) End Function Protected Overrides Sub OnClicked(ByVal controlId As Integer, _ ByVal control As GUIControl, _ ByVal actionType As _ MediaPortal.GUI.Library.Action.ActionType) If control Is buttonOne Then OnButtonOne End If If control Is buttonTwo Then OnButtonTwo End If MyBase.OnClicked(controlId, control, actionType) End Sub Private Sub OnButtonOne() Dim dlg As GUIDialogOK = CType(GUIWindowManager.GetWindow(CType(GUIWindow.Window.WINDOW_DIALOG_OK,Integer)),GUIDialogOK) dlg.SetHeading("Button has been pressed") dlg.SetLine(1, "You pressed button 1") dlg.SetLine(2, String.Empty) dlg.SetLine(3, String.Empty) dlg.DoModal(GUIWindowManager.ActiveWindow) End Sub Private Sub OnButtonTwo() Dim dlg As GUIDialogOK = CType(GUIWindowManager.GetWindow(CType(GUIWindow.Window.WINDOW_DIALOG_OK,Integer)),GUIDialogOK) dlg.SetHeading("Button has been pressed") dlg.SetLine(1, "You pressed button 2") dlg.SetLine(2, String.Empty) dlg.SetLine(3, String.Empty) dlg.DoModal(GUIWindowManager.ActiveWindow) End Sub End Class End Namespace
Appendix B: skin file for plugin
<window> <id>5678</id> <defaultcontrol>2</defaultcontrol> <allowoverlay>yes</allowoverlay> <controls> <control> <description>BackGround</description> <type>image</type> <id>1</id> <posX>0</posX> <posY>0</posY> <width>720</width> <height>576</height> <texture>background.png</texture> </control> <control> <description>an Image</description> <type>image</type> <id>1</id> <posX>75</posX> <posY>370</posY> <texture>hover_my videos.png</texture> </control> <control> <description>text label</description> <type>label</type> <id>1</id> <posX>250</posX> <posY>70</posY> <label>Some text</label> <font>font16</font> <align>right</align> <textcolor>ffffffff</textcolor> </control> <control> <description>Try Me</description> <type>button</type> <id>2</id> <posX>60</posX> <posY>97</posY> <label>Try Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>3</ondown> </control> <control> <description>Or Me</description> <type>button</type> <id>3</id> <posX>60</posX> <posY>131</posY> <label>Or Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>2</ondown> </control> </controls> </window>
Advanced Plugin Development
Reference
Tips and Tricks
This page has no comments.