Struggling with winforms + combobox data binding

I am working on a winform with a combobox…and I want to have key and value binding to the combobox.
my combobox is using this code:

self.levelcombo.DataSource = mycomboboxitem
self.levelcombo.DisplayMember = "key"
self.levelcombo.ValueMember = "value"

where I have already made a class:

class comboboxitem():
    def __init__(self, key, value):
        self.key = key
        self.value = value

    def value(self):
        return self.__dict__.values()

    def key(self):
        return self.__dict__.keys()

and I try to bind them together:

mycomboboxitem = comboboxitem(list1, list2)

but it never succeeds :frowning:
I am sure something is wrong but just cant figure it out

To make a combobox you need a list. It could be any kind of list actually but if you want to have a sensible name to display it would need to be a list of Objects.

let’s say this is your Object definition:

class MyItem():
	def __init__(self, key, value):
		self.Key = key
		self.Value = value

it requires a key and a value variable to be initiated and then has a Key and Value attributes assigned to the initial keys and values.

let’s say you have this initial data:


keys  = ["a", "b", "c"]
values = [10,20,30]

you can create a list of your objects from these two lists like so:

list_of_items = [MyItem(i,j) for i,j in zip(keys, values)]

Then in your WPF window your combobox needs to have this list as its ItemsSource:

self.levelcombo.ItemsSource = list_of_items

and then to show the key items as visible in the Combobox you need to assign its DisplayMemberPath to the attribute of your object:

self.levelcombo.DisplayMemberPath ="Key"

6 Likes

You can also use DataTable

an example

import clr
import sys
import System

#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

#import net library
from System import Array
from System.Collections.Generic import List, IList, Dictionary
clr.AddReference('System.Data')
from System.Data import DataTable

#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference("System.Drawing")
clr.AddReference("System.Windows.Forms")
import System.Drawing
import System.Windows.Forms
from System.Drawing import *
from System.Windows.Forms import *

class MainForm(Form):
	def __init__(self):
		allViewFamilyType = FilteredElementCollector(doc).OfClass(ViewFamilyType).WhereElementIsElementType()
		arrayGroupType = [[Element.Name.GetValue(g), g] for g in allViewFamilyType]
		self._tableViewFamType = DataTable("ViewFamilyType")
		self._tableViewFamType.Rows.Add() # add a empty row
		self._tableViewFamType.AcceptChanges()
		self._tableViewFamType.Columns.Add("Name", System.String)
		self._tableViewFamType.Columns.Add("ViewType", DB.Element)
		# populate dataTable
		[self._tableViewFamType.Rows.Add(i, j) for i, j in arrayGroupType]
		self.choice = None
		
		self.InitializeComponent()
	
	def InitializeComponent(self):
		#
		self._comboBoxType = System.Windows.Forms.ComboBox()
		self._comboBoxType.Location = System.Drawing.Point(40, 50)
		self._comboBoxType.Size = System.Drawing.Size(221, 21)
		self._comboBoxType.DataSource = self._tableViewFamType # or self._tableViewFamType.Copy()
		self._comboBoxType.DisplayMember = "Name"
		self._comboBoxType.ValueMember = "ViewType"
		self._comboBoxType.DropDownStyle = ComboBoxStyle.DropDownList # force ready only
		self._comboBoxType.SelectedIndexChanged += self.ComboBox1SelectedIndexChanged
		#
		self.ClientSize = System.Drawing.Size(300, 200)
		self.Controls.Add(self._comboBoxType)
		self.Name = "MainForm"
		self.Text = "MainForm"
		self.ResumeLayout(False)
		
	def ComboBox1SelectedIndexChanged(self, sender, e):

		if isinstance(sender.SelectedItem['ViewType'], System.DBNull):
			self.choice = None
		else:
			self.choice = sender.SelectedItem['ViewType']
			
objForm = MainForm()
objForm.ShowDialog()
		
OUT = objForm.choice
4 Likes

@viktor_kuzev thank you so much!!!
@c.poupin I will study this after combobox, thanks! :slight_smile:

@viktor_kuzev one more thing, when I try to display a default value when the script runs, I try to set using:

