Walkthrough: Create an Acuit Pinpoint Workstation Plug-in
This walkthrough demonstrates how to use Visual Studio 2019 to create a simple workstation plug-in that:
- Adds a custom command to the main menu.
- Manages a custom Pinpoint test type, providing the UI allowing the operator to manually indicate a test result via PASS/FAIL buttons.
Note
While this walkthrough demonstrates a way to save test results for a unit using a plug-in, normally it is recommended to use test workflows to create custom tests for Acuit Pinpoint.
The custom UI will have be added to the bottom of the screen when a unit has been scanned:
Example Source Code
The final source code from this example can be downloaded here: MyTestPlugIn.zip
Prerequisites
- Visual Studio 2019
- Acuit Pinpoint Workstation
Create Project
- From the Visual Studio startup prompt, choose Create a new project, or from the File menu, select New > Project.
- Select WPF User Control Library (.NET Framework) from the list of project templates and click Next.
- Name the project MyTestPlugIn.
- Select .NET Framework 4.7.2 for the framework.
In the example above, "D:\Dev" was used for the project location, but this should be whatever location you want to use for your projects.
Add Package Reference
- In Solution Explorer, right-click the MyTestPlugIn project and select Manage NuGet Packages.
- Make sure the nuget.org package source is selected, select Browse, and search for Acuit.Pinpoint.Workstation.Interfaces.
- Select the package and click Install.
- If you get a Choose NuGet Package Manager Format prompt, either selection can be used.
- Respond to any Preview Changes or License Acceptance messages that appear.
- The Acuit.Pinpoint.Workstation.Interfaces package, along with a number of other package dependencies, will be added to your project.
- Build your project (F6) to ensure that all of the packages are downloaded.
Create Plug-in Module Class
- In Solution Explorer, right-click the MyTestPlugIn project and select Add > Class.
- Enter PlugInModule.cs for the name and click Add.
- Update the PlugInModule class source to be as follows to make it a Prism module:
using Microsoft.Practices.Prism.MefExtensions.Modularity;
using Microsoft.Practices.Prism.Modularity;
namespace MyTestPlugIn
{
[ModuleExport("MyTestPlugIn", typeof(PlugInModule))]
public class PlugInModule : IModule
{
public void Initialize()
{
}
}
}
Set Up Debugging with Acuit Pinpoint Workstation
At this point, the plug-in does not do anything, but it can be loaded by Acuit Pinpoint Workstation. We can now test this and prepare for debugging our plug-in.
- Ensure you have the Solution Configuration set to "Debug", and then build your project (F6).
- Create a file named settings.json in the "C:\ProgramData\Acuit Pinpoint\Workstation" folder
with the following text content, with the "D:\\Dev" portion of the path updated to reflect the
actual location of your project:
(Note that backslashes must be escaped in JSON files as double-backslashes.){ "Workstation": { "PlugIns": [ "D:\\Dev\\MyTestPlugIn\\MyTestPlugIn\\bin\\Debug\\MyTestPlugIn.dll" ] } }
- In Solution Explorer, right-click the MyTestPlugIn project and select Properties.
- Select the Debug page.
- For Start action, select Start external program and specify the full path to the Acuit Pinpoint Workstation executable (e.g., "C:\Program Files (x86)\Acuit Pinpoint Workstation 7\Workstation.exe").
- In the PlugInModule class, set a debug breakpoint on the Initialize method.
- Start debugging (F5).
- Acuit Pinpoint Workstation should start and your breakpoint should be triggered.
- Continue debugging (F5).
- You can also see that your plug-in was loaded by viewing About Acuit Pinpoint Workstation
(from the Help menu, select About):
Add Custom Menu Command
We will add a custom command to the Acuit Pinpoint Workstation menu that will display a message box. To do this, we need to use some services: one that allows us to register custom menu items, and another that allows us to perform certain user interface actions like displaying message boxes. These services are imported via dependency injection into our plug-in module via a constructor.
- Update the PlugInModule class source to be as follows:
using Acuit.Pinpoint.Windows;
using Acuit.Pinpoint.Workstation;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.MefExtensions.Modularity;
using Microsoft.Practices.Prism.Modularity;
using System.ComponentModel.Composition;
using System.Windows;
namespace MyTestPlugIn
{
[ModuleExport("MyTestPlugIn", typeof(PlugInModule))]
public class PlugInModule : IModule
{
private readonly IMenuRegistry _MenuRegistry;
private readonly IUserInterfaceService _UserInterfaceService;
[ImportingConstructor]
public PlugInModule(IMenuRegistry menuRegistry, IUserInterfaceService userInterfaceService)
{
_MenuRegistry = menuRegistry;
_UserInterfaceService = userInterfaceService;
}
public void Initialize()
{
_MenuRegistry.AddMenuItem(MenuNames.Tools, new MenuItemViewModel { IsSeparator = true });
_MenuRegistry.AddMenuItem(MenuNames.Tools, new MenuItemViewModel
{
Header = "Test Plug-in Custom Command",
Command = new DelegateCommand(() => _UserInterfaceService.ShowMessageBox(MessageBoxImage.Information, "Hello from the test plug-in!"))
});
}
}
}
- Start debugging (F5).
- From the Acuit Pinpoint Workstation Tools menu, you can see the new item that you added:
- When you select the new menu item, you will see the message:
Custom Test View
To add our custom test view, we'll create a new WPF user control and register it during our module initialization to appear in a particular Acuit Pinpoint Workstation user interface region.
We'll also implement the logic to save pass/fail test results to the unit record in Acuit Pinpoint, along with always displaying the most recent test result.
This example uses the MVVM pattern, which is the recommended way to compose user interfaces using Prism, but this approach is not required.
- In Solution Explorer, right-click the UserControl1.xaml file that was created with the project and select Delete.
- In Solution Explorer, right-click the MyTestPlugIn project and select Add > User Control.
- Enter TestView.xaml for the name and click Add.
- Update the TestView.xaml source to be as follows:
<UserControl x:Class="MyTestPlugIn.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="800">
<Border BorderBrush="Black" BorderThickness="2">
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center">Custom Test:</TextBlock>
<StackPanel Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<Button Margin="16,0" Command="{Binding PassCommand}" Background="#80FF80">PASS</Button>
<Button Margin="16,0" Command="{Binding FailCommand}" Background="#FF6464">FAIL</Button>
</StackPanel>
<StackPanel Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="0,0,8,0">Last test result:</TextBlock>
<TextBox VerticalAlignment="Center" Margin="0,0,32,0" IsReadOnly="True" MinWidth="1in" TextAlignment="Center" Text="{Binding LastTestResult, Mode=OneWay}" />
</StackPanel>
</Grid>
</Border>
</UserControl>
- Update the TestView.xaml.cs source to be as follows:
using System.ComponentModel.Composition;
using System.Windows.Controls;
namespace MyTestPlugIn
{
[Export]
public partial class TestView : UserControl
{
public TestView()
{
InitializeComponent();
}
[Import]
public TestViewModel ViewModel
{
get { return (TestViewModel)DataContext; }
set { DataContext = value; }
}
}
}
- In Solution Explorer, right-click the MyTestPlugIn project and select Add > Class.
- Enter TestViewModel.cs for the name and click Add.
- Update the TestViewModel class source to be as follows:
using Acuit.Pinpoint.Client2;
using Acuit.Pinpoint.Windows;
using Acuit.Pinpoint.Workstation;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.ViewModel;
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Windows;
namespace MyTestPlugIn
{
[Export]
public sealed class TestViewModel : NotificationObject
{
private const string TestTypeName = "My Test"; // A test of this name must exist in the line configuration on Acuit Pinpoint Server
private readonly IUserInterfaceService _UserInterfaceService;
private readonly IWorkstationService _WorkstationService;
[ImportingConstructor]
public TestViewModel(IUserInterfaceService userInterfaceService, IWorkstationService workstationService)
{
_UserInterfaceService = userInterfaceService;
_WorkstationService = workstationService;
PassCommand = new DelegateCommand(() => AddTestResult(true, null));
FailCommand = new DelegateCommand(() => AddTestResult(false, "OPERATOR FAILED"));
_WorkstationService.UnitScanned += WorkstationService_UnitScanned;
_WorkstationService.UnitReleased += WorkstationService_UnitReleased;
}
public Unit UnitAtStation
{
get { return _UnitAtStation; }
set
{
if (value != _UnitAtStation)
{
_UnitAtStation = value;
RaisePropertyChanged(nameof(UnitAtStation));
// Update LastUnitTest property whenever UnitAtStation changes
LastUnitTest = UnitAtStation?.GetLatestTests().SingleOrDefault(t => t.TestType.Name == TestTypeName);
}
}
}
private Unit _UnitAtStation;
public UnitTest LastUnitTest
{
get { return _LastUnitTest; }
set
{
if (value != _LastUnitTest)
{
_LastUnitTest = value;
RaisePropertyChanged(nameof(LastUnitTest));
// Update LastTestResult property whenever LastUnitTest changes
if (LastUnitTest == null)
LastTestResult = null;
else
LastTestResult = LastUnitTest.Passed ? "PASSED" : "FAILED";
}
}
}
private UnitTest _LastUnitTest;
public string LastTestResult
{
get { return _LastTestResult; }
set
{
if (value != _LastTestResult)
{
_LastTestResult = value;
RaisePropertyChanged(nameof(LastTestResult));
}
}
}
private string _LastTestResult;
private void WorkstationService_UnitScanned(object sender, UnitScanEventArgs e)
{
UnitAtStation = e.Status.Unit;
}
private void WorkstationService_UnitReleased(object sender, UnitReleaseEventArgs e)
{
UnitAtStation = null;
}
public DelegateCommand PassCommand { get; }
public DelegateCommand FailCommand { get; }
private void AddTestResult(bool passed, string reason)
{
try
{
// It's good practice to show a wait indication whenever we're communicating with Pinpoint Server synchronously from the UI thread
using (_UserInterfaceService.WaitCursor())
{
// Add a new test result for the unit currently scanned at the station
var status = _WorkstationService.AddTestResult2(TestTypeName, passed, reason, null, null);
LastUnitTest = status.UnitTest;
}
}
catch (Exception ex)
{
// Pinpoint Server could report an error due to improper line configuration, or there could be an error communicating with Pinpoint Server
_UserInterfaceService.ShowMessageBox(MessageBoxImage.Error, ex.Message);
}
}
}
}
- Update the PlugInModule class source to be as follows:
using Acuit.Pinpoint.Windows;
using Acuit.Pinpoint.Workstation;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.MefExtensions.Modularity;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using System.ComponentModel.Composition;
using System.Windows;
namespace MyTestPlugIn
{
[ModuleExport("MyTestPlugIn", typeof(PlugInModule))]
public class PlugInModule : IModule
{
private readonly IMenuRegistry _MenuRegistry;
private readonly IRegionViewRegistry _RegionViewRegistry;
private readonly IUserInterfaceService _UserInterfaceService;
[ImportingConstructor]
public PlugInModule(IMenuRegistry menuRegistry, IRegionViewRegistry regionViewRegistry, IUserInterfaceService userInterfaceService)
{
_MenuRegistry = menuRegistry;
_RegionViewRegistry = regionViewRegistry;
_UserInterfaceService = userInterfaceService;
}
public void Initialize()
{
_MenuRegistry.AddMenuItem(MenuNames.Tools, new MenuItemViewModel { IsSeparator = true });
_MenuRegistry.AddMenuItem(MenuNames.Tools, new MenuItemViewModel
{
Header = "Test Plug-in Custom Command",
Command = new DelegateCommand(() => _UserInterfaceService.ShowMessageBox(MessageBoxImage.Information, "Hello from the test plug-in!"))
});
_RegionViewRegistry.RegisterViewWithRegion(RegionNames.MannedUnitPlugInRegion, typeof(TestView));
}
}
}
- Start debugging (F5).
- When a unit is scanned into Acuit Pinpoint Workstation, you should now see the custom test view at the bottom of the unit display, and clicking PASS or FAIL will add test results to the unit record.