Download wxlConsole.zip
Introduction
In this article I will explain how to put some basic resources in a window to interact with the user, like buttons, text fields and combo boxes.
As a bonus I will add a simple COM port library using a very basic one line terminal application as example.
I will explaine how to use wxWidget evente-based timers to automatize some actions like read the serial imput buffer and display the information in the text field.
The idea is to go step by step explaining the code for any object separately, starting with the layout of the window and adding the diferent functions and necessary code progressively.
Background
The examples of this article, and the code asociated, was maked with Code::Blocks using wxWIdgets 3.0.1. You can see how to download and compile it in this article:
Introduction-to-wxWidgets-GUI-programming-with-wxSmith
The serial communication is implemented with a simple library you can find in this article with a brief description of how to use it, I explain how to use it in this article also
Simple-Serial-communication-utilities
but instead of the list ports library used there I will use here another way to enumerate the ports.
Don't worry, I will explain anyn aspect in detail.
The code
The better way to start is for the beginning, so open the Code::Blocks ide and create a new wxWidgets project. In the first article referenced in the Background section you can find a guide to create a wxProject in C::B. Follow that steps to create the new project.
The first screen you see when create a new wxWidgets project is this:
This is the resource panel. Here we can drag and drop controls and resources we want to build the screen.
The Layout
First of all we will create the layout. So we will add some objects to the screen. But first we have to some things to spaciate and organize the layout. First add a wxBoxSizer. Sizers make the job of organize the elements in the screen for us. Then add a wxPanel inside the BoxSizer. Now add a wxGridSizer inside the panel. For more references about doing this you can see the article referenced before.
The wxGridSizer let us add elements in a matrix instead in a line as in the BoxSizer. If you select the GridSizer from the left resources tree you will see its properties in the properties menu below the tree. In the GridSizer properties you can set the row and column number.
The example consists in a list of COM ports available where you can choose wich one to connect, a CONNECT button, a text field to write the message you want to send with the SEND button and a text field to show the received messages. For simplicity I made the received field in only one line, buut is easy to make it multiline setting the correct propertie in the textfield properties.
Once we have this set of sizers in the screen we will proceed to build the screen. The goal screen is this:
As you can see in the GridSizer's properties I especified 3 rows and 2 columns.
Now we have to add the objects. That is doing by clicking on the object in the object bar and then clicking on the diagram. First click on the wxComboBox icon in the Standar bar and then click in the little square that we have as our screen. Now we have the future COM ports list. Clicking on it and in the properties list in the left we can see some attributes we can change. The principal attribute to change is the object name so we can refer to it and make the code more maintenable.
As you can see in the screenshot, I changed the var name to comSel and the Identifier to ID_COMSEL.
Then add the button on the right of the comboBox, change it's Var Name to conBut and the label to Connect
Now add a wxTextCtrl. I changed it's Var name to mesBox, the Label to Message and the Identifier to ID_MESBOX.
Add another wxButton. Label: Send, Var name: sendBut and Identifier: ID_SENDBUT.
Now add a wxStaticText, Label Received, Var name: recText and Identifier: ID_RECTEXT
Finally add a wxTextCtrl. Label: nothing because here we will display the incomming message, Var name: recBox and Identifier: ID_RECBOX. We will define it as read only setting this propertie in the style option in the propertie editor:
Remember that later when we define the functions to interact with the elements in the screen, that functions will be referenced to the var name of the objects.
Now you can build the project to see if it is working. If every thing is ok we have something like this:
But of course this doesn't have any functionality. So lets add some code to make it work.
Serial port enumerator
The available serial port enumeration is making through the Find_Comm function declared and implemented in portEnum.h and portsEnum.cpp respectively. See the code:
#include <string>
#include "portsEnum.h"
#include "windows.h"
using namespace std;
int Find_Comm(string* ports)
{
HANDLE DriverHandle;
DWORD LastError[4];
char Com_Name[10];
int portsCant = 0;
for(int x = 1 ; x < 10 ; x++)
{
switch(x)
{
case 1:
strcpy(Com_Name,"COM1");
break;
case 2:
strcpy(Com_Name,"COM2");
break;
case 3:
strcpy(Com_Name,"COM3");
break;
case 4:
strcpy(Com_Name,"COM4");
break;
case 5:
strcpy(Com_Name,"COM5");
break;
case 6:
strcpy(Com_Name,"COM6");
break;
case 7:
strcpy(Com_Name,"COM7");
break;
case 8:
strcpy(Com_Name,"COM8");
break;
case 9:
strcpy(Com_Name,"COM9");
break;
}
DriverHandle = CreateFile (Com_Name, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
LastError[x-1] = GetLastError();
if(LastError[x-1] == 0)
{
ports[portsCant]=Com_Name;
portsCant++;
}
CloseHandle(DriverHandle);
}
return portsCant;
}
I remarked the two important variables. ports
, the estring vector that contains the name of the ports availables and portscant the number of available ports.
To use it in our code first I declare two variables in the wxlConsoleFrame class declaration in wxlConsoleMain.h file, comports
and port_nr
. I declared the variables there to have them available in the functions of the frame object.
class wxlConsoleFrame: public wxFrame
{
public:
string comports[10];
int port_nr;
Very important: never modify the code between //(* and //*). This code is automatically generated and modified by wxWidgets and the IDE.
Then in wxlConsoleMain.cpp inside the constructor of wxlConsoleFrame
, wxlConsoleFrame::wxlConsoleFrame(wxWindow* parent,wxWindowID id), we have to call to the port enumerator function so when the frame is created we already have the ports listed:
port_nr = 0;
int portnum;
portnum = Find_Comm(comports);
for(int i=0; i<portnum; i++)
{
comSel->Append(comports[i]);
}
Find_comm
function ask for an string pointer as parameter and to give the ports in and return the port cuantity as an int.
The most important thing here is the first interaction with a GUI object inside the for cicle. There we fill the combo box comSel
with the available ports through the wxComboBox member function Append(string)
. Every call to Append adds a new string option to the list of the combo box.
We can build the project and verify if this is working. We have a list of available ports, now we have to choose one and connect to it.
Selecting and opening the serial port
We want to the user can select a port of the list and connect to it when pressing the Connect bbutton. Whe the user selects an option of the combo box an event is shooted, but we wont use it in this case because the will bbe maked when the connect button is pressed. So we have to define waht will occur when the connect button is pressed.
Macking double click on the connect button in the resource view (wxlConsoleFrame.wxs) we access directly to the wxlConsoleFrame::OnconButClick event function in wxlConsoleMain.cpp (maybe you have to scroll down to find it). Inside this function we can define what actions occurr when the button is pressed. This actions will be take the selected string (the selected port) from the combo box and open the port. Look that when you double-click on the button to generate the event automatically generates the function declaration in the wxlConsoleFrame class in wxlConsoleMain.h file.
void wxlConsoleFrame::OnconButClick(wxCommandEvent& event)
{
wxString comSelected = comSel->GetStringSelection(); int port_aux = 0;
if(comSelected.size() > 1) {
port_aux = (int) comSelected[3] - 48; if(port_nr == 0)
{
if(!RS232_OpenComport((port_aux-1), baudrate)) {
port_nr = port_aux; mesBox->SetValue((char)(port_aux+48)); }
}
}
}
First get the selected string with the combo box member function GetStringSelected()
. This return a wxString containing the text of the selection in the combobox. Combo boxes have another member function wich returns the ordering number of the selection.
Then check if the selection is not void and get the port number. The strings in the combo box are in the form 'COMn' where 'n' is the port number, thats the razon to take the fourth element of the string, cast it to int and subtract 48 ('0' in ascii).
The condition post_nr==0 is to check if no ports were opened previously. I use it in other parts of the code for the same utility.
RS232_OpenComport((port_aux-1), baudrate))
returns 0 if the port was succefuly opened. baudrate is declared as a member of wxlConsoleFrame class in wxlConsoleMain.h file and initialized to 9600 in the constructor.
If the port could be opened store the port number and show it in the instead receive message box to informate that it was opened using the TexCtrl member function setValue(string). You can append a message like "port n succefuly opened" using AppendText(string) instead:
mesBox->SetValue("Port ");
mesBox->AppendText((char)(port_aux+48));
mesBox->AppendText(" succefuly.");
or using formatted print:
wxString auxSt;
auxSt.Printf("Port %d opened", port_nr);
recBox->SetValue(auxSt);
Sending a message to the selected port
Once opened the port we want to communnicate with it. We have to get the text writed to the sending message text field and write it to the port when the send button is pressed. The action is maked when the button is pressed so we have to write the code in the send button event function.
Double clicking on the send button in the GUI view (wxlConsoleFrame.wxs) take us to wxlConsoleFrame::OnsendButClick on wxlConsoleMain.cpp (again maybe you have to scroll down to find it). Here we will add the code to get the text in the text field and write it to the port.
void wxlConsoleFrame::OnsendButClick(wxCommandEvent& event)
{
wxString sendMesg = mesBox->GetValue(); string sended;
sended = sendMesg.ToStdString(); if(port_nr > 0)
RS232_SendBuf(port_nr-1,(char*)sended.c_str(),sended.size());
}
First we get the text writed by the user in the mesBox
TextCtrl using the member function GetValue()
and asign it to an auxiliar wxString object.
Because the RS232 librarie is writen in C we have to give it a classic C style string (a char*) as parameter so we have first to convert the wxString to a standard C++ string and then pass to RS232_SendBuf the char* casted c_str() value.
Note that before sending the message the program check if the port is opened.
Receiving and showing the incomming message
In the other side of the communication we want to show the received message. We can do it with a button, or in the same event of the send button push. But maybe we connect to the PC a device wich emmit messages by its own and we want to show the messages automaticale when they arrive. As the RS232 library is not event driven we have to automatize the message polling. To do it we will use timers, so as a bonus of this tutorial we'll see how to implement and use the wxWidgets event driven timers.
Creating the Timer
We will use the wxTimer class. This brings us timers that we can define the interval between the timer send an event. And we will use that event to make an action. But go step by step.
First we have to create the timer object. So add a timer object to our Frame class. In wlConsoleMain.h to the wxlConsoleFrame class declaration:
wxTimer* recTimer;
void OnRecTimer(wxTimerEvent& event);
And we have to declare the event function as a member of the Frame class to.
Now we have to define the timer object defining his owner and event function. We will do this in the constructor of the Frame class in wlConsoleMain.cpp.
recTimer = new wxTimer(this, Rec_Timer);
recTimer->Start(interval);
And of course start the timer. interval is defined in this file as a global variable and is in milliseconds. The parameters of the wxTimer constructor is the owner object of the timer (wxlConsoleFrame in this case) and the event function.
The last thing we have to do to use the timer is asociate the function with the event. We do this in the same file, wlConsoleMain.cpp, in the EVENT_TABLE. There we have to add the event and associate to it the function and an ID for it:
enum {
Rec_Timer = wxID_HIGHEST,
};
BEGIN_EVENT_TABLE(wxlConsoleFrame,wxFrame)
EVT_TIMER(Rec_Timer, wxlConsoleFrame::OnRecTimer) END_EVENT_TABLE()
First we define the IDs for our events, only one in the example, and then define the event in the event table. We associate the event EVT_TIMER with the ID Rec_Timer and the function wxlConsoleFrame::OnRecTimer.
Now we have the timer sending and event every 200 milliseconds (or the value you initialize the timer with). Just do something with this ticking.
Using the timer event to show the incoming messages
To make something in the timer event we just have to write some code inside the timer event function. We want to poll the COM port and show the message if any.
So in the wlConsoleMain.cpp we add the functionality:
void wxlConsoleFrame::OnRecTimer(wxTimerEvent& event)
{
unsigned char recBuff[512];
int readed = 0;
recBuff[0] = 0;
readed = RS232_PollCompor<code>t</code>(port_nr-1, recBuff, 512); if(readed > 0)
{
recBuff[readed] = 0; recBox->SetValue(recBuff); }
}
Here we first create a char* buffer to store the message. As I said before, the RS232 library works with char* C style strings. With RS232_PollComport we check if there is any message in the serial buffer and read it to the buffer if any. This function returns the number of bytes readed.
Then sohw the readed message in the recBox textCtrl. Setvalue function changes the actual text in the window to the text passed as parameter. If you want to let the past texts in the text box you can use AppendText and if you defined the text box as multiline you can append a carriage return to show every new message in a new line.
Now we have all the functionality implemented. Only left to let the things clean.
Cleaning things on quiting the application
After leave the program we have to stop the timer and close the port. So in the OnQuit event we add some code:
void wxlConsoleFrame::OnQuit(wxCommandEvent& event)
{
recTimer->Stop()<code>; </code>
if(port_nr > 0)
RS232_CloseComport(port_nr-1); Close();
}
This event is throwed when the window is closed but after closing the application.
Here is a screenshot of the application runing, cnnected to an Arduino board acting as an echoer.
Points of Interest
In this article I tried to show how to use some wxWidgets resources, the basic ones, and add some extra useful content with the RS232 library and the wxTimer using.
Another very important thing are the functions to get and set values to the GUI resources: GetValue and SetValue. This functions let us interact easily with the resources we put in the screen.