self.levelcombo.SelectedIndex = 2

but it shows error:
ValueError: InvalidArgument=Value of ‘2’ is not valid for ‘SelectedIndex’.
Parameter name: SelectedIndex

do you have any idea? :slight_smile:

do you mind sharing more of your code

the combobox code:

self.levelcombo = SF.ComboBox()
self.levelcombo.Name = "levelcombo"
self.levelcombo.DropDownStyle = SF.ComboBoxStyle.DropDownList
self.levelcombo.DataSource = mycomboboxitem
self.levelcombo.DisplayMember = "key"
self.levelcombo.ValueMember = "value"
self.levelcombo.SelectedIndex = 2
self.levelcombo.Location = SD.Point(270, 45)
self.levelcombo.Size = SD.Size(150, 20)
self.Controls.Add(self.levelcombo)

something like that

do I need to add “SelectedIndexChanged”?

With a List, you need to use Items property

here another example

import clr
import sys
import System

clr.AddReference("System.Drawing")
clr.AddReference("System.Windows.Forms")
import System.Drawing
import System.Windows.Forms
from System.Drawing import *
from System.Windows.Forms import *

from collections import namedtuple

class MainForm(Form):
	def __init__(self, pyDict):
		MyItem = namedtuple('MyItem', ['Key', 'Value'])
		self._lstObj = [MyItem(key_, value_) for key_, value_ in pyDict.items()]
		self.choice = None
		self.InitializeComponent()
	
	def InitializeComponent(self):
		#
		self._comboBoxType = System.Windows.Forms.ComboBox()
		self._comboBoxType.Location = System.Drawing.Point(40, 50)
		self._comboBoxType.Size = System.Drawing.Size(221, 21)
		self._comboBoxType.Items.AddRange(System.Array[System.Object](self._lstObj))
		self._comboBoxType.DisplayMember = "Key"
		self._comboBoxType.ValueMember = "Value"
		self._comboBoxType.SelectedIndexChanged += self.ComboBox1SelectedIndexChanged
		#
		self.ClientSize = System.Drawing.Size(300, 200)
		self.Controls.Add(self._comboBoxType)
		self.Name = "MainForm"
		self.Text = "MainForm"
		self.ResumeLayout(False)
		
	def ComboBox1SelectedIndexChanged(self, sender, e):
		self.choice = sender.SelectedItem.Value
		self.Close()

mydict = {'a': 1, 'b': 2, 'c' : 3}
objForm = MainForm(mydict)
objForm.ShowDialog()
		
OUT = objForm.choice
3 Likes

@c.poupin it works with DataSource for me:

import clr
import sys
import System

clr.AddReference("System.Drawing")
clr.AddReference("System.Windows.Forms")
import System.Drawing
import System.Windows.Forms
from System.Drawing import *
from System.Windows.Forms import *

from collections import namedtuple
class MyItem():
	def __init__(self, key, value):
		self.Key = key
		self.Value = value
keys  = ["a", "b", "c"]
values = [10,20,30]
list_of_items = [MyItem(i,j) for i,j in zip(keys, values)]

class MainForm(Form):
	def __init__(self):
		self.choice = None
		self.InitializeComponent()
	
	def InitializeComponent(self):
		#
		self._comboBoxType = System.Windows.Forms.ComboBox()
		self._comboBoxType.Location = System.Drawing.Point(40, 50)
		self._comboBoxType.Size = System.Drawing.Size(221, 21)
		self._comboBoxType.DataSource = list_of_items
		self._comboBoxType.DisplayMember = "Key"
		self._comboBoxType.ValueMember = "Value"
		self._comboBoxType.SelectedIndexChanged += self.ComboBox1SelectedIndexChanged
		#
		self.ClientSize = System.Drawing.Size(300, 200)
		self.Controls.Add(self._comboBoxType)
		self.Name = "MainForm"
		self.Text = "MainForm"
		self.ResumeLayout(False)
		
	def ComboBox1SelectedIndexChanged(self, sender, e):
		self.choice = sender.SelectedItem.Value
		self.Close()

objForm = MainForm()
objForm.ShowDialog()
		
