This KB article will walk you through creating a Win Form (GUI) application in Python with TKinter. For this article, Visual Studio 2015 with the Python Add-in was used. You can also use Visual Studio 2017 or other compatible IDEs.
Creating the Project
We are now ready to create the project. The first thing we need to do is import support libraries. In the code window, add the following to add ‘links’ to tkinter and the Universal Library:
from tkinter import *
from builtins import *
from tkinter.ttk import Combobox
from mcculw import ul
from mcculw.enums import *
from mcculw.examples.ui.uiexample import UIExample
from mcculw.examples.props.ai import *
from mcculw.ul import ULError
import tkinter as tk
class Py_JG02(UIExample):
def __init__(self, master):
super(Py_JG02, self).__init__(master)
# Start the example if this module is being run
if __name__ == "__main__":
#Start the example
Py_JG02(master=tk.Tk()).mainloop()
That is all the boiler plate code you need!
Setting Up the UI
Before we get into the Universal Library calls, I thought it would be best to set up the user interface. By adding the user interface syntax first, it should make reading the library code calls easier to understand and more clear since you will have a more complete picture of the process. Unlike Visual Basic.NET or C#, the code to add these objects must be created manually, but it’s not as bad as you think. Creating the section of the code is pretty clear once you understand how it works.Here is the code and some useful comments (it is located just before the last bits of code from above):
def create_widgets(self):
#'''Create the tkinter UI'''
main_frame = tk.Frame(self) #Create the main code frame
main_frame.pack(fill=tk.X, anchor=tk.NW)
curr_row = 0
device_id_left_label = tk.Label(main_frame) #I wanted to add the device ID
device_id_left_label["text"] = "Device Identifier: " #This is its label
device_id_left_label.grid(row=curr_row, column=0, sticky=tk.W, padx=3, pady=3)
self.device_id_label = tk.Label(main_frame)
self.device_id_label["text"] = self.device.unique_id #this is the device’s ID
self.device_id_label.grid(row=curr_row,column=1, sticky=tk.W, padx=3, pady=3)
curr_row += 1
channel_entry_label = tk.Label(main_frame) #add a label for channel number select
channel_entry_label["text"] = "Channel Number:"
channel_entry_label.grid(row=curr_row, column=0, sticky=tk.E)
channel_vcmd = self.register(self.validate_channel_entry) #Call validate Channel number (make sure it is a number and within range of the device.
self.channel_entry = tk.Spinbox( #I wanted to use a spin button.
main_frame, from_=0, #this is how to add it.
to=max(self.ai_props.num_ai_chans- 1, 0),
validate='key',validatecommand=(channel_vcmd, '%P'))
self.channel_entry.grid(row=curr_row,column=1, sticky=tk.W)
curr_row += 1
range_label = tk.Label(main_frame) #For user selected voltage range
range_label["text"] = "Range:"
range_label.grid(row=curr_row, column=0, sticky=tk.E)
self.range_combobox = Combobox(main_frame) #Combobox to hold supported ranges
self.range_combobox["state"] = "readonly"
self.range_combobox["values"] = [
x.name for x in self.ai_props.available_ranges]
self.range_combobox.current(0)
self.range_combobox.grid(row=curr_row,column=1, padx=3, pady=3)
curr_row += 1
value_left_label = tk.Label(main_frame) #A label to tell us where the reading is
value_left_label["text"] = (
"Value read from selected channel(V):")
value_left_label.grid(row=curr_row, column=0, sticky=tk.W)
self.value_label = tk.Label(main_frame) #label to hold the measured value
self.value_label.grid(row=curr_row,column=1, sticky=tk.W)
button_frame = tk.Frame(self) #Add a frame to hold the Start/Stop & quit buttons
button_frame.pack(fill=tk.X, side=tk.RIGHT, anchor=tk.SE)
self.start_button = tk.Button(button_frame) #Start/Stop button
self.start_button["text"] = "Start"
self.start_button["command"] = self.start
self.start_button.grid(row=0,column=0, padx=3, pady=3)
self.quit_button = tk.Button(button_frame) #Quit button
self.quit_button["text"] = "Quit"
self.quit_button["command"] = self.master.destroy
self.quit_button.grid(row=0,column=1, padx=3, pady=3)
Device Discovery
We can now add device discovery code to access the Measurement Computing device (the USB-1408FS-PLUS or USB-1408 in this case).
By implementing device discovery, InstaCal is not needed prior to running the device. Just plug in the device, let the Windows OS discover it, and run the program. Here is the syntax needed for device discovery:
self.board_num = 0 #create constant for board 0
ul.ignore_instacal() #don’t use InstaCal board config file
if self.discover_devices(): #call discover_devices below. If any boards are found
self.create_widgets() #load the widgets for the screen
else:
self.create_unsupported_widgets(self.board_num)
def discover_devices(self):
#Get the device inventory
devices = ul.get_daq_device_inventory(InterfaceType.USB) #get device inventory
self.device = next((device for device in devices #find one with 1408
if "1408" in device.product_name), None) #in the name
if self.device != None: #if it is found,
ul.create_daq_device(self.board_num, self.device) #create an instance
ul.flash_led(self.board_num) #flash the LED, and
self.ai_props = AnalogInputProps(self.board_num) #get its properties
return True
return False
The above code gets placed right after this code:
class Py_JG01(UIExample):
def __init__(self, master):
super(Py_JG01, self).__init__(master)
Analog Input
Now, we will add the code to:
- Detects the selected range.
- Detects the selected analog input channel number.
- Validates the selected channel (make sure it is a number and within the channel range of the device).
- Read the voltage value from selected channel using the V_In function of UL.
- Repeat this operation every 100 ms.
This section of code goes right after the code we entered above:
def get_range(self): #Read the voltage range from the combobox
selected_index = self.range_combobox.current()
return self.ai_props.available_ranges[selected_index]
def get_channel_num(self): #Read the channel value
try:
return int(self.channel_entry.get())
except ValueError:
return 0
def validate_channel_entry(self, p): #make sure the value really is a valid number
if p == '':
return True
try:
value = int(p)
if(value < 0 or value > self.ai_props.num_ai_chans - 1):
return False
except ValueError:
return False
return True
def update_value(self): #Read the analog voltage value
try:
# Read voltage value from a channel
channel = self.get_channel_num() #from function above
ai_range = self.get_range() #from function above
value = ul.v_in(self.board_num, channel,ai_range) #call V_In from the UL
# Display the raw value
self.value_label["text"] = '{:.4f}'.format(value) #display the voltage read
if self.running:
self.after(100, self.update_value) #this is the timer to repeat 100 mS
except ULError as e:
self.show_ul_error(e)
Starting and Stopping the Operation
Since we want to have a Start and Stop button to start and stop the operation, we will add the routines for those next:
def stop(self):
self.running = False
self.start_button["command"] = self.start
self.start_button["text"] = "Start"
def start(self):
self.running = True
self.start_button["command"] = self.stop
self.start_button["text"] = "Stop"
self.update_value()
And that’s it.
Running the Application
To launch the app, from the IDE menu, click Start
The running app:

As you can see, the ID of the MCC is displayed.
Users can select the analog input channel to read and the range of the analog input channel. When the Start button is pressed, the value of the selected channel is returned every 100 ms until the user presses the Stop button or exits the application by pressing Quit.
Additional Information Resources
On TKinter from Python.org:https://wiki.python.org/moin/TkInter
Tkinter 8.5 reference: a GUI for Python: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
Go to YouTube.com, and search on “thenewboston python”