In this article, I present a ComboBox control written in C# which wraps a Gtk 3 ComboBoxText control, so as to simplify its usage.
Goals
- Encourage other programmers to use the Mono / MonoDevelop / Gtk# combination by sharing this example.
- Make it as simple to use as a basic Gtk widget, while adding as little overhead as possible.
- Make it more accessible to Windows / Forms / WPF programmers by hiding the Gtk-specific implementation details.
Credits
Thanks are due to the Mono and MonoDevelop teams for making it possible to run C# programs on Linux. Thanks also to the Gtk# team for their framework.
Background
I am in the process of porting a bunch of my Windows C# WPF / MVVM applications to Linux / Mono / Gtk3. I am learning the Gtk toolkit as I go along, and I have been trying to craft tools that will allow me to use as much of my existing code as possible. ComboBoxes are used *everywhere* so this was one of the first items I tackled.
Concept
First of all, let me say here that I sorely miss the convenience of the WPF Bindings.
They make it trivial to hook up a list of any type of class to a ComboBox
. You set the ItemsSource
, DisplayMemberPath
, SelectedValuePath
and SelectedValue
properties and WPF works its magic for you.
Here, we have to do this by ourselves, so the less we have to do, the better. Consequently, to quote the famous aircraft designer Ed Heinemann [1], we have to “simplificate and add lightness”.
If you reduce the function of the ComboBox
to its simplest form, you come to the conclusion that essentially, for it to do its job, it needs just:
- an integer “
key
” or index to be able to keep track of each item, - and an associated
string
to display as a description for that item.
So if we were to create a small class with an int Key
and String Description
, e.g., something like this:
public class ComboItem
{
public int Key { get; set; }
public String Description { get; set; }
}
and supply the ComboBox
with a list of these, it should work just fine. Turns out we don’t even have to do that, because C# very conveniently supplies us with the KeyValuePair
class. And so we come to the HComboBox
control, which manipulates a list of KeyValuePair<int,String>
items, which is as simple and generic as you can get.
Solution
The HComboBox
control derives from HBox
and contains a ComboBoxText
, hence the name.
Properties
Get
/ Set
the Height
/ Width
- Get the
ComboBoxText
member so that it can be manipulated directly by the clients if need be.
Methods
-
void LoadItems( Ilist<KeyValuePair<int,String>> lst )
Loads the list of items into the ComboBox
. WPF uses ItemsSource
, but this does the same job quite nicely, while also hiding the Gtk-specific code.
-
KeyValuePair<int,String> GetSelection()
and:
-
void SetSelection( int nKey )
are self-explanatory. They too serve to “hide” the Gtk-specific details from the user.
Events
It exposes the Changed
event which the clients can use to know when the selected item has changed.
Sample Usage
See the MainWindow
class of the attached sample program as an example of how to use this control.
To demonstrate the packing / unpacking to and from the KeyValuePair<int,String>
, let us define a simple class to act as our domain object:
public class DayOfWeek
{
public int Day { get; set; }
public String Name { get; set; }
}
and let us assume that the client has a list of these:
IList<DayOfWeek> lst = new List<DayOfWeek>()
{
new DayOfWeek() { Day = 0, Name = "" },
new DayOfWeek() { Day = 1, Name = "Sunday" },
new DayOfWeek() { Day = 2, Name = "Monday" },
new DayOfWeek() { Day = 3, Name = "Tuesday" },
new DayOfWeek() { Day = 4, Name = "Wednesday" },
new DayOfWeek() { Day = 5, Name = "Thursday" },
new DayOfWeek() { Day = 6, Name = "Friday" },
new DayOfWeek() { Day = 7, Name = "Saturday" },
};
We then need a function to convert the above list to a list of KeyValuePair
s which we can pass to the ComboBox
.
In our example, it looks something like this (expanded for readability):
private IList<KeyValuePair<int,String>> ConvertToComboList(IList<DayOfWeek>lst)
{
IList<KeyValuePair<int,String>> lstRet= new List<KeyValuePair<int,String>>();
if( lst != null && lst.Count > 0 )
{
foreach( DayOfWeek dow in lst )
{
lstRet.Add( new KeyValuePair<int, string>( dow.Day, dow.Name ) );
}
}
return lstRet;
}
Finally, we can load the list into the ComboBox
:
m_hcbx.LoadItems( ConvertToComboList( lst ) );
Once the ComboBox
has been initialized, we can set the selection thus:
DateTime dt = DateTime.Now;
m_hcbx.SetSelection( (( int )dt.DayOfWeek ) + 1 );
and to retrieve the currently selected item:
KeyValuePair<int,String> day = m_hcbx.GetSelection();
and that’s all there is to it.
Remarks
The downside of this particular approach is that for every legacy class you wish to display in the HComboBox
, you need to write a dedicated ConvertToComboList()
function.
In cases where the legacy code is available and able to be modified / enhanced, as happens in my case, one solution is to define an interface similar to this:
public interface IListable
{
int Key { get; }
String Description { get; }
}
and have the legacy classes implement it.
You then need only one templated ConvertToComboList()
function, thus:
which will work with all classes that implement the IListable
interface. In my experience, classes that appear in ComboBox
es already have an integer property which can used as a key
and a String
property which can be used as a Description
, so the implementation of the interface is trivial.
Exercises for the Reader
This control derives from HBox
and contains a ComboBoxText
. Another approach would be to derive from ComboBoxText
directly and extend it with the added functionality. As it stands, I took the coward’s way out and exposed the ComboBoxText
member via a getter property.
Requirements
- Mono (currently I have version 6.6.0.166)
- MonoDevelop (current version 7.8.4)
- Gtk3 libraries (currently at version 3.22.25.56)
The supplied sample application is a MonoDevelop Solution. It requires the Gtk 3 packages.
Since these are DLLs, they are not included in the sample project, but the packages.config file is included. After opening the solution in MonoDevelop, select restore / update Packages and NuGet will fetch them.
History
- 25th March, 2020: Initial version