OUT = objForm.choice

@newshunhk my previous code was with WPF syntax, apologies if that has misled you, however here I modified a bit the example from Cyril and the same things apply, though it’s DisplayMember (not DisplayMemberPath) and DataSource (not ItemsSource)

2 Likes

Thanks, good to know :smiley:

thank you so much for all of you :slight_smile:

by the way I found the solution for this, causing self.levelcombo.SelectedIndex = 2 doesn’t work:
I check the items no. with print(self.levelcombo.Items.Count), it displays 0

        self.levelcombo = SF.ComboBox()
        self.levelcombo.Name = "levelcombo"
        self.levelcombo.DropDownStyle = SF.ComboBoxStyle.DropDownList
        self.levelcombo.DataSource = mycomboboxitem
        self.levelcombo.DisplayMember = "key"
        self.levelcombo.ValueMember = "value"
        print(self.levelcombo.Items.Count)
        self.levelcombo.Location = SD.Point(270, 45)
        self.levelcombo.Size = SD.Size(150, 20)
        self.Controls.Add(self.levelcombo)

image

but if I put the self.Controls.Add(self.levelcombo) before the datasource, it works well!!

        self.levelcombo = SF.ComboBox()
        self.Controls.Add(self.levelcombo)
        self.levelcombo.Name = "levelcombo"
        self.levelcombo.DropDownStyle = SF.ComboBoxStyle.DropDownList
        self.levelcombo.DataSource = mycomboboxitem
        self.levelcombo.DisplayMember = "key"
        self.levelcombo.ValueMember = "value"
        print(self.levelcombo.Items.Count)
        self.levelcombo.Location = SD.Point(270, 45)
        self.levelcombo.Size = SD.Size(150, 20)

image

now I can set the default key to display when script is run :slight_smile:

import clr
import sys
import System

clr.AddReference("System.Drawing")
clr.AddReference("System.Windows.Forms")
import System.Drawing
import System.Windows.Forms
from System.Drawing import *
from System.Windows.Forms import *

from collections import namedtuple
class MyItem():
	def __init__(self, key, value):
		self.Key = key
		self.Value = value
keys  = ["a", "b", "c"]
values = [10,20,30]
list_of_items = [MyItem(i,j) for i,j in zip(keys, values)]

class MainForm(Form):
	def __init__(self):
		self.choice = None
		self.InitializeComponent()
	
	def InitializeComponent(self):
		#
		self._comboBoxType = System.Windows.Forms.ComboBox()
		self.Controls.Add(self._comboBoxType) # move to here
		self._comboBoxType.Location = System.Drawing.Point(40, 50)
		self._comboBoxType.Size = System.Drawing.Size(221, 21)
		self._comboBoxType.DataSource = list_of_items
		self._comboBoxType.DisplayMember = "Key"
		self._comboBoxType.ValueMember = "Value"
		self._comboBoxType.SelectedIndex = 2 # added
		self._comboBoxType.SelectedIndexChanged += self.ComboBox1SelectedIndexChanged
		#
		self.ClientSize = System.Drawing.Size(300, 200)
		self.Name = "MainForm"
		self.Text = "MainForm"
		self.ResumeLayout(False)
		
	def ComboBox1SelectedIndexChanged(self, sender, e):
		self.choice = sender.SelectedItem.Value
		self.Close()

objForm = MainForm()
objForm.ShowDialog()
		
OUT = objForm.choice

datasource seems does not work if I switch the script to cpython… strange

Objects under IronPython are .Net Native, this allows to use properties/attributes of Python objects as Net properties.
Python.Net (CPython engine) does not allow this, in this case you have to use .Net objects (not python objects)

1 Like

that’s mean I need to use something like PyQt5?

No, not necessarily, you just have to make sure that you are using Object .Net with CPython3

an example


you can also use a DataTable (see example in my previous post)

If you are a beginner it may be better to start with IronPython (which makes a lot of things easier)

3 Likes

thx, but can I use ironpython 3.4 in pyrevit?

except incident, the final version of Ironpython 3.4 should arrive soon, then Ipy 3.4 can be implemented or updated in software(s)

3 Likes