As you may have heard, SnagL makes extensive use of Managed Extensibility Framework (MEF) to enable additional functionality to be added through external extensions. Following is a brief example of building an IToolPanelItemViewModelExtension extension. This sample will make use of MVVMLight to implement the MVVM concepts.

Start by opening Visual Studio 2010 and starting a new project. Select the MvvmLight (SL4) project from the new project dialog. The name of the project doesn't matter, but we'll be using SnagLPluginDemo for this demo.


As we're building a plugin for SnagL, we won't be needing the App.xaml or MainPage.xaml (or their code behinds) files. Remove them from the Solution Explorer.

Add a reference to the Berico.SnagL.dll and System.ComponentModel.Composition.dll assemblies.


At this point, we can add our Silverlight User Control that will represent the viewable portion of our extension. To do this, right-click on the project and select Add -> New Item, then select the MvvmView (SL) template. Again, the actual name doesn't mater, but we'll be using EventListingToolPanelItemExtensionView.xaml for this demo.


The size of the user control is important because it has to fit appropriately in the tool panel. Do this by setting the d:DesignHeight and d:DesignWidth attributes to 50 and 200, respectively. Additionally, we won't be making use of the ViewModelLocator (part of the MVVMLight framework), so remove the DataContext attribute from the UserControl element. Complete your XAML updates by making it look like the following:

<UserControl x:Class="SnagLExtenstionTutorial.EventListingToolPanelItemExtensionView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="50" d:DesignWidth="200">

    <ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Auto">
        <Grid x:Name="LayoutRoot">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <ListBox ItemsSource="{Binding Events}">
            </ListBox>
        </Grid>
    </ScrollViewer>
</UserControl>


In order for MEF to know about our extension, we need to decorate our classes with the appropriate attributes. Start by adding the Export attribute to the code-behind for your user control, specifying IToolPanelItemViewExtension as the type. This indicates to MEF we will be exporting the class as a tool panel extension that can be consumed by SnagL. Also, ensure your code-behind implements the IToolPanelItemViewExtension interface. At this point, your code-behind should look like the following:

public partial class EventListingToolPanelItemExtensionView : UserControl, IToolPanelItemViewExtension
{
    public EventListingToolPanelItemExtensionView()
    {
        InitializeComponent();
    }
 
    public IToolPanelItemViewModelExtension ViewModel
    {
        get
        {
            throw new System.NotImplementedException();
        }
        set
        {
            throw new System.NotImplementedException();
        }
    }
}

We need to tell MEF where our ViewModel will be coming from, but we haven't yet generated one for our view. To do this, right-click the ViewModel folder and select Add -> New Item. When the Add New Item dialog appears, select the MvvmViewModel (SL) template. By convention, ViewModels are named with the same name as the view, but with ViewModel appended to it.


Switch back to the code-behind for the view so we can tell MEF where the ViewModel will come from. We do this by using the Import MEF attribute. Make the code-behind for your user control look like the following:

using System.ComponentModel.Composition;
using System.Windows.Controls;
using Berico.SnagL.Infrastructure.Modularity.Contracts;
using SnagLExtenstionTutorial.ViewModel;
 
namespace SnagLExtenstionTutorial
{
    [Export(typeof(IToolPanelItemViewExtension))]
    public partial class EventListingToolPanelItemExtensionView : UserControl, IToolPanelItemViewExtension
    {
        public EventListingToolPanelItemExtensionView()
        {
            InitializeComponent();
        }
 
        [Import(typeof(EventListingToolPanelItemExtensionViewModel))]
        public IToolPanelItemViewModelExtension ViewModel
        {
            get
            {
                return this.DataContext as EventListingToolPanelItemExtensionViewModel;
            }
            set
            {
                this.DataContext = value;
            }
        }
    }
}


At this point, we can implement our ViewModel. The same ideas apply. Ensure your ViewModel class implements the IToolPanelItemViewModelExtension interface and indicate to MEF which classes are being exported.

Add an ObservableCollection<string> property named Events to the class. You may have noticed the XAML above referenced this collection. This is where we will store the list of events exposed by SnagL. Your property should look like the following:

