Setting Up a Windows Forms or WPF Project

This article discusses how to set up C# Windows Forms and Presentation Foundation projects using Visual Studio.

Libraries

On Windows, the Leap Motion C# API is provided as a .NET assembly accompanied by two dynamically linked libraries, Leap.dll and LeapCSharp.dll. Separate libraries are provided for 32-bit, x86 architectures and 64-bit, x64 architectures.

When your application calls a function in the Leap Motion C# API, the C# code calls the matching function defined in LeapCSharp.dll. That function, in turn, calls a native function defined in Leap.dll. Thus all three libraries must be included in your application both during development and when you publish and install your application on a client computer. The Leap Motion libraries are designed to be loaded from the same directory as your application executable. You are expected to distribute the appropriate Leap Motion library with your application. The Leap Motion libraries are located in the lib folder of the Leap SDK package.

Creating a project in Visual Studio

This section illustrates how to create a project from scratch. Most of the steps also apply to adding Leap Motion support to an existing project. The example uses Visual Studio 2012.

To add Leap Motion support to a new or existing project:

Important: If you are creating a 64-bit application, use the libraries in the lib\x64 directory of the SDK, not the 32-bit libraries in the lib\x86 directory.

  1. Open or create a new project of the desired Windows Forms or WPF type.

  2. Make a LEAP_SDK system environment variable to point to your Leap Motion SDK folder. This step is optional, but does simplify creating references to the SDK files in your projects. (As a reminder, you can create and change system environment variables from the Windows System Properties dialog.)

  3. Add a reference to the Leap Motion .NET library that you wish to use.

    1. Select Project > Add Reference.

    2. In the References dialog, select Browse.

    3. Browse to your Leap Motion SDK folder.

    4. Choose either LeapCSharp.NET3.5.dll or LeapCSharp.NET4.0.dll. Both libraries support the full Leap Motion API, but you should choose the one appropriate for the version of .NET you are using in the project.

      Note: do not add any of the other Leap Motion library files as a reference.

  4. Set the target platform to x86 or x64:

    1. Open the project properties page with Project > ProjectName Properties.
    2. Select the Build page.
    3. Set the Configuration to All Configurations.
    4. Under General, set Platform target to either x86 or x64. Do not use the Any CPU target.
  5. Edit the Post Build Event to copy the native libraries to the project’s target executable directory.

    1. Open the project properties page.

    2. Select the Build Events page.

    3. Click Edit Post-build...

    4. Edit the event field to copy the libraries.

      For 32-bit, x86 projects add:

      xcopy /yr "$(LEAP_SDK)\lib\x86\Leap.dll" "$(TargetDir)"
      xcopy /yr "$(LEAP_SDK)\lib\x86\LeapCSharp.dll" "$(TargetDir)"
      

      For 64-bit, x64 projects add:

      xcopy /yr "$(LEAP_SDK)\lib\x64\Leap.dll" "$(TargetDir)"
      xcopy /yr "$(LEAP_SDK)\lib\x64\LeapCSharp.dll" "$(TargetDir)"
      

      Note: if you didn’t create the LEAP_SDK environment variable, use the path to your SDK library in place of $(LEAP_SDK).

  6. Add your source code...

Note: If you get the error, TypeInitializationException: The type initializer for 'Leap.LeapPINVOKE' threw an exception, when you run your project, then the Leap Motion .NET code is not finding the correct native libraries. This can happen if you forget to copy Leap.dll and LeapCSharp.dll to the same directory as your executable or you copy the wrong version (64-bit instead of 32-bit or vice versa). The error can also occur if you use the Any CPU target platform.

Accessing Leap Motion tracking data

The data of controls in a Windows Forms or WPF application can only be accessed from the main application thread. You can poll the Leap Motion Controller object from the main thread to get tracking data at any time. However, if you use a Listener object to receive notifications of controller events, such as a new Frame of data becoming available, be aware that the callback functions defined in your Listener implementation are invoked on separate threads. Thus you cannot update GUI controls directly from these callback functions. Instead, you must dispatch the notification to the application thread.

You can dispatch messages from one thread to another using the Windows Invoke function. The following examples use Invoke to send Listener notifications to the application thread. To avoid coupling the Listener subclass to a particular Form or Window subclass, the examples define the ILeapEventDelegate interface – this allows any class implementing this interface to act as a delegate for the Leap Motion events. Notice that the Frame object is not passed to the delegate as part of the notification. The receiving class already has its own Controller object and can get a frame anytime it needs to. The event is just a notification that a new frame is available.

To run either example, your form or window definition must have six Label controls with the names shown in the newFrameHandler() method.

Windows Forms

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
using Leap;

namespace WinFormSample
{
    public partial class FrameDataForm : Form, ILeapEventDelegate
    {
        private Controller controller;
        private LeapEventListener listener;
        public FrameDataForm()
        {
            InitializeComponent();
            this.controller = new Controller();
            this.listener = new LeapEventListener(this);
            controller.AddListener(listener);
        }