public ObservableCollection<string> Events
{
    get;
    private set;
}


Now, add the following code to the constructor:

// Refer to the documentation on IToolPanelItemViewModelExtension for an explanation of the properties below
this.Index = 2;
this.Description = "Example ToolPanel extension";
this.ToolName = "Event Lister";


The remaining properties are pretty simple to implement. If their purpose is unclear, please refer to the documentation on the IToolPanelItemViewModelExtension interface. When completely implemented, your ViewModel should resemble the following:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using Berico.SnagL.Infrastructure.Modularity.Contracts;
using GalaSoft.MvvmLight;
 
namespace SnagLPluginDemo.ViewModel
{
    [PartMetadata("ID", "ToolPanelItemViewModelExtension"), Export(typeof(EventListingToolPanelItemExtensionViewModel))]
    public class EventListingToolPanelItemExtensionViewModel : ViewModelBase, IToolPanelItemViewModelExtension
    {
        private bool _isEnabled = true;
        private string _description;
        private string _toolName;
        private ObservableCollection<string> _events = new ObservableCollection<string>();
 
        public EventListingToolPanelItemExtensionViewModel()
        {
            this.Index = 2;
            this.Description = "Example ToolPanel extension";
            this.ToolName = "Event Lister";
        }
 
        public string Description
        {
            get
            {
                return _description;
            }
            set
            {
                _description = value;
                RaisePropertyChanged("Description");
            }
        }
 
        public ObservableCollection<string> Events
        {
            get { return _events; }
            private set { _events = value; }
        }
 
        public int Index
        {
            get;
            set;
        }
 
        public bool IsEnabled
        {
            get
            {
                return _isEnabled;
            }
            set
            {
                _isEnabled = value;
                RaisePropertyChanged("IsEnabled");
            }
        }
 
        public string ToolName
        {
            get
            {
                return _toolName;
            }
            set
            {
                _toolName = value;
                RaisePropertyChanged("ToolName");
            }
        }
    }
}


At this point, you should be able to compile your project and add a reference to the XAP file (output file generated by the compiler for Silverlight) to the SnagL initialization parameters. To do this, simply add an extensionsPath attribute to the initParams items in the HTML markup for your SnagL host, e.g., extensionsPath=SnagLPluginDemo.xap. Multiple extensions can be specified and should be separated by a semicolon.

After completing the steps above, you should see your extension loaded on the tool panel.

You're probably wondering how you can subscribe to the events mentioned above. Thank fully, this is quite simple and made easier through the use of an event aggregator.

In order to subscribe to the events, you must first add a reference to the Microsoft.Practices.Prism DLL and add a using statement for Berico.SnagL.Infrastructure.Events to your ViewModel. After doing so, simply add the following lines to the bottom of your ViewModle's constructor:

// Subscribe to some events
SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseLeftButtonUpEvent>().Subscribe(OnNodeMouseLeftButtonUp, false);
SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseLeftButtonDownEvent>().Subscribe(OnNodeMouseLeftButtonDown, false);
SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseEnterEvent>().Subscribe(OnNodeMouseEnter, false);
SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseLeaveEvent>().Subscribe(OnNodeMouseLeave, false);
SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseMoveEvent>().Subscribe(OnNodeMouseMove, false);
SnaglEventAggregator.DefaultInstance.GetEvent<Berico.SnagL.UI.TimeConsumingTaskCompletedEvent>().Subscribe(OnTimeConsumingTaskCompleted, false);
SnaglEventAggregator.DefaultInstance.GetEvent<Berico.SnagL.UI.TimeConsumingTaskExecutingEvent>().Subscribe(OnTimeConsumingTaskExecuting, false);
SnaglEventAggregator.DefaultInstance.GetEvent<Berico.SnagL.Infrastructure.Layouts.LayoutExecutedEvent>().Subscribe(OnLayoutExecuted, false);
SnaglEventAggregator.DefaultInstance.GetEvent<Berico.SnagL.Infrastructure.Layouts.LayoutExecutingEvent>().Subscribe(OnLayoutExecuting, false);