        delegate void LeapEventDelegate(string EventName);
        public void LeapEventNotification(string EventName)
        {
            if (!this.InvokeRequired)
            {
                switch (EventName)
                {
                    case "onInit":
                        Debug.WriteLine("Init");
                        break;
                    case "onConnect":
                        this.connectHandler();
                        break;
                    case "onFrame":
                        if(!this.Disposing) 
                            this.newFrameHandler(this.controller.Frame());
                        break;
                }
            }
            else
            {
                BeginInvoke(new LeapEventDelegate(LeapEventNotification), new object[] { EventName });
            }
        }

        void connectHandler()
        {
            this.controller.EnableGesture(Gesture.GestureType.TYPE_CIRCLE);
            this.controller.Config.SetFloat("Gesture.Circle.MinRadius", 40.0f);
            this.controller.EnableGesture(Gesture.GestureType.TYPE_SWIPE);
        }

        void newFrameHandler(Frame frame)
        {
            //The following are Label controls added in design view for the form
            this.displayID.Text = frame.Id.ToString();
            this.displayTimestamp.Text = frame.Timestamp.ToString();
            this.displayFPS.Text = frame.CurrentFramesPerSecond.ToString();
            this.displayIsValid.Text = frame.IsValid.ToString();
            this.displayGestureCount.Text = frame.Gestures().Count.ToString();
            this.displayImageCount.Text = frame.Images.Count.ToString();
        }

        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    if (components != null)
                    {
                        components.Dispose();
                    }
                    this.controller.RemoveListener(this.listener);
                    this.controller.Dispose();
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }
    }

    public interface ILeapEventDelegate
    {
        void LeapEventNotification(string EventName);
    }

    public class LeapEventListener : Listener
    {
        ILeapEventDelegate eventDelegate;

        public LeapEventListener(ILeapEventDelegate delegateObject)
        {
            this.eventDelegate = delegateObject;
        }
        public override void OnInit(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onInit");
        }
        public override void OnConnect(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onConnect");
        }
        public override void OnFrame(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onFrame");
        }
        public override void OnExit(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onExit");
        }
        public override void OnDisconnect(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onDisconnect");
        }
    }
}

Windows Presentation Foundation

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Diagnostics;
using Leap;

namespace WPFSample
{
    public partial class MainWindow : Window, ILeapEventDelegate
    {
        private Controller controller = new Controller();
        private LeapEventListener listener;
        private Boolean isClosing = false;

        public MainWindow()
        {
            InitializeComponent();
            this.controller = new Controller();
            this.listener = new LeapEventListener(this);
            controller.AddListener(listener);
        }

        delegate void LeapEventDelegate(string EventName);
        public void LeapEventNotification(string EventName)
        {
            if (this.CheckAccess())
            {
                switch (EventName)
                {
                    case "onInit":
                        Debug.WriteLine("Init");
                        break;
                    case "onConnect":
                        this.connectHandler();
                        break;
                    case "onFrame":
                        if(!this.isClosing)
                            this.newFrameHandler(this.controller.Frame());
                        break;
                }
            }
            else
            {
                Dispatcher.Invoke(new LeapEventDelegate(LeapEventNotification), new object[] { EventName });
            }
        }

        void connectHandler()
        {
            this.controller.SetPolicy(Controller.PolicyFlag.POLICY_IMAGES);
            this.controller.EnableGesture(Gesture.GestureType.TYPE_SWIPE);
            this.controller.Config.SetFloat("Gesture.Swipe.MinLength", 100.0f);
        }

        void newFrameHandler(Leap.Frame frame)
        {
            this.displayID.Content = frame.Id.ToString();
            this.displayTimestamp.Content = frame.Timestamp.ToString();
            this.displayFPS.Content = frame.CurrentFramesPerSecond.ToString();
            this.displayIsValid.Content = frame.IsValid.ToString();
            this.displayGestureCount.Content = frame.Gestures().Count.ToString();
            this.displayImageCount.Content = frame.Images.Count.ToString();
        }

        void MainWindow_Closing(object sender, EventArgs e)
        {
            this.isClosing = true;
            this.controller.RemoveListener(this.listener);
            this.controller.Dispose();
        }
    }

    public interface ILeapEventDelegate
    {
        void LeapEventNotification(string EventName);
    }

    public class LeapEventListener : Listener
    {
        ILeapEventDelegate eventDelegate;

        public LeapEventListener(ILeapEventDelegate delegateObject)
        {
            this.eventDelegate = delegateObject;
        }
        public override void OnInit(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onInit");
        }
        public override void OnConnect(Controller controller)
        {
            controller.SetPolicy(Controller.PolicyFlag.POLICY_IMAGES);
            controller.EnableGesture(Gesture.GestureType.TYPE_SWIPE);
            this.eventDelegate.LeapEventNotification("onConnect");
        }

        public override void OnFrame(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onFrame");
        }
        public override void OnExit(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onExit");
        }
        public override void OnDisconnect(Controller controller)
        {
            this.eventDelegate.LeapEventNotification("onDisconnect");
        }

    }

}