Then, add the following two methods to your ViewModel class:

public void OnNodeMouseLeftButtonUp(NodeViewModelMouseEventArgs<MouseButtonEventArgs> eventArgs)
{
    Events.Add(string.Format("NodeMouseLeftButtonUp - {0}", eventArgs.NodeViewModel.ParentNode.ID));
}
 
public void OnNodeMouseLeftButtonDown(NodeViewModelMouseEventArgs<MouseButtonEventArgs> eventArgs)
{
    Events.Add(string.Format("NodeMouseLeftButtonDown - {0}", eventArgs.NodeViewModel.ParentNode.ID));
}
 
public void OnNodeMouseEnter(NodeViewModelMouseEventArgs<MouseEventArgs> eventArgs)
{
    Events.Add(string.Format("NodeMouseEnter - {0}", eventArgs.NodeViewModel.ParentNode.ID));
}
 
public void OnNodeMouseLeave(NodeViewModelMouseEventArgs<MouseEventArgs> eventArgs)
{
    Events.Add(string.Format("NodeMouseLeave - {0}", eventArgs.NodeViewModel.ParentNode.ID));
}
 
public void OnNodeMouseMove(NodeViewModelMouseEventArgs<MouseEventArgs> eventArgs)
{
    Events.Add(string.Format("NodeMoved - {0}", eventArgs.NodeViewModel.ParentNode.ID));
}
 
public void OnTimeConsumingTaskCompleted(Berico.SnagL.UI.TimeConsumingTaskEventArgs eventArgs)
{
    Events.Add("Time Consuming task completed");
}
 
public void OnTimeConsumingTaskExecuting(Berico.SnagL.UI.TimeConsumingTaskEventArgs eventArgs)
{
    Events.Add("Time Consuming task started");
}
 
public void OnLayoutExecuted(Berico.SnagL.Infrastructure.Layouts.LayoutEventArgs eventArgs)
{
    Events.Add(string.Format("Layout completed - {0}", eventArgs.LayoutType.ToString()));
}
 
public void OnLayoutExecuting(Berico.SnagL.Infrastructure.Layouts.LayoutEventArgs eventArgs)
{
    Events.Add(string.Format("Layout started - {0}", eventArgs.LayoutType.ToString()));
}


To unsubscribe from the events, you can add the following two lines to your Cleanup() method as so (the Cleanup method probably doesn't already exist, so just copy and paste the whole section):

public override void Cleanup()
{
    SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseLeftButtonUpEvent>().Unsubscribe(OnNodeMouseLeftButtonUp);
    SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseLeftButtonDownEvent>().Unsubscribe(OnNodeMouseLeftButtonDown);
    SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseEnterEvent>().Unsubscribe(OnNodeMouseEnter);
    SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseLeaveEvent>().Unsubscribe(OnNodeMouseLeave);
    SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseMoveEvent>().Unsubscribe(OnNodeMouseMove);
    SnaglEventAggregator.DefaultInstance.GetEvent<Berico.SnagL.UI.TimeConsumingTaskCompletedEvent>().Unsubscribe(OnTimeConsumingTaskCompleted);
    SnaglEventAggregator.DefaultInstance.GetEvent<Berico.SnagL.UI.TimeConsumingTaskExecutingEvent>().Unsubscribe(OnTimeConsumingTaskExecuting);
    SnaglEventAggregator.DefaultInstance.GetEvent<Berico.SnagL.Infrastructure.Layouts.LayoutExecutedEvent>().Unsubscribe(OnLayoutExecuted);
    SnaglEventAggregator.DefaultInstance.GetEvent<Berico.SnagL.Infrastructure.Layouts.LayoutExecutingEvent>().Unsubscribe(OnLayoutExecuting);
 
    base.Cleanup();
}


After making the updates noted above, when you run SnagL, you should see an alert box when you click any of the nodes on your graph or if you hover your mouse over a node and move away from it.

Last edited Jan 15, 2013 at 12:55 PM by senfo, version 3

Comments

No comments yet.